worldbox-observer-mod/.cursor/skills/idle-spectator-e2e/SKILL.md
DazedAnon 90d5b1604f Enhance dossier interactions and improve Lore click handling.
- Add related unit tracking for activity logs and dossiers
- Implement colorized names for related units in reason lines
- Update harness scenarios to validate new interactions
- Increment version to 0.26.1 in mod.json
2026-07-17 13:36:33 -05:00

14 KiB

name description
idle-spectator-e2e Run IdleSpectator WorldBox E2E tests via the in-mod agent harness (scripts/harness-run.sh, critical_smoke, regression suite, .harness commands). Use when adding or fixing IdleSpectator features, playtesting the mod, writing harness scenarios, or verifying spectator/focus/tip/settings/chronicle behavior.

IdleSpectator E2E harness

Always validate IdleSpectator changes with the in-game harness. Do not rely on OS mouse/keyboard automation (Wayland-flaky). Do not call Application.Quit() (can hang Steam on Linux).

Quality bar (also in .cursor/rules/idle-spectator-quality.mdc):

  1. Comprehensive / scales - fix the class via discovery, not one logged example.
  2. Long-term / maintainable - no brittle shortcuts.
  3. Always live-test - open the game, spawn/drop entities, statuses, wars, plots; do not stop at code review or a single assert.

Live inventory before special cases (required)

When a fix touches labels, prose, taxonomy, interest, or presentation for a game asset class (jobs, statuses, traits, species, tasks, happiness, plots, disasters, WorldLog ids, etc.):

  1. Enumerate first. Dump the live AssetManager library (or equivalent scanner) for that class. Do not invent an id list from the tip you saw or from memory.
  2. Derive the rule from the inventory shape, not from one awkward string. Example: citizen jobs - discover P_* families from citizen_job_library, do not hardcode gatherer_.
  3. Gate the whole set. Harness assert (or TSV dump under .harness/) must cover every live id, plus 1-2 representative samples in the dossier/caption path. One dossier_force_job=farmer green is not enough.
  4. Reject id-branch patches. if (id.StartsWith("gatherer")), one-off locale key probes, and tip-string special cases are fails unless the branch is generated from live discovery.
  5. Self-check before coding: "If the next unauthored id in this library appeared tomorrow, would this still be correct?" If no, keep going - do not ship the example patch.

Reference pattern: ActivityAssetCatalog.EnumerateLiveCitizenJobIds + citizen_job_labels_ok (writes .harness/citizen-job-labels.tsv).

Live playtest (required for features/fixes)

Harness scenarios are the gate, not a substitute for poking the world.

  • Start or reuse WorldBox with worldbox-ctl / harness-run.sh (all permissions).
  • Use harness actions that actually seed the situation: spawn, status_apply / status_apply_nearby, combat sessions, war/plot/family/outbreak sessions, wire attack sides, etc.
  • When a bug was seen in a soak or Player.log, reproduce that shape in-world (same sticky kind, pair size, status class) before declaring fixed.
  • Prefer extending HarnessScenarios.cs so the live repro becomes a repeatable --repeat 3 gate.
  • Watching the camera tip + dossier after drops counts; reading only the C# change does not.

Sandbox / permissions (required)

Never start, relaunch, or kill WorldBox / Steam from the Cursor sandbox.

  • Always run harness commands and WorldBox process control with unrestricted / all permissions.
  • If the Shell tool still wraps the command in cursorsandbox after requesting all, retry with Smart Mode approval (request_smart_mode_approval) so the user can grant unrestricted execution. Sandboxed steam -applaunch will hang or produce a broken game.
  • Fail fast: if parent cmdline contains cursorsandbox, do not call worldbox-ctl start/restart - fix permissions first.
  • Canonical process control (do not invent ad-hoc kill/launch loops):
./scripts/worldbox-ctl.sh status
./scripts/worldbox-ctl.sh start
./scripts/worldbox-ctl.sh stop
./scripts/worldbox-ctl.sh restart   # after mod.json bump / NML recompile
  • scripts/harness-run.sh may launch the game when it is not already running (same Steam app id).
  • Prefer --no-launch only when ./scripts/worldbox-ctl.sh status shows WorldBox is already up.
  • Never pkill -f with a worldbox path in the agent shell command - it can match and kill the agent process itself. Use worldbox-ctl.sh stop (killall worldbox).
  • Do not add agentd/hooks/LD_PRELOAD shims for this. The working path is: simple worldbox-ctl.sh + real unrestricted Shell.

WorldBox wiki (mechanics source of truth)

