# IdleSpectator harness reference ## WorldBox wiki Use https://the-official-worldbox-wiki.fandom.com/wiki/The_Official_Worldbox_Wiki when designing Spectator priorities or Chronicle events (Units lovers/friends, World History, kings, traits, war, disasters). Chronicle records **life/world events**, not spectator watch notes. Watch caption covers live focus context. Vanilla tooltips (`Tooltip`, `TooltipLibrary`, `tooltips/tooltip_actor`) are candidates for future UI polish. ## Architecture ```text scripts/harness-run.sh -> starts WorldBox if needed -> writes IdleSpectator/.harness/commands.jsonl -> touches reset + auto-dismiss -> regression: loops named scenarios sequentially AgentHarness.Update() (in-game) -> executes commands on Unity main thread -> FreezeDirector unless director_run is active -> writes results.jsonl + last-result.json -> logs [HARNESS]/[ASSERT]/[SNAP] ``` ## Why not OS input? Wayland makes `xdotool`/`ydotool` unreliable. Truth lives in engine state (`MoveCamera`, tips, settings), so drive the mod API directly. ## Timing InterestDirector and SpeciesDiscovery use `Time.unscaledTime`. WorldBox 5x game speed does not advance those clocks. Use harness `fast_timing` + `director_run` / `age_current` for scheduling tests. ## Hard-won failure modes | Symptom | Cause | Fix | |---------|-------|-----| | Welcome closes, idle never starts | commands.jsonl rewritten same length; offset at EOF | runner writes `offset=0` + `reset` | | Steps run before world ready | `wait_world` requeued to back | `RequeueFront` | | Tip says crab, focus is angle | watch fell back to nearest unit | require matching asset; use `pick_unit`/`asset=auto` | | Spawn then "no unit for asset" | miracle spawns die on bad tiles | prefer `pick_unit` for critical paths | | Flaky health/no_bad mid-scenario | ambient retarget while harness busy | `FreezeDirector` unless `director_run` | | Steam hang after tests | `Application.Quit` | leave game running | | Director tests take forever | production dwell/rotate | `fast_timing` (not game 5x) | | Game never opens / `Cannot run as root user` | Shell sandboxed; Steam rejects root-like env | re-run launch/`harness-run.sh` with Shell `required_permissions: ["all"]` | | Old mod still loaded after edit | NML caches until restart | kill `.../common/worldbox/worldbox`, bump `mod.json`, relaunch with `all` perms | | Harness says ready but mod broken | Stale `Harmony patches applied` / missed `error CS` | runner fails boot on IdleSpectator `error CS`; ready requires Harmony in current (truncated) Player.log | | Harness stuck on `o...` forever | Launch marker written into Player.log then wiped by Unity | do not put boot markers in Player.log; wait for process + Harmony | ## Useful asserts - `health` - idle + focus + power bar hidden + `bad==0` - `tip_matches_unit` - tip asset equals focus unit asset; if tip starts with `New species:`, label species must match - `no_bad` - StateProbe bad counter is zero - `enabled` / `show_watch_reasons` - mod settings - `current_tier` / `would_accept_curiosity` - director scheduling - `focus_same` - after `remember_focus` (ghost guard) - `presented` / `pending_discovery` - species discovery buffer - `caption_layout_ok` - dossier panel size/layout bounds (catches off-panel text) - `screenshot` harness action writes `.harness/.png` via Unity ScreenCapture ## Screenshots Harness `screenshot` writes: - `IdleSpectator/.harness/.png` - full frame - `IdleSpectator/.harness/-dossier.png` - **exact dossier rect** (`ReadPixels`) For HUD work: 1. Prefer reading `*-dossier.png` (wait ~1s after the run). 2. Validate with `./scripts/crop-dossier-hud.py --strict IdleSpectator/.harness/hud-*-dossier.png` 3. Do not judge layout from raw 2880p frames. 4. Player-pasted shots override harness crops. ## Extending scenarios Edit `IdleSpectator/HarnessScenarios.cs`: 1. Add steps with `Step(id, action, ...)`. 2. Register name in `Build`. 3. Add the name to `REGRESSION_SCENARIOS` in `scripts/harness-run.sh` when it should gate merges. 4. Run `./scripts/harness-run.sh ` then `--repeat 3 critical_smoke` (and `regression` for suite-level changes). Keep critical_smoke deterministic: - `dismiss_windows` / `wait_world` first - `pick_unit` before tip/focus asserts - `reset_counters` after spectator+focus before health (StateProbe only; use `value=all` only at batch start) - `simulate_input` last for manual-exit coverage