9.8 KiB
Data flow
How a tech / tradition definition moves from JSON on disk into the screen
the player sees, and how a click on Research flows back through the
stack.
Read path (data → screen)
┌─────────────────────────────────────────────────────────────────────────────────┐
│ public/games/age-of-dwarves/data/techs/*.json │
│ public/resources/culture/*.json │
│ │
│ ── unlocks: { buildings, units, improvements, wonders, lenses, │
│ resources_revealed, unit_upgrades, mechanics } │
└────────────────────────────────────────┬────────────────────────────────────────┘
│ DataLoader.load_theme()
│ — reads category="techs" + "culture"
│ — manifest.json silently ignored
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ DataLoader._data["techs"][id] DataLoader._data["culture"][id] │
│ (Dictionary keyed by tech_id) (Dictionary keyed by tradition_id) │
└────────────────────────────────────────┬────────────────────────────────────────┘
│
│ TurnManager.get_tech_web() / get_culture_web()
│ ─ first call lazily builds the bridge
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ TechWeb wrapper (RefCounted) CultureWeb wrapper (RefCounted) │
│ ▼ ▼ │
│ KnowledgeWeb { _bridge, _config, … } KnowledgeWeb { _bridge, _config }│
│ │ _bridge.load_from_json(JSON.stringify(…)) │
│ ▼ │
└─────────────────────────────────────────────────────────────────────────────────┘
│ JSON over GDExtension boundary
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ GdTechWeb (RefCounted) GdCultureWeb (RefCounted) │
│ ▼ ▼ │
│ mc_tech::TechWeb::from_json() mc_culture::CultureWeb │
│ │ — validate prereq DAG (acyclic) (alias of TechWeb) │
│ │ — build dependents map │
│ ▼ │
│ Immutable graph: HashMap<id, TechDefinition> + prereqs + dependents │
└─────────────────────────────────────────────────────────────────────────────────┘
Render-time queries from KnowledgeTree.gd:
─ get_pillars() → GdTechWeb.pillars() (PackedStringArray)
─ get_nodes_by_pillar(p) → GdTechWeb.techs_by_pillar(p) (PackedStringArray, sorted by tier)
─ get_node_data(id) → GdTechWeb.tech_data_json(id) → JSON.parse (Dictionary)
─ is_researched(id, pi) → Player.researched_techs.has(id) (GDScript only)
─ can_research(id, pi) → GdTechWeb.prereqs_met(id, set) (bool)
─ get_current_research(pi) → Player.researching (String)
─ get_progress_fraction(pi) → Player.research_progress / cost (float)
Write path (player click → research started)
Click "Research" button on a card
│
▼
KnowledgeTree._on_research_pressed(id)
│
▼
TechWeb.start_research(id, player_index)
│
▼
KnowledgeWeb.start_research(id, player_index)
│ ─ verifies can_research()
│ ─ writes player.researching = id
│ ─ writes player.research_progress = 0
│ ─ EventBus.emit_signal("tech_research_started", id, player_index)
▼
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ tutorial_orchestrator │ │ game_logger │
│ (listens for step 7) │ │ (writes log line) │
└─────────────────────────────┘ └─────────────────────────────┘
Per-turn accumulation (live game)
Each turn, GDScript hands the player's research state + city yields to
GdTechWeb.process_research() (and GdCultureWeb.process_culture_research()).
The Rust call:
process_research(player_json, yield_json, sci_modifier)
│
│ 1. Parse JSON → GdTechPlayerInput
│ 2. Sum (science + building_science) × (1 + science_percent) per city
│ 3. raw = (science_per_turn + city_sum) × sci_modifier
│ 4. Bootstrap PlayerTechState from researched_techs[]
│ 5. start_research(researching) on a fresh PlayerTechState
│ 6. add_science(research_progress + raw)
│ 7. Translate ResearchResult into return Dictionary:
│ Completed { tech_id, overflow, unlocks }
│ InProgress + new_progress
│ NoResearch
▼
Return: { completed_tech, new_progress, new_researching,
unlock_signals[], error }
GDScript then:
- Writes
player.research_progress = result.new_progress - Writes
player.researching = result.new_researching - For each entry in
result.unlock_signals[], callsEventBus.tech_researched.emit(tech_id, player_index)
The culture path mirrors this exactly via process_culture_research,
fed by per_turn_culture (the same yield value driving border expansion).
Per-turn accumulation (bench / MCTS)
mc-turn::TurnProcessor::process_culture_research runs alongside
process_culture and process_science. It uses the in-process
PlayerCultureState (not the flat fields), advancing it the same way
process_science advances PlayerTechState. On completion the flat
researching_tradition / culture_research_progress /
researched_traditions fields are mirrored, keeping serialisation and
GDScript reads consistent with the bench truth.
File-by-file ownership
| Layer | File | Responsibility |
|---|---|---|
| Data | public/games/.../data/techs/*.json, public/resources/culture/*.json |
Authoritative content. |
| Schema | public/games/.../data/schemas/tech.schema.json |
Validates both tech + culture JSON. |
| Loader | src/game/engine/src/autoloads/data_loader.gd |
Reads category dirs into _data[category]. |
| Rust types | src/simulator/crates/mc-tech/src/web.rs |
TechDefinition, Unlocks, typed buckets. |
| Rust state | src/simulator/crates/mc-tech/src/state.rs |
PlayerTechState, completion, unlocks signal. |
| Rust alias | src/simulator/crates/mc-culture/src/research.rs |
CultureWeb = TechWeb re-export. |
| Bridge | src/simulator/api-gdext/src/lib.rs |
GdTechWeb, GdCultureWeb, dictionary marshaling. |
| Bench loop | src/simulator/crates/mc-turn/src/processor.rs |
process_culture_research. |
| GDScript wrapper (shared) | src/game/engine/src/modules/tech/knowledge_web.gd |
Bridge proxy + Player field reads/writes. |
| GDScript wrapper (tech) | src/game/engine/src/modules/tech/tech_web.gd |
Tech config. |
| GDScript wrapper (culture) | src/game/engine/src/modules/empire/culture_web.gd |
Culture config. |
| Scene base | src/game/engine/scenes/knowledge_tree/knowledge_tree.gd |
Card grid + detail panel rendering. |
| Scene tech | src/game/engine/scenes/tech_tree/tech_tree.gd, .tscn |
Subclass + scene file. |
| Scene culture | src/game/engine/scenes/culture_tree/culture_tree.gd, .tscn |
Subclass + scene file. |
| Vocabulary | public/games/age-of-dwarves/vocabulary.json |
All player-facing strings. |
| EventBus | src/game/engine/src/autoloads/event_bus.gd |
tech_* and culture_* signals. |