Commit graph

858 commits

Author SHA1 Message Date
Natalie
d0a74e5122 fix(@projects/@magic-civilization): 🐛 update world simulation docs and objectives
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-07 19:34:57 -07:00
Natalie
c2668c8789 feat(@projects/@magic-civilization): add lithology and terrain evolution integration docs
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-07 19:07:45 -07:00
Natalie
2de6051c5f chore(objectives): fix Game-1 owner integrity + flesh out underground (g2-12) cluster
Game-1 dashboard fix: reassign 5 objectives off specialist-agent names
onto valid team-leads so objectives-report.py regenerates (it aborts on
any owner lacking a .project/team-leads/<owner>.md identity):
  p1-61 game-data->terraformer, p1-57 game-systems->envoy,
  p2-73/p2-60/p2-74 godot-engine|godot-ui->wireguard.

Underground (Game 2, stays OOS): decompose g2-12 umbrella into 8 ordered
phase objectives g2-12a..h (data model+save, worldgen, excavation, cross-
layer movement+pathfinding, per-layer fog, collapse, layer UI, AI
awareness); g2-11 blocked_by tightened to [g2-12a, g2-12c].

Dashboard regenerated: 319 objectives, README.md + objectives.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:24:32 -07:00
Natalie
30bcde26d5 feat(fog): dwarven cartographer's-fog for unexplored tiles
Replace flat-black unexplored fog with an antique-cartography treatment
(Civ-VI style), themed as dwarven 'unmapped vellum/slate' — reveals no
real terrain, only a stylized stone surface.

- fog_renderer.gd: unexplored tiles painted with a procedurally generated
  opaque vellum texture (FastNoiseLite domain-warped FBM → warm dark
  slate→parchment gradient), generated synchronously at init so there is
  no reveal-before-ready leak. No binary art asset required. Visible/seen
  paths unchanged; frontier stays a clean opaque edge.
- design-tokens.json -> ui_theme.tres: fog.unexplored 000000ff -> 1a160fff
  (warm 'unmapped vellum' tone) so the minimap unexplored cover matches.

Render-verified on apricot (iter_7q proof, fog enabled): undiscovered
renders as warm stone/vellum on map + minimap, no terrain bleed, clean
frontier, lit tiles unaffected. gdlint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 06:35:04 -07:00
Natalie
c22b497a27 fix(fog): fully hide undiscovered tiles on map + minimap
Undiscovered (never-seen) terrain was leaking through fog on both the
main map and minimap; only the lit(visible) vs unlit(seen) distinction
worked. Root cause: unexplored overlay was sub-opaque + undersized.

- fog_renderer.gd: UNEXPLORED_COLOR alpha 0.85 -> 1.0 (opaque); edge-fade
  softening now gated to the FOGGED state only — unexplored tiles stay
  fully opaque even at vertices bordering a visible tile (was revealing
  undiscovered terrain along the exploration frontier).
- design-tokens.json -> regenerated ui_theme.tres: fog.unexplored
  alpha 0.90 -> 1.0 (fixed at token source, not hand-edited).
- minimap.gd: unexplored now drawn as a full tile-pitch opaque cover
  instead of a 3x3px dot (the dot left ~5px gaps at minimap scale,
  leaking terrain). fog.explored (seen dimming) left unchanged.

Verified on apricot via iter_7q_worldmap_visual_proof with fog ENABLED:
undiscovered renders solid black on map + minimap, clean hard frontier,
lit tiles unaffected. (GUT cannot prove this — render-verified.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 03:30:58 -07:00
Natalie
26bf44f077 feat(designs): sprite showcase /sprites route + standalone scoped gallery
Lands remaining bridge-cse worktree WIP onto main:
- SpriteShowcase.tsx React page + wired /sprites route (App.tsx) + Index nav link
- standalone scoped sprite-gallery.html (160 units / 165 buildings, 0 missing)
- p2-74 cluster-3 HUD/minimap proof screenshot

SpriteShowcase typechecks clean (@game-assets glob + theme, no WASM dep).
Excluded tsconfig.tsbuildinfo (build cache); tile_info_panel.gd kept main's
global-theme version. User-authorized override of autocommit (daemon idle).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 02:11:21 -07:00
Natalie
5953f4f465 merge(magic): integrate bridge-cse branch into main — p2-74 design tokens + p2-65 mc-state crate + sprites/gallery
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>
2026-06-06 01:04:16 -07:00
Natalie
413f6ea828 docs(@projects): 📝 update research port objective status details
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-06 00:07:32 -07:00
autocommit
ef3c494002 chore(index): regen (3 missing units authored, p0-26b re-scored, de-hardcode clusters 1-2)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:25:02 -07:00
autocommit
d1b7ff8efc docs(p0-26b): record post-port tier_peak batch; bullet 5 stays open (no baseline)
Ran the 10-seed T300 autoplay smoke batch on apricot against the committed
build (origin/main @ f5c4ee9c3, the live pick_research_via_bridge dispatch).
Recorded the per-seed tier_peak table as cited evidence: all 5 clans present,
all games resolved to victory with both sides active (seed5's 37-turn win is a
legitimate tinkersmith domination rush), tier_peak spans T1-T6 with no
collapse — a healthy, clan-diverse post-port distribution.

Bullet 5 remains [ ] / status partial: the acceptance is comparative
("unchanged-or-better vs the pre-port baseline") and no recorded pre-port
baseline exists. The nearest pre-port SHA is confounded by ~3 weeks of
unrelated changes; the only confound-free before/after (port commits reverted)
isn't reachable by the apricot launcher (forge-fetch only, revert branch is
ACS-unpushed). Decision-correctness is already pinned by the bullet-4 parity
test (8/8). tier_peak extracted directly from player_stats[].tier_peak because
autoplay-report.py aborted on a pre-existing autoplay-validate.py bug
(unhashable type: list) — left unfixed (out of fence).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:23:20 -07:00
autocommit
4b0b661907 docs(p2-74): record cluster-2 (HUD panels + notifications) progress
Log the 17 tokenized HUD/notification scripts, the three landing commits, the
const-Color-trap conversions, GUT baseline parity (16 pre-existing failures,
zero new), proof paths, and the held-for-world_map remainder (world_map_hud,
weather_visualizer, minimap, encyclopedia). Status stays stub — clusters for
world_map / encyclopedia / minimap remain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:20:52 -07:00
autocommit
db309a4236 refactor(ui): tokenize HUD overlay colors off raw Color() literals
p2-74 cluster 2 (HUD panels + notifications), overlays sub-cluster. Final 6 HUD
scripts onto ThemeAssets.color() tokens; completes the cluster-2 fence.

