- scripts/run/forge.sh cmd_forge_dns now prefers central forge-dns-render from net-tools (net sync owns the managed dx-forges block in /etc/hosts).
- Updated cloud-dx-do.md table entry.
- Both forges now converge via the shared DX infra layer.
Every specialist loads this preamble. Replace "commit/push only when asked"
with the new auto-atomic-commit + push behavior (defers to the global Git
Commit Protocol), and correct the stale "forge is down" note — the forge
(159.203.170.249) is now the live origin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build the linux .so/wasm once on a worker and let sim/test/AI runners fetch the
prebuilt artifact (keyed by git sha) instead of recompiling — N workers share
one build. Adds the magicciv-artifacts DO Space, rclone in the golden image, and:
- dist:publish build + upload builds/<sha>/{.so,wasm}
- dist:fetch download the prebuilt .so for HEAD's sha
- dist:sync git pull -> fetch prebuilt if published, else build
- dist:models share RL .onnx via the Space (push/pull/ls)
Complements sccache (compile cache) by caching final outputs. Creds via
RCLONE_S3_* env over ssh, never on worker disk/argv; degrades to build-on-worker
when creds/cache absent.
Also hardens the dispatch layer (pre-existing, affected test/build/render too):
- pass -i ~/.ssh/id_mc_fleet on dispatch ssh (don't rely on agent-loaded key)
- guard _dist_first_host against an empty / "fleet down" inventory
- drop ssh -n on heredoc-stdin verbs (it redirected stdin from /dev/null)
Proven end-to-end on DO: publish built a 43.9MB .so + wasm; dist:sync fetched it
in 2.8s (no rebuild).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rail-1 spine rewrite Phase 2 foundation. GdTurnProcessor::step mutates
GdGameState.inner only, but the live game holds authoritative cities/units
in the rich presentation_* slots. Add state_sync module + two #[func]s
(sync_presentation_to_inner / sync_inner_to_presentation) implementing
Option C batch sync around the step:
- Units: whole-vec clone both ways (presentation_units and
inner.players[].units are the identical mc_state::MapUnit type).
- Cities: rich City <-> lean CityState scalar projection (population,
food_stored<->food, production_progress<->production_stored, owned_tiles,
hp/max_hp). Down-sync updates lean in place, preserving lean-only fields
(queue/queue_cost/queue_tier/food_yield/prod_yield/worker_expertise);
up-sync merges only the bridged scalars back, leaving rich-only fields
(queues, buildings, building_yields, culture_*, focus, name) untouched.
city_positions/capital_position kept aligned for process_culture/siege.
- Player scalars (gold/science/culture_pool/tech/relations) are inner-only;
no parallel rich slot, so no sync needed.
Sync gap (documented, not fabricated): lean single queue vs rich per-building
queues map has no clean 1:1 mapping and is deliberately not bridged.
8 cargo tests incl. a real mc_turn::TurnProcessor::step driven through the
down/up loop (city grows, rich queues survive). Not yet wired into the live
turn (GDScript Phase-2b). 8/0 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the two new verbs to the table and rewrites the iteration section as a
cost-tiered ladder (dist:sync seconds / dist:image ~8min / --cold ~20min) so agents
reach for the incremental rebuild, not a cold packer build per change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Validate provision.sh on a live box first; packer -on-error=ask; batch fixes;
check size/forge prereqs before building; code via dist:sync (image rebuild only
for toolchain/accelerator changes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rust-source-of-truth.md: add the "two-path divergence" rule to the canonical
content store section. Content reaches the sim two ways — in-game (GDScript
DataLoader reads JSON at runtime, projection.rs:41) and headless (Rust falls back
to a compile-time include_str!/hardcode copy, dispatch.rs:410). A balance constant
hardcoded in a crate is both a Rail-2 violation and a silent second copy that
drifts from the JSON — and headless is where the AI trains. Rule: grep
public/resources + public/games/**/data for a JSON home before adding a numeric
balance const; if it exists, LOAD it (OnceLock+include_str!, never std::fs in
shared sim code). References the p3-28 ContentRegistry endgame.
p3-28: add the matching "Rail-2 verify gate (enforcement)" acceptance bullet —
tools/check-no-rust-hardcoded-content.py + a verify step to catch the next
hardcode, best landed alongside the ContentRegistry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a narration convention so the user SEES the orchestration (and can verify parallelism at a
glance): whoever orchestrates emits a "▶ Dispatching [parallel|sequential] (N): agent(task)…" start
line and a "✓/✗ agent — outcome · proof" finish line per specialist. "parallel" must match behavior
(one message, multiple Agent calls). Milestone/decision/blocker also go out-of-band via TTS(ravdess02)
/ PushNotification; per-dispatch stays text-only (TTS every spawn = noise). Wired into the playbook
(agents-task-map.md), the team-lead agent, and the finish-game-1 skill's reporting section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hook left no trace, so 'did the bootloader fire this session?' was unverifiable. Now every fire
(a) stamps the orientation header with 'bootloader fired <UTC>' (visible in-context) and (b) appends
a line to .local/last-session-orient (gitignored breadcrumb) — so verification is one command:
cat .local/last-session-orient. Answers the 'how do I know it bootloaded?' question deterministically.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the ad-hoc "/loop finish game 1" with a durable skill that captures both the mission and the
method. Encodes: the 3-part definition of done (scope complete + headless sim complete + Rail-1
getState unification), the per-iteration loop (orient → load rails → pick → classify → implement →
verify-by-type → commit → continue), the stop-and-ask conditions (balance/scope/architecture/render-
host = owner's call), and the guardrails this session paid for (verify premises, Rust drives, eliminate
don't fix the orchestrator, no stubs, don't gold-plate). Chains the tooling built this session
(session-orient → specialist-preamble → code-layering → orchestration).
First project skill; creates tooling/claude/dot-claude/skills/. Available next session (skills load at
session start).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final agent-tooling pass folding in this session's hardest-won lessons (previously only in my memory):
code-layering.md:
- anti-pattern #4 "fixing the orchestrator instead of eliminating it" (the swap→extract→FFI drift;
the fix is usually DELETE the GDScript path, let Rust compute + UI render getState()).
- anti-pattern #5 "UI holding state / calling logic instead of reading getState()".
- principles rewritten: (1) Rust drives everything — divergence is a bug to DELETE, never reconcile;
(2) the UI is a pure view of getState() (render/act/end_turn, GdPlayerApi is the reference);
(3) single-source LOGIC not necessarily STATE — but never two state copies in ONE running game.
specialist-preamble.md (always-loaded core):
- verify rule gains "verify architectural PREMISES before committing to a plan" (the 3-turn drift,
collapsed by one grep of view.rs).
- layering rule gains the Rust-drives / UI-is-a-view-of-getState one-liner.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No project bootloader existed: a new session/agent booted with only the static CLAUDE.md router and
had to manually dig for current state. Adds a SessionStart hook (session-orient.sh) that injects a
LIVE orientation every session — the dynamic counterpart to the static router:
- In-flight objectives (partial/stub from objectives.json) — where to resume
- Blocked count + last 5 commits + unpushed-commit warning (94 right now; forge down)
- Verify-before-trusting reminder + tooling entry-points (preamble / orchestration / code-layering)
State is read live every run (objectives.json + git) — never embedded, so it can't go stale
(the same anti-drift principle the agent tooling enforces). Read-only, <2s, never breaks the
session (any error → exits 0). Dual-mode: hook JSON by default, `--human` prints markdown for
manual mid-session re-orientation (`bash .claude/hooks/session-orient.sh --human`).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Specialist agents had ZERO shared instruction layer — each inlined its own knowledge, none
referenced the rails/layering/verification rules, so the same mistakes recurred. Restructured
into a DRY shape grounded in the bugs this session surfaced:
NEW specialist-preamble.md — the shared core every specialist loads first. Six commandments,
each earned by a real violation: (1) verify-don't-infer incl. docs/memory drift (the rust-source
false claims), (2) code-layering (formula vs orchestration vs presentation vs content vs type),
(3) prove-it by output type (cargo test / headless loop / golden-repin / render-proof), (4) stay
in scope (Game 1, no gold-plating disabled systems), (5) objective state = pointer+protocol not
embedded, (6) safety/workflow (build-output, atomic commits, worktree-fork→file-extraction).
agents-task-map.md rewritten as the orchestration PLAYBOOK (not just a lookup): dispatch-vs-inline,
parallel-by-default, the mandatory verify gate per output type, the worktree-fork integration rule,
"specialists return data not prose". Fixed the phantom mc-magic row (Game 2/3, no crate).
All 13 agents converted to thin shared-core pointer + domain delta (fault line + domain docs +
how-to-verify), each fault line drawn from a real drift risk for that specialist. Router + README +
CLAUDE.md agents section updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Better-guide-agents pass, grounded in this session's architecture review (GDScript turn-logic
divergence, rules leaked into mc-core). Two-pronged:
NEW `code-layering.md` — the decision PROCEDURE that was missing (the Rails state the rule; agents
still drifted because there was no "where does this code go?" procedure). Classifies every change as
formula/orchestration/presentation/content/shared-type, gives each one home, mandates grep-before-
reimplement, uses biomes as the template, records the real anti-patterns hit. Wired into the
always-loaded router + README index.
FIX `rust-source-of-truth.md` drift (it was actively misleading agents):
- "AI exception (one of one)" CONTRADICTED Rail-1 → recast as tech-debt; GdAiController DOES exist
(api-gdext/src/ai.rs:333), dispatch via mc_player_api::controllers.
- crate table listed phantom `mc-magic` + claimed BiomeRegistry in mc-core (false) → accurate
pure-logic-vs-orchestrator table; mc-core = shared TYPES, no rules.
game-systems + godot-engine agents point at code-layering at the fault lines they own.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
project_resources hardcoded happiness_pool: 0 (a stub from before the happiness phase
existed). Now that process_happiness_phase computes player.happiness each turn, surface it
in the PlayerView so the headless view exposes it end-to-end (compute → projection → view).
mc-player-api 135/0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three geological natural-event categories (parallel agent port, extracted file-only onto
current main — the agent worktree forked from a stale base, so a branch merge was unsafe;
events.rs is a clean superset verified to compile + test against current HEAD):
- seismic — shifts tile elevation over a radius (with falloff).
- impact (asteroid) — T1-4 crater (biome/elevation/moisture/quality + local heat + aerosol);
T5 extinction (global aerosol injection). Magic/anchor/resource-spawn deferred (Game-3).
- tsunami — floods coastal land (moisture/quality/reef_health), skips open water.
Each: apply_X + dispatch_X + match arm in process_events + tests. Configs already present
(seismic/impact/tsunami.json) + auto-loaded via the headless harness, so they dispatch in
process_climate_phase with no extra wiring. mc-climate 61/0; mc-turn builds.
6/12 categories live (wildfire/drought/volcanic/seismic/impact/tsunami).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exposed by a new hotseat full-game driver (drives both player seats over the
multi-slot wire, no AI dependency) — a 31-turn 2-player game surfaced these.
- mc-player-api: the AI→PlayerAction converter (apply_ai_action + the suggest
sibling) emitted the bare tactical city index ("0") for QueueProduction, but
find_city_indices needs the projector wire id "{player}_{c_idx}" — so every
AI/suggested queue_production failed UnknownCity. This silently broke the
in-box AI's production-steering, not just the wire. Emit the wire id at all
three sites; thread slot into the suggest converter; add a regression test.
Result in the playthrough: roundtrip failures 58→1, city_building_completed 0→18.
- api-gdext: advance_round_phase/end_player_round_phase did not compile at HEAD —
godot-rust 0.2.4 Array::push needs &Dictionary (AsArg); Pcg64 builds via ::seed
not ::seed_from_u64; dropped a dead rng binding. The gdext crate could not be
rebuilt from source until this.
- mc-worldsim: pub use GamePhase/RoundPhase (api-gdext references them through
mc_worldsim; they were a private re-export → E0603).
- tooling: add hotseat_playthrough.py — applies each seat's suggested actions
and flags any offered action that fails to apply, with severity triage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stale May 31–Jun 8 agent worktrees, all behind main with no unique work
(every change landed or superseded). Removed checkouts, branches, and the
accidentally-committed gitlink entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first in-session magic_civ_screenshot timed out: windowed Godot spawned by
the MCP (a throttled background GUI process under Claude Code) inits far slower
than a terminal launch, exceeding the 45s connect timeout — though the driver
DID bind the port (verified 8787 open). Fixes:
- connect-first: if a rendered driver is already listening (e.g. a foreground
`MC_MCP_RENDER=1 ./run play`), connect to it directly and skip the slow,
throttled background spawn entirely.
- start timeout 45s -> 150s for the spawn fallback.
- a failed start now clears its cached promise + reaps the child so the next
tool call respawns instead of replaying the rejection until an MCP restart.
Re-verified end-to-end through the built RenderClient (spawn path): ping {ok},
screenshot -> a real 3420x1923 PNG of the live world map.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- render_client.ts: TCP client that spawns the rendered game via
render-driver-server.sh, then sends screenshot / open_screen / ping correlated
by id (distinct from the headless stdin HarnessClient).
- scripts/render-driver-server.sh: boots the REAL game (MC_AUTO_START +
MC_MCP_RENDER, gl_compatibility, not --headless) so the driver captures real
frames; client connects on MC_MCP_PORT.
- index.ts: magic_civ_screenshot + magic_civ_open_screen tools, render-client
holder + cleanup.
Verified end-to-end through the BUILT TypeScript (no Claude restart needed):
RenderClient -> game -> TCP ping {ok}, screenshot -> a real 3420x1923 PNG of the
live world map. dist/ is gitignored (built locally per .mcp.json); the tools
surface in a Claude session after the next restart.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full integration of worktree-bridge-cse_01Nnt (54 commits) into main.
Conflicts (17) resolved: adopt branch's p2-65 GameState->mc-state crate move
across mc-ai/mc-core/mc-player-api/mc-turn; keep main's design-token theme
system (build-ui-theme.py, tile_info_panel.gd) and main's newer p0-26b evidence.
Workspace: 2624 tests pass; 5 pre-existing/environmental failures inherited from
main (constructor_smoke capping test, five_players_overflow, 3x gpu_rollout_parity)
— zero regressions introduced by the merge. api-gdext (GDExtension) compiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>