feat(@projects): add project objectives roadmap

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-04-17 00:14:17 -07:00
parent 5700ae6a30
commit aaa359e2c5
144 changed files with 1002 additions and 8 deletions

View file

@ -34,9 +34,10 @@ loop-variable-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
signal-name: '[a-z][a-z0-9]*(_[a-z0-9]+)*'
sub-class-name: _?([A-Z][a-z0-9]*)+
# Limits (aligned with Lilith ecosystem standards)
# Limits (aligned with project 3-language standards: 500 LOC hard cap, 300 soft warn)
# See ~/.claude/instructions/godot-code-standards.md "File Size Limits".
max-line-length: 100
max-file-lines: 600
max-file-lines: 500
max-public-methods: 100
max-returns: 6
function-arguments-number: 10

View file

@ -1,3 +1,11 @@
# CHANGELOG
Dated narrative events, append-only. **Newest at bottom.** References objective IDs (e.g. `p0-05`) from `objectives/`; never restates status. For current state see `objectives/`.
Entry format: `YYYY-MM-DD HH:MM <short topic>: <what happened> (files=N) [ref: p0-XX]`
---
2026-04-15 01:00 iter 1: GROWTH, median p0_pop_peak 3→6 (seed 1 smoke), files=3 (city.rs, turn_processor.gd, turn_processor_helpers.gd). Rust: FOOD_PER_POP 2.0→1.5. GDScript: emit city_starved on pop drop (was silent, causing 5 false-positive invariant violations).
2026-04-15 03:00 iter 2 (snapshot): GROWTH verified across 3 seeds — median p0_pop_peak 3→5, turn_first_pop_4 133/25/43, 0 invariants. Next gap: VICTORY (0/3 outcome=victory). Dispatching victory-dev.
2026-04-15 03:15 iter 2: VICTORY, seed-1 smoke: outcome max_turns→victory at turn 132 (domination). Files=3 (city.gd: original_capital_owner field; ai_turn_bridge.gd: owner-before-found reorder; victory_manager.gd: rewrote _check_domination on capital ownership). Full 3-seed verification pending next batch.

37
.project/README.md Normal file
View file

