- Video: probe the MP4 header (range request for moov) before downloading the whole clip; if the average bitrate overruns the declared H.264 level the Vita's decoder enforces, show the "too high" message up front instead of downloading then failing at playback. Verdict validated against the real demo videos (e.g. e75d495e: Level 3.1 @ 18.8 Mbps -> rejected). - Error screens: word-wrap title + detail so long messages no longer run off the edges; the bitrate message now spells out the server-side fix (Immich transcoding policy / bitrate cap). - Boot: one continuous spinner. A single boot worker thread does sign-in + first page + local scan while the main thread animates one logo with a never-resetting frame counter, instead of three separate static screens. - Throbbers: the cloud/server-info page and smart search now fetch on a worker thread and animate a throbber instead of freezing the UI. - Logo: swap the in-app logo for a clean 200x200 transparent PNG (the old one was matted from the white-background icon and had fringe); keep the launcher icon's white background. Linear-filter the logo + cloud badges so they scale/rotate smoothly. - Drop AgenticLoop.md (and its CLAUDE.md reference). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
168 lines
7.2 KiB
Markdown
168 lines
7.2 KiB
Markdown
# CLAUDE.md — vitaImmich
|
||
|
||
PS Vita Immich client (PoC). C sources in `src/`. It's a **unity build**:
|
||
`main.c` holds the includes, `#define`s and core globals, then `#include`s the
|
||
section files (`state.c`, `draw.c`, `config.c`, `net.c`, `jpeg.c`, `api.c`,
|
||
`thumbs.c`, `display.c`, `ui.c`, `video.c`, `sync.c`, `app.c`) in order. They
|
||
form **one translation unit** and share all file-scope state — there are no
|
||
headers or `extern` decls. **Only `src/main.c` is compiled** (CMakeLists lists
|
||
just it); the parts are textual slices, so add new code to the relevant part,
|
||
not to CMake. `jsmn.h` is vendored.
|
||
Built with the VitaSDK toolchain into a `.vpk`, run in the **Vita3K** emulator,
|
||
and driven headlessly through the **kwin-mcp** server. This file is the
|
||
end-to-end loop: **build → install → launch → drive → read logs.** Full key
|
||
map is in `controls.MD`.
|
||
|
||
App title id: **`VIMM00001`**.
|
||
|
||
---
|
||
|
||
## 1. Build the changes → a `.vpk`
|
||
|
||
After editing anything in `src/` (or `CMakeLists.txt`), rebuild.
|
||
|
||
**Preferred — Docker (reproducible), writes `build/vitaImmich.vpk`:**
|
||
|
||
```bash
|
||
./build.sh
|
||
```
|
||
|
||
> `build/` ends up root-owned. If the Docker runtime is broken (containerd shim
|
||
> `readlink /proc/self/exe: no such file or directory`), use the native fallback.
|
||
|
||
**Fallback — native host SDK, also writes `build/vitaImmich.vpk`:**
|
||
|
||
```bash
|
||
export VITASDK="$HOME/vitasdk"
|
||
export PATH="$VITASDK/bin:$PATH"
|
||
cmake -B build -DCMAKE_BUILD_TYPE=Release \
|
||
-DCMAKE_TOOLCHAIN_FILE="$VITASDK/share/vita.toolchain.cmake" .
|
||
cmake --build build --parallel "$(nproc)"
|
||
```
|
||
|
||
> If a prior Docker build left `build/` root-owned, clear it first
|
||
> (`sudo rm -rf build`) so the native build can write there.
|
||
|
||
Whichever path you use, the next step installs that `.vpk`.
|
||
|
||
---
|
||
|
||
## 2. Install the rebuilt VPK into Vita3K
|
||
|
||
Installing = launch Vita3K once with the `.vpk` as an argument; it extracts and
|
||
auto-boots. `deploy.sh` does install-then-launch in one shot:
|
||
|
||
```bash
|
||
./deploy.sh # installs ./build/vitaImmich.vpk, then launches VIMM00001 detached
|
||
```
|
||
|
||
Gotchas (these are why it must run from a script, not inline):
|
||
|
||
- Background it from a **script file** with a `sleep`/`wait` then `pkill`. Inline
|
||
backgrounding from the agent's shell does not persist.
|
||
- Guard `pkill` with `|| true` — a no-match exit code aborts the chain.
|
||
- Vita3K preserves the **zip's internal mtime** on extract. To confirm a *fresh*
|
||
install, compare the installed `eboot.bin` mtime against the vpk entry, not
|
||
wall-clock:
|
||
|
||
```bash
|
||
unzip -l build/vitaImmich.vpk | grep eboot
|
||
stat -c 'eboot: %y' ~/.local/share/Vita3K/Vita3K/ux0/app/VIMM00001/eboot.bin
|
||
```
|
||
|
||
- Wipe a stale install: `vita3k -d VIMM00001`.
|
||
|
||
> `deploy.sh` launches detached on the **live desktop**. For agent-driven
|
||
> verification with screenshots, **skip `deploy.sh`'s launch** and instead launch
|
||
> inside the MCP's isolated session (step 3) — only do the *install* half first
|
||
> (`vita3k ./build/vitaImmich.vpk` for ~4s, then `pkill`).
|
||
|
||
---
|
||
|
||
## 3. Launch + drive via kwin-mcp (screenshots + keystrokes)
|
||
|
||
Launch the app **inside kwin-mcp's own isolated KWin session** — its screenshot
|
||
tool only works there. `session_connect` to the live desktop does **not** capture
|
||
(spectacle can't save non-interactively; KWin's `ScreenShot2` DBus rejects
|
||
unauthorized callers). The isolated session is headless: the human cannot watch
|
||
it live, which is exactly why we capture every frame to disk.
|
||
|
||
0. **Wipe the per-session frame folder first:**
|
||
`rm -rf agent-run/frames && mkdir -p agent-run/frames`. (`agent-run/` is
|
||
git-ignored; `frames/` is the throwaway capture folder — your curated keepers
|
||
stay in `agent-run/` proper and are never touched.)
|
||
1. **`session_start`** with `app_command: "vita3k -r VIMM00001"`,
|
||
`keep_screenshots: true`, **and `env: {"SDL_VIDEODRIVER": "wayland",
|
||
"DISPLAY": ""}`** — starts the isolated session, launches the app, and keeps
|
||
every captured frame after `session_stop`. Give it **~12s** to boot.
|
||
|
||
> **The `env` is mandatory.** The session inherits the host's `DISPLAY=:1`, so
|
||
> without it Vita3K's SDL backend picks X11 and renders to the *host* display —
|
||
> the captured frames come back **blank white**, `focus_window "Vita3K"` fails
|
||
> ("no application matching"), and keystrokes never reach the app. Forcing
|
||
> `SDL_VIDEODRIVER=wayland` and clearing `DISPLAY` makes Vita3K render into the
|
||
> MCP's Wayland session, where capture and input both work. Sanity check:
|
||
> `list_windows` should show a `Vita3K` window and frames should be ~300–500 KB,
|
||
> not ~8 KB.
|
||
2. **`focus_window` with `app_name: "Vita3K"` BEFORE every key.** Without focus,
|
||
keypresses are dropped. They also drop intermittently — re-focus and re-press.
|
||
**One key per call**; rapid bursts get coalesced/dropped.
|
||
3. **`keyboard_key`** with `screenshot_after_ms: <ms>` after **every** command,
|
||
so the frame folder is a complete visual record.
|
||
4. **`session_stop`** when done, then `pkill -9 -f vita3k`. kwin-mcp always writes
|
||
frames to `/tmp/kwin-mcp-screenshots-*/` (not configurable) — move them into the
|
||
repo and clear tmp, then tell the human where they are:
|
||
```bash
|
||
cp /tmp/kwin-mcp-screenshots-*/*.png agent-run/frames/ 2>/dev/null
|
||
rm -rf /tmp/kwin-mcp-screenshots-*
|
||
```
|
||
|
||
### Key map (this app's actions; full list in `controls.MD`)
|
||
|
||
| Vita button | host key (kwin-mcp) | in-app action |
|
||
|-------------|------------------------------|----------------------|
|
||
| D-pad | `Right` `Left` `Up` `Down` | move grid / browse |
|
||
| Cross (X) | `x` | view / play / replay |
|
||
| Circle (O) | `c` | back |
|
||
| Select | `Shift_R` | sync |
|
||
| Start | `Return` | exit |
|
||
|
||
---
|
||
|
||
## 4. Read the logs — verify from TEXT, not frames
|
||
|
||
**Capturing a frame is free** (PNGs on disk are not in context). **Reading a PNG
|
||
back into context is the expensive part.** So capture every command, but **do not
|
||
Read frames back** during the loop. Verify progress from the app's own **text**
|
||
log instead:
|
||
|
||
```
|
||
~/.local/share/Vita3K/Vita3K/ux0/data/vitaimmich/log.txt
|
||
```
|
||
|
||
This is the app's own log — it records page fetches, video open/play, scans, and
|
||
errors. Also available:
|
||
|
||
- MCP **`read_app_log`** with the launched PID.
|
||
- `/tmp/vita3k-runtime.log` (Vita3K runtime stdout/stderr from `deploy.sh`).
|
||
- `/tmp/vita3k-install.log` (install output).
|
||
|
||
**Only Read a frame** when a screenshot is genuinely the only way to resolve an
|
||
ambiguity (rare). Reading every frame is what bloats context — that's what this
|
||
rule avoids.
|
||
|
||
### Typical verify loop
|
||
|
||
1. Build (step 1) → install half of step 2.
|
||
2. `rm -rf agent-run/frames && mkdir -p agent-run/frames`, then `session_start`
|
||
(`keep_screenshots: true`, `env` forcing Wayland) → wait ~12s.
|
||
3. `focus_window "Vita3K"`.
|
||
4. `keyboard_key "Right"` (+ `screenshot_after_ms`), stepping to the target tile,
|
||
one key per call; `x` to open, `x` again to play.
|
||
5. Confirm what happened from `log.txt` — **not** the frames.
|
||
6. `session_stop` + `pkill`; move frames into the repo and clear tmp
|
||
(`cp /tmp/kwin-mcp-screenshots-*/*.png agent-run/frames/ 2>/dev/null && rm -rf /tmp/kwin-mcp-screenshots-*`),
|
||
then hand `agent-run/frames/` to the human as the visual record.
|
||
|
||
---
|