- 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
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):
- Comprehensive / scales - fix the class via discovery, not one logged example.
- Long-term / maintainable - no brittle shortcuts.
- 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.):
- Enumerate first. Dump the live
AssetManagerlibrary (or equivalent scanner) for that class. Do not invent an id list from the tip you saw or from memory. - Derive the rule from the inventory shape, not from one awkward string.
Example: citizen jobs - discover
P_*families fromcitizen_job_library, do not hardcodegatherer_. - 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. Onedossier_force_job=farmergreen is not enough. - 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. - 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(allpermissions). - 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.csso the live repro becomes a repeatable--repeat 3gate. - 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 /
allpermissions. - If the Shell tool still wraps the command in
cursorsandboxafter requestingall, retry with Smart Mode approval (request_smart_mode_approval) so the user can grant unrestricted execution. Sandboxedsteam -applaunchwill hang or produce a broken game. - Fail fast: if parent cmdline contains
cursorsandbox, do not callworldbox-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.shmay launch the game when it is not already running (same Steam app id).- Prefer
--no-launchonly when./scripts/worldbox-ctl.sh statusshows WorldBox is already up. - Never
pkill -fwith aworldboxpath in the agent shell command - it can match and kill the agent process itself. Useworldbox-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:
- Hub: https://the-official-worldbox-wiki.fandom.com/wiki/The_Official_Worldbox_Wiki
- Useful pages: World History, Units (lovers/best friends), Unit Stats, Kings and Leaders, Creature Traits, War, Disasters, Ages
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/:
- 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.
- Bump
IdleSpectator/mod.jsonversion (NML recompiles on game load). - If the game was already running, recompile with:
./scripts/worldbox-ctl.sh restart - 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.
- Extend or add a scenario in
IdleSpectator/HarnessScenarios.cswhen the change is new behavior. - Day-to-day / PR gate:
./scripts/harness-run.sh --repeat 3 critical_smoke - After larger changes (director, discovery, settings, input, tips, chronicle), also run:
./scripts/harness-run.sh regression - If you added a focused scenario, run it directly too with
--repeat 3. - Iterate until all repeats PASS. Do not stop after one flaky pass.
- 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:
- 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 viaReadPixels- use this)
- Wait ~1s after the run so the end-of-frame crop finishes, then:
./scripts/crop-dossier-hud.py --strict IdleSpectator/.harness/hud-*-dossier.png - Open the
*-dossier.pngwith the Read tool. Never judge layout from the raw 2880p frame. - If the player pastes an in-game shot, treat that as ground truth.
- 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
- Nametag row visible: species icon,
- Fix and re-screenshot until the dossier crop matches the intended layout.
- Report the
*-dossier.pngpath(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: repoIdleSpectator/)WORLDBOX_LOG- Player.log pathHARNESS_TIMEOUT/HARNESS_BOOT_TIMEOUTWORLDBOX_APP_ID- Steam app id (default1206560)
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
- Add asserts/steps to the most specific scenario (or
critical_smokeif core). - Register new scenarios in
HarnessScenarios.Buildand inREGRESSION_SCENARIOSinsidescripts/harness-run.sh. - Use
asset=auto/label="New species: {asset}"afterpick_unit. - Keep InterestDirector frozen while
AgentHarness.FreezeDirector(Busy unlessdirector_run). wait_worldmust stay front-of-queue on retry.
Command cheat sheet
wait_world,dismiss_windows,wait,director_run,fast_timingage_current,expire_grace,remember_focusspectator(value=on|off, optionalexpect=blocked)pick_unit,spawn,focus,watch,watch_ghost,trigger_interestsimulate_input(expect=graceto assert grace hold)set_setting(expect=enabled|show_watch_reasons|show_dossier_caption|chronicle_enabled)discovery_reset,discovery_note,discovery_mark,discovery_drainchronicle_force,chronicle_open,chronicle_jumpassertexpects: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_arrowsdossier_click_history_other,dossier_force_named_reason- dossier row → Lore other; gold names in orange reasonsnapshot,reset_counters,scenario
Process safety
- Kill only the real game binary (
.../common/worldbox/worldbox). - Prefer
./scripts/harness-run.sh; avoidApplication.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_smokepasses 3 times - Larger work:
regressionPASS - Do not lower asserts to greenwash flakes
More detail
- Command and scenario notes: reference.md