- hotkey_sheet: dim → background.overlay; panel → background.panel/border.panel;
  title/key labels → text.title; group headers → accent.gold; descriptions →
  text.primary; footer → text.muted; empty rows → text.disabled.
- overlay_panel: panel → background.panel/border.divider; active-lens font →
  semantic.positive; view-center label → text.muted.
- chronicle_panel: backdrop scrim → background.overlay.
- comms_renderer: dev heartbeat strip → background.hud/border.divider; per-event
  toast accents → accent.gold/goldResource, semantic.positive/negative/warning,
  player.purple (first-contact, sighting, blackout, new-capital, vision share,
  heartbeat sent/missed, envelope tap) — semantic, not inline RGB.
- debug_menu: dev panel → background.panel/semantic.diplomacy; button styleboxes
  → button.bgHover/bgPressed/background.raised + border.panel/focus; font →
  text.button/buttonHover; find label → text.secondary.
- tutorial_overlay: dim → background.overlay; panel → background.panel/border.panel;
  title → text.title; counter → text.muted; body → text.primary; action badge →
  semantic.positive (done) / semantic.warning (required).

Visual-only; no logic change (Rail 3). 0 inline Color() remain across the entire
cluster-2 fence (named hud panels + notifications). All edited scripts compile
with autoloads present (GATE_OK).

Proof: hotkey_sheet_proof.tscn captured on apricot (headless weston) shows the
themed purple panel, gold border/title, gold group headers, primary-text
descriptions, and the muted "(no bindings yet)" empty row.
.project/screenshots/p2-74-cluster2-hotkey-sheet.png

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:18:44 -07:00
autocommit
d6c84e4b4f refactor(ui): tokenize HUD notification colors off raw Color() literals
p2-74 cluster 2 (HUD panels + notifications), notifications sub-cluster.
Route turn_notification.gd / comms_toast.gd / capital_blackout_overlay.gd
inline Color() literals onto ThemeAssets.color() design tokens.