When deciding what is “interesting” for Spectator or Chronicle, read the official wiki before inventing behavior:

Chronicle = notable life/world events (kills, lovers, crowning, wars), not “what Idle Spectator is staring at.” Watch caption = live AFK context for the current focus. Vanilla Tooltip / TooltipLibrary (tooltips/tooltip_actor, etc.) may be reusable for polish later.

Mandatory workflow

After any feature or bug fix in IdleSpectator/:

  1. Design for full coverage (discovery / class rules), not the single failing tip. For asset-class work, complete Live inventory before special cases above before writing branches.
  2. Bump IdleSpectator/mod.json version (NML recompiles on game load).
  3. If the game was already running, recompile with:
    ./scripts/worldbox-ctl.sh restart
    
  4. Live-test: spawn/drop the relevant entities and statuses in-world (via harness scenario or focused harness commands). Confirm tip/focus/dossier behavior matches the contract.
  5. Extend or add a scenario in IdleSpectator/HarnessScenarios.cs when the change is new behavior.
  6. Day-to-day / PR gate:
    ./scripts/harness-run.sh --repeat 3 critical_smoke
    
  7. After larger changes (director, discovery, settings, input, tips, chronicle), also run:
    ./scripts/harness-run.sh regression
    
  8. If you added a focused scenario, run it directly too with --repeat 3.
  9. Iterate until all repeats PASS. Do not stop after one flaky pass.
  10. Report: scenario name(s), PASS/FAIL counts, what you spawned/live-checked, and any new asserts.

HUD / visual changes - screenshot gate (required)

For dossier, Chronicle HUD, captions, icons, layout, or any on-screen chrome:

  1. Run a scenario that takes a screenshot (e.g. chronicle_smoke). The harness writes:
    • .harness/hud-….png (full frame)
    • .harness/hud-…-dossier.png (exact dossier UI rect via ReadPixels - use this)
  2. Wait ~1s after the run so the end-of-frame crop finishes, then:
    ./scripts/crop-dossier-hud.py --strict IdleSpectator/.harness/hud-*-dossier.png
    
  3. Open the *-dossier.png with the Read tool. Never judge layout from the raw 2880p frame.
  4. If the player pastes an in-game shot, treat that as ground truth.
  5. Checklist - fail the change if any are wrong:
    • Nametag row visible: species icon, Name (species), level icon and number, task, sex
    • No large empty void beside the avatar when History is empty (traits or history should fill it)
    • History lines appear beside the avatar only when the subject has History
    • Traits packed (no huge empty gaps); prefer full labels when space allows
    • No clipped/overlapping chips; no header buried under the portrait frame
  6. Fix and re-screenshot until the dossier crop matches the intended layout.
  7. Report the *-dossier.png path(s) and what you verified.

Harness asserts catch logic; Unity dossier crops catch layout. Both are required for UI work.

For tiny doc-only edits, harness is optional. For anything that touches camera, spectator, tips, settings, discovery, chronicle, or input exit, harness is required.

Runner

./scripts/harness-run.sh
./scripts/harness-run.sh critical_smoke
./scripts/harness-run.sh --repeat 3 critical_smoke
./scripts/harness-run.sh regression
./scripts/harness-run.sh chronicle_smoke
./scripts/harness-run.sh --no-launch critical_smoke
./scripts/harness-run.sh --cmd '{"id":"1","action":"snapshot"}'

Env overrides:

  • IDLE_SPECTATOR_MOD - mod folder (default: repo IdleSpectator/)
  • WORLDBOX_LOG - Player.log path
  • HARNESS_TIMEOUT / HARNESS_BOOT_TIMEOUT
  • WORLDBOX_APP_ID - Steam app id (default 1206560)

Paths:

  • Mod: IdleSpectator/
  • Runner: scripts/harness-run.sh
  • Inbox/results: IdleSpectator/.harness/ (gitignored)
  • Log: ~/.config/unity3d/mkarpenko/WorldBox/Player.log

The runner starts Steam/WorldBox when needed, touches .harness/reset + rewinds offset, enables auto-dismiss, waits for last-result.json. A loaded world is still required.

Built-in scenarios

