Conversation
…stroy Agents were deciding with incomplete data — steel=0, wood=0, components=0, power_net=0.0 were all hardcoded. This caused agents to skip building research benches (thinks no wood) and ignore power management entirely. - Wire /api/v1/resources/stored to get real material counts (WoodLog, Steel, ComponentIndustrial) instead of returning 0 - Wire /api/v1/map/power/info for real power_net (current - consumption) - Add PowerData and FactionData schemas to GameState - Wire /api/v1/factions for faction goodwill (DefenseCommander needs this) - Fix equip action: was in system prompt + WRITE_CATALOG but had no client method or executor handler — agents proposed it and it silently failed - Wire repair_rect and destroy_rect executor handlers (same gap as equip) Refs #6 (agents must beat baseline), #7 (save creation pipeline) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Agents saw mood=0.40 but didn't know why. Now they get structured alerts
("Need Beds", "Colonist Starving") with target colonist IDs. Scenarios
relied on the storyteller to fire their signature events — now they
trigger incidents deterministically at specific ticks.
- Wire GET /api/v1/ui/alerts → AlertData model, added to GameState
- Wire POST /api/v1/incident/trigger for programmatic incident control
- Wire GET /api/v1/incidents/top for probability-weighted predictions
- Wire POST /api/v1/camera/screenshot → ScreenshotResponse model
- Add TriggeredIncident to ScenarioConfig + _fire_scheduled_incidents()
in game loop (fires after pause, before state refresh)
- Update scenario YAMLs: toxic_fallout fires at tick 1, raid at tick 2
with 500 points, plague at tick 1
- Game loop: opt-in screenshots_enabled, screenshot_data_uri in tick JSON
- Add ui_alerts to READ_CATALOG, camera_screenshot to WRITE_CATALOG
Refs #6 (alerts improve agent mood responses), #7 (deterministic saves)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unblocks #7 save creation pipeline — scenarios can now spawn pawns, items, and change weather via RIMAPI instead of XML manipulation. Setup commands run after save load and before the game loop starts. - Add spawn_pawn() — POST /api/v1/pawn/spawn (named colonists with position) - Add spawn_item() — POST /api/v1/item/spawn (with optional quality/stuff) - Add send_drop_pod() — POST /api/v1/map/droppod (items at position) - Add change_weather() — POST /api/v1/map/weather/change - Add SetupCommand model to ScenarioConfig (type + params dict) - Wire setup command dispatch in run_scenario.py after unforbid - Pass triggered_incidents from scenario config to game loop Refs #7 (advanced save creation without manual dev mode) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enables scenario-specific pawn customization for the #7 save creation pipeline. Ship launch pawns need high Construction + Research, plague saves need specific health states, etc. - edit_pawn_skills(pawn_id, skills, passions) — set levels and passion - edit_pawn_traits(pawn_id, add, remove) — add/remove traits - edit_pawn_health(pawn_id, heal_all, restore_parts, cure_diseases) - edit_pawn_needs(pawn_id, needs) — set food/rest/mood with 0-1 validation - Add all four to WRITE_CATALOG in api_catalog.py These are setup/testing helpers only — not wired into agent action system. Refs #7 (advanced save creation with custom pawn states) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the XML manipulation pipeline with a pure RIMAPI call sequence:
load base save → spawn items/pawns via API → trigger incidents → set
research → save. No more brittle XML surgery, no more manual dev mode.
- create_scenario_saves.py: full rewrite
- SCENARIOS dict declares items, incidents, extra_pawns, research per save
- build_scenario_save() loads base, applies setup, saves via client
- --only flag to build a single scenario
- --difficulty-only flag for offline byte-patching (no game needed)
- Uses spawn_item, spawn_pawn, trigger_incident, set_research_target,
save_game, load_game all through RimAPIClient
- Dropped 200+ lines of XML regex surgery (clone_pawn, make_item_xml,
extract_pawn_block, loadID remapping, uniqueIDsManager patching)
- run_benchmark.py: add mock routes for new endpoints (resources/stored,
map/power/info, factions, ui/alerts) so smoke tests exercise the new
code paths instead of falling through to error handlers
Closes #7 save creation pipeline via API.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All 5 advanced saves now contain their scenario-specific state: - toxic_fallout: ToxicFallout incident actively triggered - raid_defense: +2 pawns, weapons, armor, resources - plague_response: +2 pawns, medicine stockpile, meals - ship_launch: +2 pawns, resources (Steel, Plasteel, Gold, Uranium), 2 research targets set (more were already completed in base save) - first_winter: clean baseline with Medium difficulty Script fixes discovered during first live run: - Stack splitting: spawn_item can't handle amount > max_stack; split WoodLog, MealSurvivalPack, etc into chunks of MAX_STACK each - Save flush delay: save_game() returns before Unity writes the file to disk. Added _wait_for_save_written() to poll until size stabilizes above 5 MB (observed 67 KB - 1.4 MB partial writes without this) - Load settle: load_game() returns before the map is fully usable. Wait 10s after colonist_count > 0 before issuing spawns - Between-scenario pause: 8s delay between scenarios to let previous map tear down cleanly - Between-op pause: 0.3s between each spawn call - Per-scenario exception handling — one scenario failing doesn't kill the rest; RIMAPI alive check between runs Closes #7 remaining checklist items (advanced save states). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The plan explicitly called for test_scheduled_incidents_fire_at_tick but it was missing. Added 3 tests to TestScheduledIncidents: - End-to-end: run 3 ticks with scheduled incidents at tick 0 and 2 - Null case: no incidents configured, no trigger_incident calls - Unit-ish: _fire_scheduled_incidents() invokes client with correct args including tick boundary checks (fires on exact tick_offset only) Also added /api/v1/incident/trigger to mock write routes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Plan audit identified two test names missing by literal name: - test_alerts_empty_on_error: verifies get_alerts() returns [] when RIMAPI is unreachable (was functionally untested) - test_edit_traits: plan asked for single test; split into _add + _remove previously. Added combined test alongside existing ones Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three lessons from rebuilding scenario saves that would have saved us multiple cycles had we known upfront: CLAUDE.md RIMAPI gotchas section: - Writes are async — save_game/load_game return before Unity executes them. save_game specifically returns before file flush (poll size). load_game needs ~10s settle after colonist_count populates. - spawn_item can't split stacks — amount > max_stack triggers a null ref that cascades and destabilizes the entire game - Null-ref errors persist across calls — only game restart recovers CLAUDE.md Save Loading section: - Mention setup_commands dispatch in run_scenario.py - Add pointer to create_scenario_saves.py for regenerating the 5 advanced saves (replaces the old XML pipeline) .claude/rules/rimapi.md: two new rules capturing the same (concise). Memory files (separate, user-scoped, not committed): - project_rimapi_async_writes.md — detailed gotchas + max_stack table - reference_scenario_save_regen.md — how to run create_scenario_saves.py - feedback_save_testing.md — updated to clarify mirror IS intentional for canonical scenario saves (different from ad-hoc test iteration) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All issues from the self-review pass fixed in one commit: Correctness: - [HIGH] get_power_info() was called twice per tick (once in get_resources internally, once in get_game_state). Refactored get_resources to accept an optional power_info param; get_game_state fetches once and passes through. Test: TestPowerInfoNotCalledTwice asserts the call count. - [MED] Replaced ".get(k) or default" anti-pattern with _pick() helper that uses explicit membership check. Legitimate zero/empty values no longer get silently overwritten by defaults. - [MED] change_weather now uses JSON body instead of unescaped query string. Test: test_change_weather_sends_json_body verifies the body. - [MED] first_winter documented as day-0 snapshot with explanation of why (no tick-advance endpoint in RIMAPI yet). - [LOW] trigger_incident now explains the str(map_id) quirk in docstring (RIMAPI's TriggerIncidentRequestDto.MapId is typed as C# string). Conventions: - [MED] Added public client.ping() — replaces client._get() leakage in create_scenario_saves.py (_check_rimapi_alive) and run_scenario.py (the post-load wait loop now uses client.get_colony() instead). - [LOW] Hardcoded COLONY_X, COLONY_Z (132, 137) replaced with runtime _compute_colony_center() — centroid of live colonist positions, with fallback to the old constants. Protects against silent breakage when the base save is regenerated. Tests added (7 new, total now 376): - test_change_weather_sends_json_body - test_trigger_incident_nested_parms (raid_strategy, arrival_mode) - test_flat_list_response (get_resources_stored with list shape) - test_ping_returns_true_when_alive - test_ping_returns_false_on_connection_error - test_power_info_called_once_per_tick (perf regression test) - test_fire_scheduled_incidents_swallows_exceptions (game loop robustness) Not addressed (out of scope / deliberate): - take_screenshot manual envelope unwrap: the rest of the codebase uses "raw dict from _post" pattern widely; changing _post semantics would ripple across all write methods. Left as-is with inline comment. - .rws files in git: deliberate trade-off for Docker benchmark reproducibility, noted in PR description. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Self-review + fixesRan a review pass on this PR (since I wrote it all, wanted critical eyes). Found 7 issues ranging from a real perf bug to convention nits. All addressed in commit High/medium issues — fixed
Low-severity — fixed
Not addressed (deliberate)
Test coverage added (7 new, total 376)
Verification
🤖 Generated with Claude Code |
_get unwrapped the {"success": bool, "data": ...} envelope but _post
returned it raw, forcing take_screenshot to do `result.get("data", result)`
by hand. Future write endpoints that return real payloads (e.g.
spawn_pawn → {"pawn_id", "name"}) would hit the same trap.
Extract _unwrap_envelope() and apply it in both methods. Unwrap is
conditional on "data" in body, so {"success": true}-only responses pass
through intact — the 29 existing "result['success'] is True" write tests
keep working without changes. Only test_spawn_pawn needed updating since
its mock actually carries a data payload. Verified no production caller
in src/ or scripts/ inspects the success flag on writes.
call() now returns Any (consistent shape for both verbs), and write
methods that forward _post's return are annotated Any to match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Pushed The trap: Why changing
Changes
Verification: |
- Remove duplicate _wait_for_save_written in create_scenario_saves.py (second definition shadowed the first — drift risk if defaults diverged). - Coerce rect coords to int in _h_repair_rect / _h_destroy_rect to match the pattern used in _h_move (LLM output can be str). - _h_equip now checks thing_id is None instead of falsy (thing_id=0 would have been silently skipped). - get_resources_stored logs a warning when items are missing def_name — catches upstream RIMAPI schema drift instead of silently dropping entries. - Document nickname/bio_age/chrono_age wire-name remapping in spawn_pawn docstring. - _default_rimworld_save_dir builds paths with chained / segments instead of embedded slashes in strings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Self-review pass — found 6 issues, all fixed in 26d6bcf: Bug
Minor
Nits
Verification: |
The committed settings should be portable across contributors. Remove user-specific absolute paths (leaked Windows username and local repo layout) and dead/redundant entries; move machine-specific paths and debugging shortcuts to .claude/settings.local.json (gitignored). Changes: - Remove Read(//c/Users/redmo/...) rules — those were granting read access to per-user absolute paths; anyone who needs cross-repo access now adds their own additionalDirectories in settings.local. - Remove additionalDirectories block for the same reason. - Drop 4 exact-match entries already covered by broader patterns or referencing dead flags (--dry-run was replaced by --smoke-test). - Collapse python -m mypy invocations to a single `python -m mypy:*`. - Add portable patterns: uv run:*, python -m uv run:*, uv pip:*, uv sync:*, docker compose:* (tighter than docker run:*), docker build:*, docker exec:*, dotnet build:*, gh pr comment:*. - Scope the ruff auto-fix hook to *.py files — previously fired a process on every Edit/Write regardless of file type. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Wires every useful RIMAPI endpoint into the RLE client and fixes the data gaps that were hurting agent decisions. Makes partial progress on #7's save creation pipeline by rewriting it to use RIMAPI (replacing XML surgery), but does NOT fully close #7 — see "What's still needed for #7" below.
steel,wood,components,power_netwere hardcoded to 0 inget_resources(). Agents skipped research benches because they thought they had no wood. Now wired to/api/v1/resources/storedand/api/v1/map/power/info. Also wired faction goodwill and fixed theequip/repair_rect/destroy_rectactions that were in the catalog but silently failing./api/v1/ui/alerts) gives agents structured problem signals like "Need Beds" / "Colonist Starving" with target IDs. Incident trigger lets scenarios fire ToxicFallout/RaidEnemy/Plague deterministically instead of hoping the storyteller cooperates. Screenshots opt-in viascreenshots_enabled.spawn_pawn,spawn_item,send_drop_pod,change_weatherplusSetupCommandin scenario YAMLs. Rewrotecreate_scenario_saves.py— 200+ lines of XML regex surgery replaced with declarative RIMAPI calls.edit_pawn_skills,edit_pawn_traits,edit_pawn_health,edit_pawn_needsfor scenario customization.What scenario saves got built (and what they still lack)
All 5 advanced saves were rebuilt via RIMAPI and committed to
docker/saves/. They have the item/pawn/incident state from setup commands, but they're still day-0 snapshots because RIMAPI has no endpoint to fast-forward the game clock:first_wintertoxic_falloutraid_defenseplague_responseship_launchWhat's still needed for #7
This PR delivers the client-side infrastructure for save creation but not the advanced colony states the issue spec originally called for. The remaining work needs either:
set_research_target(force=True)only queues a project, it doesn't instantly complete it — noted in thecreate_scenario_saves.pycomments so future sessions don't try to reuse that approach for advancement.How this moves the needle on #6
Notable discoveries during live testing
save_game()returns before Unity flushes the file to disk — without polling we got 67 KB truncated writes. Script now polls file size until it stabilizes above 5 MB.spawn_itemcan't auto-split amounts abovemax_stack— it null-refs and destabilizes the game. Script splits intoMAX_STACK[def_name]chunks.load_game()calls fail — previous map needs time to tear down. Script adds 8s between scenarios and 10s after load before first write command.equipwas in the agent system prompt + WRITE_CATALOG but had no client method or executor handler. Agents proposed it and it silently fell through to generic dispatch with wrong params.Test plan
mypy src/strict cleanruff check src/ tests/ scripts/clean_fire_scheduled_incidentswith MockClient assertions🤖 Generated with Claude Code