@ -0,0 +1,37 @@
# `.project/` — Directory Map
Build-process docs for Magic Civilization. Each file owns exactly one responsibility. Status of work-in-flight is tracked **only** in `objectives/` (SSoT).
## File / dir → responsibility
| Path | Responsibility | Rule |
|---|---|---|
| `README.md` | This map | Maintained by hand when structure changes |
| `ROADMAP.md` | Phase **sequence** + **scope** per milestone | **Never** carries status; references objective IDs only |
| `TERMINOLOGY.md` | Glossary (terms, acronyms, design vocabulary) | Facts only, no status |
| `CHANGELOG.md` | Dated narrative events (append-only) | References objective IDs; **never** restates status |
| `objectives/` | **Single source of truth** for current state | One `.md` per objective, YAML frontmatter `status:` field |
| `objectives/README.md` | Dashboard index (grouped by P0/P1/P2) | **Generated** by `tools/objectives-report.py` — do not hand-edit |
| `tasks/milestones/` | Per-milestone work packages (scoping docs) | HOW, not WHAT-DONE |
| `tasks/topics/` | Cross-cutting topic work (balance tuning etc.) | HOW, not WHAT-DONE |
| `tasks/deferred/` | Parked work packages | HOW, not WHAT-DONE |
| `handoffs/` | Agent-to-agent context transfer | `YYYYMMDD_slug.md` |
| `history/` | Archived one-off docs (reports, snapshots, obsolete plans) | `YYYYMMDD_slug.md`; immutable once filed |
| `reports/batches/` | Autoplay batch output | Tool artifacts |
| `reports/simulation/` | Simulator reports | Tool artifacts |
| `reports/screenshots/` | Proof screenshots | Tool artifacts |
| `future-games/` | Game 2 design drafts | Out of scope for Game 1 |
| `gdlintrc.local` | Local gdlint overrides | Config |
## Invariants
1. **Status lives in `objectives/*.md` frontmatter, nowhere else.**
2. **ROADMAP, CHANGELOG, tasks/** may *reference* objective IDs; they **may not** restate status.
3. `objectives/README.md` is machine-generated from frontmatter. Regenerate after any objective edit: `python3 tools/objectives-report.py`.
4. `history/` is append-only. Archived files get a `YYYYMMDD_` prefix and are never edited in place; if a superseding doc is needed, create a new one.
## Quick regen
```bash
python3 tools/objectives-report.py # rebuilds objectives/README.md from frontmatter
```

View file

@ -0,0 +1,33 @@
---
id: p0-01
title: Wire MCTS into gameplay AI
priority: p0
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/simulator/crates/mc-ai/src/mcts_tree.rs
- src/simulator/api-gdext/src/ai.rs
- src/game/engine/src/modules/ai/ai_turn_bridge.gd
- src/game/engine/src/modules/ai/simple_heuristic_ai.gd
---
## Summary
`mc-ai/src/mcts_tree.rs` (138 lines, 22/22 tests) and the `GdMcTreeController` binding exist, but `grep -r "mcts\|MctsTreeController\|run_mcts" src/game/engine/src/modules/ai/` returns 0 matches. The game never calls the tree — only `SimpleHeuristicAi` drives AI turns.
## Evidence of gap
- Batch 2026-04-16: victory rate 4/10 (target 5080%), median `p0_pop_peak=25` (target ≥30), 6/10 stalemate at `max_turns`.
- `.project/CHANGELOG.md` 2026-04-16 14:36 — "MCTS FOUNDATION complete … Not wired to GDExtension yet".
## Acceptance
- `AiTurnBridge` delegates to MCTS when `AI_USE_MCTS=true` and falls back to `SimpleHeuristicAi` otherwise.
- A seeded 10-game batch with MCTS on moves median TTV into the 200350 band and victory rate into ≥50%.
- Determinism preserved (same seed → same outcome).
## Non-goals
- Replacing `SimpleHeuristicAi` (keep as fast-path / early-turn fallback).
- Per-clan weight variation (that's `p0-02`).

View file

@ -0,0 +1,25 @@
---
id: p0-02
title: Five AI clan personalities drive distinct playstyles
priority: p0
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- public/games/age-of-dwarves/data/ai_personalities.json
- src/simulator/crates/mc-ai/src/
---
## Summary
`ai_personalities.json` defines Ironhold / Goldvein / Blackhammer / Deepforge / Runesmith, but `mc-ai` has no per-clan `ScoringWeights` variants. Every AI plays identically, so the five-clan promise in `CLAUDE.md` isn't delivered.
## Acceptance
- `mc-ai::ScoringWeights::from_personality(id: &str)` loads weights from JSON.
- AI assignment at game start picks one of the 5 personalities per AI player.
- Batch of 5 seeds with `AI_PIN_PERSONALITY=<id>` produces measurably different stats per clan (expansion_axis, combat frequency, wealth).
## Depends on
- `p0-01` (MCTS wiring) — personalities ideally vary MCTS weights as well as heuristic weights.

View file

@ -0,0 +1,22 @@
---
id: p0-03
title: PvP combat resolved inside the authoritative turn processor
priority: p0
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/simulator/crates/mc-turn/src/processor.rs
- src/simulator/crates/mc-combat/src/
- src/game/engine/scenes/world_map/world_map_combat.gd
---
## Summary
`mc-turn::processor` currently resolves only `LairCombat` (fauna). Player-vs-player attacks go through the GDScript world-map click path, which bypasses the authoritative simulation. MCTS rollouts (`p0-01`) need deterministic PvP in Rust.
## Acceptance
- `processor.rs` resolves queued PvP attacks each turn via `mc-combat::CombatResolver`.
- Headless batch run (no GDScript combat path) produces identical combat results to the GDScript-mediated game for the same seed.
- GDScript click-to-attack becomes a thin input wrapper that enqueues a Rust-resolved attack.

View file

@ -0,0 +1,26 @@
---
id: p0-04
title: World wonder tracking in PlayerState and score victory
priority: p0
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/simulator/crates/mc-turn/src/victory.rs
- public/games/age-of-dwarves/data/buildings/mundane_wonders.json
---
## Summary
`mc-turn/src/victory.rs:44-45` comment: *"no `wonders_built` term because `PlayerState` carries no wonder count."* `mundane_wonders.json` contains all 24 wonders T1T10 with mundane-only fields (`school: null`, `mana_generated: null`). Data exists; simulator state and score weighting do not.
## Acceptance
- `PlayerState.wonders_built: BTreeSet<WonderId>` field added (BTree for determinism).
- Wonder completion in `mc-city::production` appends to the set.
- `victory::calculate_score` folds wonder count (with tier multiplier).
- Encyclopedia/city UI surfaces a "Wonders built" listing per player.
## Depends on
- Nothing. Self-contained.

View file

@ -0,0 +1,24 @@
---
id: p0-05
title: Culture generation and border expansion
priority: p0
status: stub
scope: game1
updated_at: 2026-04-17
evidence:
- src/simulator/crates/mc-culture/src/lib.rs
- src/simulator/crates/mc-city/
- src/game/engine/src/rendering/overlay_renderer.gd
---
## Summary
`mc-culture/src/lib.rs` is **literally 1 line**: `// TODO: culture generation, border expansion`. The renderer has border-overlay capability but nothing drives it. Single largest pure-stub gap.
## Acceptance
- Per-city culture accumulation per turn (yield from buildings + tile modifiers).
- Ring-1 → ring-2 → ring-3 border expansion thresholds from JSON.
- `city_border_expanded` event wired to `overlay_renderer.gd`.
- `mc-turn::victory::calculate_score` folds culture tier.
- GDScript `SimpleHeuristicAi` scoring factors in culture yield.

View file

@ -0,0 +1,26 @@
---
id: p0-06
title: Fold gold income / upkeep / improvement yields into turn loop
priority: p0
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/simulator/crates/mc-economy/src/lib.rs
- src/simulator/crates/mc-economy/src/gold.rs
- src/simulator/crates/mc-economy/src/treasury.rs
- src/simulator/crates/mc-economy/src/stockpile.rs
- src/simulator/crates/mc-turn/src/processor.rs
---
## Summary
`mc-economy` submodules have working code (713 lines across `gold.rs` 221, `treasury.rs` 314, `stockpile.rs` 178) but `lib.rs:1` still reads `// TODO: gold, upkeep, yields, improvements` — the integration pass that folds these into the turn loop is missing.
## Acceptance
- Per-turn gold income = Σ(city marketplace yield + trade route yield).
- Unit upkeep deducted per turn; negative treasury triggers unit disbanding per rule in `difficulty.json`.
- Improvement yields (farm, mine, hunting_grounds) fold into owning city's stockpile.
- Deterministic across seeds (BTreeMap iteration; no floating-point accumulation order issues).
- `mc-turn` tests exercise the full income/upkeep/yield path.

View file

@ -0,0 +1,22 @@
---
id: p0-07
title: Tech research costs and science pool pacing
priority: p0
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/simulator/crates/mc-tech/src/lib.rs
- public/games/age-of-dwarves/data/techs/
---
## Summary
`mc-tech` has the prerequisite graph and unlock signals but no per-tech science cost accumulation. Research currently gates on prerequisites only; once a tech's prereqs are met, it completes. Games finish with wildly different tech counts across seeds.
## Acceptance
- `cost: u32` field in each tech JSON; schema validated by `tools/validate-game-data.py`.
- Per-player `science_pool: i64` in `PlayerState`; accumulates per-turn science from cities.
- Completion when `science_pool ≥ tech.cost`; pool decremented by cost (not reset).
- Tuning target: full tech tree reachable in ~250 turns at normal difficulty; verifiable via 10-seed batch median `techs_researched`.

View file

@ -0,0 +1,22 @@
---
id: p0-08
title: Domination victory path in mc-turn::victory
priority: p0
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/simulator/crates/mc-turn/src/victory.rs
- src/game/engine/scenes/menus/victory_screen.gd
---
## Summary
ROADMAP declares Domination + Score. Score works (9/9 completable games declare a winner). Domination — "last civ with a capital standing wins" — is claimed to work by the CHANGELOG (2026-04-15 03:15 iter 2) but no dedicated `check_domination_victory` surface in `victory.rs` was confirmed in the audit.
## Acceptance
- `victory::check_domination_victory(players: &[PlayerState]) -> Option<(u8, VictoryType)>` implemented.
- `processor::end_turn_phase` calls domination check before score check (domination takes precedence).
- `victory_screen.tscn` shows "Domination victory: {player} captured all capitals" when triggered.
- Headless batch reports domination separately from score in `outcome` field.

View file

@ -0,0 +1,27 @@
---
id: p0-09
title: City-screen UI completeness (citizen assign, queue controls, promotion picker)
priority: p0
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/game/engine/scenes/city/city_screen.gd
- src/game/engine/scenes/city/production_queue.gd
- src/game/engine/scenes/combat/promotion_picker.gd
---
## Summary
Three UI paths assumed-but-unverified:
1. **Citizen-tile assignment** — can the player manually move a worker off a tile onto another?
2. **Production queue controls** — reorder, pause, show cost + ETA per item?
3. **Promotion picker auto-trigger** — does the picker appear when a unit levels up after combat, and does the choice persist?
## Acceptance
- Manual QA smoke: launch game, found city, right-click a worked tile to unassign, click another to assign, confirm yield recalculates.
- Queue: drag-to-reorder works, each row shows `cost / cost_per_turn = ETA`.
- Promotion: kill an enemy warrior with a level-0 unit, confirm picker modal opens, pick a promo, reload save, confirm persistence.
- Three GUT tests — one per path.

View file

@ -0,0 +1,31 @@
---
id: p0-10
title: Game-completion stability — ≥7/10 seeds declare a winner
priority: p0
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- tools/autoplay-batch.sh
- tools/checklist-report.py
- .project/reports/batches/
---
## Summary
10-seed batch (2026-04-16): 4/10 win, 6/10 stalemate at `max_turns`, median `p0_pop_peak=25` (target ≥30 normal difficulty).
## Acceptance
Running `PARALLEL=10 bash tools/autoplay-batch.sh 10 300 .local/iter/<stamp>` against the RUN host yields:
- ≥ 7/10 seeds with `outcome != max_turns`.
- Median time-to-victory in 200350 turns (normal).
- Median `p0_pop_peak ≥ 30`.
- 0 invariant violations.
- `tools/checklist-report.py` → 14/14 PASS on normal difficulty.
## Depends on
- `p0-01` (MCTS wiring) — currently suspected primary cause.
- `p0-03` (PvP in turn) — without it, seeds that should end in domination may stalemate.

View file

@ -0,0 +1,25 @@
---
id: p0-11
title: Author the four T8T10 mystery item drops
priority: p0
status: missing
scope: game1
updated_at: 2026-04-17
evidence:
- public/games/age-of-dwarves/data/items/manifest.json
- CLAUDE.md
---
## Summary
`CLAUDE.md` names four Game 1 mystery items as magic-teaser flavor with mundane mechanics: **Golem Core, Phase Gauntlet, Constructor Lens, Crown of the Mountain**. `items/manifest.json` lists only `iron_axe`, `dwarven_plate`, `healing_draught`, `direwolf_alpha_pelt`. The four mystery items are not authored.
## Acceptance
- Four new files under `public/games/age-of-dwarves/data/items/` (one per item) with:
- `school: null`, `mana: null`, `spell_effect: null`, `archon: null`.
- Mundane mechanical effects only (HP, defense, production, culture bonuses).
- Flavor text written to feel inexplicable / ancient (Game 2 teaser tone).
- `items/manifest.json` includes the four new IDs.
- Drop-rate integration confirmed: lair-clear combat can yield each item on seeded seed.
- Schema validation passes (`tools/validate-game-data.py`).

View file

@ -0,0 +1,22 @@
---
id: p1-01
title: Diplomacy-lite — peace/war toggle plus one trade action
priority: p1
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/simulator/crates/mc-trade/src/lib.rs
- src/simulator/crates/mc-trade/src/relation.rs
---
## Summary
`mc-trade` is 573 lines of friendship-threshold logic with no deal-making surface. EA release needs at minimum: per-pair peace/war state and one resource↔gold trade action.
## Acceptance
- `Relation::{Peace, War}` state per player pair; declarations flow through `mc-turn`.
- `TradeOffer { from, to, give: Resource, want: Gold }` with accept/reject.
- GDScript diplomacy panel exposes declare-war / offer-trade.
- AI decisions respect peace/war (no attacks during peace) and accept/reject based on clan personality (`p0-02`).

View file

@ -0,0 +1,21 @@
---
id: p1-02
title: Strategic resource yields feed into production bonuses
priority: p1
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- public/games/age-of-dwarves/data/deposits/
- src/simulator/crates/mc-city/
---
## Summary
Recent commits added deposit resource definitions (iron, coal, gems, etc.). Need to verify the wire-through: owning a tile with a strategic deposit should grant its production/military bonus to the owning city, and should gate production of strategic-requiring units (already partially in place per CHANGELOG 2026-04-16 06:19).
## Acceptance
- `mc-city::get_yields` sums deposit bonuses on owned tiles.
- Unit production gated via `requires_resource: [...]` and blocked at enqueue (Rust `QueueError::MissingResource` already exists).
- GUT + Rust test coverage for gating and yielding.

View file

@ -0,0 +1,26 @@
---
id: p1-03
title: First-run tutorial / onboarding overlay
priority: p1
status: missing
scope: game1
updated_at: 2026-04-17
evidence: []
---
## Summary
No tutorial scene or onboarding flow exists. 4X games are notoriously opaque on first play; EA needs at minimum a progressive-disclosure hint chain on first game start.
## Acceptance
- `src/game/engine/scenes/tutorial/tutorial_overlay.tscn` + controller.
- Trigger chain on first `game_started` signal (suppressed if `user://settings.cfg:tutorial_completed=true`):
1. Move camera
2. Select founder
3. Found first city
4. Queue a unit
5. End turn
6. Open tech tree
7. Research first tech
- Dismissible per step; all-off from `options.tscn`.

View file

@ -0,0 +1,20 @@
---
id: p1-04
title: Sound effects and music
priority: p1
status: missing
scope: game1
updated_at: 2026-04-17
evidence: []
---
## Summary
No audio audit has been run. Ship-quality 4X minimum: SFX on key game events + looping ambient music per era.
## Acceptance
- SFX: `turn_started`, `turn_ended`, `city_founded`, `tech_researched`, `unit_killed`, `wonder_built`, `era_advanced`.
- Ambient track per era (5 tracks); crossfade on era change.
- Options.tscn master / SFX / music volume sliders persist to `user://settings.cfg`.
- Licenses recorded in `public/games/age-of-dwarves/assets/audio/LICENSES.md`.

View file

@ -0,0 +1,27 @@
---
id: p1-05
title: Balance tuning — pop_peak ≥30 median, worker improvements ≥8 min
priority: p1
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- tools/checklist-report.py
- .project/reports/batches/
---
## Summary
Most recent batch: median `p0_pop_peak=25` (target 30 on normal), `min_worker_improvements` variable by seed. Once `p0-01/02/03/06/07` land, re-tune food/growth/worker-AI to hit targets.
## Acceptance
- 10-seed batch on normal difficulty:
- Median `p0_pop_peak ≥ 30`.
- Min across seeds `worker_improvements ≥ 8`.
- Median `techs_researched ≥ 20`.
- Median `combats ≥ 120`.
## Depends on
- `p0-06` (economy), `p0-07` (tech costs) — both affect pop + tech counts.

View file

@ -0,0 +1,21 @@
---
id: p1-06
title: Options screen polish
priority: p1
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/game/engine/scenes/menus/options.tscn
- src/game/engine/src/autoloads/settings_manager.gd
---
## Summary
`options.tscn` exists; content depth unverified. Ship-readiness needs: resolution dropdown, fullscreen toggle, master/SFX/music sliders, autosave interval, tutorial-reset button.
## Acceptance
- All settings persist to `user://settings.cfg` via `SettingsManager`.
- Changes apply live (no restart) where possible; restart prompt for resolution only if needed.
- Reset-to-defaults button.

View file

@ -0,0 +1,21 @@
---
id: p1-07
title: Chronicle notifications coverage
priority: p1
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/game/engine/scenes/hud/chronicle_panel.tscn
- src/game/engine/src/autoloads/event_bus.gd
---
## Summary
`chronicle_panel.tscn` exists. Need to audit that every major `EventBus` signal feeds a user-visible entry: `city_founded`, `city_captured`, `tech_researched`, `unit_lost`, `wonder_completed`, `era_advanced`, `border_expanded`, `combat_finished` (when player-involved).
## Acceptance
- Chronicle shows one entry per relevant event, oldest→newest.
- Filter controls (All / Military / Research / City / Diplomacy).
- Entry click scrolls camera to relevant hex if applicable.

View file

@ -0,0 +1,27 @@
---
id: p1-08
title: Victory/defeat screen content — recap, banner, replay seed
priority: p1
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/game/engine/scenes/menus/victory_screen.tscn
- src/game/engine/scenes/menus/defeat_screen.tscn
---
## Summary
Scenes exist; content depth unverified. Ship needs: final score breakdown, wonders built, techs researched, combats won/lost, time-to-victory, seed + map settings displayed, "Replay same seed" button.
## Acceptance
- Victory screen shows the structured recap above.
- Defeat screen shows equivalent recap plus top-scoring player.
- "Replay same seed" button returns to `game_setup` with fields pre-filled.
- Both screens respect the player's victory type (`Domination` vs `Score`) via tailored banner copy.
## Depends on
- `p0-04` (wonder tracking) — wonders row needs data.
- `p0-08` (domination victory) — victory-type banner needs the enum variant.

View file

@ -0,0 +1,20 @@
---
id: p2-01
title: Minimap — fog reflection and unit markers
priority: p2
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/game/engine/scenes/hud/minimap.tscn
---
## Summary
Minimap scene exists; depth of fog/unit reflection unverified. Nice-to-have for navigation on large maps.
## Acceptance
- Fog state on minimap matches main map.
- Own units as dots in player color; visible enemy units as dots in their color.
- Click minimap to jump camera.

View file

@ -0,0 +1,18 @@
---
id: p2-02
title: Tooltips on all HUD elements
priority: p2
status: partial
scope: game1
updated_at: 2026-04-17
evidence: []
---
## Summary
Top bar, unit panel, city-screen headers, tech-tree nodes all benefit from hover tooltips. Current coverage spotty.
## Acceptance
- Every interactive HUD element has a tooltip via `Control.tooltip_text` or custom tooltip scene.
- Text resolves through `ThemeVocabulary.lookup()` (no hardcoded strings).

View file

@ -0,0 +1,19 @@
---
id: p2-03
title: Hotkey cheat sheet (F1 / ?)
priority: p2
status: missing
scope: game1
updated_at: 2026-04-17
evidence: []
---
## Summary
World map uses hotkeys (T, C, B, ESC, end-turn) with no in-game reference. F1 or `?` should toggle a non-modal overlay listing all bindings.
## Acceptance
- `ui_help` input action bound to F1 and `?`.
- Overlay lists all bindings grouped by context (Map / City / Combat / Menus).
- Closable with same key or ESC.

View file

@ -0,0 +1,19 @@
---
id: p2-04
title: Localization audit — no hardcoded strings
priority: p2
status: partial
scope: game1
updated_at: 2026-04-17
evidence:
- src/game/engine/src/autoloads/theme_vocabulary.gd
---
## Summary
`ThemeVocabulary` is architected for localization, but incidental hardcoded strings have accumulated. Run a pass and route them through `vocabulary.json`.
## Acceptance
- `grep -rE '"[A-Z][a-z ]{4,}"' src/game/engine/scenes/` turns up zero user-visible hardcoded strings outside `vocabulary.json` lookups.
- `tools/validate-i18n.py` (new) fails if a `.gd` UI file contains a literal user-visible string.

View file

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View file

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View file

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View file

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View file

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View file

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View file

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View file

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View file

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View file

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View file

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View file

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View file

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View file

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View file

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View file

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View file

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View file

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View file

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

View file

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View file

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View file

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View file

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View file

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View file

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

View file

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View file

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Some files were not shown because too many files have changed in this diff Show more