- turn_notification: const CATEGORY_COLORS dict (a const trap — token lookups
  can't sit in a const initializer) converted to var _category_colors populated
  in _ready(); category palette → semantic.negative/positive, accent.science,
  accent.goldResource, player.purple, semantic.diplomacy, text.primary. Panel
  bg/border → background.panel/border.panel; title/header → text.title; scrim
  dims → background.hud/overlay; muted hints → text.muted.
- comms_toast: panel bg/border → background.panel/border.panel; accent strip →
  accent.gold; title → text.title; body → text.primary. The configure() accent
  default Color literal (param defaults can't call the autoload) becomes a null
  sentinel resolved to accent.gold in the body.
- capital_blackout_overlay: dim/glitch scrims sourced from background.deepest /
  semantic.negative with explicit .a; title → semantic.negative; subtitle →
  text.title.

Adds tools/capture-proof.sh: reusable single-proof-scene capture under a private
headless weston on the RUN host, pulling the PNG back. Visual-only; no logic
change (Rail 3). 0 Color() remain in the three scripts.

Proof: hud_proof.tscn captured on apricot (headless weston) shows the themed
purple Turn Summary panel, gold border/title, copper filter checkboxes, and
semantic per-category entry coloring (red combat, green founding, blue tech,
gold economy, violet magic).
.project/screenshots/p2-74-cluster2-hud-notifications.png

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:57:41 -07:00
autocommit
3a04052385 docs(objectives): revise p2-65 bullet-9 + p2-72a array-removal resume notes (stale-premise audit)
p2-65 (stays partial K=8/9): rewrite Phase-7 resume note. The 'no-persistent-
GdGameState / discards combat_balance' premise is STALE — game_state.gd:269
loads combat_balance.json into the persistent _gd_state singleton via
GdGameState::set_combat_balance_json (api-gdext/src/lib.rs:3541), and the driver
objective p2-55f is already done with its bullet 2 marked [x]. Bullet 9 must NOT
be closed on that path (it bypasses mc-state entirely — would be an
objective-integrity reframe). Remaining work is architectural consolidation
(mc_state::SimConfig owning global config), now an owner decision, not a
mechanical close-out.

p2-72a (stays partial): array-removal increment re-checked and NOT executed.
All three gate-feasibility blockers reproduce — cold Godot import cache, no
mc-godot Docker image (freeze-safe path unbuilt), operator-gated visual proof
unavailable this session. game_state.gd UNTOUCHED (npc_buildings count = 33).
Clean stop re-confirmed; predecessor's execution recipe unchanged.

Regen objectives index. No status frontmatter changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:55:08 -07:00
autocommit
f80609097b chore(index): regen objectives.json (p2-73 done, p2-65 8/9, de-hardcode cluster 1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:48:45 -07:00
autocommit
42cd08d8f3 refactor(ui): tokenize menus + city + combat scene colors off raw Color()
p2-74 cluster 1 (modals + menus), batch 2. Route inline Color() literals in
12 scene scripts onto ThemeAssets.color() design tokens by semantic role:
- end_game_summary: banner victory/defeat → accent.gold/semantic.negative,
  standings rank → semantic.positive, score → accent.goldResource, body roles.
- victory/defeat_screen: result + row colors → accent/semantic/text tokens.
- credits/how_to_play/throne_room_spoils/game_setup: titles → accent.goldResource
  /text.title, dividers → border.divider, body → text.primary/secondary/muted.
- loading_screen: PLAYER_COLORS const removed; player.color now routes through
  ThemeAssets.get_player_color() (palette-aware) with player.gray fallback.
- throne_room: placeholder label → text.primary@0.65; the 12 _layer_to_color
  decoration tints intentionally retained (categorical, no token home).
- city/building_panel: DISABLED_OUTLINE_COLOR const → @onready var off
  semantic.negative@0.85 (const can't call autoload).
- city_buildable_helper: built→positive, locked/blocked→negative, worked→
  text.title, unworked→text.muted.
- city_screen focus toggle: active→accent.gold, inactive→text.muted.
- combat_preview grudge badge → semantic.negative.

Visual-only; no logic change. Data-driven clan/player colors and Color.WHITE/
GRAY fallbacks left intact. gdlint HEAD-vs-now shows zero new violations per
file. Proofs on apricot (weston headless): statistics_proof (5 tabs) +
end_game_summary_proof (4 outcomes) compile-load and render the themed purple
panels / gold titles / semantic coloring with no magenta token fallbacks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:11:03 -07:00
autocommit
5c578e0d01 docs(p2-72a): 📋 scope array-removal increment + correct the save-format coupling claim
Read-only analysis of the npc_buildings serialize chain (no code touched;
game_state.gd unchanged). Finding: the resume map's "array removal forces a
SaveManager rewrite, cannot land independent of save-format-migration" claim is
over-stated. `_serialize_npc_buildings` → `serialize_npc_buildings` calls
`b.to_dict()` on each `Building` view, and that view's `to_dict()` already
proxies to `_gd_state.npc_building_dict(_idx)` — serialized bytes are already
sourced from the Rust mirror. Re-pointing serialize at the mirror directly is
byte-identical; the save FORMAT does not change. This objective owns the whole
increment.

STOPPED this session deliberately (gate-feasibility, not coupling): the increment
edits the highest-regression live-world-map file and must land as one green commit
gated by GUT-headless + cargo + build:gdext + operator-gated world-map visual
proof. Godot import cache is cold (host-freeze risk per godot-docker.sh), no
mc-godot Docker image / warm volume exists, and the visual proof needs operator
sign-off — not completable safely without a runway session. Precise execution
recipe (serialize re-point + 6 reader retargets + array/spatial-index/view-fn
deletion + gate steps) recorded for the next session. Status stays partial.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:06:53 -07:00
autocommit
bbacf5ff8d docs(p2-65): 📊 Phase 4+5 landed — bullets 1/2/3/4/5/6/7/8 ✓, K=8/9 partial
Recount p2-65 acceptance after the Wave-D session-2 consumer sweep
(C1 1917c3db5 / C2 efbfa6ae3 / C3 0bdf7b57c):
- bullet 6 (grep mc_turn::game_state|mc_turn::GameState → 0): ✓ — both shims
  deleted, brace+inline+nested forms all swept.
- bullet 5 (consumer Cargo downgrades): ✓ — mc-state added to 6 crates;
  mc-turn dropped from mc-save/mc-vision/mc-mcts-service.
- bullets 1/2/3/4/7/8: ✓ with honest footnotes (combat_balance resolved via
  mc-core not mc-state; mc-ai dep on mc-turn retained = logic dispatch not a
  cycle; --features gpu has pre-existing unrelated CombatResult drift).
- bullet 9 (Phase 7 SimConfig wiring):  STILL OPEN — sole remaining item.

Status stays `partial` (objective-integrity K=N rule; K=8/9). Phase-7 resume
note appended (mc_state::SimConfig → api-gdext init, closes p2-55f bullet 2).
Index regenerated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 20:00:11 -07:00
autocommit
ba52171b70 refactor(ui): tokenize statistics modal colors off raw Color() literals
p2-74 cluster 1 (modals + menus). Route statistics.gd's 23 inline Color()
literals onto ThemeAssets.color() design tokens: panel bg/border →
background.panel/border.panel, title → text.title, body/muted/disabled text
roles, rank-badge gradient → semantic.positive/warning/diplomacy/negative,
score value → accent.goldResource, trend arrows → semantic.positive/negative,
backdrop → background.overlay. Converted const RANK_COLORS → var _rank_colors
populated in _ready() (token lookups can't sit in a const initializer).
Data-driven clan/player line colors left intact. Visual-only; no logic change.

Proof: statistics_proof.tscn captured on apricot (weston headless) shows the
themed purple panels, gold borders/title, and semantic rank coloring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:58:12 -07:00
autocommit
cea53e1ee4 feat(p2-73): 🎨 generate ui_theme.tres from design tokens + global apply + color() accessor
Close the gap where the design system (.project/designs/design-tokens.json)
drove the React guide but not the Godot game.

- tools/build-ui-theme.py: compiles the W3C/style-dictionary token SoT into a
  complete Godot Theme (7 StyleBoxFlat sub-resources, Button/Label/Panel/
  PanelContainer/ItemList/RichTextLabel colors + font sizes + corner radii/
  border widths per UI_DESIGN_SYSTEM.md §3/§4/§6). ui_theme.tres is now a
  GENERATED artifact; tokens are the single source of truth. Deterministic
  output (sorted keys, fixed float fmt, preserved uid://ui_theme_fantasy) with
  a --check drift gate. Idempotent; --import does not rewrite it.
- project.godot [gui] theme/custom: applies ui_theme.tres at viewport level so
  every non-overriding default Control renders the copper fantasy styling.
- ThemeAssets.color(name) -> Color: resolves dotted token names (accent.gold,
  semantic.positive, text.primary, …) against the metadata/tokens JSON blob
  baked into the .tres by the generator. Fully data-driven from the SoT, no
  hardcoded color map. (Godot rejects dots in Theme color item names, so the
  token table ships as resource metadata.) Unknown names return an explicit
  fallback. This is the API p2-74 will de-hardcode 45 scripts onto.
- ui_theme_proof.{tscn,gd}: bare-widget + color()-swatch proof scene.
  test_theme_assets_color.gd: GUT accessor coverage (5/5 headless).

Proof captured on apricot under weston, reviewed in conversation:
.project/screenshots/p2-73-ui-theme-proof.png. Workspace green — full unit
(16==16) and integration (18==18) suites show identical HEAD-baseline-vs-patch
failure counts, zero regressions; patch adds +5 passing tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:42:01 -07:00
autocommit
180c521977 docs(p2-65): 📊 Phases 1/3a/3b landed — status stub→partial, Phase-4 resume note
mc-state crate created + GameState moved into it (green @ 0ed21945c). Records the
3 green-between commits (0bace0e6c / 45e9adea9 / 0ed21945c), the save-format +
parity evidence, the honest ~5/9 acceptance recount (bullet 6 still 20+ shim
leaks → status stays partial, NOT done), and the precise Phase-4 path: sweep
all mc_turn::game_state/GameState sites (external + mc-turn internals + the
lib.rs:62 re-export) to mc_state::, delete both shims, with the dev-dep rule for
mc-core/derived_stats (the one upstream-of-mc-state consumer). FINISH_GAME1_PLAN
Wave-D row 1 updated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:20:40 -07:00
autocommit
f05231b825 docs(plan): Wave F — design-system fidelity gap (p2-73 pipeline + p2-74 de-hardcode)
Quantified gap: design-tokens.json drives the web guide but not Godot; ui_theme.tres never applied as root theme; 45 scene scripts hardcode 973 Color() + 307 theme overrides. Two objectives filed; foundation (p2-73) dispatched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:16:20 -07:00
autocommit
39bf244f74 docs(p1-29j): 📋 File autoplay→Rust action-application objective (p1-29d unblock)
p1-29i's full-game validation pinned the root cause that gates all of Wave-C
AI convergence: the autoplay AI applies city-founding/capture via GDScript
(`ai_turn_bridge_dispatch.gd:170 dispatch_found_city` → CityScript.new() →
EventBus.city_founded.emit), NEVER calling Rust `mc_turn::processor::
try_found_city` / `process_siege` where the balance levers live. So every
Rust-side founding/siege lever (p1-29i refound_suppression, and successors) is
inert by construction on the p1-29d gate surface — a Rail-1 violation on the
action-application seam.

Spec-only (Rail-1, scope game1, owner warcouncil, status stub). Large separate
effort, gated on p2-65 (mc-state gives the bridge a persistent GdGameState to
apply actions against). References p1-29d Findings A/2/3 and p1-29i's terminal
result. Added to objectives README dashboard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 19:00:19 -07:00
autocommit
9f1a28b4e8 docs(p1-29i): 📊 Full-game validation — refound lever inert on autoplay gate; do NOT author cd=5
Ran the deferred full-game validation as a controlled same-build before/after:
one GDExtension built once on apricot from pinned SHA 3d83f4781 (carries the
lever); combat_balance.json is runtime-loaded, so only cooldown_turns would
change between arms.

Pre-flight killed the batch before it ran — cd=5 is inert by construction on the
p1-29d autoplay gate surface, for two independent reasons:

1. Architectural: autoplay applies founding via GDScript dispatch_found_city,
   never calling the Rust try_found_city/process_siege where the refound gate
   lives (same class as process_science bypassed by GdTechWeb). Lever cannot fire.
2. Behavioral: autoplay produces terminal capital-capture eliminations, never
   refound churn — no event for cooldown_turns to gate (4-seed cd=0 run shows
   cities_lost 0–1 per game, all terminal; corroborated by the 10-seed
   20260529_185955 table).

Arm B (cd=5) NOT run: byte-identical by logic (zero qualifying events) — a hollow
"no effect" confirmation, the inverse of the batch-attribution trap. The pre-flight
clause authorizes stopping.

Verdict: do NOT author cd=5. combat_balance.json left at default 0 (the gridded
5/9→8/9 lift is real on the gridded harness but does NOT transfer — recontextualized
as a surface mismatch, NOT retracted). p1-29h elim bullet scoped to the gridded
surface. p1-29d D1 re-pointed: no longer gated on the refound lever (it does not
unblock D1); real unblock is the autoplay→Rust action-application architecture gap
(out of fence).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:24:28 -07:00
autocommit
4c719a073e docs(p2-48a): phase-gate approved — end-game summary GUT+proof done
Orchestrator-reviewed clean proof (all 4 GameOverReason variants, themed copy) + GUT green. partial -> done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:20:08 -07:00
autocommit
fd690d2483 feat(combat): add lair Assault/Raid/Siege mode picker on lair engagement
Advances p3-10a. Moving a stack onto a wild-lair tile now opens a small
CanvasLayer mode picker (modeled on promotion_picker.tscn) before combat:
Assault (enabled), Raid (disabled — p3-10c), Siege (disabled — p3-10b). The
picker emits mode_chosen(mode) / cancelled(); world_map_combat.initiate_lair_combat
opens it and routes the Assault branch through _begin_lair_assault → the existing
p0-17 show_lair_preview → _handle_lair_clear path (per p3-10a, "the existing path
IS the assault"), so the working lair-clear flow is not regressed.

Scope note: Assault routes through the live p0-17 flow, NOT GdLair.assault()
(api-gdext/src/lair.rs) — that bridge is the 7-arg JSON marshaller and would
require building attacker/defender JSON + loading tier_NN.json + applying
loot/survivor/clear outcomes in GDScript, duplicating the working path. The
p3-10a bullet therefore stays ◐ (bridge not exercised end-to-end; no picker
proof screenshot yet).

GUT: tests/unit/test_lair_mode_picker.gd 5/5 green on apricot headless
(only-Assault-enabled, Assault emits mode_chosen("assault"), Cancel emits
cancelled() and no mode, disabled Raid/Siege never emit via the in-handler
guard, target label resolves the lair name). All-Dwarf vocab keys
(lair_picker_*, lair_mode_*) authored in vocabulary.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:17:38 -07:00
autocommit
1c0d136117 feat(vocab): author statistics + end-game summary copy, drop placeholder keys
The stats modal (statistics.gd) and end-game summary (end_game_summary.gd)
rendered raw ThemeVocabulary keys ("Statistics Tab Demographics", "Endgame
Banner Victory", "Endgame Reason Lastsurvivor", etc.) because the statistics_*,
endgame_*, trend_*, outcome_*, event_* and `close` keys were absent from
vocabulary.json — lookup() falls back to title-case on a miss.

Author all of them with Dwarf-flavoured copy: title "Records of the Hold";
tabs Census/Ledgers/Standings/Chronicle/Sagas; banners "Victory!" / "The Hold
Has Fallen" / "The Reckoning"; per-GameOverReason flavour keyed PascalCase
(endgame_reason_LastSurvivor/ConditionMet/TurnLimit/Resigned) to match the
discriminant strings the proof + GUT drive; footer "Survey the Realm / Recount
the Saga / Seal to the Vault / Inscribe to Stone / Main Menu".

statistics_proof.gd: rebuild StatsTracker.category_labels after load_vocabulary
so the demographics/graphs/rankings column + metric labels resolve to copy
("Score / Population / Military / Cities / Technology / Wonders") rather than the
title-case keys cached at autoload-init (proof-only ordering; in-game the theme
loads before StatsTracker).

Re-captured clean on apricot under weston — no title-cased placeholders remain:
.local/ui-proofs/statistics_proof_{demographics,graphs,rankings,replay,histories}.png
.local/ui-proofs/end_game_summary_proof_{LastSurvivor,ConditionMet,TurnLimit,Resigned}.png

Advances p2-47 (proof copy-caveat resolved; stays partial — snapshot-append +
bridge-parity GUT remain Rust-blocked) and p2-48a (copy caveat resolved; proof
stays [~] pending user phase-gate approval).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 18:17:02 -07:00
autocommit
40bb2b1f2c test(p1-29i): geometry ensemble — robust elimination lift, corrected baseline framing
A single-seed sweep is uninformative (scripted path barely responds to rng), so
add a geometry ensemble over INITIAL CONDITIONS: start-distance {4,5,6} ×
attacker-warriors {3,4,5} = 9 surfaces, at cooldown {0,3,5,8}.

Result (elimination-rate X/9):
  cd=0 (baseline): 5/9   (attacker-wins 5 / def 4)
  cd=3:            7/9   (att 7 / def 2)
  cd=5:            8/9   (att 7 / def 2)   <- peak
  cd=8:            6/9   (att 5 / def 4)

The refound cooldown produces a ROBUST lift (soft hump peaking cd 3-5, every
value >= baseline; cd=5 weakly dominates cd=0 cell-by-cell — better in 3/9,
worse in 0/9), shifting outcomes toward the heavier attacker. This overturns the
single-seed pessimism in the prior commit.

CORRECTED PREMISE: p1-29h's cited "20 captures / 0 eliminations" was a
single-GEOMETRY artifact (the dist=5/w=4 cell). Eliminations already occur in
5/9 baseline geometries — the lever RAISES an existing rate (5/9 -> 8/9), it
does not unlock elimination. p1-29h elimination bullet updated to reflect this.

DELIBERATELY NOT DONE (honest, evidence-bounded):
- No cd value authored into combat_balance.json — the lift is gridded-micro-
  surface only (9 geometries, 1 seed each); a live balance value needs the
  full-game 10-seed batch (tools/p1-survival-score.py, the multi-seed-tournament
  rule). The cd response is also an unexplained hump (cd=8 -> 6/9). Lever stays
  defaulted off.
- p1-29d NOT re-scored as converged — its gate is the full-game multi-gate
  scorecard (D1: P1 elim<=T100 OR stalled, 10/10), a different + heavier surface
  not run this pass. The brief's "re-score p1-29d" is gated on that measurement.

Tests (apricot): gridded harness non-ignored 1/1; ensemble + sweep run via
--ignored; cargo check --workspace 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:38:27 -07:00
autocommit
a49e24c969 feat(p1-29i): post-capture refound-suppression lever (defaulted off) + measurement
p1-29h Phase 2 isolated the elimination wall. A per-player diagnostic added to
the gridded harness shows per_player_min_cities=[1,1] — both empires ARE pressed
to one city but neither is eliminated; the loser instantly refounds. That
[1,1]-but-no-kill signal makes refound-suppression the correctly-targeted lever.

Lever (data-driven, Rail 2, DEFAULTED OFF — zero live-balance change):
- mc_core::CombatBalance::refound_suppression { cooldown_turns: u32 } (default 0).
- mc_turn::PlayerState::last_city_lost_turn stamped at the capture site.
- processor::try_found_city refuses a replacement while
  turn - last_city_lost_turn < cooldown_turns (0 short-circuits).

Measurement (single-seed sweep, 160t): cooldown 0->0 elim, 5->1, 10->0, 20->0,
40->0. The lone cd=5 elimination is NOT credited as convergence — it is
single-seed noise: non-monotone (more suppression -> fewer eliminations), the
WINNER flips slot-to-slot across the sweep (chaotic-snowball perturbation, not a
lever response), and it fails p1-29h's literal gate. The lever demonstrably
suppresses refounding (founds trend down) but suppression ALONE does not robustly
convert captures into eliminations. Honest measured-negative per the brief; NO
non-zero combat_balance.json value authored, p1-29d NOT re-scored as converged.

Tests (apricot): mc-core 262 lib; gridded harness non-ignored 1/1; cargo check
--workspace 0; cargo test --workspace --no-run 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:29:04 -07:00
autocommit
05ad5fae86 feat(p3-10b): wire lair siege into the live turn loop
tick_siege/decay_siege + SiegeState (persisted on GameState::siege_pressure)
shipped earlier but were dead code — never called from the turn loop. This
adds the per-turn lair-siege phase + a data-driven config loader.

- mc-turn/src/lair_siege.rs: LairSiegeConfig loader from
  lair_combat_modes.json::siege.siege_pressure (per-tier resistance + tuning,
  Rail 2 — consumes authored data, no fabricated per-lair siege_resistance);
  tick_one_lair pure helper; LairSiegeEvent.
- processor::process_lair_sieges (Phase 5d in step): iterates siege_pressure,
  detects besieger adjacency, ticks/decays, surrenders + rolls loot + drops
  the entry, logs to TurnResult::lair_siege_log. authored_lair_siege_config()
  embeds the JSON build-time + OnceLock (same pattern as encounter_rates).
- resolve_lair_combat Siege arm stays single-shot-NotImplemented BY DESIGN
  (multi-turn state can't flow through LairAssaultParams -> AssaultOutcome;
  same precedent as resolve_raid's separate entry) — doc'd to point at the
  live per-turn phase.

Honest boundary: nothing in the live loop INITIATES a siege yet (a player
picks Siege via the GdLair bridge / UI mode picker — godot-ui follow-up that
owns GdLair::begin_siege). The phase is exercised by tests + the future
bridge; mechanic is live, initiation is the remaining dependency. GDExt
begin_siege/siege_pressure bullet stays open (K=4/5).

Tests (apricot): lair_siege lib 5/5 (incl. named pressure_accumulates_then_
surrenders + pressure_decays_when_unattended) + config loaders 3; integration
lair_siege_phase 4/4 (through processor.step + save/load round-trip); mc-turn
240 lib, mc-combat 143 lib, serde_roundtrip 6/6; cargo check --workspace 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 16:10:58 -07:00
autocommit
cd9f2b9e0d feat(p2-48): author end_game_summary.tscn + wire it to the game-over flow
The EndGameSummary script existed but its .tscn did not, and nothing
instantiated it — the summary scene was orphaned, so no screen showed
at game-over for any GameOverReason. Author end_game_summary.tscn with
all 14 @onready %-named nodes the script requires, and instantiate it
in main.gd under a persistent CanvasLayer (layer 25): the control
self-connects to EventBus.game_over and unhides itself for every reason
(LastSurvivor, ConditionMet, TurnLimit, Resigned).

Add the three feature-local GameState members the footer handlers
dereference (read_only_mode, pending_replay_game_id, clear()) — without
them 2/5 footer buttons null-crashed; referenced only by this feature
(+ replay_viewer reads pending_replay_game_id). read_only_mode is a
flag with no enforcement consumer yet (honest partial).

Rewrite end_game_summary_proof from the old _draw() mockup to BOOT the
real scene for all four GameOverReason variants; add GUT tests
test_award_computation (3/3) + test_end_game_footer_actions (5/5).

GUT full default suite on apricot: 645/693 pass, only deltas vs the
pre-change baseline are +8 (these new tests) and -1 (a pre-existing
detached-node bug fixed in test_statistics_modal) — no regressions.
All four end-game variants proof-rendered on apricot.lan and reviewed.

Production game_over emission (mc-turn) and a GdReplayPlayer::get_awards
bridge stay Rust-lane; the live awards path shows the pending notice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:57:45 -07:00
autocommit
843db4116a feat(p2-47): author statistics.tscn wrapper so the 5-tab modal opens
The 709-line self-building StatisticsModal script existed but its scene
asset did not — world_map.gd and ingame_menu.gd both push_overlay
"statistics.tscn", so F9 / info-button / Stats-menu dead-ended at a
missing resource. Author the thin one-node Control wrapper with the
script attached; both entry points now open the modal.

Also add statistics_proof.{gd,tscn} (boots the real wrapper, seeds a
4-clan 12-turn StatsTracker fixture, captures one screenshot per tab)
and fix a pre-existing detached-node bug in test_statistics_modal's
close-fallback test (called _ready()+_on_close() on a node never added
to the tree). GUT: test_statistics_modal 8/8 green on apricot. All 5
tabs proof-rendered on apricot.lan and reviewed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:50:00 -07:00
autocommit
5503f04b25 feat(p3-10a): GdLair::assault GDExtension bridge over resolve_assault
New api-gdext/src/lair.rs exposing GdLair::assault(...) #[func], registered
in lib.rs. Mirrors the GdLootRoller JSON-string pattern (Rail 3 — file IO
stays in GDScript, the bridge only marshals):

  assault(attackers_json, defenders_json, loot_tier_json, lair_id,
          lair_tier, turn_seed, defender_terrain_bonus) -> Dictionary

GDScript reads public/resources/lairs/loot/tier_NN.json + builds the
stack/defender JSON; the bridge parses (build_params — Godot-free,
unit-tested) into LairAssaultParams, calls mc_combat::resolve_assault, and
returns a cleared/repulsed/withdrawn outcome Dictionary (error, never panic,
on malformed JSON).

The Assault/Siege/Raid UI mode picker is a godot-ui follow-up, noted in the
objective — NOT built in this Rust lane.

Tests (apricot): GdLair 6/6 (cleared+guaranteed-loot, repulsed via real
wild_combat_stats(8,huge,carnivore), withdrawn, params-assembly, 2 parse-error
paths); api-gdext lib 29/29; cargo check --workspace exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:49:46 -07:00
autocommit
cd339ff7dd feat(p2-57c): wire production-quality consumer into the live spawn path
Closes p2-57c bullet-2's apply half and p2-57b's pipeline live-loop gap.

- mc_units::UnitStats gains a flattened CombatStats { hp, max_hp?, attack,
  defense, ranged_attack, range } (serde-default; the unit JSON already
  authors these — the catalog was dropping them on load).
- mc-turn processor::resolve_spawn_combat resolves a spawning unit's base
  combat line from the units catalog BY unit_id, then applies the stamped
  QualityTier delta from combat_balance.quality_deltas (Rail 2). Both
  try_spawn_unit and spawn_unit_typed call it.
- Fixes a latent live bug: try_spawn_unit hardcoded 60/12/1 on EVERY unit
  type, so queued non-warriors spawned with warrior stats.

Honest scope: the apply half is now live; the stockpile->tier STAMP source
(per-city typed ResourceStockpile p2-57a + per-unit gating p2-57b) is not
wired into process_city_production, so live units carry quality:None today.
Bullet 2 stays partial (apply proven; stamp-source half gated on infra).

Tests (apricot): mc-turn 235 lib, mc-units 12, mc-city 262, mc-combat 217,
mc-core 143; new quality_spawn_live_processor 3/3; cargo check --workspace 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:43:38 -07:00
autocommit
de21f96fe1 docs(plan): finish-game1 gap analysis (28 objectives) + plan of record
- true-state/path-forward/blockers section appended to every open objective
- status corrections: p2-48 done→partial (end_game_summary.tscn orphaned, over-claim); p2-72b blocked_by cleared (Path 2 chosen)
- .project/FINISH_GAME1_PLAN.md: 5 waves (wiring/lairs/AI-convergence/architecture/CI), AI refound-suppression flagged as the at-risk long pole, audio+paid-sprites+guide as accepted-open
- objectives.json regenerated

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:50:59 -07:00
autocommit
3821b297bf docs(p2-65): 📝 record Phase-3 cascade verdict + resume note for next session
game_state.rs references 6 mc-turn sibling DATA types in field decls (not
turn-logic calls): RansomQueue, 6 combat_event structs + TurnResult,
CapturePosture, PatrolOrder, CombatBalance (already mc-core). Phase 3 is a
2-step — relocate these to mc-core first (Phase 0c pattern), then move
game_state.rs to mc-state behind a pub-use shim. Save-format round-trip is the
gate. p2-72 gated on full p2-65, 2+ sessions out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:16:56 -07:00
autocommit
eaec14406d refactor(p2-65): 🧱 Phase 0c — relocate 4 tactical data types to mc-core (break mc-state cycle vector)
Pre-flight for the mc-state extraction found Phase 0 under-scoped: GameState/
PlayerState carry 4 mc_ai::tactical typed fields (TacticalMemory, BuildingPriors,
TacticalUnitSpec, TacticalBuildingSpec), so mc-state (forbids mc-ai dep) could
not hold GameState until they moved. All verified data-only.

- New mc_core::tactical_types holds the 4 structs verbatim + is_buildable +
  default_tier + 3 unit tests (serde round-trip, memory state-machine, gates).
- mc-ai memory.rs/state.rs re-export from mc-core (pub use) — every existing
  mc_ai::tactical::* path still resolves; duplicate defs/tests removed.
- mc-turn game_state.rs 5 type refs → mc_core::* (incl ScoringWeights),
  forward-looking for the game_state→mc-state move. Save format byte-identical.

Independently kills the irregular mc-turn→mc-ai data dependency the spec Context
flags. Gates green on apricot: check --workspace --tests clean; mc-core
tactical_types 3/3; mc-ai all green; mc-turn 235 lib; mc-player-api 126+integ.
Pre-existing five_players_overflow failure (MAX_PLAYERS 4→12 stale; touches no
moved type; fails on HEAD) is not a regression. p2-65 stays stub (mc-state crate
not yet created — Phase 1+).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 12:14:13 -07:00
autocommit
4f2f15219a feat(p1-29h): 🧪 gridded fair-duel surface — army-lock engages, elimination measured-negative
Phase 2 (bullets 5+6). Builds option (b): a gridded GameState Rust harness so
compute_vision populates and both combatant slots run the scripted-default
controller through the persistent drive_ai_slot mem::take seam (fair two-
scripted:default — a passive ender drives every other slot via apply_end_turn).

Findings on the fair surface (160t): lock ENGAGES (ever_committed=true,
committed_with_target=true), 20 CityCaptured fire, but 0 eliminations and both
empires grow (15 vs 22 cities). min_total_cities never dips below start despite
20 captures (38 refounds offset every loss). Verdict: targeting lock works;
bottleneck is capture-stickiness / refound-suppression — confirms p1-29d's
indecisive-war root cause + the spec's weight-insensitivity risk, empirically.

- gridded_fair_surface_engages_army_lock: green always-on guard (vision>0 +
  lock engages) — the Phase-1-on-production-seam coverage the gridless
  p1_29h_persistent_memory_seam guard could not provide.
- fair_scripted_duel_elimination_measurement (#[ignore]): records the 160t
  signals; elimination NOT asserted (measured-negative, bullet 3 stays open).

p1-29h stays partial K=5/6; resume note redirects next wave at refound-
suppression, not the targeting lock. mc-player-api 126 + integration green;
cargo check --workspace --tests clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 11:50:22 -07:00
autocommit
e48a6d5acf docs(p2-67-followup): flip MCTS tactical TreeState impl to done (Wave-3 audit)
Audit confirmed the full surface already in-tree (TacticalTreeState +
apply_tactical_action all 14 variants + score_for_player [0,1] + generic
most_visited_root_action_cloned + bench wiring). No rebuild.

Empirical gates on apricot (CARGO_TARGET_DIR shared):
- cargo check --workspace clean; mc-ai 278/278 lib pass
- claude_real_mcts_vs_heuristic_ais_transcript: 283s/293s (<300s), Claude
  #1 (68 cities vs 19 / eliminated), action diversity move×453/queue_unit×48/
  queue_building×3/found×1, two runs byte-identical (determinism).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 11:37:01 -07:00
autocommit
a7a1fc89f3 test(mc-player-api): persistent tactical-memory seam guard + p1-29h → partial
Phase 2 of p1-29h. Adds the only regression coverage for the persistent
memory seam: `apply_end_turn → drive_ai_slot` (the mem::take + write-back
that survives the per-turn TacticalState snapshot). The mc-ai movement test
calls decide_movement directly and never touches this seam, so this guards
the dispatch threading itself — drives 60 turns without panic, reads the
memory field back, asserts coherence (committed ⇒ has a target).

Phase 2 measurement (bullets 3/5/6) NOT met — objective flips stub → partial
(K=3/6, bullets 1/2/4 cited from Phase 1's green mc-ai tests). Verified
finding recorded in the objective + test: the persistent army-lock lives ONLY
on the drive_ai_slot/apply_end_turn path; both existing full-game drivers
bypass it (p1-clean-baseline.py clones memory via suggest(); full_game_transcript
passes a transient default). The seam test's gridless GameState::default()
fixture yields an EMPTY vision set (sees_own_city=false, visible_tiles=0), so
no enemy city is ever visible and want_attack never fires — engagement needs a
real grid+vision surface (Godot autoplay / AUTO_PLAY_ALL_AI driving end_turn).
Lock engagement on full visibility is already proven by the mc-ai movement test.

cargo check --workspace --tests green on apricot; seam test 1/1 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:48:10 -07:00
autocommit
ae06b42e15 feat(api-gdext): GdGameState::civic read-side query surface (p3-05a-gdext-bridge)
Adds the read-side civic query the civics UI needs, completing the bridge
split out of p3-05a. `GdGameState::civic(pi, axis) -> Dictionary` returns
`{ choice, anarchy_turns_remaining, in_anarchy }`, riding on GdGameState
next to request_civic_switch/get_anarchy_turns_remaining (the canonical
per-player accessor surface — there is no per-GdPlayer shim in the tree).

`choice` is serialised via serde with quotes stripped (symmetric with the
request_civic_switch parse path): named AxisChoice variants → snake_case,
Anarchy → "anarchy", untagged Custom(id) → id verbatim. Unknown axis or
out-of-range pi → empty Dictionary.

Tests (green headless on apricot.lan):
- engine/tests/unit/civics/test_civic_query_bridge.gd — 7/7
- engine/tests/integration/test_gdextension_contract.gd — civic in the
  GdGameState method contract; test_gd_game_state_has_expected_methods passes
- cargo check --workspace green; build-gdext.sh release build green

Objective p3-05a-gdext-bridge: stub → done (4/4 acceptance, cited).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 08:54:10 -07:00
autocommit
461588bad8 docs(objectives): collect-and-analyze sweep for shipwright batch sign-offs
Re-verified p1-05 and p1-38 follow-up batch acceptance against the fresh
committed-build smoke batch on apricot (20260604_011524/smoke, built
7d77fe728 from origin/main, 10 seeds T300, completed 2026-06-04 08:28).

p1-05: bullet 2 (personality_win_balance) satisfied upstream via p0-02
(done); bullet 1 (luxury variance >=3 distinct/seed) still fails -- no
p0-08 tempo in main (median end-turn is stochastic same-SHA variance, not
a build property), no distinct-luxury-variance extraction tool, scalar
read fails on seeds 1/5/9; bullet 3 out of fence. Stays stub.

p1-38: coupled mode still gated static_terrain in main (7d77fe728 and
5c97ce3f9), run-headless-batch.sh absent, no forest-tile metric emitted,
flip is a forbidden source edit. Un-runnable under no-edit committed-build
fence, not a balance miss. Stays stub.

Per feedback_batch_attribution_discipline + feedback_balance_philosophy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 08:44:15 -07:00
autocommit
3f6a4a09cd docs(objectives): wave-1 status flips + index regen + new specs + tooling
- p3-05e, p2-56 -> done (ratified); p0-26b, p2-57b, p2-57c, sprites -> partial; honest evidence per objective-integrity
- new specs: p1-29h (stateful tactical decisiveness), p2-57c (mc-units quality consumer)
- tools/regen-objectives-index.py: reusable objectives.json generator (claire offline); index regenerated (303 obj, missing 7->1)
- deploy-guide.sh: cloudflare-pages mode (p2-18, dry-run only; live publish held for approval)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 04:40:40 -07:00
autocommit
61ba6298af feat(audio): generated audio.json manifest + ledger cleanup (p2-16)
- audio-generate-manifest.py: derive data/audio.json from library + subscription (SSoT, not hand-authored), drift-gated in audio-validate.py
- sources.csv: pruned 13 corrupt rows (now 106 == on-disk files); audio-licenses-render guard rejects non-audio/ paths
- all 106 streams resolve, schema-valid; unblocks guide @data/audio.json import
- p2-16: held in_progress pending human listen-test

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 04:40:23 -07:00
autocommit
f88e9b072e feat(sprites): OSS standin coverage p2-23..27 (536 PNGs) + xi-v11 charter
- 536 game-icons.net CC-BY-3.0 standins fill every renderer slot (units/buildings/wonders/city-tiers), id-keyed flat layout
- LICENSES.md (536 ledgered rows, SHA256), STANDINS.md, sprite-license-audit passes
- build_standins.py rewritten data-driven off manifest + icon_rules.json (replaces mapping.json)
- juggernaut-xi-v11 added to approved model list (charter + 2 instruction modules), operator decision
- objectives p2-23..27 + p2-22: partial (standin coverage; final art deferred)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 04:40:14 -07:00
autocommit
864095c136 docs(objectives): 📝 Add/clarify objectives and implementation details for the pioneer escort mechanic (P2-59) in the project's README and dedicated documentation
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 04:29:08 -07:00
autocommit
f2494b6f6f docs(objectives): 📝 revise survival and learned controller bridge objectives documentation
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 04:06:43 -07:00
autocommit
f87d9ef52a docs(objectives): 📝 Update survival objective documentation with baseline run status and findings for P1-29D-P1
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 03:50:10 -07:00
autocommit
079964e2f3 docs(objectives): 📝 Update survival objective documentation with new operator ruling and cleaned surface requirements
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 03:32:40 -07:00
autocommit
eebe37afae docs(objectives): 📝 Update survival objective scoring criteria and status documentation
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 02:17:30 -07:00