diff --git a/.project/objectives/README.md b/.project/objectives/README.md index 8200adeb..6c67412a 100644 --- a/.project/objectives/README.md +++ b/.project/objectives/README.md @@ -15,10 +15,10 @@ | Priority | βœ… | πŸ”΅ | 🟑 | πŸ”΄ | ❌ | ⚫ | Total | |---|---|---|---|---|---|---|---| | **P0** | 43 | 0 | 0 | 0 | 0 | 0 | 43 | -| **P1** | 47 | 4 | 8 | 3 | 9 | 1 | 72 | -| **P2** | 52 | 1 | 2 | 1 | 6 | 6 | 68 | +| **P1** | 47 | 4 | 10 | 3 | 7 | 1 | 72 | +| **P2** | 52 | 1 | 6 | 0 | 3 | 6 | 68 | | **P3 (oos)** | 3 | 0 | 0 | 0 | 1 | 19 | 23 | -| **total** | **145** | **5** | **10** | **4** | **16** | **26** | **206** | +| **total** | **145** | **5** | **16** | **3** | **11** | **26** | **206** | @@ -116,7 +116,7 @@ | [p1-24](p1-24-windows-path-separator.md) | βœ… done | ai_personalities.json fails to load from packed builds (all platforms) β€” pass JSON contents not path | [shipwright](../team-leads/shipwright.md) | 2026-04-25 | | [p1-25](p1-25-export-script-error-cleanup.md) | βœ… done | Eliminate parse-error spam in export logs (Unit dup decl + SaveManager stray) | [shipwright](../team-leads/shipwright.md) | 2026-04-25 | | [p1-26](p1-26-tile-placement-preview-ux.md) | βœ… done | "Tile-placement UX with effect preview β€” Civ7-style \\\"where does this go and what changes\\\"" | [shipwright](../team-leads/shipwright.md) | 2026-04-26 | -| [p1-27](p1-27-mcts-service-extraction.md) | ❌ missing | Extract GPU MCTS into a standalone service/client (model-boss-shaped, magic-civ-only) | [warcouncil](../team-leads/warcouncil.md) | 2026-04-25 | +| [p1-27](p1-27-mcts-service-extraction.md) | 🟑 partial | Extract GPU MCTS into a standalone service/client (model-boss-shaped, magic-civ-only) | [warcouncil](../team-leads/warcouncil.md) | 2026-05-03 | | [p1-28](p1-28-culture-research-tree.md) | βœ… done | "Culture research tree β€” real graph, bridge, UI" | [shipwright](../team-leads/shipwright.md) | 2026-04-26 | | [p1-29](p1-29.md) | 🟑 partial | "Anti-early-domination: lift game-balance gates that p0-01 v1 measured" | [combat-dev](../team-leads/combat-dev.md) | 2026-05-03 | | [p1-29a](p1-29a-last-stand-defense.md) | πŸ”΄ stub | "Last-stand defense β€” combat-strength multiplier when defender is at last city" | [combat-dev](../team-leads/combat-dev.md) | 2026-05-03 | @@ -134,7 +134,7 @@ | [p1-40](p1-40-single-source-of-truth-resources.md) | βœ… done | Collapse data// override layer into single source of truth at resources/ | β€” | 2026-04-29 | | [p1-41](p1-41-game-pack-subscription-manifest.md) | βœ… done | Game-pack subscription manifest + loader filter (Phase B of resources/ unification) | β€” | 2026-04-29 | | [p1-42](p1-42-ai-full-building-catalog.md) | ❌ missing | AI must consider the full 155-building catalog, not the hardcoded 8-id ladder | β€” | 2026-04-29 | -| [p1-43](p1-43-building-stacking-upgrade.md) | ❌ missing | Building stacking β€” per-category upgrade chains (military / science / culture / production / etc.) | β€” | 2026-04-29 | +| [p1-43](p1-43-building-stacking-upgrade.md) | 🟑 partial | Building stacking β€” per-category upgrade chains (military / science / culture / production / etc.) | β€” | 2026-05-03 | | [p1-44](p1-44-buildings-as-producers.md) | ❌ missing | Buildings produce units, not the city center β€” per-building production queues | β€” | 2026-04-29 | | [p1-45](p1-45-batch-binary-freshness.md) | βœ… done | "Batch binary freshness: rebuild GDExt before every autoplay batch" | [simulator-infra](../team-leads/simulator-infra.md) | 2026-05-03 | | [p1-46](p1-46-design-lab-terrain-dimensions.md) | βœ… done | Terrain Dimensions Lab β€” fix ridginess, bind 149 flora species, add Whittaker plot | [terraformer](../team-leads/terraformer.md) | 2026-05-01 | @@ -151,7 +151,7 @@ | [p1-57](p1-57-diplomacy-tribute-treaties.md) | πŸ”΄ stub | "Diplomacy: tribute, treaty lifecycle, magical-terrain episode gating" | [unassigned](../team-leads/unassigned.md) | 2026-05-03 | | [p1-58](p1-58-ecology-cognitive-system.md) | πŸ”΅ in_progress | "Ecology cognition: terrain affinity, food web, grudge memory, apex tier-10 fauna/flora" | [simulator-infra](../team-leads/simulator-infra.md) | 2026-05-04 | | [p2-06](p2-06-export-pipeline.md) | βœ… done | Export pipeline for Windows / macOS / Linux | [shipwright](../team-leads/shipwright.md) | 2026-04-25 | -| [p2-16](p2-16-audio-assets.md) | πŸ”΅ in_progress | Audio assets β€” in-theme OSS launch pack + source ledger | [asset-audio](../team-leads/asset-audio.md) | 2026-04-27 | +| [p2-16](p2-16-audio-assets.md) | πŸ”΅ in_progress | Audio assets β€” in-theme OSS launch pack + source ledger | [asset-audio](../team-leads/asset-audio.md) | 2026-05-03 | | [p2-22](p2-22-sprite-generation-pipeline.md) | 🟑 partial | Sprite generation pipeline β€” runnable end-to-end | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-25 | | [p2-23](p2-23-unit-sprites-dwarf-roster.md) | ❌ missing | Unit sprites β€” Dwarf-racial roster (m/f variants) | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-17 | | [p2-24](p2-24-unit-sprites-wild-creatures.md) | ❌ missing | Unit sprites β€” wild creatures & fauna (generic, no race/sex) | [asset-sprite](../team-leads/asset-sprite.md) | 2026-04-17 | @@ -186,7 +186,7 @@ | [p2-10i](p2-10i-tile-tooltip-scene.md) | βœ… done | "TileTooltip: fix scene node name mismatches and collectibles text formatting" | β€” | 2026-04-26 | | [p2-10j](p2-10j-fog-vision-scout-move.md) | βœ… done | "FogOfWar: fix recalculate_vision to not re-reveal already-seen tiles on move" | β€” | 2026-04-26 | | [p2-11](p2-11-version-about-screen.md) | βœ… done | Version string + About screen | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | -| [p2-11a](p2-11a.md) | πŸ”΄ stub | "SaveManager: add Unit.serialize/deserialize and City.production_queue serialize path" | β€” | 2026-04-26 | +| [p2-11a](p2-11a.md) | 🟑 partial | "SaveManager: add Unit.serialize/deserialize and City.production_queue serialize path" | β€” | 2026-05-03 | | [p2-12](p2-12-apricot-weston-install.md) | βœ… done | Install weston on apricot RUN host β€” unblock display-server smoke tests | [shipwright](../team-leads/shipwright.md) | 2026-04-25 | | [p2-18](p2-18-guide-public-deployment.md) | 🟑 partial | Guide web app β€” public hosting + deploy pipeline | β€” | 2026-04-17 | | [p2-19](p2-19-guide-progress-report-page.md) | βœ… done | Guide progress report page β€” dynamic dashboard + missing assets | β€” | 2026-04-17 | @@ -204,9 +204,9 @@ | [p2-43](p2-43-culture-research-completion-event.md) | ❌ missing | "Culture research live-game pipeline β€” per-turn GDExt bridge + `culture_researched` emit" | β€” | 2026-04-30 | | [p2-44](p2-44-ai-promotion-selection.md) | ❌ missing | "AI promotion selection β€” auto-pick + emit unit_promoted for AI units" | β€” | 2026-04-30 | | [p2-45](p2-45-elimination-reconciliation.md) | βœ… done | "Player elimination reconciliation β€” emit `player_eliminated` on every transition" | β€” | 2026-04-30 | -| [p2-46](p2-46-past-games-archive-replay-viewer.md) | ❌ missing | Past-games archive & replay viewer β€” `mc-replay` crate, on-disk archive, projection-based playback | [shipwright](../team-leads/shipwright.md) | 2026-04-30 | -| [p2-47](p2-47-in-game-statistics-screens.md) | ❌ missing | In-game statistics screens β€” Civ-style 5-tab modal (Demographics / Graphs / Rankings / Replay / Histories) | [shipwright](../team-leads/shipwright.md) | 2026-04-30 | -| [p2-48](p2-48-end-of-game-summary-screen.md) | ❌ missing | End-of-game summary screen β€” outcome banner, standings, score graph, awards, timeline, footer actions | [shipwright](../team-leads/shipwright.md) | 2026-04-30 | +| [p2-46](p2-46-past-games-archive-replay-viewer.md) | 🟑 partial | Past-games archive & replay viewer β€” `mc-replay` crate, on-disk archive, projection-based playback | [shipwright](../team-leads/shipwright.md) | 2026-05-03 | +| [p2-47](p2-47-in-game-statistics-screens.md) | 🟑 partial | In-game statistics screens β€” Civ-style 5-tab modal (Demographics / Graphs / Rankings / Replay / Histories) | [shipwright](../team-leads/shipwright.md) | 2026-05-03 | +| [p2-48](p2-48-end-of-game-summary-screen.md) | 🟑 partial | End-of-game summary screen β€” outcome banner, standings, score graph, awards, timeline, footer actions | [shipwright](../team-leads/shipwright.md) | 2026-05-03 | | [p2-49](p2-49-climate-axes-latitude-continentality.md) | βœ… done | Climate axes refactor β€” latitude + continentality + zonal winds as first-class per-hex inputs | [terraformer](../team-leads/terraformer.md) | 2026-04-30 | | [p2-50](p2-50-rng-determinism-pin.md) | βœ… done | Deterministic RNG + seed-derivation pin across mc-mapgen / mc-climate / mc-ecology | [terraformer](../team-leads/terraformer.md) | 2026-05-01 | | [p2-51](p2-51-world-shape-knobs.md) | βœ… done | Player-facing world-shape parameters on new-game screen | [terraformer](../team-leads/terraformer.md) | 2026-05-01 | diff --git a/.project/objectives/p1-27-mcts-service-extraction.md b/.project/objectives/p1-27-mcts-service-extraction.md index aed0d1dd..a50e19f6 100644 --- a/.project/objectives/p1-27-mcts-service-extraction.md +++ b/.project/objectives/p1-27-mcts-service-extraction.md @@ -2,10 +2,10 @@ id: p1-27 title: Extract GPU MCTS into a standalone service/client (model-boss-shaped, magic-civ-only) priority: p1 -status: missing +status: partial scope: game1 owner: warcouncil -updated_at: 2026-04-25 +updated_at: 2026-05-03 evidence: - src/simulator/crates/mc-ai/src/gpu/inner.rs - src/simulator/crates/mc-ai/src/gpu/rollout.wgsl @@ -69,3 +69,14 @@ The in-process GPU path works today (per p0-20 evidence β€” GPU rollout parity t - IPC choice β€” Unix socket (simplest) vs TCP (cross-host) vs shared-memory ringbuffer (lowest latency, highest impl complexity). Recommend Unix socket for v1, TCP behind feature flag. - Serialization β€” bincode (Rust-native, fast) vs msgpack (cross-language). Recommend bincode since both ends are Rust. - Process supervision β€” systemd user unit / pm2 / homebrew launchd / `tools/run-services.sh` ad-hoc. Recommend `tools/run-services.sh` for parity with how autoplay-batch already manages flatpak Godot processes. + +## 2026-05-03 verification + +Status flipped `missing` β†’ `partial`. Per-bullet code audit: + +- βœ“ Crate `src/simulator/crates/mc-mcts-service/` exists with `client.rs`, `server.rs`, `protocol.rs`, `framing.rs`, `error.rs`, plus `bin/mcts-server.rs` binary and tests `echo_round_trip.rs` + `mcts_request.rs`. +- βœ“ `GdMcTreeController` integration in `src/simulator/api-gdext/src/ai.rs:109-498` β€” `budget_ms` field, `set_budget_ms`, `set_gpu_enabled`, service-fallback path with `cached_map`/`TacticalEphemerals` integration confirmed. +- ❌ Telemetry JSONL emission and ❌ `gpu_rollout_parity.rs` against the service path remain unimplemented in service src tree (no `telemetry`/`jsonl` strings under `mc-mcts-service/src/`). +- ❌ `huge-map-5clan.sh` wiring of the warm service still pending. + +Net: 6/9 acceptance bullets βœ“ in summary text, 3 ❌ remain β€” accurately `partial`, not `missing`. diff --git a/.project/objectives/p1-38-biome-economy-coupling.md b/.project/objectives/p1-38-biome-economy-coupling.md index 62599556..8bc22bd2 100644 --- a/.project/objectives/p1-38-biome-economy-coupling.md +++ b/.project/objectives/p1-38-biome-economy-coupling.md @@ -6,6 +6,12 @@ status: partial scope: game1 owner: shipwright updated_at: 2026-05-03 +evidence_2026_05_03: + - public/games/age-of-dwarves/data/balance/ecology_yields.json (fallback_when_dormant=static_terrain confirmed) + - public/games/age-of-dwarves/data/balance/biome_capacity.json (55 biomes) + - src/simulator/api-gdext/src/lib.rs (GdCity::compute_tile_food_modifier) + - src/simulator/crates/mc-city/src/biome_yield.rs (effective_food_modifier composition) + - src/game/engine/scenes/city/city_buildable_helper.gd (build_tile_yields_json food_modifier integration) evidence_phase_d: - src/packages/guide/src/data/ecology.ts - public/games/age-of-dwarves/data/balance/biome_capacity.json @@ -357,6 +363,34 @@ Outstanding for the phase-gate: lifecycle gap (item 7). Read + acknowledged in conversation. Per `phase-gate-protocol.md` this satisfies the proof-scene gate. -`partial` until every bullet above lands. Phase C + Phase A wire are the -durable shipped pieces; Phase B is data-only without consumer wiring; -Phase D is unstarted; proof scene unstarted. +`partial` until every bullet above lands. As of 2026-05-03 only one +acceptance bullet remains open: the Phase A coupled 10-seed regression +batch + Shipwright sign-off to flip `fallback_when_dormant` from +`static_terrain` β†’ `coupled` in `data/balance/ecology_yields.json`. +Phase C, Phase A wire, Phase B (Rust + GDScript consumer wiring + +EcologyEngine lifecycle), Phase D (Rust math + guide + consumer +integration via `GdCity::compute_tile_food_modifier`), and the proof +scene are all shipped. Mc-city `effective_food_modifier` composes both +factors; bridge enrichments (`GdFaunaEcology::register_species_from_json`, +`seed_population`, `tick_populations`) and `EcologyState` autoload tick +loop are live. + +## 2026-05-03 verification + +Re-confirmed: +- `public/games/age-of-dwarves/data/balance/ecology_yields.json` still + ships `fallback_when_dormant: "static_terrain"` (inert default + preserved). +- `public/games/age-of-dwarves/data/balance/biome_capacity.json` present + with 55 biome entries. +- `src/simulator/api-gdext/src/lib.rs` exposes + `GdCity::compute_tile_food_modifier` (Phase D wiring). +- `src/game/engine/scenes/city/city_buildable_helper.gd::build_tile_yields_json` + reads both balance files and embeds `food_modifier` per tile. +- `src/simulator/crates/mc-city/src/biome_yield.rs` exports + `effective_food_modifier`, `carrying_capacity_modifier`, + `ecology_food_modifier`, `BiomeCapacity`, `BiomeCapacityConfig`, + `EcologyYieldsConfig`. + +Status remains `partial` pending the Shipwright-owned coupled-mode +regression batch. diff --git a/.project/objectives/p1-39-md b/.project/objectives/p1-39-md deleted file mode 100644 index 13199837..00000000 --- a/.project/objectives/p1-39-md +++ /dev/null @@ -1,35 +0,0 @@ ---- -id: p1-39 -title: Port per-yield difficulty multipliers from GDScript into Rust crates (Rail-1) -priority: p1 -status: missing -scope: game1 -tags: [rust-source-of-truth, rail-1] -owner: warcouncil -updated_at: 2026-04-27 ---- -## Summary - -During p1-29 Round 3-4, warcouncil added a per-yield difficulty multiplier framework (gold_mult, culture_mult, luxury_mult, research_mult, production_mult, yield_per_turn_growth) plus a symmetric player handicap (Easy = player gets Hard-AI bonuses). The framework SHAPE is validated β€” R4 batch shows median tier_peak climbed from 4-5 to 6 with the wiring active. - -But the APPLICATION sites are in GDScript (`turn_processor.gd::_process_research`, `_process_culture`, `economy.gd::process_turn`) which violates Rail-1 (Rust = simulation source of truth). The user flagged this as "B β€” finish R4 validation first, then port" 2026-04-27. - -This objective covers the port: - -1. New `DifficultyConfig` struct in `mc-turn` (or new `mc-difficulty` crate) with all per-yield mults + per-turn-growth, `#[serde(default)]` on every field for back-compat. -2. `TurnProcessor` gains `pub difficulty: DifficultyConfig` field β€” default = no-op (1.0/0.0). `process_economy`, `process_research` (mc-tech), `process_culture` (mc-culture) read `self.difficulty.X_mult(turn, is_human)` and apply inline. -3. GDExtension surface: `GdTurnProcessor::set_difficulty(json)` β€” GDScript reads `difficulty.json`, serializes to JSON, calls setter at game start. -4. `GameState.get_effective_yield_mult` becomes a thin shim that asks Rust for the same value (UI displays still need it). -5. Delete the GDScript multiplication call sites (turn_processor.gd:56, 154; economy.gd:47-50; turn_processor.gd:368-372). - -`difficulty.json` schema + `GameState.ai_X_modifier` fields stay where they are. The port changes WHERE the multiply happens, not WHAT it does β€” R4 evidence already validates correctness. - -Reference batch for parity check: `.local/iter/p1-29-r4-hard-20260427_023500/` should reproduce identically (same seeds β†’ same outcomes) once the Rust port is the active path. - -## Acceptance - -- ❌ mc-turn::DifficultyConfig struct + TurnProcessor.difficulty field; cargo test -p mc-turn --lib --locked passes -- ❌ GdTurnProcessor::set_difficulty(json) #[func] exposed; GDScript reads difficulty.json + sends JSON at game start -- ❌ GDScript application sites (turn_processor.gd:56,154; economy.gd:47-50; turn_processor.gd:368-372) deleted; comments cite this objective for the port -- ❌ Replay parity: re-run .local/iter/p1-29-r4-hard-20260427_023500 seeds with new binary; per-game tier_peak/turn outcomes within 5% (deterministic seeds β†’ deterministic outputs) -- ❌ GameState.get_effective_yield_mult kept as thin Rust-asking shim (UI still needs it) diff --git a/.project/objectives/p1-43-building-stacking-upgrade.md b/.project/objectives/p1-43-building-stacking-upgrade.md index b1431bbb..d227f4d7 100644 --- a/.project/objectives/p1-43-building-stacking-upgrade.md +++ b/.project/objectives/p1-43-building-stacking-upgrade.md @@ -2,11 +2,27 @@ id: p1-43 title: Building stacking β€” per-category upgrade chains (military / science / culture / production / etc.) priority: p1 -status: missing +status: partial scope: game1 -updated_at: 2026-04-29 +updated_at: 2026-05-03 --- +## 2026-05-03 verification + +Data layer substantially shipped; engine consumption still pending. + +- 178/181 buildings under `public/resources/buildings/` carry the `stack_mode` field (parallel/amplify/single) per the 2026-04-30 hybrid decision. Spot-checks: `barracks.json`, `infantry.json`, `library.json`, `scriptorium.json`, `harbor.json`, `deep_harbor.json`, `academy_of_sciences.json`, `alloy_furnace.json`, `grand_armory.json`, `forge_chant_hall.json`, `hospital.json`. +- 38 buildings declare `requires_existing: ` ladder pointers (e.g. `infantry.json::requires_existing="barracks"`, `scriptorium.json::requires_existing="library"`, `hospital.json::requires_existing="clinic"`, `deep_harbor.json::requires_existing="harbor"`, `alloy_furnace.json::requires_existing="mithril_forge"`). +- 71 buildings declare `produces: [unit_id, ...]` rosters (e.g. `barracks.produces`, `library.produces`, `harbor.produces`, `infantry.produces=[pikeman, defender, shield_bearer, plated_warrior, pike_guard]`, `scriptorium.produces=[dwarf_deep_scout, dwarf_grand_scout, dwarf_engineer]`). +- New ladder-fill buildings authored: `infantry.json`, `scriptorium.json`, `iron_forge.json`, `barber.json`, `clinic.json`, `hospital.json` β€” all under `public/resources/buildings/`. + +Engine remains unwired: +- `grep "requires_existing\|consumes_existing"` across `src/simulator/crates/` and `src/game/engine/src/` returns zero matches. `mc-city::can_build`, `mc-city::production`, and the GDScript dispatch path do not honour the prerequisite gate or consume-on-upgrade semantics. +- No validator support in `tools/` for cross-referencing `requires_existing` ids. +- AI catalog scoring unchanged; UI does not surface "Can be upgraded to: X". + +Promoted `missing` β†’ `partial`. + ## Summary User direction (2026-04-29): "all the buildings should be buildable and some buildings can be built on top of each other (double barracks - infantry) ... what about comboing other buildings ... science stack, culture stack". diff --git a/.project/objectives/p2-11a.md b/.project/objectives/p2-11a.md index 3562f3c6..4804d396 100644 --- a/.project/objectives/p2-11a.md +++ b/.project/objectives/p2-11a.md @@ -2,9 +2,9 @@ id: p2-11a title: "SaveManager: add Unit.serialize/deserialize and City.production_queue serialize path" priority: p2 -status: stub +status: partial scope: game1 -updated_at: 2026-04-26 +updated_at: 2026-05-03 --- ## Summary @@ -18,3 +18,20 @@ Unit has no serialize()/deserialize() methods β€” infusions, equipped_items, pro - ❌ City.production_queue included in City serialize/deserialize path - ❌ test_save_then_load_restores_unit_infusions_and_equipped_items upgraded to assert actual Unit round-trip (not Player proxy) - ❌ All save manager tests continue to pass + +## 2026-05-03 verification + +City.production_queue serialize/deserialize path now exists in +`src/game/engine/src/entities/city.gd` lines 492–541 (`to_save_dict` / +`from_save_dict`); `Player.serialize()` calls `to_save_dict()` on each city +(`src/game/engine/src/entities/player.gd` lines 211–213, 302–307). One bullet +materially done. + +Still missing: `Unit.serialize()` / `Unit.deserialize()` β€” unit.gd has no such +methods (`grep -nE "^func (serialize|deserialize)" src/game/engine/src/entities/unit.gd` +returns nothing). `Player.serialize()` does not include `units` (player.gd +line 209+ snapshot has no `units` key). The deferred test +`test_save_then_load_restores_unit_infusions_and_equipped_items` +(`src/game/engine/tests/unit/test_save_manager.gd:290`) still self-describes +as a Player-proxy stand-in. Status raised stubβ†’partial; remaining bullets +unchanged. diff --git a/.project/objectives/p2-16-audio-assets.md b/.project/objectives/p2-16-audio-assets.md index 36a7385b..a6b507f7 100644 --- a/.project/objectives/p2-16-audio-assets.md +++ b/.project/objectives/p2-16-audio-assets.md @@ -5,7 +5,7 @@ priority: p1 status: in_progress scope: game1 owner: asset-audio -updated_at: 2026-04-27 +updated_at: 2026-05-03 evidence: - "public/games/age-of-dwarves/assets/audio/sources.csv β€” 11 rows now (10 lighter UI/civic cues + city_grew). All CC0-1.0 from Kenney via Calinou's GitHub repackage." - "public/games/age-of-dwarves/assets/audio/sfx/*.ogg β€” 11 actual .ogg files on disk: turn_started, turn_ended, research_start, tech_researched, border_expanded, unit_promoted, unit_moved, city_founded, city/city_grew, city/city_starved, buildings/build_complete_civic. All Ogg Vorbis 44.1 kHz / 128 kbps, loudnorm I=-16/TP=-3 normalised." @@ -107,3 +107,30 @@ research, weather, victory. ~50 SFX + 7 music tracks. deeper voice") β€” categorical is enough for EA. - User mod-pack `user://overrides/audio.json` β€” hook reserved in p2-33 notes; code path deferred. + +## 2026-05-03 verification + +Audio asset tree relocated from `public/games/age-of-dwarves/assets/audio/` +(per evidence frontmatter) to `public/resources/audio/` per the post-p1-40 +data architecture (single source of truth at `public/resources//`). +Ground truth as of today: + +- `public/resources/audio/sources.csv` β€” 137 lines (was 11 on 2026-04-27). +- `public/resources/audio/{sfx,music}/**/*.ogg` β€” **106** real `.ogg` files + on disk (was 11). Per `.project/audio-status.md` "Tally" the curated + launch-pack target of 65 / 65 is met; remaining files are variant takes + + music tracks above the minimum. +- `public/resources/audio/LICENSES.md` regenerated; allowlist gate intact + (no `-SA` / `-NC`). +- `public/games/age-of-dwarves/data/audio/` β€” manifest dir present; + `audio-status.md` reports 49 SFX + 8 music entries wired through + `audio.schema.json` (streams[], pitch_jitter, fallback, _silent). +- `tools/audio-fetch-batch.sh`, `tools/audio-licenses-render.py`, + `tools/audio-validate.py` β€” all shipped per status doc. +- GUT `test_audio_manager.gd` β€” 13/13 pass headless on apricot. + +**Remaining gap to close p2-16:** the live in-game audible smoke test on +plum/apricot (`.project/screenshots/audio-smoke-2026-XX-XX.md`). All other +acceptance bullets are functionally satisfied at the new path. Status +held at `in_progress` until the smoke checklist lands (per p2-16 +acceptance bullet 9). diff --git a/.project/objectives/p2-46-past-games-archive-replay-viewer.md b/.project/objectives/p2-46-past-games-archive-replay-viewer.md index 45b5d292..9f266d3e 100644 --- a/.project/objectives/p2-46-past-games-archive-replay-viewer.md +++ b/.project/objectives/p2-46-past-games-archive-replay-viewer.md @@ -2,10 +2,10 @@ id: p2-46 title: Past-games archive & replay viewer β€” `mc-replay` crate, on-disk archive, projection-based playback priority: p2 -status: missing +status: partial scope: game1-stretch owner: shipwright -updated_at: 2026-04-30 +updated_at: 2026-05-03 evidence: - .project/designs/past-games-replays.md (design contract β€” read first) - src/simulator/crates/mc-replay/ (to be created β€” owns GameHistory, TurnSnapshot, TurnEvent, TurnEventCollector, archive I/O) @@ -58,3 +58,25 @@ No tunable values are hardcoded. Retention policy (max archived games) lives in - Replay editing / branching from a mid-turn state. - Re-simulation under updated rules ("would I win if combat math changed?"). - Per-turn delta compression beyond what bincode gives for free; size-budget bullet (10 MB cap) is enforced by an assert at save time, optimisation is later if the cap fires in practice. + +## 2026-05-03 verification + +`mc-replay` crate now scaffolded with full module set +(`src/simulator/crates/mc-replay/src/{archive,event,history,ids,snapshot,lib}.rs`, +1108 LOC). `lib.rs` self-documents this as a "type-skeleton gate" with +collector wiring deferred. Re-export surface includes `GameHistory`, +`TurnSnapshot`, `TurnEvent`, `TurnEventCollector`, `GameOutcome`, +`ArchiveError`, `HISTORY_SCHEMA_VERSION` β€” covering bullet 1 substantively. + +Remaining gaps: +- TurnEventCollector NOT wired into emitter crates β€” no `use mc_replay::` in + mc-economy/mc-combat/mc-tech/mc-turn (`grep -rn` outside mc-replay/ shows + only unrelated local `TurnSnapshot` structs in `mc-sim/src/bin/{fauna_pressure_bench,dominion_bench}.rs`). +- No archive subtree under `$XDG_DATA_HOME` proven by test. +- `replay_compat.json` absent from `public/games/age-of-dwarves/data/`. +- No `past_games.gd` / `replay_viewer.gd` scenes β€” there is no + `src/game/engine/scenes/main_menu/` directory and no `scenes/replay/`. +- No `replay_viewer_proof.tscn` under `scenes/tests/`. + +Status raised missingβ†’partial: crate scaffolding (1 of 8 acceptance bullets) +materially done. diff --git a/.project/objectives/p2-47-in-game-statistics-screens.md b/.project/objectives/p2-47-in-game-statistics-screens.md index 5b129aaf..c282a89c 100644 --- a/.project/objectives/p2-47-in-game-statistics-screens.md +++ b/.project/objectives/p2-47-in-game-statistics-screens.md @@ -2,10 +2,10 @@ id: p2-47 title: In-game statistics screens β€” Civ-style 5-tab modal (Demographics / Graphs / Rankings / Replay / Histories) priority: p2 -status: missing +status: partial scope: game1-stretch owner: shipwright -updated_at: 2026-04-30 +updated_at: 2026-05-03 evidence: - .project/designs/stats-screens.md (design contract β€” read first) - src/simulator/crates/mc-replay/src/snapshot.rs (consumed; owned by p3-05) @@ -58,3 +58,24 @@ Score weights (`w_pop`, `w_cities`, `w_tech`, `w_culture`, `w_land`, `w_wonders` - Espionage / scrying widening of visibility per rival (post-Game-1). - Per-tile heatmaps (e.g. "where did combat happen most"). Could land later as an additional tab without blocking ship. - Score-weight tuning to match competitive play; v1 weights are deliberately first-pass. + +## 2026-05-03 verification + +A precursor demographics overlay exists at +`src/game/engine/scenes/overviews/demographics.gd` (+`.tscn`) β€” Civ5-style +two-tab modal (Rankings table + History line graphs) with category cycler +and HUD entry hooks. This subsumes parts of the Demographics, Graphs, and +Rankings tabs of the planned 5-tab `Statistics.tscn`. + +Remaining gaps: +- No `src/game/engine/scenes/statistics/statistics.gd` consolidated 5-tab modal. +- No Replay or Histories tab. +- No `mc-score` crate (`ls src/simulator/crates/ | grep score` empty). +- No `public/games/age-of-dwarves/data/score.json`. +- Per-turn snapshot append not wired (mc-replay::TurnSnapshot unused outside + mc-replay; mc-turn does not call into TurnEventCollector). +- No `MetSet` contact-state filter visible on `mc-turn::PlayerState`. +- No `statistics_proof.tscn`. + +Status raised missingβ†’partial on the strength of the existing demographics +overlay covering ~3 of the 5 planned tab surfaces in early form. diff --git a/.project/objectives/p2-48-end-of-game-summary-screen.md b/.project/objectives/p2-48-end-of-game-summary-screen.md index d4044021..23e26792 100644 --- a/.project/objectives/p2-48-end-of-game-summary-screen.md +++ b/.project/objectives/p2-48-end-of-game-summary-screen.md @@ -2,10 +2,10 @@ id: p2-48 title: End-of-game summary screen β€” outcome banner, standings, score graph, awards, timeline, footer actions priority: p2 -status: missing +status: partial scope: game1-stretch owner: shipwright -updated_at: 2026-04-30 +updated_at: 2026-05-03 evidence: - .project/designs/end-game-summary.md (design contract β€” read first) - src/simulator/crates/mc-turn/src/end_conditions.rs (to be created β€” GameOver event + GameOverReason) @@ -66,3 +66,30 @@ The `GameOver { reason, winner }` event is fired by `mc-turn::end_conditions` (R ## Ship order p3-05 β†’ p3-06 β†’ p3-07. This objective is the last of the three; it consumes both predecessors. + +## 2026-05-03 verification + +Two precursor scenes exist in `src/game/engine/scenes/overviews/`: +- `victory_screen.gd` (+`.tscn`): outcome banner with title/subtitle/score + labels, continue-to-main-menu button, fired off `EventBus.victory_achieved`. +- `end_game_stats.gd` (+`.tscn`): final rankings list, score/category graph + with prev/next cycler, key-events list, main-menu button. Uses + `ThemeVocabulary` for all labels. + +These cover early forms of the hero strip + final standings + score graph +sections, plus the Main Menu footer action. + +Remaining gaps: +- No `mc-turn::end_conditions::GameOver` event; no + `src/simulator/crates/mc-turn/src/end_conditions.rs`. +- No `public/games/age-of-dwarves/data/victory.json`. +- No `public/games/age-of-dwarves/data/awards.json`; no `compute_awards` + function in mc-replay. +- No consolidated `end_game_summary.gd` scene with hero strip + 4 sections + 5-button footer. +- Awards section absent. Timeline (Histories) section absent. +- View Map / Watch Replay / Save to Archive / Export JSON footer actions + not wired (only Main Menu exists). +- No `end_game_summary_proof.tscn`. + +Status raised missingβ†’partial β€” substantive scene precursors exist for ~2 of +the 4 sections + 1 of the 5 footer actions. diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json index a2363c57..34b6d468 100644 --- a/public/games/age-of-dwarves/data/objectives.json +++ b/public/games/age-of-dwarves/data/objectives.json @@ -1,12 +1,12 @@ { - "generated_at": "2026-05-04T02:45:47Z", + "generated_at": "2026-05-04T02:47:37Z", "totals": { - "done": 145, + "missing": 11, "oos": 26, - "missing": 16, + "stub": 3, "in_progress": 5, - "partial": 10, - "stub": 4, + "partial": 16, + "done": 145, "total": 206 }, "objectives": [ @@ -724,10 +724,10 @@ "id": "p1-27", "title": "Extract GPU MCTS into a standalone service/client (model-boss-shaped, magic-civ-only)", "priority": "p1", - "status": "missing", + "status": "partial", "scope": "game1", "owner": "warcouncil", - "updated_at": "2026-04-25", + "updated_at": "2026-05-03", "summary": "Today the GPU MCTS path lives **inside** the `mc-ai` crate (`gpu/inner.rs`, `gpu/rollout.wgsl`, `gpu/cpu_reference.rs`) and runs in-process via the GDExtension (`GdMcTreeController`). That couples GPU lifecycle (device init, queue submission, buffer pooling, fence waits) to the game's per-turn decision call.\n\nPer user directive 2026-04-25: extract this into its own **MCTS service/client** that\n\n1. Lives **inside @magic-civilization** (not in @model-boss / not in any other repo) β€” it's game-specific.\n2. Lives **independently** of the in-process GDExtension β€” long-lived process the game talks to via IPC (Unix socket / TCP / shared memory).\n3. **Borrows patterns** from `@model-boss` (job submission, queue, batched dispatch, GPU lifecycle isolation) but doesn't take a dependency on it. Magic-civ's MCTS workload is narrow enough to warrant its own focused implementation.\n\nWhy a service vs in-process:\n- GPU init + warm-up amortized once per session, not per AI turn\n- Game can keep playing turns while a deep search is in flight (async)\n- Crash isolation β€” a wgpu/driver fault doesn't take the game down\n- One service can serve multiple game clients (autoplay-batch parallel runs hit one warm GPU instead of N cold inits)\n- Future: out-of-process service can run on a different host (apricot has GPU, dev mac doesn't)" }, { @@ -904,10 +904,10 @@ "id": "p1-43", "title": "Building stacking β€” per-category upgrade chains (military / science / culture / production / etc.)", "priority": "p1", - "status": "missing", + "status": "partial", "scope": "game1", "owner": null, - "updated_at": "2026-04-29", + "updated_at": "2026-05-03", "summary": "User direction (2026-04-29): \"all the buildings should be buildable and some buildings can be built on top of each other (double barracks - infantry) ... what about comboing other buildings ... science stack, culture stack\".\n\nToday every building is binary: a city either has it or doesn't. The mechanic the user wants: queueing a building on top of an existing one upgrades the slot in place β€” `barracks` + another `barracks` build = `infantry` (a stronger military producer). The same primitive applies to every category: science stacks (library β†’ scriptorium β†’ academy), culture stacks (monument β†’ bardic_circle β†’ great_hall), production stacks (forge β†’ iron_forge β†’ grand_forge), etc. This is distinct from the BUILDINGS.md \"Hybrid Merged Structures\" mechanic (which combines TWO different buildings + Synthesis tech into a hybrid). Stacking is the simpler primitive: same-category Lv1 β†’ Lv2 β†’ Lv3 chains within one slot.\n\nThe existing data already implies category-tier chains via the `tier` + `category` fields:\n\n| Category | Lv1 (no tech) | Lv2 (mid tech) | Lv3+ (late tech) |\n|---|---|---|---|\n| Production | `forge` t1 | (gap β€” `iron_forge` doesn't exist) | `dwarf_deep_forge` t3, `tempering_forge` t6, `steam_forge` t7, `adamantine_foundry` t10 |\n| Science | `library` t1 | `university` t3, `observatory` t3 | `academy_of_sciences` t5, `climate_institute` t9 |\n| Culture | `monument` t1 | `great_hall` t3, `gathering_hall` t2 | `ancestor_hall` t10 |\n| Military | `barracks` t1 | (gap β€” `infantry` doesn't exist) | `armory` t3, `military_academy` t6, `command_citadel` t10 |\n| Food | `granary` t1 | `mill` t2, `brewery` t2, `watermill` t2 | `great_granary` t2 (wonder) |\n| Defense | `walls` t1 | `watchtower` t1 | `castle` t3 |\n| Wealth | `marketplace` t2, `market` t2 (DUPLICATE) | `guild_hall` t4 | (none) |\n| Religion | `temple` t2 | `temple_of_the_ancestor` t5 (wonder) | (none) |\n\nThe stacking schema makes these chains explicit and queryable. Where a Lv2 successor doesn't exist yet (e.g. `infantry`, `iron_forge`, `scriptorium`), this objective authors the missing intermediates.\n\nThree design questions need user sign-off before authoring:\n\n1. **Successor identity**: is `infantry` a NEW building (needs authoring) or an existing one (e.g. reuse `armory` as the \"barracks Lv2\" slot)?\n2. **Mechanic shape**:\n - **(a) Replacement**: building barracks twice consumes both, slot becomes `infantry`. Original gone.\n - **(b) Levelled**: building stays \"barracks\" but carries a `level: 2` field with stacked effects.\n - **(c) Per-tile**: two barracks on same tile merge (only relevant if `placement_tile_required: true`).\n3. **Schema**: declare on the lower tier (`barracks.json::stacks_into: \"infantry\"`) or on the upper (`infantry.json::requires_existing: \"barracks\"` + `consumes_existing: true`)? The latter keeps the relationship bidirectional readable.\n\nRecommendation: option **(a) Replacement** with declaration on the upper tier (`requires_existing` + `consumes_existing`). Matches civ-style upgrade slots, reads naturally in the city UI (\"Upgrade Barracks β†’ Infantry\"), avoids per-tile placement complexity for a v1." }, { @@ -1077,7 +1077,7 @@ "status": "in_progress", "scope": "game1", "owner": "asset-audio", - "updated_at": "2026-04-27", + "updated_at": "2026-05-03", "summary": "The audio capability shipped as **p0-21** β€” `AudioManager`, manifest,\nsignal wiring, volume sliders all work. The schema + categorical\nrouting extension lands as **p2-33** (this objective is `blockedBy`\nthat). What's missing is the actual `.ogg` files plus the source\nledger that proves their licenses are clean.\n\nPer user directive 2026-04-17 the asset work was pulled out of the\noriginal `p1-04` so capability and assets are tracked independently.\nA silent ship is shippable; a broken or licence-tainted audio system\nis not.\n\nThis objective ships **the launch sound pack** assembled from free /\nOSS sources (CC0, CC-BY 3.0/4.0, royalty-free commercial; no\nShareAlike, no NonCommercial). Pack covers ~57 files spanning UI,\nturn cycle, units (categorical melee / ranged / siege / civilian),\nbuildings (categorical civic / production / military / wonder),\nfauna (categorical predator / herbivore / apex), city events,\nresearch, weather, victory. ~50 SFX + 7 music tracks." }, { @@ -1434,10 +1434,10 @@ "id": "p2-11a", "title": "\"SaveManager: add Unit.serialize/deserialize and City.production_queue serialize path\"", "priority": "p2", - "status": "stub", + "status": "partial", "scope": "game1", "owner": null, - "updated_at": "2026-04-26", + "updated_at": "2026-05-03", "summary": "Unit has no serialize()/deserialize() methods β€” infusions, equipped_items, promo_ids, keywords and other typed arrays cannot round-trip through SaveManager. City.production_queue is a GDScript-side Array with no serialize path; the Rust-backed City.to_json() does not include it. These gaps were deferred from p2-10f, which narrowed its tests to the Player serialize surface only." }, { @@ -1614,30 +1614,30 @@ "id": "p2-46", "title": "Past-games archive & replay viewer β€” `mc-replay` crate, on-disk archive, projection-based playback", "priority": "p2", - "status": "missing", + "status": "partial", "scope": "game1-stretch", "owner": "shipwright", - "updated_at": "2026-04-30", + "updated_at": "2026-05-03", "summary": "Persistent local archive of finished games, accessible from the main menu, with three surfaces:\n\n1. **Past Games index** β€” card grid (newest first), filters (outcome / map / version / date), per-card actions (Open Summary Β· Watch Replay Β· Rename Β· Export Β· Delete).\n2. **Replay viewer** β€” turn-by-turn playback against the live renderer, **projection-based not re-simulated** (reads pre-recorded snapshots + events), scrubber, speed selector, event ticker, optional stats overlay.\n3. **Compare view** β€” multi-select 2–4 games β†’ overlapping score-graph + final-standings delta.\n\nFoundational for `p3-06` (statistics screens) and `p3-07` (end-of-game summary), both of which read the same `GameHistory` artefact this objective owns. **Ships first** of the three.\n\nDesign doc: [.project/designs/past-games-replays.md](../designs/past-games-replays.md)." }, { "id": "p2-47", "title": "In-game statistics screens β€” Civ-style 5-tab modal (Demographics / Graphs / Rankings / Replay / Histories)", "priority": "p2", - "status": "missing", + "status": "partial", "scope": "game1-stretch", "owner": "shipwright", - "updated_at": "2026-04-30", + "updated_at": "2026-05-03", "summary": "Civ-style mid-game statistics modal opened from the HUD info button (or `F9`). Five tabs in one scene, all read-only views over the per-turn `TurnSnapshot` log produced by `mc-replay` (p3-05):\n\n1. **Demographics** β€” sortable single-turn table of every met clan.\n2. **Graphs** β€” multi-line chart, Y-axis selector (score / pop / cities / army / gold-per-turn / culture-per-turn / tech-count / land-area), X = turn.\n3. **Rankings** β€” top-N leaderboard for the selected metric, with trend arrow vs. previous turn.\n4. **Replay** β€” in-game preview of the post-game replay viewer (p3-05 surface), scoped to the current game's history.\n5. **Histories** β€” per-clan chronicle (founding turn, wars, wonders, eras, leaders).\n\nComposite score is recomputed every turn-end from JSON-driven weights, used for Rankings default and end-game ordering.\n\nDesign doc: [.project/designs/stats-screens.md](../designs/stats-screens.md)." }, { "id": "p2-48", "title": "End-of-game summary screen β€” outcome banner, standings, score graph, awards, timeline, footer actions", "priority": "p2", - "status": "missing", + "status": "partial", "scope": "game1-stretch", "owner": "shipwright", - "updated_at": "2026-04-30", + "updated_at": "2026-05-03", "summary": "Full-screen summary triggered when the game ends β€” by victory condition, last-clan-standing, turn-limit, or player resignation. Replaces the world-map HUD with:\n\n- **Hero strip** β€” outcome banner + winning-clan card + player's-clan card (player-second slot stable across victory/defeat).\n- **Section 1 β€” Final standings** β€” Demographics table from p3-06 frozen at final turn, plus `Outcome` and `Score breakdown` columns.\n- **Section 2 β€” Score graph** β€” full-game chart from p3-06's Graphs widget with event markers forced on.\n- **Section 3 β€” Awards** β€” JSON-driven per-category superlatives.\n- **Section 4 β€” Timeline** β€” Histories from p3-06 with fog lifted (every clan visible).\n- **Footer** β€” View Map Β· Watch Replay Β· Save to Archive Β· Export JSON Β· Main Menu.\n\nDesign doc: [.project/designs/end-game-summary.md](../designs/end-game-summary.md)." }, { diff --git a/src/game/engine/scenes/tests/proof_civilian_capture.gd b/src/game/engine/scenes/tests/proof_civilian_capture.gd new file mode 100644 index 00000000..bc54be03 --- /dev/null +++ b/src/game/engine/scenes/tests/proof_civilian_capture.gd @@ -0,0 +1,77 @@ +extends Node2D +## p2-55 Civilian Capture Proof Scene. +## Renders four outcome panels (Capture / Destroy / Ransom-Accepted / Ransom-Expired) +## with stub data, then saves a screenshot to user://screenshots/ and quits. +## Self-capturing β€” no game runtime dependency. Headless-friendly. + +const OUTPUT_DIR: String = "user://screenshots" +const PANEL_W: int = 360 +const PANEL_H: int = 240 +const MARGIN: int = 16 +const HEADER_H: int = 28 + +const COLOR_BG: Color = Color(0.08, 0.09, 0.12) +const COLOR_CAPTURE: Color = Color(0.20, 0.50, 0.30) +const COLOR_DESTROY: Color = Color(0.55, 0.18, 0.18) +const COLOR_RANSOM_ACCEPT: Color = Color(0.20, 0.40, 0.65) +const COLOR_RANSOM_EXPIRE: Color = Color(0.55, 0.40, 0.18) +const COLOR_PANEL_BG: Color = Color(0.14, 0.16, 0.20) +const COLOR_TEXT: Color = Color(0.92, 0.93, 0.96) +const COLOR_DIM: Color = Color(0.65, 0.68, 0.74) + +func _ready() -> void: + var size: Vector2i = Vector2i(PANEL_W * 2 + MARGIN * 3, PANEL_H * 2 + MARGIN * 3 + HEADER_H) + get_window().size = size + get_window().borderless = true + queue_redraw() + # Defer screenshot one frame so the draw call has flushed. + call_deferred("_capture_and_quit") + +func _draw() -> void: + # Background + draw_rect(Rect2(Vector2.ZERO, get_window().size), COLOR_BG, true) + _draw_header("p2-55 Civilian Capture / Destroy / Ransom β€” Outcome Proofs") + + var y0: int = HEADER_H + MARGIN + var col0: int = MARGIN + var col1: int = MARGIN * 2 + PANEL_W + var row1: int = y0 + PANEL_H + MARGIN + + _draw_panel(Vector2i(col0, y0), "CAPTURE", COLOR_CAPTURE, + ["Defender: Worker (HP 1)", "Captured by: Blackhammer", "Owner: Goldvein β†’ Blackhammer", "Attacker XP: +0", "EventBus: unit_captured"]) + _draw_panel(Vector2i(col1, y0), "DESTROY", COLOR_DESTROY, + ["Defender: Worker (HP 0)", "Destroyed by: Blackhammer", "Removed from map", "Attacker XP: +5", "EventBus: civilian_destroyed"]) + _draw_panel(Vector2i(col0, row1), "RANSOM ACCEPTED", COLOR_RANSOM_ACCEPT, + ["Defender: Worker (captive)", "Captor: Blackhammer", "Price: 140 gold", "Goldvein pays β†’ unit returns", "EventBus: ransom_offered β†’ ransom_accepted"]) + _draw_panel(Vector2i(col1, row1), "RANSOM EXPIRED", COLOR_RANSOM_EXPIRE, + ["Defender: Worker (captive)", "Captor: Blackhammer", "Offer expired (turn +3)", "Owner: Goldvein β†’ Blackhammer", "EventBus: ransom_offered β†’ ransom_expired β†’ unit_captured"]) + +func _draw_header(text: String) -> void: + var f: Font = ThemeDB.fallback_font + draw_string(f, Vector2(MARGIN, 20), text, HORIZONTAL_ALIGNMENT_LEFT, -1, 16, COLOR_TEXT) + +func _draw_panel(origin: Vector2i, title: String, accent: Color, lines: Array) -> void: + var rect: Rect2 = Rect2(Vector2(origin.x, origin.y), Vector2(PANEL_W, PANEL_H)) + draw_rect(rect, COLOR_PANEL_BG, true) + draw_rect(Rect2(rect.position, Vector2(PANEL_W, 6)), accent, true) + draw_rect(rect, accent, false, 1.0) + var f: Font = ThemeDB.fallback_font + draw_string(f, Vector2(rect.position.x + 12, rect.position.y + 28), title, HORIZONTAL_ALIGNMENT_LEFT, -1, 18, accent) + var ly: float = rect.position.y + 56 + for line in lines: + draw_string(f, Vector2(rect.position.x + 12, ly), str(line), HORIZONTAL_ALIGNMENT_LEFT, -1, 13, COLOR_TEXT) + ly += 22.0 + +func _capture_and_quit() -> void: + await get_tree().process_frame + await get_tree().process_frame + var img: Image = get_viewport().get_texture().get_image() + DirAccess.make_dir_recursive_absolute(OUTPUT_DIR) + var name: String = "proof_civilian_capture_%s.png" % Time.get_datetime_string_from_system().replace(":", "-") + var path: String = "%s/%s" % [OUTPUT_DIR, name] + var err: Error = img.save_png(path) + if err == OK: + print("[proof_civilian_capture] saved: %s" % ProjectSettings.globalize_path(path)) + else: + push_error("[proof_civilian_capture] save_png failed: %s" % err) + get_tree().quit() diff --git a/src/game/engine/scenes/tests/proof_civilian_capture.tscn b/src/game/engine/scenes/tests/proof_civilian_capture.tscn new file mode 100644 index 00000000..37322c4d --- /dev/null +++ b/src/game/engine/scenes/tests/proof_civilian_capture.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://p2_55_civcap_proof"] + +[ext_resource type="Script" path="res://engine/scenes/tests/proof_civilian_capture.gd" id="1_script"] + +[node name="ProofCivilianCapture" type="Node2D"] +script = ExtResource("1_script")