feat(@projects): ✨ add tech-culture domain categorization system
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
a8b45cc083
commit
e203bd1a09
5 changed files with 164 additions and 23 deletions
|
|
@ -59,7 +59,8 @@ const CATEGORIES: readonly CategoryDef[] = [
|
|||
{ id: "Agriculture", label: "Agriculture", color: "#66e666" },
|
||||
{ id: "Governance", label: "Governance", color: "#e68c73" },
|
||||
{ id: "Culture", label: "Culture", color: "#a07cc9" },
|
||||
{ id: "Exploration", label: "Exploration", color: "#66bfff" },
|
||||
{ id: "Science", label: "Science", color: "#66bfff" },
|
||||
{ id: "Exploration", label: "Exploration", color: "#73a6e6" },
|
||||
{ id: "Engineering", label: "Engineering", color: "#bbb09a" },
|
||||
{ id: "Medicine", label: "Medicine", color: "#7cd9a0" },
|
||||
];
|
||||
|
|
|
|||
84
.project/objectives/p1-50-tech-culture-domain-propagation.md
Normal file
84
.project/objectives/p1-50-tech-culture-domain-propagation.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
id: p1-50
|
||||
title: "Tech & Culture domain field — propagate categorization through Rust, Godot UI, and player analysis"
|
||||
priority: p1
|
||||
status: in_progress
|
||||
scope: game1
|
||||
owner: unassigned
|
||||
updated_at: 2026-05-03
|
||||
plan: ~/.claude/plans/tech-culture-domain-propagation.md
|
||||
evidence: []
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The designs app at `.project/designs/app/` now drives `/tech-tree` and
|
||||
`/culture-tree` from real JSON data with a 10-domain categorization
|
||||
(Military / Economy / Industry / Agriculture / Governance / Culture /
|
||||
Science / Exploration / Engineering / Medicine), authored as
|
||||
`tech.domain` directly in `public/resources/techs/*.json`. Culture
|
||||
policies use the `pillar` field as their tab axis.
|
||||
|
||||
This objective propagates that categorization through the rest of the
|
||||
system — the Rust simulator, the Godot gameplay UI (`knowledge_tree`,
|
||||
`tech_tree`, `culture_tree`), and player data analysis surfaces — so
|
||||
every consumer reads the same Single Source of Truth.
|
||||
|
||||
The shared TreeView component (`.project/designs/app/src/components/tree/`)
|
||||
is the reference implementation. Other surfaces should match its UX:
|
||||
domain tab bar, matching-tab-floats-to-top sort, inline unlocks on each
|
||||
node.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] **Rust** — `mc-tech::Tech` carries `domain: String` deserialised
|
||||
from JSON. Schema test in `mc-data` asserts every authored tech has
|
||||
a non-empty domain belonging to the 10-value enum. `cargo test -p
|
||||
mc-tech -p mc-culture -p mc-data` passes.
|
||||
|
||||
- [ ] **GDExtension** — `GdTechWeb` exposes `domains() ->
|
||||
PackedStringArray` and `techs_by_domain(domain) -> Array<Dictionary>`.
|
||||
`tech_data_json(id)` includes `domain` in returned dict. Build clean:
|
||||
`bash src/simulator/build-gdext.sh`.
|
||||
|
||||
- [ ] **GDScript** — `tech_web.gd` exposes `get_domains()` and
|
||||
`get_nodes_by_domain(domain)`. `knowledge_tree.gd` renders a domain
|
||||
tab bar above the existing pillar columns. Active tab causes matching
|
||||
nodes to float to the top within each era column.
|
||||
|
||||
- [ ] **Gameplay proof** — `tech_tree_proof.tscn` captures a screenshot
|
||||
showing the domain tab bar, with one tab (e.g. "Science") active and
|
||||
the 21 Science techs floated to the top of their era columns.
|
||||
`culture_tree_proof.tscn` updated similarly with pillar tabs (the
|
||||
culture-tree's natural tab axis).
|
||||
|
||||
- [ ] **Designs app polish** — `/tech-tree` and `/culture-tree` already
|
||||
ship the desired UX; verify they still typecheck and the homepage
|
||||
index shows both routes under sensible categories.
|
||||
|
||||
- [ ] **Player analysis** — at least one of `/stats`, `/replay`, or the
|
||||
in-game stats screen renders a domain-binned breakdown of a player's
|
||||
research history. Driven by deterministic replay fixture data.
|
||||
|
||||
- [ ] **Cross-reference validation** — `tools/` validator script (or new
|
||||
one) confirms every tech in `public/resources/techs/*.json` has a
|
||||
`domain` field set to one of the 10 canonical values.
|
||||
|
||||
- [ ] **Phase-gate proof screenshot** — captured per
|
||||
`phase-gate-protocol.md`. Archived under `.project/screenshots/`.
|
||||
|
||||
## Notes
|
||||
|
||||
- This objective is additive — `pillar` field remains the canonical
|
||||
data-org axis used by simulator code; `domain` is a metadata layer for
|
||||
UI/analysis grouping. No save-format change.
|
||||
|
||||
- The 10 domains are: Military, Economy, Industry, Agriculture,
|
||||
Governance, Culture, Science, Exploration, Engineering, Medicine. "All"
|
||||
is a UI sentinel only, never authored on a tech.
|
||||
|
||||
- Culture policies use `pillar` directly as their tab axis (6 trees).
|
||||
No `domain` field on culture data is required for this objective.
|
||||
|
||||
- Work parallelizable across simulator-infra, godot-ui, godot-engine,
|
||||
game-systems specialists. Coordinate via experts-loop / experts-team.
|
||||
|
|
@ -157,7 +157,7 @@
|
|||
"name": "Meteorology",
|
||||
"description": "Barometers, hygrometers, and systematic weather records. Pressure becomes a measurable quantity for the first time — wind patterns your scouts always noticed now gain scientific precision and predictive power.",
|
||||
"pillar": "natural_philosophy",
|
||||
"domain": "Exploration",
|
||||
"domain": "Science",
|
||||
"era": 4,
|
||||
"tier": 4,
|
||||
"cost": 95,
|
||||
|
|
@ -191,7 +191,7 @@
|
|||
"name": "Geology",
|
||||
"description": "Study of rock strata, volcanic processes, and mineral formation over geological time. Aerosol signatures from volcanic eruptions can now be tracked and used to predict seismic hazard zones.",
|
||||
"pillar": "natural_philosophy",
|
||||
"domain": "Exploration",
|
||||
"domain": "Science",
|
||||
"era": 4,
|
||||
"tier": 4,
|
||||
"cost": 90,
|
||||
|
|
@ -268,7 +268,7 @@
|
|||
"name": "Astronomy",
|
||||
"description": "Celestial mechanics and long-range atmospheric forecasting. Enables a 3-turn weather prediction overlay by synthesizing the pressure trends and wind corridors in your observation history. Accuracy scales with history depth.",
|
||||
"pillar": "natural_philosophy",
|
||||
"domain": "Exploration",
|
||||
"domain": "Science",
|
||||
"era": 5,
|
||||
"tier": 5,
|
||||
"cost": 125,
|
||||
|
|
@ -305,7 +305,7 @@
|
|||
"name": "Natural Philosophy",
|
||||
"description": "Unified empirical theory of matter, energy, and natural forces. The complete atmospheric composition — oxygen, carbon dioxide, methane — becomes legible. Climate change is no longer a surprise; it is a calculation.",
|
||||
"pillar": "natural_philosophy",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 5,
|
||||
"tier": 5,
|
||||
"cost": 135,
|
||||
|
|
@ -343,7 +343,7 @@
|
|||
"name": "Hydrology",
|
||||
"description": "The complete science of water movement — aquifers, groundwater tables, flood plains, and tidal forcing. Dwarven tunnelers can now map underground water sources before they breach them, preventing catastrophic flooding in deep holds.",
|
||||
"pillar": "natural_philosophy",
|
||||
"domain": "Exploration",
|
||||
"domain": "Science",
|
||||
"era": 6,
|
||||
"tier": 6,
|
||||
"cost": 160,
|
||||
|
|
@ -385,7 +385,7 @@
|
|||
"name": "Geophysics",
|
||||
"description": "The physics of tectonic plates, seismic waves, and volcanic systems. Provides advance warning of earthquakes, eruptions, and unstable ground — invaluable for civilizations whose cities are built into the rock itself.",
|
||||
"pillar": "natural_philosophy",
|
||||
"domain": "Exploration",
|
||||
"domain": "Science",
|
||||
"era": 7,
|
||||
"tier": 7,
|
||||
"cost": 195,
|
||||
|
|
@ -424,7 +424,7 @@
|
|||
"name": "Oceanography",
|
||||
"description": "Deep-ocean current mapping, thermocline science, and tidal modeling. Reveals the ocean as a heat-distribution engine that drives global climate — and unlocks the fastest deep-water trade routes.",
|
||||
"pillar": "natural_philosophy",
|
||||
"domain": "Exploration",
|
||||
"domain": "Science",
|
||||
"era": 8,
|
||||
"tier": 8,
|
||||
"cost": 230,
|
||||
|
|
@ -463,7 +463,7 @@
|
|||
"name": "Climatology",
|
||||
"description": "Long-cycle climate modeling integrating ocean, atmosphere, and geological drivers. Extends weather prediction to 5 turns and reveals multi-decade climate trends — ice ages, monsoon regime shifts, and the slow creep of desertification.",
|
||||
"pillar": "natural_philosophy",
|
||||
"domain": "Exploration",
|
||||
"domain": "Science",
|
||||
"era": 9,
|
||||
"tier": 9,
|
||||
"cost": 275,
|
||||
|
|
@ -513,7 +513,7 @@
|
|||
"name": "World Theory",
|
||||
"description": "The synthesis of all natural philosophy into a unified planetary model — geology, hydrology, atmosphere, ocean, biosphere, and magic as one interacting system. Reveals the health of the entire living world in a single lens.",
|
||||
"pillar": "natural_philosophy",
|
||||
"domain": "Exploration",
|
||||
"domain": "Science",
|
||||
"era": 10,
|
||||
"tier": 10,
|
||||
"cost": 330,
|
||||
|
|
@ -560,7 +560,7 @@
|
|||
"name": "Empiricism",
|
||||
"description": "Systematic observation supplanting saga-only knowledge. The world is measured, recorded, and questioned — not merely remembered.",
|
||||
"pillar": "natural_philosophy",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 2,
|
||||
"tier": 2,
|
||||
"cost": 30,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"name": "Scholarship",
|
||||
"description": "Systematic recording and study of knowledge on stone tablets and vellum. The foundation of all scientific pursuit.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 1,
|
||||
"tier": 1,
|
||||
"cost": 20,
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
"name": "Mathematics",
|
||||
"description": "Geometry, ballistics, and precise calculation. Essential for siege engines, architecture, and astronomical observation.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 2,
|
||||
"tier": 2,
|
||||
"cost": 40,
|
||||
|
|
@ -95,7 +95,7 @@
|
|||
"name": "Advanced Scholarship",
|
||||
"description": "Deep study of natural philosophy, optics, and geological science. Enables universities and observatories.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 3,
|
||||
"tier": 3,
|
||||
"cost": 70,
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
"name": "High Lore",
|
||||
"description": "The accumulated wisdom of generations, codified into grand treatises. Dramatically accelerates all future research.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 5,
|
||||
"tier": 5,
|
||||
"cost": 130,
|
||||
|
|
@ -167,7 +167,7 @@
|
|||
"name": "Stellar Mapping",
|
||||
"description": "Systematic observation and charting of celestial bodies — their positions, cycles, and relationships to the seasons and geological shifts. The foundation of calendars, navigation, and the first understanding that the world above mirrors the world below.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Exploration",
|
||||
"domain": "Science",
|
||||
"era": 5,
|
||||
"tier": 5,
|
||||
"cost": 125,
|
||||
|
|
@ -206,7 +206,7 @@
|
|||
"name": "Rune Archives",
|
||||
"description": "Formalized rune-script for permanent record. Vault-libraries cut into living stone, where every saga, ledger, and grudge is preserved against fire, flood, and forgetting.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 4,
|
||||
"tier": 4,
|
||||
"cost": 90,
|
||||
|
|
@ -237,7 +237,7 @@
|
|||
"name": "Clockwork Calculation",
|
||||
"description": "Mechanical computation engines — geared, brass-plated, housed in their own halls. Compute trajectories, tax balances, and probability with precision that shames any savant.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Engineering",
|
||||
"domain": "Science",
|
||||
"era": 6,
|
||||
"tier": 6,
|
||||
"cost": 175,
|
||||
|
|
@ -265,7 +265,7 @@
|
|||
"name": "Optical Arts",
|
||||
"description": "Precision lenses, telescopic instruments, observatory works carved into the highest peaks. The dwarven gaze extends past the edge of the world for the first time.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 7,
|
||||
"tier": 7,
|
||||
"cost": 230,
|
||||
|
|
@ -296,7 +296,7 @@
|
|||
"name": "Cryptarchives",
|
||||
"description": "Encoded knowledge vaults, war-intelligence locked behind clan-rune ciphers. Knowledge as weapon — denied to the enemy, leveraged by the keeper.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 8,
|
||||
"tier": 8,
|
||||
"cost": 290,
|
||||
|
|
@ -324,7 +324,7 @@
|
|||
"name": "Rune Simulation",
|
||||
"description": "Predictive models inscribed in vast rune-arrays — entire battles, harvests, market-cycles run forward in stone before they occur in flesh. Foreknowledge as engineering discipline.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 9,
|
||||
"tier": 9,
|
||||
"cost": 360,
|
||||
|
|
@ -352,7 +352,7 @@
|
|||
"name": "Omnilexica",
|
||||
"description": "Total knowledge synthesis: the Library Ascendant, where every saga, ledger, blueprint, and grudge of every clan since the First Delving is held in a single accessible record.",
|
||||
"pillar": "scholarship",
|
||||
"domain": "Culture",
|
||||
"domain": "Science",
|
||||
"era": 10,
|
||||
"tier": 10,
|
||||
"cost": 440,
|
||||
|
|
|
|||
|
|
@ -1223,6 +1223,19 @@ func _pick_research(player: RefCounted) -> void:
|
|||
"ecology": 1.0 + (exp + prod) / 2.0 * 0.5,
|
||||
}
|
||||
|
||||
# p1-29 cycle 3: catch-up dynamics. Compare this player's tech_era ceiling
|
||||
# against the highest opponent's; when behind by ≥2 eras, boost military +
|
||||
# metallurgy pillars 1.5× and waive the tier-3 mercantile penalty so the
|
||||
# losing player prioritises closing the tech gap rather than minting more
|
||||
# era-1 economy techs (the failure mode in the lever-3+4 batch where p1
|
||||
# stayed at tier_peak=1 forever, never developing).
|
||||
var self_tier_peak: int = _player_tier_peak(player)
|
||||
var opp_tier_peak: int = _max_opponent_tier_peak(player)
|
||||
var is_behind: bool = (opp_tier_peak >= self_tier_peak + 2)
|
||||
if is_behind:
|
||||
pillar_mult["military"] = float(pillar_mult["military"]) * 1.5
|
||||
pillar_mult["metallurgy"] = float(pillar_mult["metallurgy"]) * 1.5
|
||||
|
||||
# Pass 1: compute raw score for every tech (ignoring availability).
|
||||
var raw_score: Dictionary = {}
|
||||
for tech: Dictionary in all_techs:
|
||||
|
|
@ -1237,7 +1250,10 @@ func _pick_research(player: RefCounted) -> void:
|
|||
# Tier-3+ penalty for mercantile clans (low aggression AND low production).
|
||||
# Guards goldvein/runesmith from racing to tier_peak=6 identically to
|
||||
# ironhold/blackhammer. agg < 0.5 catches raw ≤5; prod < 0.5 catches raw ≤5.
|
||||
if int(tech.get("tier", 0)) >= 3 and agg < 0.5 and prod < 0.5:
|
||||
# p1-29 cycle 3: waive the penalty when this player is behind on tech —
|
||||
# a losing mercantile player needs to catch up rather than be further
|
||||
# slowed by their personality bias.
|
||||
if int(tech.get("tier", 0)) >= 3 and agg < 0.5 and prod < 0.5 and not is_behind:
|
||||
var trade_factor: float = (wlth + trd) / 2.0 # mercantile bias, [0, 1]
|
||||
sc *= maxf(0.4, 1.0 - trade_factor * 0.6) # up to 60% penalty for full mercantile clans
|
||||
for uid: Variant in tech.get("unlocks", {}).get("units", []):
|
||||
|
|
@ -1291,6 +1307,46 @@ static func _norm_axis(axes: Dictionary, key: String) -> float:
|
|||
return (clampf(raw, 1.0, 10.0) - 1.0) / 9.0
|
||||
|
||||
|
||||
static func _player_tier_peak(player: RefCounted) -> int:
|
||||
## Compute the highest tech-era this player has researched. Mirrors the
|
||||
## per-player tier_peak metric written into turn_stats.jsonl. Used by
|
||||
## _pick_research's catch-up branch (p1-29 cycle 3) to detect when this
|
||||
## player is falling behind on the tech ladder.
|
||||
if player == null:
|
||||
return 0
|
||||
var techs: Array = player.researched_techs if player.get("researched_techs") != null else []
|
||||
var peak: int = 0
|
||||
for tid: Variant in techs:
|
||||
var data: Dictionary = DataLoader.get_tech(str(tid))
|
||||
var era: int = int(data.get("era", 0))
|
||||
if era > peak:
|
||||
peak = era
|
||||
return peak
|
||||
|
||||
|
||||
static func _max_opponent_tier_peak(self_player: RefCounted) -> int:
|
||||
## Compute the highest opponent's tier_peak across all OTHER players in
|
||||
## GameState.players. Used by _pick_research's catch-up branch.
|
||||
## Returns 0 when self is the only player or no opponents have researched
|
||||
## anything yet (degenerate first-turn case).
|
||||
var max_peak: int = 0
|
||||
if GameState == null or GameState.players == null:
|
||||
return 0
|
||||
var self_idx: int = -1
|
||||
if self_player != null and self_player.get("index") != null:
|
||||
self_idx = int(self_player.index)
|
||||
for p: Variant in GameState.players:
|
||||
if p == null:
|
||||
continue
|
||||
var pi: int = int(p.index) if p.get("index") != null else -1
|
||||
if pi == self_idx:
|
||||
continue
|
||||
var peak: int = _player_tier_peak(p)
|
||||
if peak > max_peak:
|
||||
max_peak = peak
|
||||
return max_peak
|
||||
|
||||
|
||||
func _score_site(pos: Vector2i, game_map: RefCounted) -> float:
|
||||
## Score a hex as a city site. Food*2 + production*1.5 + resources.
|
||||
var tile: Resource = game_map.get_tile(pos)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue