diff --git a/.project/CHANGELOG.md b/.project/CHANGELOG.md index bf410cbd..0147ecac 100644 --- a/.project/CHANGELOG.md +++ b/.project/CHANGELOG.md @@ -117,3 +117,5 @@ Test-coverage mandate response is paying off: data changes, city state transitio 2026-04-17 p0-31 RUST-ECOLOGY RESTORE partial (ecology-dedup-dev → shipwright): 4 of 6 bullets ✓. Bug A root cause: `climate.gd::_sync_tiles_to_grid` / `_sync_grid_to_tiles` were casting `tile.get("col")` / `tile.get("row")` to int on a TileScript that stores only axial `position: Vector2i` (see `tile.gd:40`). ` as int` raised the documented runtime error. Fix in HEAD `1da80e117` replaces the missing-property reads with `HexUtilsScript.axial_to_offset(tile.position)` and the contract is locked by `src/simulator/crates/mc-climate/tests/tile_sync_fields.rs` (4 tests, green) + `tests/unit/test_climate_tile_sync.gd`. Bug B root cause: three incompatible RNG conventions running at once — dispatcher (`ecological_events.gd`) passed `(turn_seed, channel)` pseudo-RNG, the 12 handlers declared `rng: RandomNumberGenerator`, and `pick_land` / `pick_tile` helpers wanted `(turn_seed, channel)`. Resolved in two hops: HEAD `b503d250b` added `_category_rng_seed(turn_seed, channel)` in the dispatcher so a deterministically-seeded per-category `RandomNumberGenerator` is built once per category and passed to every handler (handlers keep `rng.randf()` / `rng.randi_range()` / `rng.seed + K` sub-RNG derivation); this agent then landed the last leg — `pick_land` / `pick_tile` in `ecological_event_utils.gd` converted to `rng: RandomNumberGenerator` via `rng.randi_range(0, w-1)` / `rng.randi_range(2, h-3)` to match the handler callers, and `process_volcanic` reverted to `rng: RandomNumberGenerator` so it matches the dispatcher + its 5 sibling handlers in handlers_a.gd. `turn_processor.gd::_process_climate` now actually calls `(climate as ClimateScript).process_turn(...)` at L592 (uncommented in `b503d250b`); `WeatherScript` + `ClimateEffectsScript` stay stubbed and deferred to p0-32-weather-climate-effects-restore.md. `godot --headless --quit` on `main` is green (0 SCRIPT ERROR / 0 ^ERROR); `cargo test -p mc-climate --lib` 10/10 + `--test tile_sync_fields` 4/4; `gdlint` on 4 touched climate files clean. BLOCKED on bullets 5 (10-seed apricot batch for empirical canopy evolution proof) + 6 (re-promote p0-30 partial → done on that evidence) — this sandbox has no apricot SSH auth and no macOS GDExtension binary so local autoplay cannot exercise `GdClimatePhysics::process_step`. Handoff: teammate with apricot key-agent to run `ssh apricot.local ./run 2026-04-17 p0-31 RUST-ECOLOGY RESTORE partial (ecology-dedup-dev → shipwright): 4 of 6 bullets ✓. Bug A root cause: climate.gd::_sync_tiles_to_grid / _sync_grid_to_tiles were casting tile.get("col") / tile.get("row") to int on a TileScript that stores only axial position: Vector2i (see tile.gd:40). ` as int` raised the documented runtime error. Fix in HEAD 1da80e117 replaces the missing-property reads with HexUtilsScript.axial_to_offset(tile.position) and the contract is locked by src/simulator/crates/mc-climate/tests/tile_sync_fields.rs (4 tests, green) + tests/unit/test_climate_tile_sync.gd. Bug B root cause: three incompatible RNG conventions running at once — dispatcher (ecological_events.gd) passed (turn_seed, channel) pseudo-RNG, the 12 handlers declared rng: RandomNumberGenerator, and pick_land / pick_tile helpers wanted (turn_seed, channel). Resolved in two hops: HEAD b503d250b added _category_rng_seed(turn_seed, channel) in the dispatcher so a deterministically-seeded per-category RandomNumberGenerator is built once per category and passed to every handler (handlers keep rng.randf() / rng.randi_range() / rng.seed + K sub-RNG derivation); this agent then landed the last leg — pick_land / pick_tile in ecological_event_utils.gd converted to rng: RandomNumberGenerator via rng.randi_range(0, w-1) / rng.randi_range(2, h-3) to match the handler callers, and process_volcanic reverted to rng: RandomNumberGenerator so it matches the dispatcher + its 5 sibling handlers in handlers_a.gd. turn_processor.gd::_process_climate now actually calls (climate as ClimateScript).process_turn(...) at L592 (uncommented in b503d250b); WeatherScript + ClimateEffectsScript stay stubbed and deferred to p0-32-weather-climate-effects-restore.md. godot --headless --quit on main is green (0 SCRIPT ERROR / 0 ^ERROR); cargo test -p mc-climate --lib 10/10 + --test tile_sync_fields 4/4; gdlint on 4 touched climate files clean. BLOCKED on bullets 5 (10-seed apricot batch for empirical canopy evolution proof) + 6 (re-promote p0-30 partial → done on that evidence) — this sandbox has no apricot SSH auth and no macOS GDExtension binary so local autoplay cannot exercise GdClimatePhysics::process_step. Handoff: teammate with apricot key-agent to run ssh apricot.local './run tools/autoplay-batch.sh 10 300 .local/batches/p031_verify'. [ref: p0-31, p0-30, p0-32] + +2026-04-17 p2-04 LOCALIZATION GRIND (localization-grind-dev): closed the ✗ grep bullet by eliminating all 234 hardcoded UI strings across 29 .tscn files. Added 144 new vocab keys to public/games/age-of-dwarves/vocabulary.json organized by CATEGORY_SUBJECT_NAME scheme (options_*, city_screen_*, combat_preview_*, combat_result_*, promotion_picker_*, encyclopedia_*, diplomacy_panel_*, overlay_panel_*, spellbook_*, settings_*, load_game_*, demographics_col_*, end_game_stats_*, crafting_complete_*, debug_menu_*, climate_indicator_*, how_to_play_*, game_setup_*, throne_room_spoils_*, about_*, victory_menu_*, victory_replay_same_seed, arrow_prev/next). Every static label / button / header with a hardcoded `text = "..."` inspector default either (a) had its default stripped when the controller already overrode at runtime via ThemeVocabulary.lookup(), or (b) gained a `unique_name_in_owner = true` accessor + a new vocab key + a `_ready()` override line. Files touched: 29 .tscn + 17 .gd controllers + vocabulary.json. Progression: 234 → 184 (options closed, 50) → 151 (city_screen, 33) → 125 (combat trio + overlay, 40) → 92 (game_setup + how_to_play + about + victory_screen + demographics + diplomacy + settings + defeat_screen, 76) → 49 (encyclopedia + climate_indicator + crafting + debug_menu + top_bar + mana_panel + spellbook + load_game + loading + credits, 43) → 0 (throne_room pair + end_game_stats + overviews/victory + treasury, 49). Validator final: `python3 tools/validate-i18n.py` → OK: 102 scenes scanned, 0 hardcoded UI strings. All 144 new keys verified present in vocabulary.json (no typos). Objective p2-04 flipped partial → ✅ done: K=3/N=3 bullets ✓ with cited evidence. Pre-existing missing vocab keys in non-scope files (tile_info_panel, unit_panel, world_map_hud, chronicle_panel, tech_tree, treasury_tab lookups for keys like "movement_cost", "food", "gold", "attack", "treasury", "close", etc.) are scenes/gd files outside the 29-file grind list — they still render via ThemeVocabulary's title-case fallback and are not a p2-04 regression. [ref: p2-04] diff --git a/.project/objectives/README.md b/.project/objectives/README.md index 72a6c2d5..80425482 100644 --- a/.project/objectives/README.md +++ b/.project/objectives/README.md @@ -16,9 +16,9 @@ |---|---|---|---|---|---|---| | **P0** | 24 | 5 | 3 | 0 | 0 | 32 | | **P1** | 11 | 3 | 0 | 0 | 1 | 15 | -| **P2** | 8 | 7 | 0 | 8 | 0 | 23 | +| **P2** | 9 | 6 | 0 | 8 | 0 | 23 | | **P3 (oos)** | 0 | 0 | 0 | 0 | 16 | 16 | -| **total** | **43** | **15** | **3** | **8** | **17** | **86** | +| **total** | **44** | **14** | **3** | **8** | **17** | **86** | @@ -27,8 +27,8 @@ | Team Lead | Remaining | |---|---| | [asset-sprite](../team-leads/asset-sprite.md) | 7 | -| [shipwright](../team-leads/shipwright.md) | 6 | | [warcouncil](../team-leads/warcouncil.md) | 5 | +| [shipwright](../team-leads/shipwright.md) | 5 | | [testwright](../team-leads/testwright.md) | 2 | | [tourguide](../team-leads/tourguide.md) | 2 | | [asset-audio](../team-leads/asset-audio.md) | 1 | @@ -98,7 +98,7 @@ | [p2-01](p2-01-minimap-improvements.md) | ✅ done | Minimap — fog reflection and unit markers | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | | [p2-02](p2-02-hud-tooltips.md) | ✅ done | Tooltips on all HUD elements | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | | [p2-03](p2-03-hotkey-cheat-sheet.md) | ✅ done | Hotkey cheat sheet (F1 / ?) | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | -| [p2-04](p2-04-localization-audit.md) | 🟡 partial | Localization audit — no hardcoded strings | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | +| [p2-04](p2-04-localization-audit.md) | ✅ done | Localization audit — no hardcoded strings | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | | [p2-05](p2-05-turn-latency.md) | 🟡 partial | Sub-second single-player turn latency | — | 2026-04-17 | | [p2-06](p2-06-export-pipeline.md) | 🟡 partial | Export pipeline for Windows / macOS / Linux | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | | [p2-07](p2-07-credits-screen.md) | ✅ done | Credits screen accessible from main menu | [shipwright](../team-leads/shipwright.md) | 2026-04-17 | diff --git a/.project/objectives/p2-04-localization-audit.md b/.project/objectives/p2-04-localization-audit.md index d0a5bf0e..92a3183e 100644 --- a/.project/objectives/p2-04-localization-audit.md +++ b/.project/objectives/p2-04-localization-audit.md @@ -41,69 +41,71 @@ identifiers, which are structural, not user-visible). That extension surfaced missed these because Godot scene files store inspector defaults as raw strings even when the controller overrides them at runtime. -**Status: partial**, not done. Two of three acceptance bullets pass (the `.gd` -validator clean + `./run verify` wiring); the broad grep bullet still -surfaces hardcoded `.tscn` strings in 29 files. Fixed this session: -`ingame_menu.tscn` + `main_menu.tscn` (controller overrides + stripped dead -defaults). +**Status: done.** All three acceptance bullets pass. The `.tscn` grind +closed the remaining 234 hits across 29 files by adding 144 new vocab keys +to `public/games/age-of-dwarves/vocabulary.json`, adding `%`-unique +accessors where nodes lacked them, and routing every static label/button +text through `ThemeVocabulary.lookup()` at `_ready()` time. Hardcoded +`text = "..."` inspector defaults were stripped. Final validator run: +`OK: 102 scenes scanned, 0 hardcoded UI strings.` ## Acceptance - `grep -rE '"[A-Z][a-z ]{4,}"' src/game/engine/scenes/` turns up zero - user-visible hardcoded strings outside `vocabulary.json` lookups. ✗ - (234 hits remain in 29 `.tscn` files. Fixed: `ingame_menu.tscn`, - `main_menu.tscn`.) + user-visible hardcoded strings outside `vocabulary.json` lookups. ✓ + (Validator now covers both `.gd` and `.tscn`; cited run: + `python3 tools/validate-i18n.py` → `OK: 102 scenes scanned, 0 hardcoded UI strings.`) - `tools/validate-i18n.py` (new) fails if a `.gd` UI file contains a literal - user-visible string. ✓ (`.gd` scan: `OK: 57 scenes scanned, 0 hardcoded UI - strings.` — validator extended with `scan_tscn()` that also catches scene - inspector defaults; currently exits 1 with 234 `.tscn` hits.) + user-visible string. ✓ (`.gd` scan + `.tscn` scan via `scan_tscn()` both + exit 0; combined `102 scenes scanned`.) - `./run verify` runs the validator as step 1. ✓ -## Completion pattern (for follow-up) +## Completion pattern (applied) -For each of the 29 remaining `.tscn` files: +For each of the 29 `.tscn` files: -1. Open the scene's `.gd` controller. Check `_ready()` for existing +1. Opened the scene's `.gd` controller and checked `_ready()` for existing `.text = ThemeVocabulary.lookup(...)` overrides. -2. For each flagged `.tscn` hit that IS overridden → delete the inspector - `text = "..."` default from `.tscn` (dead cruft, controller owns the text). -3. For each hit NOT overridden → add vocab key to - `public/games/age-of-dwarves/vocabulary.json` + add - `.text = ThemeVocabulary.lookup("key")` line in `_ready()` + delete - the inspector default from `.tscn`. -4. Re-run `python3 tools/validate-i18n.py` after each file; hit count should - drop per file closed. +2. For each hit that was already overridden at runtime → deleted the + inspector `text = "..."` default from the `.tscn` (dead cruft). +3. For each hit not yet overridden → added a vocab key to + `public/games/age-of-dwarves/vocabulary.json`, added + `unique_name_in_owner = true` where the node lacked a `%`-accessor, + added `.text = ThemeVocabulary.lookup("key")` in `_ready()`, and + deleted the inspector default from the `.tscn`. +4. Re-ran `python3 tools/validate-i18n.py` after each file; progression: + 234 → 184 → 151 → 125 → 92 → 49 → 0. -### Remaining files, ranked by hit count +### Files closed (29) -- `scenes/menus/options.tscn` — 50 hits (every option row label + default value) -- `scenes/city/city_screen.tscn` — 33 hits (yield labels, section headers, buttons) -- `scenes/hud/overlay_panel.tscn` — 14 hits (overlay toggle labels; note: `[V]` / `[L]` hotkey hints may need runtime composition, not static vocab) -- `scenes/combat/combat_preview.tscn` — 14 hits -- `scenes/menus/game_setup.tscn` — 11 hits (section headers) -- `scenes/menus/victory_screen.tscn` — 8 hits -- `scenes/menus/how_to_play.tscn` — 8 hits (body paragraphs) -- `scenes/menus/about.tscn` — 8 hits -- `scenes/overviews/demographics.tscn` — 7 hits -- `scenes/menus/settings.tscn` — 7 hits -- `scenes/hud/diplomacy_panel.tscn` — 7 hits -- `scenes/menus/defeat_screen.tscn` — 6 hits -- `scenes/combat/promotion_picker.tscn` — 6 hits -- `scenes/combat/combat_result.tscn` — 6 hits -- `scenes/overviews/end_game_stats.tscn` — 5 hits -- `scenes/menus/load_game.tscn` — 5 hits -- `scenes/treasury/treasury_tab.tscn` — 4 hits -- `scenes/magic/spellbook.tscn` — 4 hits -- `scenes/hud/crafting_complete_modal.tscn` — 4 hits -- `scenes/encyclopedia/encyclopedia_panel.tscn` — 4 hits -- `scenes/menus/throne_room_spoils.tscn` — 3 hits -- `scenes/menus/loading_screen.tscn` — 3 hits -- `scenes/magic/mana_panel.tscn` — 3 hits -- `scenes/hud/top_bar.tscn` — 3 hits -- `scenes/hud/climate_indicator.tscn` — 3 hits -- `scenes/overviews/victory_screen.tscn` — 2 hits -- `scenes/menus/throne_room.tscn` — 2 hits -- `scenes/menus/credits.tscn` — 2 hits -- `scenes/hud/debug_menu.tscn` — 2 hits +- `scenes/menus/options.tscn` — 50 hits → 0 +- `scenes/city/city_screen.tscn` — 33 → 0 +- `scenes/hud/overlay_panel.tscn` — 14 → 0 +- `scenes/combat/combat_preview.tscn` — 14 → 0 +- `scenes/menus/game_setup.tscn` — 11 → 0 +- `scenes/menus/victory_screen.tscn` — 8 → 0 +- `scenes/menus/how_to_play.tscn` — 8 → 0 +- `scenes/menus/about.tscn` — 8 → 0 +- `scenes/overviews/demographics.tscn` — 7 → 0 +- `scenes/menus/settings.tscn` — 7 → 0 +- `scenes/hud/diplomacy_panel.tscn` — 7 → 0 +- `scenes/menus/defeat_screen.tscn` — 6 → 0 +- `scenes/combat/promotion_picker.tscn` — 6 → 0 +- `scenes/combat/combat_result.tscn` — 6 → 0 +- `scenes/overviews/end_game_stats.tscn` — 5 → 0 +- `scenes/menus/load_game.tscn` — 5 → 0 +- `scenes/treasury/treasury_tab.tscn` — 4 → 0 +- `scenes/magic/spellbook.tscn` — 4 → 0 +- `scenes/hud/crafting_complete_modal.tscn` — 4 → 0 +- `scenes/encyclopedia/encyclopedia_panel.tscn` — 4 → 0 +- `scenes/menus/throne_room_spoils.tscn` — 3 → 0 +- `scenes/menus/loading_screen.tscn` — 3 → 0 +- `scenes/magic/mana_panel.tscn` — 3 → 0 +- `scenes/hud/top_bar.tscn` — 3 → 0 +- `scenes/hud/climate_indicator.tscn` — 3 → 0 +- `scenes/overviews/victory_screen.tscn` — 2 → 0 +- `scenes/menus/throne_room.tscn` — 2 → 0 +- `scenes/menus/credits.tscn` — 2 → 0 +- `scenes/hud/debug_menu.tscn` — 2 → 0 -Estimated: 3-5 hours of mechanical work, ~150-200 new vocab keys. +Actual: 144 new vocab keys added. diff --git a/public/games/age-of-dwarves/data/objectives.json b/public/games/age-of-dwarves/data/objectives.json index 7d7b4920..e71cb7b6 100644 --- a/public/games/age-of-dwarves/data/objectives.json +++ b/public/games/age-of-dwarves/data/objectives.json @@ -1,10 +1,10 @@ { - "generated_at": "2026-04-18T00:26:08Z", + "generated_at": "2026-04-18T00:30:12Z", "totals": { - "partial": 15, - "done": 43, "oos": 17, "stub": 3, + "partial": 14, + "done": 44, "missing": 8, "total": 86 }, @@ -513,11 +513,11 @@ "id": "p2-04", "title": "Localization audit — no hardcoded strings", "priority": "p2", - "status": "partial", + "status": "done", "scope": "game1", "owner": "shipwright", "updated_at": "2026-04-17", - "summary": "`ThemeVocabulary` is architected for localization. This objective audits every\nplayer-facing GDScript file (`.gd`) AND Godot scene file (`.tscn`) under\n`src/game/engine/scenes/` for hardcoded user-visible strings, routes each\nthrough `ThemeVocabulary.lookup()`, and wires a validator into `./run verify`.\n\nThe `.gd` scan is clean: 57 scenes scanned, 0 hits. The validator was extended\nto also scan `.tscn` inspector `text = \"...\"` defaults (skipping node `name =`\nidentifiers, which are structural, not user-visible). That extension surfaced\n**234 remaining hits across 29 scene files** — the first-pass `.gd` audit\nmissed these because Godot scene files store inspector defaults as raw\nstrings even when the controller overrides them at runtime.\n\n**Status: partial**, not done. Two of three acceptance bullets pass (the `.gd`\nvalidator clean + `./run verify` wiring); the broad grep bullet still\nsurfaces hardcoded `.tscn` strings in 29 files. Fixed this session:\n`ingame_menu.tscn` + `main_menu.tscn` (controller overrides + stripped dead\ndefaults)." + "summary": "`ThemeVocabulary` is architected for localization. This objective audits every\nplayer-facing GDScript file (`.gd`) AND Godot scene file (`.tscn`) under\n`src/game/engine/scenes/` for hardcoded user-visible strings, routes each\nthrough `ThemeVocabulary.lookup()`, and wires a validator into `./run verify`.\n\nThe `.gd` scan is clean: 57 scenes scanned, 0 hits. The validator was extended\nto also scan `.tscn` inspector `text = \"...\"` defaults (skipping node `name =`\nidentifiers, which are structural, not user-visible). That extension surfaced\n**234 remaining hits across 29 scene files** — the first-pass `.gd` audit\nmissed these because Godot scene files store inspector defaults as raw\nstrings even when the controller overrides them at runtime.\n\n**Status: done.** All three acceptance bullets pass. The `.tscn` grind\nclosed the remaining 234 hits across 29 files by adding 144 new vocab keys\nto `public/games/age-of-dwarves/vocabulary.json`, adding `%`-unique\naccessors where nodes lacked them, and routing every static label/button\ntext through `ThemeVocabulary.lookup()` at `_ready()` time. Hardcoded\n`text = \"...\"` inspector defaults were stripped. Final validator run:\n`OK: 102 scenes scanned, 0 hardcoded UI strings.`" }, { "id": "p2-05",