Name Purpose
watch_reason_clarity Orange reason = event sentence (is fighting, is seeking a lover); no Signal gate; fill has empty reason
critical_smoke PR gate: settings gate, idle health, tip match, retarget, manual exit
settings_full Disable-while-active, show_watch_reasons
ghost_guard Fake new-species tip must not steal focus onto wrong unit
retarget_stability Many watches without focus/power-bar flicker
input_exit Post-enable grace + camera-drag exit
director_tiers Score-margin cut-in (Action over Curiosity); peer rotate still behind MinDwell
director_lifecycle Hold / resume / MaxWatch / quiet grace → ambient
director_gaps Forced session interrupt + resume after higher-score cut
focus_interrupt Instant score-margin cut (no settle); hold; still-worth-watching; 6s grace
discovery Present dedupe + force drain + tip match
world_log Story/Epic interest interrupt via collector
chronicle_smoke History inject, death-cause samples, World feed isolation, History|World tabs, HUD jump
chronicle_subject_bench Timed Fallen list rebuilds at rising subject caps; recommends a budgeted max
smoke Short enable/focus health check
tip_match Tip asset must match focused unit
regression Shell suite of all gate scenarios above

Out of gate: real battles, map-gen discovery, OS hotkey I. Prefer pick_unit over miracle spawn in critical paths.

Timing note

Director and discovery use Time.unscaledTime. Game speed 5x does not shorten harness waits. Use fast_timing / director_run instead.

Adding coverage for a new fix

  1. Add asserts/steps to the most specific scenario (or critical_smoke if core).
  2. Register new scenarios in HarnessScenarios.Build and in REGRESSION_SCENARIOS inside scripts/harness-run.sh.
  3. Use asset=auto / label="New species: {asset}" after pick_unit.
  4. Keep InterestDirector frozen while AgentHarness.FreezeDirector (Busy unless director_run).
  5. wait_world must stay front-of-queue on retry.

Command cheat sheet

  • wait_world, dismiss_windows, wait, director_run, fast_timing
  • age_current, expire_grace, remember_focus
  • spectator (value=on|off, optional expect=blocked)
  • pick_unit, spawn, focus, watch, watch_ghost, trigger_interest
  • simulate_input (expect=grace to assert grace hold)
  • set_setting (expect=enabled|show_watch_reasons|show_dossier_caption|chronicle_enabled)
  • discovery_reset, discovery_note, discovery_mark, discovery_drain
  • chronicle_force, chronicle_open, chronicle_jump
  • assert expects: idle, health, power_bar, no_bad, tip_matches_unit, unit_asset, tip_contains, enabled, show_watch_reasons, current_tier, would_accept_curiosity, focus_same, presented, pending_discovery, pending_interest, in_grace, dossier_contains, caption_contains, dossier_traits_ok, dossier_statuses_ok, citizen_job_labels_ok, tooltip_library_ok, history_other_rows, lore_detail_is_other, reason_names_colored, fallen_killer_banner, caption_layout_ok, dossier_history_contains, chronicle_count, chronicle_latest_contains, chronicle_latest_dated, focus_arrows
  • dossier_click_history_other, dossier_force_named_reason - dossier row → Lore other; gold names in orange reason
  • snapshot, reset_counters, scenario

Process safety

  • Kill only the real game binary (.../common/worldbox/worldbox).
  • Prefer ./scripts/harness-run.sh; avoid Application.Quit.
  • After code changes, restart/relaunch so NML recompiles.

Launching WorldBox (required for agents)

Steam refuses to start when the Shell tool runs sandboxed (looks like root): Error: Cannot run as root user / game never opens.

Always launch or restart with unrestricted permissions:

# Shell tool: required_permissions: ["all"]
cd /home/dazed/Projects/worldbox-mods/idle-mode
./scripts/harness-run.sh --repeat 3 critical_smoke

That runner starts Steam via steam -applaunch 1206560 when needed.

Manual restart after a mod.json bump

# Shell tool: required_permissions: ["all"]
# Kill only the game binary (not Steam itself):
kill $(pgrep -f '/Steam/steamapps/common/worldbox/worldbox$') 2>/dev/null
# or: kill -9 <pid of .../common/worldbox/worldbox>

nohup steam -applaunch 1206560 >/tmp/idle-spectator-harness-launch.log 2>&1 &

# Wait until NML loaded the mod (not just process up):
# ~/.config/unity3d/mkarpenko/WorldBox/Player.log contains:
#   [IdleSpectator]: Harmony patches applied
# Fail fast if log shows: error CS

Do not call steam -applaunch (or ./scripts/harness-run.sh when the game is down) from a sandboxed Shell - it fails silently from the user's POV.

Definition of done

  • Relevant harness scenario(s) PASS
  • Core work: critical_smoke passes 3 times
  • Larger work: regression PASS
  • Do not lower asserts to greenwash flakes

More detail