From aefbb4bd8849d6c83cd7f6f252783d9ceeea5680 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sat, 6 Jun 2026 12:41:52 +0200 Subject: [PATCH] docs(meta): switch ticket-reference convention #N -> T-N (pql migration phase 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopts the T-NNN convention (T-N == old #N == pql ticket id) across the active operational layer: governance/ decision records, .claude/{rules,agents,skills}, CLAUDE.md, DECISIONS.md. 283 references rewritten. Guarded against false positives (17 correctly skipped, each logged): - PR references kept (PR #136/#138/... — PRs are a separate #-namespace) - non-ticket numbers kept (#4122; the "#1 process failure" idiom; "task #3") - only #N where N is an actual ticket id is rewritten; the 1-4 digit word-bounded match also excludes 6-digit hex colours in the visual decision records Git history is NOT rewritten (a commit's #N already equals T-N numerically), and historical archives (docs/sprints, docs/discussions, docs/workshops) keep their point-in-time #N. The /pr-process ticket-ID extraction logic moves to T-NNN in the Phase 4 consumer cutover. Verified: pql decisions validate ok; sync 357 records / 1057 refs / broken 0 (the prose edits don't affect decision parsing or the tickets.decision_ref linkage). Transform committed at tooling/pql-migrate/retag_ticket_refs.py. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/agents/clerk.md | 2 +- .claude/rules/asset-pipeline.md | 12 +- .claude/skills/pr-process/SKILL.md | 2 +- .claude/skills/pr-review/SKILL.md | 2 +- governance/README.md | 4 +- governance/decisions/architecture.md | 216 +++++++++++------------ governance/decisions/content.md | 50 +++--- governance/decisions/economics.md | 14 +- governance/decisions/perception.md | 28 +-- governance/decisions/process.md | 2 +- governance/decisions/scope.md | 14 +- governance/questions/architecture.md | 32 ++-- governance/questions/content.md | 58 +++--- governance/questions/process.md | 2 +- governance/questions/scope.md | 4 +- governance/rejected/economics.md | 2 +- governance/rejected/perception.md | 2 +- tooling/pql-migrate/retag_ticket_refs.py | 91 ++++++++++ 18 files changed, 314 insertions(+), 223 deletions(-) create mode 100644 tooling/pql-migrate/retag_ticket_refs.py diff --git a/.claude/agents/clerk.md b/.claude/agents/clerk.md index ade8843cc..72f60af6e 100644 --- a/.claude/agents/clerk.md +++ b/.claude/agents/clerk.md @@ -9,7 +9,7 @@ You are the CLERK, the institutional guardrail on a game development team buildi ## Your personality -Precise, dispassionate, thorough. You are not a reviewer — you don't judge code quality. You are a consistency checker. You say things like "File X contradicts D-142" and "Ticket #890 describes outcome Y but implementation does Z." You do not have opinions about design. You have facts about what was decided and whether the code matches. +Precise, dispassionate, thorough. You are not a reviewer — you don't judge code quality. You are a consistency checker. You say things like "File X contradicts D-142" and "Ticket T-890 describes outcome Y but implementation does Z." You do not have opinions about design. You have facts about what was decided and whether the code matches. ## Your role diff --git a/.claude/rules/asset-pipeline.md b/.claude/rules/asset-pipeline.md index 91b2ee6e6..d5ff93659 100644 --- a/.claude/rules/asset-pipeline.md +++ b/.claude/rules/asset-pipeline.md @@ -37,14 +37,14 @@ itself didn't change. --- -## The meta table stamp (#855, #856) +## The meta table stamp (T-855, T-856) After every successful non-dry-run, each generator writes a row to the `meta` table: ```sql CREATE TABLE meta ( generator_name TEXT PRIMARY KEY, -- 'import_economics' | 'generate_atlas' - schema_version TEXT NOT NULL, -- monotonic semver string (e.g. "1.0.0") — see #888 + schema_version TEXT NOT NULL, -- monotonic semver string (e.g. "1.0.0") — see T-888 schema_sha TEXT, -- SHA-1 of server/data/systems-schema.sql (tamper detection) generator_sha TEXT NOT NULL, -- SHA-1 of the generator source file(s) generated_at TEXT NOT NULL DEFAULT (datetime('now')) @@ -56,7 +56,7 @@ It is defined as the `SCHEMA_VERSION` constant in `tooling/schema_version.py` and must be bumped manually whenever the schema changes in a backwards-incompatible way. Unlike a SHA-1 hash, semver strings are orderable — this enables savegame migration lineage in Phase 5+: a save file can record which schema version it derives from and -determine exactly which migrations to apply (#888). The old SHA-1 is preserved in +determine exactly which migrations to apply (T-888). The old SHA-1 is preserved in `schema_sha` for tamper detection alongside the semver. The `generator_sha` is the SHA-1 of the concatenated bytes of the generator's @@ -96,7 +96,7 @@ fine: the freshness guarantee comes from the stamp, not from bytewise DB equalit --- -## Pre-push hook (#857) +## Pre-push hook (T-857) `.config/hooks/pre-push` (installed via `make install-hooks`) checks that whenever `server/data/systems.db` is in the push, its meta stamp matches the current generator @@ -119,7 +119,7 @@ the `/pr-push` skill's source-file watch list. --- -## /pr-push integration (#858) +## /pr-push integration (T-858) The `/pr-push` skill checks whether any generator source files are modified on the branch. If they are, it automatically runs `make regen-db` and stages the updated @@ -177,7 +177,7 @@ no hand-edit path that survives regen. ## Savegame migration lineage (Phase 5+) -`meta.schema_version` now stores a monotonic semver string (#888). When the savegame +`meta.schema_version` now stores a monotonic semver string (T-888). When the savegame system is built (Phase 5+), a save file records its `schema_version` string; the loader can determine which migrations to apply by comparing that version to the current one. `meta.schema_sha` retains the old SHA-1 for tamper detection. diff --git a/.claude/skills/pr-process/SKILL.md b/.claude/skills/pr-process/SKILL.md index e012af234..e34e90f40 100644 --- a/.claude/skills/pr-process/SKILL.md +++ b/.claude/skills/pr-process/SKILL.md @@ -214,7 +214,7 @@ git merge origin/main --no-edit If merge conflicts, **stop and report** — let the user resolve. If clean, continue. -### 4a. Regen systems.db if generator sources or data changed (#858) +### 4a. Regen systems.db if generator sources or data changed (T-858) Check whether any file in the **source-file watch list** was modified on this branch versus `origin/main`. This list covers generator code AND the data files that feed them. diff --git a/.claude/skills/pr-review/SKILL.md b/.claude/skills/pr-review/SKILL.md index d802e8b88..82d9eb1eb 100644 --- a/.claude/skills/pr-review/SKILL.md +++ b/.claude/skills/pr-review/SKILL.md @@ -62,7 +62,7 @@ unchecked, include a top-level note: > **Merge-path smoke not performed.** PR test plan has unchecked manual > smoke box(es): [list]. A reviewer or the team must run the smoke before -> merge approval. Sprint 36 bug #872 (New Game hangs on 'connecting') +> merge approval. Sprint 36 bug T-872 (New Game hangs on 'connecting') > landed exactly here — do not skip. Unchecked merge-path smoke boxes downgrade the verdict from APPROVED to diff --git a/governance/README.md b/governance/README.md index 432b91c21..d0b1279eb 100644 --- a/governance/README.md +++ b/governance/README.md @@ -96,7 +96,7 @@ line in place — keep the Q-record for the audit trail rather than deleting it. - [D-027: Vertical slice — smuggler + detective, two-character proof [SUPERSEDED]](decisions/scope.md#d-027-vertical-slice--smuggler--detective-two-character-proof-superseded) — _scope_ - [D-028: Dialogue architecture — tagged line pools with four relational layers](decisions/content.md#d-028-dialogue-architecture--tagged-line-pools-with-four-relational-layers) — _content_ - [D-029: Population entanglement ratio — 30/50/20](decisions/content.md#d-029-population-entanglement-ratio--305020) — _content_ -- [D-030: Testability architecture — 8 decisions for ticket #214](decisions/architecture.md#d-030-testability-architecture--8-decisions-for-ticket-214) — _architecture_ +- [D-030: Testability architecture — 8 decisions for ticket T-214](decisions/architecture.md#d-030-testability-architecture--8-decisions-for-ticket-t-214) — _architecture_ - [D-031: Time system — game clock and day phases](decisions/architecture.md#d-031-time-system--game-clock-and-day-phases) — _architecture_ - [D-032: Separate monologue pools per character [SUPERSEDED — deferred to Phase 6]](decisions/content.md#d-032-separate-monologue-pools-per-character-superseded--deferred-to-phase-6) — _content_ - [D-033: Entity color = relationship to player](decisions/perception.md#d-033-entity-color--relationship-to-player) — _perception_ @@ -371,7 +371,7 @@ line in place — keep the Q-record for the audit trail rather than deleting it. - [Q-089: Procedural star rendering for space viewports and star map](questions/architecture.md#q-089-procedural-star-rendering-for-space-viewports-and-star-map) — _architecture_ - [Q-090: Markov chains for procedural name/text generation in Rust](questions/architecture.md#q-090-markov-chains-for-procedural-nametext-generation-in-rust) — _architecture_ - [Q-091: Event-driven audio system](questions/architecture.md#q-091-event-driven-audio-system) — _architecture_ -- [Q-092: Modular settings menu as foundation for #735](questions/architecture.md#q-092-modular-settings-menu-as-foundation-for-735) — _architecture_ +- [Q-092: Modular settings menu as foundation for T-735](questions/architecture.md#q-092-modular-settings-menu-as-foundation-for-t-735) — _architecture_ - [Q-093: Tile-based exploration map in player insert (Google Maps for the implant)](questions/architecture.md#q-093-tile-based-exploration-map-in-player-insert-google-maps-for-the-implant) — _architecture_ - [Q-097: Strip "What They Don't Talk About" from corporation pages](questions/content.md#q-097-strip-what-they-dont-talk-about-from-corporation-pages) — _content_ - [Q-099: Mod content catalog — body rows / terrain_reference overlay for systems.db](questions/architecture.md#q-099-mod-content-catalog--body-rows--terrain-reference-overlay-for-systemsdb) — _architecture_ diff --git a/governance/decisions/architecture.md b/governance/decisions/architecture.md index b0e1f3499..3922f0c31 100644 --- a/governance/decisions/architecture.md +++ b/governance/decisions/architecture.md @@ -88,7 +88,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Raised by:** Team Leader (timestamp model), Tyre (technical validation), Gestalt (scope tags) - **Dissent:** None -### D-030: Testability architecture — 8 decisions for ticket #214 +### D-030: Testability architecture — 8 decisions for ticket T-214 - **Date:** 2026-02-11 - **Decision:** The v0.1 testability architecture is confirmed with 8 sub-decisions from the Gap Analysis Workshop (Round 18): 1. **Rust test organization = Hybrid.** `#[cfg(test)]` for unit tests inside modules + `tests/` directory for integration tests. Both via `cargo nextest run`. @@ -97,7 +97,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser 4. **Production code constraints + CauseChain.** No `#[cfg(test)]` in production. Public API is the test surface. ECS World setup replaces mock injection. CauseChain is a production component (monologue provenance, journal, debugging) that tests also leverage. 5. **Test runner tooling.** `cargo-nextest` (Rust) + gdUnit4 (Godot) + bash wrapper scripts in `test/` directory, whitelistable for agent use. 6. **Test output format = JSON summary.** Consistent schema across all runners (suite, total, passed, failed, failures array). JUnit XML as secondary CI format. - 7. **#201 (Deterministic replay) promoted to CRITICAL.** Simulation must consume time, randomness, and input exclusively through injectable resources (`SimulationTime`, `SimRng`, `InputQueue`). Required by D-010 principle 4. + 7. **T-201 (Deterministic replay) promoted to CRITICAL.** Simulation must consume time, randomness, and input exclusively through injectable resources (`SimulationTime`, `SimRng`, `InputQueue`). Required by D-010 principle 4. 8. **Test priority aligned with hard blockers.** Phase 1 (sprint 1-2): test infra + collision/pathfinding/time. Phase 2 (sprint 3-4): monologue pipeline integration test + information boundary negative tests. Phase 3 (sprint 5+): CauseChain verification + divergent snapshots. - **Rationale:** Two rounds of analysis by Tyre (Technical Architect) and Hoshe (QA Engineer) with cross-validation from all design agents. Key change: gdUnit4 over GUT driven by agent-driven development requirements (JSON output, headless stability, bus factor). CauseChain endorsed unanimously after all design agents independently identified the need for information provenance tracking. - **Raised by:** Tyre (architecture), Hoshe (testability analysis). Full workshop endorsed. @@ -119,26 +119,26 @@ Technical foundation decisions that constrain implementation: engine, client-ser ### D-041: Knowledge Graph Data Model - **Date:** 2026-02-11 - **Decision:** The knowledge graph is a per-entity bevy_ecs Component with BTreeMap storage for deterministic iteration. Each entity that has knowledge (player character, Active-tier NPCs, Background-tier NPCs) gets a `KnowledgeGraph` component containing: (1) entity knowledge map: `BTreeMap`, (2) fact knowledge map: `BTreeMap`. Knowledge confidence uses a 4-level hierarchy: `Suspects < KnowsOf < KnowsDetails < Direct`. Knowledge state tracks temporal/logical status: `Active` (believed true), `Contradicted` (conflicting information exists), `Stale` (aged beyond threshold). Knowledge source provides provenance per entry: `DirectObservation`, `Heard`, `ToldBy`, `Inferred`, `Background`. Stable entity IDs (`StableId(u64)`) replace bevy_ecs Entity handles in knowledge references, mapped via `EntityRegistry` resource for bidirectional `StableId <-> Entity` lookup. Knowledge updates flow through event-driven architecture: perception systems emit `KnowledgeEvent` to `KnowledgeEventQueue` resource, knowledge update system drains queue and writes to `KnowledgeGraph` components. Basic decay runs once per game-minute (every 10 ticks per D-031), downgrading confidence levels based on `last_observed_tick` age against configurable `DecayThresholds`. -- **Sprint 2 scope:** Full data structures + direct observation flow + basic decay + observer snapshot integration (#112). Deferred to Sprint 3+: NPC-to-NPC gossip, `ToldBy`/`Inferred` source generation, `Contradicted` state detection, `Stale` state logic, knowledge-driven dialogue filtering, monologue triggering, misinformation. +- **Sprint 2 scope:** Full data structures + direct observation flow + basic decay + observer snapshot integration (T-112). Deferred to Sprint 3+: NPC-to-NPC gossip, `ToldBy`/`Inferred` source generation, `Contradicted` state detection, `Stale` state logic, knowledge-driven dialogue filtering, monologue triggering, misinformation. - **Canonical reference:** Full Rust struct definitions at `docs/workshops/knowledge-graph-information-boundaries/round2-synthesis.md` Part 3 (lines 320-752). All implementation must conform to those types. - **Key design choices:** - - **BTreeMap over HashMap:** D-010 principle 4 (deterministic simulation) requires stable iteration order. BTreeMap provides O(log N) lookups (~6-8 comparisons at N=50-200), deterministic serialization, and predictable replay behavior. HashMap iteration is non-deterministic and incompatible with deterministic replay (D-030 sub-decision #7). + - **BTreeMap over HashMap:** D-010 principle 4 (deterministic simulation) requires stable iteration order. BTreeMap provides O(log N) lookups (~6-8 comparisons at N=50-200), deterministic serialization, and predictable replay behavior. HashMap iteration is non-deterministic and incompatible with deterministic replay (D-030 sub-decision T-7). - **Per-entity Component, not centralized Resource:** Enables `Changed` dirty tracking, per-entity serialization for D-026 tier transitions, no shared mutable state, natural ECS query patterns. - **4-level confidence hierarchy:** Resolves Q-016. `Suspects` = "something's off", gates initial investigation. `KnowsOf` = "X is involved in Y", gates topic-specific dialogue and peer-tier access (D-028). `KnowsDetails` = actionable detail, gates confrontation and secret-tier dialogue. `Direct` = currently in LOS, provides live position data and maximum rendering fidelity. Maps to D-028 access tiers: `surface` available at any level, `real` at KnowsOf+, `secret` at KnowsDetails+. - - **KnowledgeState for contradiction detection:** THE FRIEND arc (D-034, D-039 wow moment #3) requires detecting when a `ToldBy` entry conflicts with a `DirectObservation` entry. Both entries receive `Contradicted` state, triggering monologue event and relationship state shift (PersonOfInterest). Sprint 2 only uses `Active` state; contradiction detection ships Sprint 3. + - **KnowledgeState for contradiction detection:** THE FRIEND arc (D-034, D-039 wow moment T-3) requires detecting when a `ToldBy` entry conflicts with a `DirectObservation` entry. Both entries receive `Contradicted` state, triggering monologue event and relationship state shift (PersonOfInterest). Sprint 2 only uses `Active` state; contradiction detection ships Sprint 3. - **StableId for knowledge references:** Partially resolves Q-019 for server-side and knowledge graph purposes. Knowledge graphs reference `StableId(u64)` that persists across save/load cycles, not bevy_ecs `Entity` (generational index). `EntityRegistry` maintains bidirectional mapping. Assigned once at entity creation, never changes. Client-side mapping (Godot StableId -> scene node) remains open. - **Event-driven updates:** Phase 2 (perception) emits events. Phase 3 (knowledge) consumes events and writes graphs. Phase 4 (snapshot) reads graphs. Prevents mutable borrow conflicts in bevy_ecs. - **Performance budget:** ~14 KB per NPC knowledge graph (50 entities + 20 facts). Active tier (80 NPCs) = ~1.1 MB. Background tier (2,000 NPCs, 10 entries each) = ~5 MB. Total live memory: ~6 MB. Knowledge lookups are O(log N) at N=50 (~100ns per query). Not on critical path (shadowcasting/spatial queries consume 10-20ms per tick, knowledge operations <3ms). - **Resolves:** Q-016 (knowledge hierarchy). Partially resolves Q-019 (entity ID stability, server-side). -- **Blocks:** #352 (Observer Snapshot Pipeline Workshop) -- **Raised by:** Tyre (architecture synthesis), Dudley (implementation analysis), Gestalt (mechanics validation), Paula (narrative requirements). Workshop participants: Tyre, Dudley, Gestalt, Paula, SI. Source: Knowledge Graph & Information Boundaries Workshop (Epic #351), 2026-02-11. +- **Blocks:** T-352 (Observer Snapshot Pipeline Workshop) +- **Raised by:** Tyre (architecture synthesis), Dudley (implementation analysis), Gestalt (mechanics validation), Paula (narrative requirements). Workshop participants: Tyre, Dudley, Gestalt, Paula, SI. Source: Knowledge Graph & Information Boundaries Workshop (Epic T-351), 2026-02-11. - **Dissent:** None. Gestalt initially proposed dual-axis model (confidence + understanding) but validated that single-axis confidence with future content-driven understanding progression is architecturally sufficient. Dudley proposed HashMap; synthesis chose BTreeMap per D-010 principle 4 with Dudley's explicit acknowledgment ("iteration order is a determinism time bomb"). ### D-042: UI microcopy format — YAML via GDScript autoload - **Date:** 2026-02-13 - **Decision:** UI strings (interaction prompt labels, knowledge panel labels, relationship state descriptors, HUD labels, tutorial text) are stored in YAML format at `client/data/ui-strings.yaml` and loaded via a dedicated GDScript autoload singleton (`UIStrings`). UI strings are NOT hardcoded as GDScript constants in `client/scripts/constants/ui_strings.gd`. - **Rationale:** YAML format enables editing UI text without rebuilding the client and supports future localization infrastructure (all player-facing text in one format). UI microcopy is **client-side rendering data** per D-020 (Godot is the renderer) — distinct from server-side game content (dialogue/monologue lines). UI labels are presentation metadata that never cross the protocol boundary, so they live in the client repository and load via a client-side autoload rather than the content loader system. Hardcoded constants would require client recompilation for copy edits. -- **Related ticket:** #409 (UI microcopy) +- **Related ticket:** T-409 (UI microcopy) - **Raised by:** Team decision in Sprint 5 planning - **Dissent:** None @@ -175,7 +175,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Fog shader ([D-059](perception.md#d-059-fog--shader-based-five-layers-knowledge-graph-driven)):** Unaffected — fog is screen-space, driven by PointLight2D vision cone and LOS mask from sim-resolution shadowcasting. Gradient edge "3-4 tiles" is retuned to 6-8 sim tiles (= 3-4 visual tiles) to preserve the intended softness. - **Cursor/interaction:** No change — cursor already resolves to sim tile from pixel position. Interaction range of ~2 sim tiles = 1m (arm's length). - **Map authoring:** Author at 1m visual scale. Subdivision tool expands each visual tile to 4 sim tiles (2x2). Validation enforces 2x2 minimum on all world geometry layers. -- **Amends:** OQ-01 resolution (ticket #444). Sim tile size remains 0.5m; visual presentation changes from 1:1 to 2:1 retina factor. +- **Amends:** OQ-01 resolution (ticket T-444). Sim tile size remains 0.5m; visual presentation changes from 1:1 to 2:1 retina factor. - **Cross-reference:** Tile-based movement ([D-054](#d-054-tile-based-movement-with-same-tile-occupancy)), shadowcasting ([D-238](perception.md#d-238-symmetric-shadowcasting-albert-ford-selected-for-los-computation)), fog ([D-059](perception.md#d-059-fog--shader-based-five-layers-knowledge-graph-driven)), art direction ([D-043](perception.md#d-043-art-direction--visual-style-functional-warmth)), z-stack ([D-049](perception.md#d-049-z-level-rendering-stack-8-layers)), stance system ([D-053](scope.md#d-053-movement-as-stance-toggle-system)) - **Rationale:** 0.5m sim tiles give stealth-grade granularity for movement stances, cover peeking, and interaction range. 1m visual tiles make spaces feel proportional, sprites look right, and world geometry readable. The 2x2 minimum on geometry eliminates visual/sim mismatch for cover and LOS — the only sub-visual-tile positioning is entity movement, which is communicated through fog feedback, not tile counting. Analogous to macOS Retina: logical resolution (visual) differs from physical resolution (sim), but the system is coherent because both agree on where solid objects are. - **Raised by:** Team Leader (Jeroen) — proposed retina scaling analogy and 2x2 geometry constraint. Tyre (feasibility: trivial, half-day integration). Gestalt (approved with 2x2 constraint resolving LOS readability concern). Ozzie (approved: solves sprite scale without uncanny mismatch). @@ -244,7 +244,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Rationale:** Chunk size of 32×32 visual (64×64 sim) gives a 32m streaming cell — large enough to hold a meaningful space, small enough for efficient streaming. The 2×2-chunk block provides a generator planning unit with enough granularity for per-chunk variation. The 4×4 block district (256×256 visual) gives a full district footprint generalisable as a template for the Q-036 generator. The chunk-based fill system within blocks allows the generator to place buildings of varying scale without hard-coding building dimensions. - **Raised by:** Tyre (chunk/block spec and memory confirmation), confirmed by team. Lead ratified district = 4×4 blocks. - **Dissent:** Araminta preferred 32×32 visual chunk size (effectively halving the chunk to a 16m cell). Overruled by lead and team majority — 32m chunk is the minimum viable streaming cell for the simulation architecture. -- **Source:** Station District Layout Workshop, Ticket #153, Sprint 20. Round document: `docs/discussions/round-20-station-district-layout.md` +- **Source:** Station District Layout Workshop, Ticket T-153, Sprint 20. Round document: `docs/discussions/round-20-station-district-layout.md` - **Cross-reference:** D-012 (tile spec — amended), D-014 (v0.1 map spec — district bounding box superseded), D-066 (dual-scale grid), D-093 (Sova Transit District layout using this hierarchy), Q-036 (district generator) --- @@ -253,7 +253,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Date:** 2026-02-27 - **Decision:** Two layout modes coexist for district generation. `Grid`: Commission-planned districts with rectilinear block placement. `Organic`: pioneer/growth districts with block offsets (±16 sim tiles per axis), rotation (0–3 steps, 15° increments), variable street width (0.75–2.0×). Hard technical ceiling: maximum rotation ±45°. Beyond 45°, tile-based pathfinding produces unacceptable movement artifacts. Organic districts produce curved-street impressions through angular jogs and irregular setbacks. Grid vs. Organic proportions must vary per seed to prevent predictable meta-level patterns. - **Rationale:** Grid = power imposed (Commission-planned). Organic = power negotiated (pioneer settlements, organic growth). Both modes encode political and settlement history in spatial form. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. Full spec: `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-1. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. Full spec: `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-1. - **Raised by:** Tyre (technical architecture), Miri (cultural grammar). Full team sign-off. - **Dissent:** None. - **Cross-reference:** D-094 (spatial hierarchy), Q-036 (district generator) @@ -262,7 +262,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Date:** 2026-02-27 - **Decision:** The district generator runs a `GuaranteeAuditResult` with three tiers of spatial guarantees. **Tier 1 — Universal (all inhabited):** Social Hub, Informal Zone, Encounter Corridor. **Tier 2 — Full-complexity:** Traffic Chokepoint, Institutional Space, Insider Space, Economic Node, Horizon View Corridor (coastal), BreachOnly Zone (≥1), Rooftop Discovery Zone (tall structures). **Tier 3 — Conditional:** A-1 Elevated Vantage, A-2 Egress Multiplicity, A-3 Temporal Opacity Window, A-4 Non-Institutional Route, Economic Asymmetry Signal, Power Gradient Visibility. A Full-complexity coastal urban hub gets up to 13 checks. Archetype placement must vary in angular position (not just distance) across seeds — audit fails if archetypes cluster predictably across a test batch of N seeds. - **Rationale:** The generator makes contracts it keeps. Guaranteed affordances ensure every playstyle has spatial affordances in any district, without hand-crafting each location. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. Full spec: `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-2. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. Full spec: `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-2. - **Raised by:** Gestalt (tier structure + assassin lens integration), Tyre (GuaranteeAuditResult struct). Full team sign-off. - **Dissent:** None. - **Cross-reference:** D-103 (assassin lens guarantees A-1 through A-4), D-102 (horizon view corridor — Tier 2 coastal) @@ -272,7 +272,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Date:** 2026-02-27 - **Decision:** Two complementary enums classify tiles behind wall surfaces. `WallBackside` (structural): what is physically there — `AdjacentSpace | StructuralFill | ServiceVoid | ChunkBoundary | Exterior`. `TileBehindState` (gameplay): what kind of space this represents — `StructuralFill | HiddenRoom | Interstitial`. Mapping: `ServiceVoid → Interstitial`; `AdjacentSpace → HiddenRoom or StructuralFill` depending on access tier. Era-tagged infrastructure cavity contents with standardized color codes: Era 1 power conduit only (`#c8b840`), Era 2 power + water/coolant (`#4888c8`) + comm lines (`#b8b8b8`), Era 3 full bundle. Backside assignments within a template must have seed-driven variation — not fixed template values. - **Rationale:** "Every wall is a secret keeper." No tile is ever void. Dual classification separates structural truth (what's there physically) from gameplay meaning (what does this imply for the player's investigation). -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-4. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-4. - **Raised by:** Tyre (WallBackside), Gestalt (TileBehindState). Full team sign-off. - **Dissent:** None. @@ -280,7 +280,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Date:** 2026-02-27 - **Decision:** Generator output is immutable after Phase 1. All post-generation modifications are applied via overlay, not re-generation. `DamageOverlay` struct: `overlay_type` (GasExplosion | Fire | Structural { collapse_direction } | Flooding), `epicenter: ChunkLocalPos`, `radius: f32`, `intensity: f32`, `scatter_seed: u64` (variation within zone only). `RegenerationStrategy` enum: `LocalOverlay(DamageParameters)` for in-playthrough events (MANDATORY), `SoftReseed { seed_modifier: u64 }` at scenario boundaries only, `FullReseed` at era-level discontinuities only. Trauma event → visual stage mapping: PhysicalDestruction/ViolenceEvent → Stage 2 (Fresh Aftermath), decays to Stage 3; EconomicDisruption/PoliticalShock/MigrationShock → quarter fill modifier. Full stage sequence: Stage 1 Active → Stage 2 Fresh Aftermath → Stage 3 Stabilized → Stage 4 Reconstruction → Stage 5 Healed Scar. Destruction palette is corruption-only: no new colors introduced by destruction. Single exception: `#c8d8f0` open-sky tile appears when a roofed structure has its roof removed. See D-109 for the XOR prohibition as architectural mandate. - **Rationale:** Modification history diverges per playthrough on the same seed. Same world, different event histories, different delta layers — this is the replayability engine. Causal legibility requires the player to be able to read what happened from the world state. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-5. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-5. - **Raised by:** Tyre (structs), Gestalt (LocalOverlay mandate). Destruction stages and palette constraint: Araminta (Round 5). - **Dissent:** None. - **Cross-reference:** D-109 (XOR prohibition as architectural mandate), D-107 (trauma events — cultural track) @@ -289,7 +289,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Date:** 2026-02-27 - **Decision:** Zone palettes use `ZonePalette { base: BasePalette, modifiers: Vec }`. Eight canonical base terrain types: T1 temperate farmland (warm organic, natural lighting) / T2 industrial farmland (cool grey-green, artificial lighting) / T3 wilderness / T4 grassland / T5 coastal water (deep near-black blue, animated specular; referenced by D-102 horizon corridor guarantee) / T6 beach/coastal margin (warm dark tan) / T7 mountain/high terrain (dark blue-grey stone, snow at elevation) / T8 desert/arid. T1 and T2 are explicitly distinct farmland types. Additional terrain types must be specified with new numbers — not silent replacements for existing types. Modifier axes: A (heritage root → material character), B (economic tier → condition/density), C (era → material generation), plus faction overlay, climate, condition, season. Palette modifiers influence NPC appearance as well as environment (people dress like they're from here). - **Rationale:** A zone's visual identity must be legible at a glance. Palette modifiers create cultural visual identity without rewriting base terrain. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-6. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-6. - **Raised by:** Araminta (terrain types and color specs, canonical T5/T7 numbering corrected Round 5), Tyre (palette struct). Full team sign-off. - **Dissent:** None. - **Cross-reference:** D-102 (horizon view corridor — T5 coastal water is the referenced terrain type), D-104 (heritage grammar overlay — modifier axis A) @@ -300,7 +300,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Date:** 2026-02-27 - **Decision:** A **negative-space** reservation for coastal districts: ≥8 visual tiles unobstructed view corridor from nearest public street to water's edge. No building, tree, or z=4 element may occupy this corridor. A low z=2 element (railing, bench, bollard) marks the waterfront point as a designed viewing location. Tier 2 Conditional guarantee — applies to Full-complexity coastal districts. Position within the district must vary per seed; the Wow Moment of seeing the horizon must be discovered, not expected. - **Rationale:** "Negative-space reservation" framing — the generator reserves space by prohibiting placement, not by placing something. The view of the horizon is a spatially guaranteed player experience. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-7. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-7. - **Raised by:** Araminta (visual grammar and negative-space framing), Tyre (implementation constraint). Full team sign-off. - **Dissent:** None. - **Cross-reference:** D-097 (guarantee tier system — Tier 2), D-101 (ZonePalette — T5 coastal water is the terrain type this guarantee references) @@ -309,7 +309,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Date:** 2026-02-27 - **Decision:** Four derived spatial properties validated by the guarantee audit for Full-complexity districts. These are **derived properties of existing spatial configuration**, not assassin-tagged features — they add no generation cost; the audit validates existing output. **A-1 Elevated Vantage** (Tier 3): ≥1 position with clear LOS cone to Traffic Chokepoint. **A-2 Egress Multiplicity** (Tier 3): ≥2 exit routes to adjacent districts. **A-3 Temporal Opacity Window** (Tier 3): ≥1 time window where Social Hub has reduced ambient NPC coverage. **A-4 Non-Institutional Route** (mandatory Full-complexity): ≥1 route to any Insider zone not passing through high-security institutional spaces. A-1/A-2/A-3 are Tier 3 Conditional (trigger on `complexity_tier == Full`). A-4 is mandatory for all Full-complexity districts regardless of playstyle. - **Rationale:** The investigator/assassin playstyle needs guaranteed affordances without the generator explicitly building for assassination. Derived properties keep generation cost zero while ensuring spatial conditions exist. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-8. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-8. - **Raised by:** Gestalt (assassin lens framing and derived-properties insight). Full team sign-off. - **Dissent:** None. - **Cross-reference:** D-097 (guarantee tier system — Tier 3) @@ -318,7 +318,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Date:** 2026-02-27 - **Decision:** Four height tiers: S1 (1–2 z-levels, surface + roof/mezzanine), S2 (3–10), S3 (11–30), S4 (30+). Shadow length is the primary height signal in top-down view (2–40 visual tiles). Lazy z-level loading: `ZLevelLoadState: Loaded | Skeleton | Ungenerated` — only current + adjacent z-levels filled by Phase 2. **Rooftop Bar Clause:** Every tall structure (z_band_count ≥ 3) must assign `RooftopConfig: Restricted | PublicWithHiddenLayer`. Discovery layer mandatory in both configurations. Heritage root **weights the probability** between the two configs — it does not determine the outcome. Final config is seeded per-building; a minority of buildings of any heritage root may be the non-dominant type (a Frost building with a rooftop bar must be possible). Z-band floor boundaries must have seed-variation within cultural ordering constraints. Vertical access routes are playthrough-history dependent. - **Rationale:** Height has meaning — floor 30 has information floor 1 cannot have because it is harder to reach. Full determination of rooftop config by heritage root kills the discovery moment. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-11. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-11. - **Raised by:** Tyre (z-level architecture), Ozzie (Rooftop Bar Clause — discovery guarantee). Ozzie + Araminta corrected "determines" → "weights probability" in Round 5. - **Dissent:** None. - **Cross-reference:** D-094 (spatial hierarchy), D-097 (guarantee tier system — Rooftop Discovery Zone is Tier 2) @@ -327,7 +327,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Date:** 2026-02-27 - **Decision:** Entity-carried interior space attached to a mobile world entity. Not a district — uses the same chunk fill primitives in a simpler flat structure (no Phase 1/Phase 2 split, no block grid, no zone negotiation). Key structs: `MobileChunk`, `MobileInterior`, `VesselClass`, `MobileMovementState` (Docked / InTransit / InterSystem / Idle), `TransitSocialModifier`, `MobileNpcSlot`, `NpcPersistence` (Crew | Passenger). `Idle` = vessel parked at a location but not docked to infrastructure (anchored ship, grounded shuttle). Vessels are **persistent world entities** — interior cache keyed by entity_id persists across voyages for crew state. `Docked` state requires `dock_position`, `connected_chunk: Option`, `docked_since: SimTick`, `scheduled_departure: Option`. `scheduled_departure` must be populated by the generator; vessels without departure schedules are an error state. Cultural grammar: `TransitSocialModifier` with `TransitVariant` (BoundedLinear | BoundedMobile | InterSystem). Vessel visual grammar (5 rules): (1) hull uses vessel-identity material, not zone palette; (2) windows reveal exterior context (docked vs. transit); (3) compression modifier tightens proportions; (4) section transitions use vessel-identity threshold elements; (5) class stratification via proportion, not palette. Replayability requirements R-V-1 through R-V-6 in `docs/workshops/generator-architecture/round-4-notes.md` §5. Memory: ~0.5–4KB metadata + up to 64KB ChunkData per vessel; paged by streaming model. - **Rationale:** "The journey is content — mobile environments are social pressure cookers, not loading screens with chairs." Vessel persistence and crew state continuity make the world feel real. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-13. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-13. - **Raised by:** Tyre (struct design), Miri (cultural grammar — `miri-round4.md`), Nigel (replayability requirements), Ozzie (player experience). Visual grammar: Araminta (`araminta-round4.md` §2). - **Dissent:** Nigel initially proposed instanced districts for vessels; lead ruled entity-carried MobileChunk for persistence. - **Note:** The `Idle` movement state is the canonical primitive for player-owned stationary installations (space stations, orbital platforms, parked vessels as permanent bases). A MobileChunk in `Idle` with no `scheduled_departure` is architecturally equivalent to a static chunk from the simulation's perspective — it participates in the same tile/zone system. This design prevents future over-engineering of a separate 'player installation' system. @@ -337,7 +337,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Date:** 2026-02-27 - **Decision:** XOR reseeding for in-playthrough events is **architecturally prohibited**. `LocalOverlay` is the mandatory modification strategy for all events that occur while the player is present. `SoftReseed` and `FullReseed` are permitted only at scenario-boundary and era-level discontinuities respectively — events the player was not present for, where causal legibility is not required. This prohibition is filed as a separate D-record from D-100 because it establishes the modification principle for the entire game, not just the overlay mechanics. - **Rationale:** Causal legibility: the player must be able to look at a damaged district and understand what happened. XOR reseeding destroys the causal thread. Unanimous consensus across all workshop participants — the strongest architectural agreement of the entire workshop. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-14. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-14. - **Raised by:** Gestalt (XOR prohibition framing), Tyre (RegenerationStrategy struct). Unanimous. - **Dissent:** None. - **Cross-reference:** D-100 (DamageOverlay + RegenerationStrategy full specification) @@ -438,7 +438,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - `WalkabilityMap` API is unchanged — callers don't know about palettes. - BTreeMap for deterministic iteration per D-010 principle 4. - Palette keys are `char` (single Unicode codepoint) for direct mapping from tile string arrays. -- **Raised by:** Tyre (architecture), requested by #586 (Epic: extensible tile data model). +- **Raised by:** Tyre (architecture), requested by T-586 (Epic: extensible tile data model). - **Dissent:** None anticipated — this is a design-only D-record for post-v0.1 implementation. - **Cross-reference:** D-054 (tile-based movement), D-066 (dual-scale grid), D-094 (spatial hierarchy), D-099 (WallBackside classification), D-100 (DamageOverlay), D-012 (chunk architecture) @@ -515,7 +515,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Architecture note:** The existing `EntityRenderer` (`client/scripts/rendering/entity_renderer.gd`) currently uses a single `Sprite2D` per entity. Under this decision, `EntityRenderer` is extended to instantiate a `CharacterCompositor` scene (Node3D subtree) instead. The compositor API is specified in `docs/design/compositor-api-spec.md`. - **Raised by:** Team Leader (Jeroen) — spike prototype confirmed; Sprint 28 workshop decision - **Dissent:** None -- **Cross-reference:** [D-148](#d-148-30-low-angle-camera-with-45-map-rotation--supersedes-d-019), [D-150](#d-150-character-outline--inverted-hull-method), [D-151](#d-151-direction-count--8-server-side-facings-4-visual-groups-client-sprint-28), [D-152](#d-152-character-lod--performance-driven-budget-not-distance-threshold), ticket #693 (compositor implementation) +- **Cross-reference:** [D-148](#d-148-30-low-angle-camera-with-45-map-rotation--supersedes-d-019), [D-150](#d-150-character-outline--inverted-hull-method), [D-151](#d-151-direction-count--8-server-side-facings-4-visual-groups-client-sprint-28), [D-152](#d-152-character-lod--performance-driven-budget-not-distance-threshold), ticket T-693 (compositor implementation) ### D-150: Character outline — inverted hull method - **Date:** 2026-03-17 @@ -596,11 +596,11 @@ Technical foundation decisions that constrain implementation: engine, client-ser | 5 | Player control & in-world rendering — character, walls/stairs/doors, lighting, drawn on generated tiles (no test map) | Player viewport with final-version assets on the generated world | | 6 | Detail coloring — room-level content, cultural architecture | Only when the world is walkable | -- **Amendment (2026-05-22):** Phases 4 and 5 swapped — **world generation now precedes player control**. The rule: no player-control or in-world rendering work begins until the generator can deterministically seed-generate every tile of every world via the full multilayer cascade. The original Phase 4 "2-floor test map" is **dropped** — test layers are produced by the generator itself once layer-drawing begins; we start drawing the world only when generation knows what to draw. Generation progress is viewed as **per-layer maps in the implant Atlas** (the Phase 3 deliverable, already built), not via an in-world renderer. The existing in-world rendering code is **left as-is until Phase 5** — neither built upon nor removed before then. Rationale: building player systems against a throwaway test substrate means rebuilding them against real generated tiles later; gating player work on deterministic generation avoids that waste. Epics #749 (now Phase 5) and #750 (now Phase 4) and the CLAUDE.md cascade table are updated to match. +- **Amendment (2026-05-22):** Phases 4 and 5 swapped — **world generation now precedes player control**. The rule: no player-control or in-world rendering work begins until the generator can deterministically seed-generate every tile of every world via the full multilayer cascade. The original Phase 4 "2-floor test map" is **dropped** — test layers are produced by the generator itself once layer-drawing begins; we start drawing the world only when generation knows what to draw. Generation progress is viewed as **per-layer maps in the implant Atlas** (the Phase 3 deliverable, already built), not via an in-world renderer. The existing in-world rendering code is **left as-is until Phase 5** — neither built upon nor removed before then. Rationale: building player systems against a throwaway test substrate means rebuilding them against real generated tiles later; gating player work on deterministic generation avoids that waste. Epics T-749 (now Phase 5) and T-750 (now Phase 4) and the CLAUDE.md cascade table are updated to match. - **Rationale:** The pattern of negotiating pragmatic v0.2 cuts while discussing room-level detail repeatedly produced superseded decisions, confused agents, and distracted from building the actual product. The cascade enforces a first-things-first discipline: each layer of the game is grounded in the layer below it before detail is added. - **Raised by:** Jeroen, established 2026-03-24 during world generation workshop. - **Dissent:** None. -- **Cross-reference:** Initiative #745, Epics #746–751, CLAUDE.md cascade table, `docs/workshops/world-generation/workshop-outcomes.md` +- **Cross-reference:** Initiative T-745, Epics T-746–751, CLAUDE.md cascade table, `docs/workshops/world-generation/workshop-outcomes.md` --- @@ -722,7 +722,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Current canonical format:** markers.json is stored in heightmap pixel space. Every marker file declares a `grid: { w, h }` header — the generator, the 6 hand-authored templates (Lendel, Edict, Vuurkloof, Røros, Cairnside, Estrade), and all 2394 procedural seed files ship `{"w": 512, "h": 256}`. Every position is a two-element **array** `[row, col]` of integer pixels into that grid, where `row ∈ [0, h)` and `col ∈ [0, w)` (row is the first axis to match NumPy convention and the flood-fill / A* / cost-grid code that `tooling/planet-gen/` already runs in). Polyline geometry (`roads[*].path`, `railroads[*].path`, `rivers[*].path`) is `[[row, col], [row, col], ...]`. - **markers.json top-level schema:** - `grid`: `{"w": 512, "h": 256}` - - `cities[]`: `{id, name, kind, center: [row, col], population}` — `kind` is `capital` or `city`; `name` is empty when awaiting gemma_naming.py (#833). + - `cities[]`: `{id, name, kind, center: [row, col], population}` — `kind` is `capital` or `city`; `name` is empty when awaiting gemma_naming.py (T-833). - `roads[]`: `{id, name, kind, path: [[row, col], …]}` — `kind` is `commercial` by default for generated roads; hand-authored roads use `highway`, `rural`, etc. - `railroads[]`: same shape as `roads[]`; generated default `kind` is `passenger_freight`. - `pois[]`: `{id, name, kind, center: [row, col]}` — generated POIs are `kind: "transit"`; hand-authored POIs use `institutional`, `cultural`, `corporate`, etc. @@ -752,13 +752,13 @@ Technical foundation decisions that constrain implementation: engine, client-ser 8. Stations show at system view with mini data panel (no drill-down) - **Rationale:** The heightmap pipeline was designed with Phase 3 in mind — 2,394 base maps exist. The critical path is content (filling empty markers.json arrays), not technology (the atlas panel follows the established implant component pattern). Sequential settlement growth simulation produces more realistic city networks than scatter placement — each city's location is informed by previous placements, terrain, and economic logic. Using the Gemma 2 voice pipeline for naming tests the in-game LLM quality while generating content. Population-scaled depth without manual tier classification keeps the pipeline simple and the authoring burden manageable. -- **Raised by:** Full planning team workshop, Sprint 34 (#748). Participants: Gestalt (systems design), Tyre (technical architecture), Miri (worldbuilding), with Jeroen as workshop participant. +- **Raised by:** Full planning team workshop, Sprint 34 (T-748). Participants: Gestalt (systems design), Tyre (technical architecture), Miri (worldbuilding), with Jeroen as workshop participant. - **Dissent:** None. - **Cross-reference:** [D-166](architecture.md#d-166) (development cascade — Phase 3), [D-036](content.md#d-036) (Sova as canonical setting), [D-093](content.md#d-093-sova-transit-district--spatial-layout-and-district-topology) (Sova spatial layout), [D-094](#d-094) (district hierarchy), [D-095](content.md#d-095) (Horizon stations), [D-170](#d-170) (HUD visibility/implant apps), [D-169](#d-169) (implant component library), [D-181](economics.md#d-181-signal-vocabulary) (signal vocabulary/visibility ladder), [D-174](economics.md#d-174-shadow-economy-layer) (shadow economy intensity), [D-175](economics.md#d-175-corporation-taxonomy-and-prerequisite) (corporation taxonomy), [D-138](content.md#d-138-llm-re-voicing-pipeline-for-npc-voice) (Gemma 2 voice pipeline) ### D-192: Drop PROTOCOL_VERSION lockstep handshake -- **Decision:** Deprecate the snapshot envelope `version` field, the `PROTOCOL_VERSION` constants on both server (`server/src/bridge/types.rs`) and client (`client/scripts/protocol/protocol.gd`), and the version-mismatch guard in `Protocol.decode_snapshot()`. Removal is tracked in ticket **#868** (server + client coordinated, sprint 37 or later). Once removed, genuine schema mismatches will surface as MessagePack decode errors or missing-field errors at the consumer; that signal is sufficient for our deployment model. Until #868 lands, the field and guard stay in place — they are no longer load-bearing, but removing them requires coordinated edits on both sides and fresh fixture regeneration. +- **Decision:** Deprecate the snapshot envelope `version` field, the `PROTOCOL_VERSION` constants on both server (`server/src/bridge/types.rs`) and client (`client/scripts/protocol/protocol.gd`), and the version-mismatch guard in `Protocol.decode_snapshot()`. Removal is tracked in ticket **T-868** (server + client coordinated, sprint 37 or later). Once removed, genuine schema mismatches will surface as MessagePack decode errors or missing-field errors at the consumer; that signal is sufficient for our deployment model. Until T-868 lands, the field and guard stay in place — they are no longer load-bearing, but removing them requires coordinated edits on both sides and fresh fixture regeneration. - **Rationale:** The version constants were designed for a network deployment where client and server can ship out of sync. Our actual deployment is a subprocess: the Godot client launches the Rust server it was built with. They are *always* in sync at runtime — the version check has never caught a real mismatch in the field, only dev-time forgetfulness. The cost has been measurable: every protocol-shaping sprint requires bumping two constants in lockstep, and we accumulated tautological tests asserting `PROTOCOL_VERSION == N` (deleted in sprint 36 — see ticket from this D-record). Removing the handshake makes the per-sprint cost zero. **Reversibility:** When/if networked multiplayer arrives (no firm date — see [D-005](#d-005-architecture-godot-client--rust-server-via-subprocess)), the natural fit is a one-time handshake at connection time (a single client-version vs. server-version exchange in the connection protocol), not a per-snapshot version stamp. So even the multiplayer path doesn't argue for keeping the per-snapshot field — that field would be doubly redundant once a connection-time check exists. The design space hasn't been narrowed. - **What we lose:** A single eager, human-readable error at connect time ("client v22 ↔ server v23"). A genuine dev-time schema drift will now surface as a downstream decode/missing-field error, possibly seconds into a session rather than at handshake. - **What we keep:** All field-presence and roundtrip tests in `test_protocol_bridge.gd`, `test_signal_sprint24.gd`, etc. — these cover the *behavior* the version constant was meant to gate. Decode failure in `Messagepack.decode()` still rejects malformed payloads. @@ -778,17 +778,17 @@ Technical foundation decisions that constrain implementation: engine, client-ser - The mix is self-contained per city: two cities with the same economic role, population tier, and political archetype produce the same district type distribution (modulo seed-driven noise). No city-to-city state dependency. - Integer weights throughout — no f32 for D-010 determinism. - **Rationale:** Economic role should visibly shape a city's physical form. A financial hub looks different from a mining hub. Population tier prevents cities from being too small to sustain their economic function. Political archetype encodes power structure in spatial form — Corporate settlements are commercially dense, Commission settlements are institutionally heavy. The three-component model is the minimum set to produce legible variety; adding more inputs risks over-constraining the generator. -- **Ticket:** #920 -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-920 +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-191 (atlas pipeline), D-213 (DistrictType enum), D-214 (PoliticalArchetype), D-216 (BlockIrregularity) ### D-195: Attractor-Matching Compatibility Matrix for Generative City Placement - **Date:** 2026-05-01 - **Decision:** City placement on a planetary surface uses an attractor-matching model. A `GeographicAttractor` is a terrain feature that increases city placement score at nearby positions. Seven `AttractorType` variants: `RiverMouth`, `CoastalAccess`, `RiverCrossing`, `ValleyFloor`, `PassEntrance`, `LakeShore`, `PlainCenter`. A `CompatibilityMatrix` is a 10×7 scoring table (10 `economic_role` values × 7 attractor types) whose cells are float weights (0.0–3.0) representing how strongly that economic role favors that terrain feature. Examples: manufacturing → RiverMouth 2.8, ValleyFloor 2.1; financial → CoastalAccess 2.5, PlainCenter 1.8; agricultural → ValleyFloor 3.0, PlainCenter 2.5. The matrix is authored data (not computed at runtime). Attractor extraction from heightmaps is defined in D-209. Matching algorithm is defined in D-211. - **Rationale:** Terrain-naive city placement produces spatially incoherent worlds. The compatibility matrix gives different city types different terrain affinities, so financial hubs appear on coasts and agricultural cities appear in fertile valleys — without hard-coding placement rules per city type. The weight matrix gives graduated preference, not binary requirement. -- **Amended 2026-06-03 (#955):** the matrix weights and the whole placement-scoring path are **integer basis-points, not f32** (D-010 determinism / D-227 save-critical). When #955 wired `match_cities` into the deterministic generation cascade, the original f32 scoring became a live cross-platform divergence risk (a near-tie score comparison or the Hungarian's f32 reductions can round differently per platform → a different world from the same seed). Now: `CompatibilityMatrix.weights` are `i32` bps (10000 = 1.0×; the examples above are 28000 / 30000 / 25000 …), `GeographicAttractor.strength` and `terrain_modification_cost` are bps, and `cell_score` / the Hungarian / `CityPlacement.score` use integer arithmetic. The 0.0–3.0 affinity semantics are unchanged; only the representation is now integer. -- **Ticket:** #919, #925, #955 -- **Raised by:** Generation cascade workshop (#897) +- **Amended 2026-06-03 (T-955):** the matrix weights and the whole placement-scoring path are **integer basis-points, not f32** (D-010 determinism / D-227 save-critical). When T-955 wired `match_cities` into the deterministic generation cascade, the original f32 scoring became a live cross-platform divergence risk (a near-tie score comparison or the Hungarian's f32 reductions can round differently per platform → a different world from the same seed). Now: `CompatibilityMatrix.weights` are `i32` bps (10000 = 1.0×; the examples above are 28000 / 30000 / 25000 …), `GeographicAttractor.strength` and `terrain_modification_cost` are bps, and `cell_score` / the Hungarian / `CityPlacement.score` use integer arithmetic. The 0.0–3.0 affinity semantics are unchanged; only the representation is now integer. +- **Ticket:** T-919, T-925, T-955 +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-191 (atlas pipeline), D-208 (D8 drainage — attractor extraction), D-209 (feature tag extraction), D-211 (attractor-matching pipeline) ### D-196: SettlementClass Enum and Latent Settlement Active/Ghost Logic @@ -808,8 +808,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - `EconomicTriggered` settlements collapse to ghost state when their triggering economic condition lapses (e.g., a mining outpost depopulates when the mine is exhausted). - `OrganicGrowth` settlements are not in `atlas_city_names` at generation time; they are written to the table during simulation when a settlement emerges organically. - **Rationale:** Not every named location needs full generation, and not every simulated location is named. The classification separates authorial intent (NameLocked) from economic reality (PopulationBudget, EconomicTriggered) and simulation emergence (OrganicGrowth). Ghost settlements are important for world texture — abandoned mining towns and depopulated frontier outposts are as legible as thriving hubs. -- **Ticket:** #913 -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-913 +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-191 (atlas pipeline), D-200 (CityGenerationContext), D-203 (BodyWorldState), D-207 (atlas_city_names) ### D-197: prosperity_baseline Derivation Formula with Topographic Gradient @@ -822,8 +822,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - Formula: `base + pop_bonus + terrain_bonus + noise`, clamped to [0.1, 0.95]. - `prosperity_baseline` is not the current prosperity level — it is the simulation's starting point and decay/growth target. The live pressure simulation (D-026) drifts from this value based on trade flows, events, and faction pressure. - **Rationale:** A flat random baseline produces economically incoherent worlds. Terrain-informed prosperity encodes real-world patterns: port cities are wealthy, river-mouth cities are strategic. The log-scale population bonus prevents megacities from dominating without eliminating small-city character. Clamping to [0.1, 0.95] prevents degenerate all-thriving or all-collapsing starting states. -- **Ticket:** #920 (consumer of prosperity_baseline) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-920 (consumer of prosperity_baseline) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-194 (district mix — consumes prosperity_baseline), D-195 (attractor types that produce terrain bonus) ### D-198: Economic Simulation Independence from Layer 1–2 Spatial Data @@ -832,8 +832,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Prohibited:** Generator code must not modify `PressureState`. Generator code must not query live simulation state during async background generation tasks (race condition risk). Generator reads a snapshot of pressure state taken at generation dispatch time. - **Allowed:** The generator reads `economic_health`, `prosperity_baseline`, `industries`, and `faction_influence` from the snapshot. These are read-only inputs to Phase 1 skeleton classification and Phase 2 condition application. - **Rationale:** Bidirectional coupling between generator and simulation creates initialization order dependencies and potential circular references. The one-way data flow (simulation → generator snapshot → generator) keeps both systems independently testable and avoids race conditions in the Rayon thread pool (D-206). The generator is a consumer of economic state, not a participant in economic evolution. -- **Ticket:** #915 (CityGenerationContext reads economic snapshot) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-915 (CityGenerationContext reads economic snapshot) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-026 (simulation tiers), D-200 (CityGenerationContext), D-206 (background generation queue) - **Amended 2026-05-25 (D-233 — structural-fill / condition-overlay two-pass split):** The "Phase 2 condition application" language is replaced by a formal two-pass model. **Pass 1 — Structural fill** (frozen; reads t=0 initial-economics only; re-derivable from `seed + initial_economics_snapshot`): zone-type per block, building footprint shape+position, building-type vocabulary tags (`BuildingTag`), `founded_era`, operations-surface extent (bulk industries), and the labor-demand signal feeding adjacent residential blocks. **Pass 2 — Condition overlay** (the sanctioned rolling-economy consumer; a paint layer OVER the frozen fill; refreshable on a cadence): `BuildingConditionState = New|Maintained|Worn|Derelict|Abandoned`; `OccupancyState = Full|Partial|Vacant`; `VegetationEncroachment` (Abandoned in appropriate sub-biomes, D-210); feeds the D-100 tile `DamageOverlay`. **Hard wall:** the condition overlay CANNOT change a `BuildingTag` (a mine plant becomes an *abandoned* mine plant, never an office). The fill generator's signature is `fill_chunk(ctx: CityGenerationContext, seed: SeedChain)` — NO access to `PressureState`, price signals, or tâtonnement output. The condition overlay is a SEPARATE Bevy system, triggered by `EconEvent` (D-180), with its own read set. Cross-ref D-233, D-100, D-180, D-210, D-217. Raised by: Burnelli/Tyre/Gestalt, economic-built-world workshop round 2. @@ -849,8 +849,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - Fields 1–5 are read from `systems.db` (bodies table + economics tables). Field 6 is derived at generator dispatch time. - All 6 fields must be present before a generation task is dispatched. Missing fields abort the task with a logged error; generation does not proceed with partial context. - **Rationale:** A fixed minimum read set prevents generators from accumulating unbounded dependencies on simulation state. The 6 fields cover the minimum information needed to produce a correctly-classified skeleton. The abort-on-missing-fields rule ensures generator output is always deterministic from a complete context, never silently degraded from a partial one. -- **Ticket:** #915 (CityGenerationContext implementation) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-915 (CityGenerationContext implementation) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-196 (SettlementClass), D-197 (prosperity_baseline), D-198 (economic simulation independence), D-200 (CityGenerationContext struct), [D-237](#d-237) (authored specialization layer — read-set extended) - **Amended 2026-05-25 (D-229/D-232/D-233):** `CityGenerationContext` gains `morphology_zone`, `flavor_profile`/`architecture_flavors`, `dominant_bulk_class`, `dominant_production_ubiquity` (the record already permits >6 fields). - **Amended 2026-05-31 ([D-237](#d-237) — authored specialization layer):** the read set gains `economic_specialization` and `cultural_specialization` (both new columns on `system_economy`). `dominant_faction` (field 4) is now sourced from authored values on `system_factions` where present, heuristic fallback otherwise. `economic_specialization` is the upstream source of `dominant_bulk_class`/`dominant_production_ubiquity` (resolved via `specialization_vocabulary`, D-233 re-amendment); `cultural_specialization` feeds the D-232 template pool. Still bounded — three authored fields, all NULL-safe with deterministic fallbacks. @@ -858,7 +858,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser ### D-200: Three-Tier Execution Model (Build-Time / Runtime-Background / Runtime-On-Demand) - **Date:** 2026-05-01 - **Decision:** The generation pipeline operates at three distinct execution tiers with no cross-tier mutation: - 1. **Build-time (Python pipeline):** Runs `make regen-db`. Produces `systems.db` tables including `atlas_city_names`, `atlas_province_boundaries`, `body_radius_km`. Output is a static artifact committed to the repo. Never runs during gameplay. *(Amended #963, D-202: `atlas_body_heightmaps` is no longer produced — elevation is now a per-body 16-bit `heightmap.png` file, not a DB table.)* + 1. **Build-time (Python pipeline):** Runs `make regen-db`. Produces `systems.db` tables including `atlas_city_names`, `atlas_province_boundaries`, `body_radius_km`. Output is a static artifact committed to the repo. Never runs during gameplay. *(Amended T-963, D-202: `atlas_body_heightmaps` is no longer produced — elevation is now a per-body 16-bit `heightmap.png` file, not a DB table.)* 2. **Runtime-background (Rayon thread pool, D-206):** Triggered by content-spidering events (player approaches a system, NPC names a location, news ticker references a place). Runs D8 drainage analysis (D-208), attractor extraction (D-209), settlement placement, and Phase 1 district skeleton generation. Output goes into `BodyWorldState` cache (D-203). Transparent to main tick thread. 3. **Runtime-on-demand (main tick thread):** Triggered when the player crosses a chunk boundary. Runs Phase 2 chunk fill for the approaching chunk. Must complete within 5ms. Reads from `BodyWorldState` cache (always populated before this tier runs). - **Tier boundary rules:** @@ -880,8 +880,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser } ``` - **Rationale:** Three tiers with explicit boundaries eliminates the "where does this code run?" question. Build-time is deterministic and committable. Runtime-background is parallelizable. Runtime-on-demand has strict latency budgets. Cross-tier mutation would create race conditions between the Rayon thread pool and the main tick thread. -- **Ticket:** #915 -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-915 +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-026 (simulation tiers), D-194 (district mix), D-203 (BodyWorldState), D-206 (background generation queue), tyre-sw1r3.md Layer 5/6 architecture ### D-201: Spatial Hierarchy — Eight Tiers with Locked Dimensions @@ -901,12 +901,12 @@ Technical foundation decisions that constrain implementation: engine, client-ser | 9 | Chunk | 64×64 tiles (64m) | Streaming/serialization unit (D-222) | - Tiers 6–9 (District → Chunk) are the sub-settlement spatial hierarchy, canonical in [D-222](#d-222) — renamed/resized from the original D-094 ladder (the old 512m "District" is now the Quarter; District is now 2048m), at the Tile = 1m / Subtile = 0.5m scale of D-220. This decision formalizes Tiers 1–5 with equivalent lock status. - - Tier 3 heightmap resolution (512×256 equirectangular working grid; 1024×512 PNG) is the canonical format. Deviation requires amending D-191. **Amended (#963, D-202):** the canonical *stored* heightmap is now a per-body 16-bit grayscale `heightmap.png` at **1024×512** carrying native elevation (the prior PNG was a 1024×512 RGB *relief*, now renamed `reliefmap.png`). PNG dimensions are unchanged (1024×512); Layer 1 downsamples to the 512×256 working grid. This amendment is the explicit deviation gate being satisfied — format/content changed, resolution preserved. + - Tier 3 heightmap resolution (512×256 equirectangular working grid; 1024×512 PNG) is the canonical format. Deviation requires amending D-191. **Amended (T-963, D-202):** the canonical *stored* heightmap is now a per-body 16-bit grayscale `heightmap.png` at **1024×512** carrying native elevation (the prior PNG was a 1024×512 RGB *relief*, now renamed `reliefmap.png`). PNG dimensions are unchanged (1024×512); Layer 1 downsamples to the 512×256 working grid. This amendment is the explicit deviation gate being satisfied — format/content changed, resolution preserved. - Tier 4 province boundaries are pre-computed at build-time and stored in `atlas_province_boundaries` (D-205). They are not re-computed at runtime. - The `SettingType` enum on `DistrictSkeleton` is the interface between Tier 5 (settlement planning) and the skeleton cell (the 512m Quarter, Tier 7 — `DistrictSkeleton` is pending rename to match D-222). - **Rationale:** Locking spatial dimensions prevents the generative layers from drifting in incompatible directions. The heightmap pipeline, atlas pipeline, and district generator all assume these dimensions and would need coordinated migration if they changed. Formalization prevents silent per-system variation. -- **Ticket:** #912 (WorldTier enum), #913 (SettlementClass) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-912 (WorldTier enum), T-913 (SettlementClass) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-094 (district hierarchy — Tiers 6–8), D-191 (atlas pipeline — Tier 3), D-205 (province boundaries — Tier 4), D-208 (D8 drainage — Tier 3 analysis) ### D-202: Heightmap BLOB Storage Schema (atlas_body_heightmaps) @@ -927,16 +927,16 @@ Technical foundation decisions that constrain implementation: engine, client-ser - The Rust loader reads the BLOB via `bytemuck::cast_slice::()` after fetching from SQLite. No endian conversion needed on LE-native systems; the pipeline stores LE explicitly. - Only inhabited bodies receive heightmap rows at build-time. Uninhabited bodies are generated on-demand (runtime-background tier, D-200). - This table is populated by the `import_heightmaps` build-time step in the asset pipeline (D-191 §9 pipeline order). It is read-only at runtime. -- **Amendment (2026-05-23, [#963](#)):** the DB-BLOB store is **superseded** by a per-body **file-based 16-bit grayscale `heightmap.png`** stored next to the body's other assets (path from `bodies.terrain_reference`). Rationale for the change: raw float grids committed inside a binary `systems.db` are the exact binary-merge-conflict trap the asset-pipeline rule warns against, and ~512KB×N bloats the DB; a per-body file matches D-203's on-demand model and keeps `systems.db` lean. Concretely: +- **Amendment (2026-05-23, [T-963](#)):** the DB-BLOB store is **superseded** by a per-body **file-based 16-bit grayscale `heightmap.png`** stored next to the body's other assets (path from `bodies.terrain_reference`). Rationale for the change: raw float grids committed inside a binary `systems.db` are the exact binary-merge-conflict trap the asset-pipeline rule warns against, and ~512KB×N bloats the DB; a per-body file matches D-203's on-demand model and keeps `systems.db` lean. Concretely: - **Naming fix:** the existing color hypsometric render (today's `heightmap.png`, 1024×512 RGB) is renamed **`reliefmap.png`** — it is a relief visualization, not elevation. Display-only. - **Canonical elevation:** a new **`heightmap.png`** = 16-bit grayscale (luminance = normalized elevation), **1024×512** (2× per axis / 4× the cells of the old 512×256 sim grid — the PNG dimensions are unchanged from the prior canonical 1024×512 in D-201; only the *content* changed from an RGB relief to native 16-bit elevation). 1024×512 bounds install size (~190 MB across 267 inhabited bodies) while sub-pixel detail is synthesized by the lower cascade layers. Single source of truth — the reliefmap and all computed geography derive from it, so it is bit-identical/deterministic by construction. - **Multi-resolution:** the stored heightmap is high-res for the lower layers (region/block/tile sample local detail); **Layer 1** (continental drainage/basins/mountain-ranges) calls `BodyHeightmap::downsample` to the `GRID_W×GRID_H = 512×256` working resolution first, decoupling continental compute cost (~45ms) from stored resolution. - **Rust loader:** `heightmap.rs::load_heightmap_png` reads the 16-bit grayscale PNG (via the `png` crate), normalizes to f32 [0,1]; rejects RGB (a reliefmap can't be misread as elevation). `sea_level` is stored **in the PNG** as a `tEXt` chunk (the heightmap is self-describing), with a caller-supplied default as fallback. - **`atlas_body_heightmaps` is dropped**; `import_heightmaps.py` writes the PNG file instead of a DB row. The bake runs in the content pipeline (numpy/scipy) once; the runtime cascade is pure Rust loading the file. - - **Implementation status (#963):** Consumer done — `heightmap.rs::load_heightmap_png` reads the 16-bit grayscale PNG + `sea_level` tEXt chunk, rejects RGB, downsamples for Layer 1. Producer done — `import_heightmaps.py` is the bake (rename legacy `heightmap.png`→`reliefmap.png` for all bodies incl. Sol; for non-Sol inhabited bodies write a fresh clean `reliefmap.png` + 16-bit `heightmap.png` from `simulate()` at the bumped 1024×512 grid). `atlas_body_heightmaps` dropped via MIGRATION_SQL + removed from `systems-schema.sql`. Godot client (`atlas_viewer.gd`) loads `reliefmap.png` for display. Sim determinism guarded by `test_sim_determinism.py`. -- **Rationale:** Storing heightmaps in `systems.db` keeps the DB as the single source of truth for all generation inputs, avoids a separate file-fetching path in the Rust server, and allows the pre-push hook (asset pipeline rules) to detect stale heightmap data. The float32 LE layout matches what NumPy and PIL produce natively, minimizing conversion overhead in the Python pipeline. *(Superseded by the #963 amendment above — the file-based model won out because the DB-as-single-source goal conflicts with binary-merge-conflict avoidance and DB size; a per-body committed PNG is itself a queryable, diffable-by-render asset.)* -- **Ticket:** #901 (schema), #906 (import), #916 (Rust loader) -- **Raised by:** Generation cascade workshop (#897) + - **Implementation status (T-963):** Consumer done — `heightmap.rs::load_heightmap_png` reads the 16-bit grayscale PNG + `sea_level` tEXt chunk, rejects RGB, downsamples for Layer 1. Producer done — `import_heightmaps.py` is the bake (rename legacy `heightmap.png`→`reliefmap.png` for all bodies incl. Sol; for non-Sol inhabited bodies write a fresh clean `reliefmap.png` + 16-bit `heightmap.png` from `simulate()` at the bumped 1024×512 grid). `atlas_body_heightmaps` dropped via MIGRATION_SQL + removed from `systems-schema.sql`. Godot client (`atlas_viewer.gd`) loads `reliefmap.png` for display. Sim determinism guarded by `test_sim_determinism.py`. +- **Rationale:** Storing heightmaps in `systems.db` keeps the DB as the single source of truth for all generation inputs, avoids a separate file-fetching path in the Rust server, and allows the pre-push hook (asset pipeline rules) to detect stale heightmap data. The float32 LE layout matches what NumPy and PIL produce natively, minimizing conversion overhead in the Python pipeline. *(Superseded by the T-963 amendment above — the file-based model won out because the DB-as-single-source goal conflicts with binary-merge-conflict avoidance and DB size; a per-body committed PNG is itself a queryable, diffable-by-render asset.)* +- **Ticket:** T-901 (schema), T-906 (import), T-916 (Rust loader) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-191 (atlas pipeline — heightmap source), D-203 (BodyWorldState — consumer), D-208 (D8 drainage — reads this table) ### D-203: BodyWorldState Bevy Resource with LRU Cache @@ -959,8 +959,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser ``` - The resource is initialized empty and populated on demand. Accessing a body not in the cache triggers a background generation task (D-206). - **Rationale:** The D8 drainage analysis (D-208) and attractor extraction (D-209) are expensive (target: ~50ms/body). Running them on the main tick thread would cause frame drops. The LRU cache ensures the main thread only reads pre-computed data. 50-body capacity covers the typical gameplay scenario (player in one system, neighboring system pre-cached) with margin. -- **Ticket:** #917 -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-917 +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-200 (three-tier execution model), D-202 (heightmap BLOB — input), D-206 (background generation queue — populates cache), D-208 (D8 drainage — produces river network) ### D-204: body_radius_km Column on bodies Table @@ -982,8 +982,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - `other` / unknown: 6,371 km (Earth default) - Fallback is applied at query time, not stored back. The column remains NULL until authoritative data is available. - **Rationale:** Surface area scales with radius squared; a body twice Earth's radius has four times the potential settlement density. Without this field the generator must use a flat default for all planets, producing physically implausible city counts on super-earths and moons alike. The fallback ensures the generator works before all bodies have explicit radius data. -- **Ticket:** #905 (schema), #910 (populate from planet_class fallback) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-905 (schema), T-910 (populate from planet_class fallback) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-191 (atlas pipeline — body catalog), D-200 (CityGenerationContext — footprint_radius_km) ### D-205: Province Boundary Pre-Computation (atlas_province_boundaries) @@ -1004,8 +1004,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Province count target:** 4–12 provinces per inhabited body, derived naturally from watershed analysis. Bodies with less topographic relief (plains worlds, ocean worlds) produce fewer, larger provinces. - **At runtime:** Province boundaries are read from `atlas_province_boundaries` at generation dispatch time and cached in `BodyWorldState` as `drainage_basins` (D-203). They are not re-computed at runtime. - **Rationale:** Province boundaries define the cultural geography of a world — the mountain ranges and river systems that separated civilizations and produced distinct regional identities. Pre-computing them at build time keeps the runtime-background tier focused on city placement and district generation rather than watershed analysis. Storing as polylines (not rasterized masks) keeps the table compact and human-readable. -- **Ticket:** #904 (schema), #907 (populate from watershed analysis) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-904 (schema), T-907 (populate from watershed analysis) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-191 (atlas pipeline), D-201 (spatial hierarchy — Tier 4 Region), D-203 (BodyWorldState — caches province data), D-208 (D8 drainage — source of basin divides) ### D-206: Background Generation Priority Queue and Rayon Thread Infrastructure @@ -1017,8 +1017,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Event-driven pre-generation:** A `SystemNameIndex` (Aho-Corasick automaton over all body/system names from `systems.db`) scans NPC dialogue output and news ticker text. When a scan match hits, the referenced body is queued at `Low` priority if not already cached. This is the mechanism by which "NPC mentions a place → player travels there → world is already generated on arrival." - **Completion notification:** Completed tasks send a `GenerationComplete` event to the main tick thread via a `crossbeam` channel. The main thread drains this channel once per tick. - **Rationale:** The Rayon thread pool handles the D-200 runtime-background tier. The priority queue prevents low-priority speculation from blocking urgent work (player approaching). The Aho-Corasick name index enables cheap always-on scanning — NPC dialogue is low-bandwidth enough that scanning every output line has negligible cost. Pre-generation triggered by narrative content (NPC mentions a place) is the mechanism for making the world feel pre-existing rather than loading-on-demand. -- **Ticket:** #924 (background queue), #926 (SystemNameIndex) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-924 (background queue), T-926 (SystemNameIndex) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-200 (three-tier execution model — runtime-background tier), D-203 (BodyWorldState — output of background tasks), D-208 (D8 drainage — enqueued as AnalyzeBody) ### D-207: Fully Generative Placement — markers.json Stripped to Topographic Features @@ -1042,15 +1042,15 @@ Technical foundation decisions that constrain implementation: engine, client-ser - `reserved = 1` rows are scenario-specific cities that must not be relocated by the generation algorithm; the generator places other cities around them. - **`markers.json` authored city data** (hand-written city center positions in the 6 hand-authored templates: Lendel, Edict, Vuurkloof, Røros, Cairnside, Estrade) is migrated to `atlas_city_names` and treated as `reserved = 1` rows. The markers.json files for these templates then have their city arrays cleared. - **Rationale:** Authored city positions in markers.json created a split between hand-authored content and procedurally generated content that was impossible to query, diff, or validate consistently. Moving city identity to a table allows: SQL joins against economic data, corp HQ cross-references, scenario reservations, and attractor-matching validation. The topographic features (rivers, mountains) remain in JSON because they are polygon/polyline geometry better suited to JSON than relational rows. -- **Ticket:** #902 (schema), #908 (populate from wiki), #909 (corp HQ cross-reference) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-902 (schema), T-908 (populate from wiki), T-909 (corp HQ cross-reference) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-191 (atlas pipeline — markers.json canonical format), D-196 (SettlementClass — column in atlas_city_names), D-200 (CityGenerationContext — reads from this table) ### D-208: D8 Priority-Flood Drainage Routing — Layer 1 Empty World - **Date:** 2026-05-01 - **Decision:** Drainage routing (the computation of flow direction, flow accumulation, and river network extraction) uses the **D8 priority-flood** algorithm on the body's float32 heightmap. This is the first Layer 1 computation run on a body before any city placement or attractor extraction. - **Algorithm:** D8 assigns each cell's flow direction to one of 8 neighbors based on the steepest descent. Priority-flood fills depression cells before routing to avoid spurious sinks. Flow accumulation is the count of upstream cells draining through each cell. - - **River threshold:** A cell is classified as a river cell when `flow_accumulation > 200`. This threshold produces river networks of realistic density on the 512×256 Layer-1 working grid (D8 runs at 512×256, downsampled from the 1024×512 stored heightmap per D-202 amended #963). + - **River threshold:** A cell is classified as a river cell when `flow_accumulation > 200`. This threshold produces river networks of realistic density on the 512×256 Layer-1 working grid (D8 runs at 512×256, downsampled from the 1024×512 stored heightmap per D-202 amended T-963). - **Outputs** stored in `BodyWorldState.river_network`: - `river_cells: Vec<(u16, u16)>` — pixel positions of all river cells - `confluences: Vec<(u16, u16)>` — positions where two or more rivers merge @@ -1059,8 +1059,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Performance target:** ~50ms per body on a single Rayon thread for canonical 512×256 resolution. - **Determinism:** Integer-only arithmetic throughout. No f32 in the priority-flood comparisons (use integer-scaled elevation). D-010 compliant. - **Rationale:** D8 is the standard GIS drainage routing algorithm and produces the river networks that drive attractor scoring (river mouths, confluences = high-value `RiverMouth` attractors). The flow accumulation threshold of 200 was chosen empirically against the Lendel heightmap to produce ~8–15 named rivers per inhabited body — enough for cultural geography without over-fragmenting the landscape. -- **Ticket:** #918 -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-918 +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-195 (attractor types — river mouth is highest-scoring), D-203 (BodyWorldState — output stored here), D-205 (province boundaries — derived from drainage divides), D-209 (feature tag extraction — reads river network) ### D-209: Geographic Feature Tag Extraction (7 Settlement Attractor Tags) @@ -1076,8 +1076,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - `PlainCenter`: cells in flat terrain (slope < 2°) away from all other attractors. Strength = habitability score × 0.4. - Sub-biome classification (vegetation, aridity, temperature zones) is derived in parallel and stored as `SubBiomeVariant` on the attractor for use by the ZonePalette modifier system (D-101). - **Rationale:** The 7 attractor types cover the terrain features that historically determine city placement. Their extraction from the heightmap is deterministic and cheap given the D8 analysis is already complete. The strength normalization ensures attractor scores are comparable across bodies with different elevation ranges. -- **Ticket:** #925 (types), #919 (matching pipeline that consumes these) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-925 (types), T-919 (matching pipeline that consumes these) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-195 (CompatibilityMatrix — attractor types), D-203 (BodyWorldState — storage), D-208 (D8 drainage — prerequisite), D-211 (attractor-matching pipeline — consumer) ### D-210: Sub-Biome Variant Classification and terrain_modification_cost @@ -1088,8 +1088,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - Sub-biome classification uses: elevation percentile (of body total), local slope, moisture proxy (distance to nearest river mouth or coast), and temperature proxy (latitude of the equirectangular pixel). - Sub-biome data is stored in `BodyWorldState` alongside the attractors; it is not a separate DB table. - **Rationale:** Two cities on coastal terrain feel different when one is a tropical lowland port and the other is a cold Nordic fjord. Sub-biome tags enable the ZonePalette to select the correct visual register (T6 beach/coastal with tropical modifier vs T7 mountain/high with coastal modifier). The `terrain_modification_cost` gives the generator a principled reason to prefer some attractor positions over others for lower-prosperity cities. -- **Ticket:** #919 (attractor matching — uses terrain_modification_cost) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-919 (attractor matching — uses terrain_modification_cost) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-101 (ZonePalette modifier system — consumes sub_biome), D-195 (attractor types), D-209 (feature tag extraction — assigns sub_biome) ### D-211: Attractor-Matching Five-Phase Pipeline for Settlement Placement @@ -1103,8 +1103,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Two-tier mismatch flagging:** If a matched city-attractor pair has score < 0.35, log a `WARNING` (below expected quality). If score < 0.15, log an `ERROR` and flag for manual review. Generation proceeds in both cases; the flags are for content auditing, not hard blockers. - **Output:** `Vec` written to `atlas_city_positions` at build time. - **Rationale:** Greedy-first for large/locked cities ensures anchor cities (capitals, corp HQs, wiki-named cities) are placed at terrain features that match their lore role. Hungarian for medium cities finds the globally optimal assignment, not just locally optimal. Synthetic overflow prevents the algorithm from failing on bodies where city count exceeds natural attractor count (dense, flat worlds). The two-tier warning system enables content QA without blocking generation. -- **Ticket:** #919, #925 -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-919, T-925 +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-195 (CompatibilityMatrix), D-207 (atlas_city_names), D-209 (GeographicAttractor — input), D-210 (terrain_modification_cost — input) ### D-212: TerritorialStatus Priority-Ordered Derivation Algorithm @@ -1124,11 +1124,11 @@ Technical foundation decisions that constrain implementation: engine, client-ser - `placed_at_generation: bool` flag on `Province` distinguishes classification at build time (true) from runtime re-classification during simulation (false). Build-time status is the starting state; simulation can change it, and the flag ensures the original classification is recoverable for reset/new-game scenarios. - Faction influence values are read from `systems.db` (economics tables) at build time using the same D-199 economic read pattern. - **Rationale:** Territory status is a high-level descriptor visible to the player on the Atlas overlay (D-191 §7, political zones overlay). It must be derivable from the generation inputs without runtime simulation state. The priority-ordered algorithm ensures clear, predictable classification — no ambiguous provinces. The `placed_at_generation` flag enables the game to show "how this province was at settlement time" vs. "how it is now." -- **Amended 2026-06-05 (#956 — implementation):** two changes from the original spec. +- **Amended 2026-06-05 (T-956 — implementation):** two changes from the original spec. 1. **New variant `AutonomistHeld`** added between `FrontierUnclaimed` and `IndigenousHeld`. The original four control buckets (Commission/Corp/Contested/Frontier) predate the richer faction canon (`wiki/factions/`): the **Compact of Westphalia** is a self-governing autonomist bloc that rejects Concord Assembly authority — it governs its systems firmly, so it is neither `CommissionControlled` (it is the Assembly's *rival*), nor `FrontierUnclaimed` (it is *not* ungoverned), nor locally `ContestedZone` (the Compact is locally dominant). `AutonomistHeld` is its bucket, and gives the Compact its own colour on the political-zones overlay. 2. **Derivation source.** The numeric per-faction `faction_influence` thresholds the original record specifies are **not present in the data** — only a single authored `dominant_faction` per system exists (D-237's 8-value vocabulary). So the implementation maps `dominant_faction → TerritorialStatus` instead (`attractor_matching::territorial_status_from_faction`), grounded in faction canon: `concord_assembly`/`veil_institute` → `CommissionControlled` (the Assembly is the Reach's central government; the Veil Institute is Assembly-funded and -aligned); `syndic_dominant` → `CorpTerritory`; `compact`/`compact_sympathetic` → `AutonomistHeld`; `disputed`/`mixed` → `ContestedZone`; `independent`/NULL/unknown → `FrontierUnclaimed`. `IndigenousHeld` and `Derelict` remain unreachable from `dominant_faction` alone (they need the cultural-corridor autonomy flag / population density) — deferred. `dominant_faction` is system-level, so status is uniform across a body's provinces for now; it is still stored per-basin on `DrainageBasin.territorial_status` (forward-compatible for per-province faction data). `placed_at_generation` is not yet modelled (no runtime re-classification exists yet). -- **Ticket:** #921, #956 (implementation + amendment) -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-921, T-956 (implementation + amendment) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-191 (atlas overlay — political zones), D-199 (economic read set), D-205 (Province — this status is a field on it), [D-237](#d-237) (authored `dominant_faction` source), D-214 (PoliticalArchetype — consumes this) ### D-213: FoundingOrientation Enum and Spatial Grid Rotation @@ -1148,8 +1148,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - The district skeleton generator (Phase 1) applies `FoundingOrientation` as the base rotation for the outermost district ring. Interior districts inherit the orientation unless overridden by a `PoliticalArchetype` modifier. - **Hard constraint:** Maximum ±45° deviation from the parent orientation per district (same limit as D-096 `BlockPlacement.rotation_steps`). Beyond ±45°, tile-based pathfinding produces movement artifacts. - **Rationale:** Street grids reflect the terrain and founding logic of the original settlement. Roman camps faced cardinal directions. River towns align with the river. Coastal cities face the water. Encoding this as a named enum rather than a raw angle makes the orientation legible in the data model and debuggable during generation. -- **Ticket:** #914 -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-914 +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-096 (DistrictLayoutMode — inherits orientation), D-211 (attractor-matching — derives orientation), D-214 (PoliticalArchetype — may override orientation), D-215 (spatial arrangement patterns — uses orientation) ### D-214: PoliticalArchetype Enum and Settlement Spatial Character @@ -1171,9 +1171,9 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **`AttractorAssignment` disambiguation:** `OrganicGrowth` (a `DistrictType` value and also an `EraCause` value) is always unambiguous in context. On `DistrictType`, it means the district grew without a planning mandate. As `EraCause`, it means the era tag was acquired through organic settlement expansion rather than a discrete historical event. Both usages are permitted; the type system distinguishes them. - **Rationale:** Power structure should be legible in a city's spatial form without the player reading a wiki entry. Commission cities look different from Corporate cities look different from Pioneer cities — not just in palette, but in street geometry, district type distribution, and building scale. Encoding this as a named enum ensures the distinction is consistent across all generation code. - **Amended 2026-05-31 ([D-237](#d-237) — authored specialization layer):** for named systems `dominant_faction` (a D-214 derivation input via `TerritorialStatus`/D-199) is now an **authored** value (8-value vocabulary: `concord_assembly | compact | compact_sympathetic | syndic_dominant | veil_institute | independent | disputed | mixed`) rather than one the heuristic guesses from hop-distance/currency; the existing derivation remains the fallback for unauthored systems. The archetype mapping itself is unchanged — it now reads a more trustworthy faction for the ~40–60 named systems where the heuristic was demonstrably wrong (e.g. Groombridge resolves `syndic_dominant` → Corporate, not the hop-2 default that would yield Commission). `lattice_commission` is deliberately **not** a faction value — the Commission is a regulator, not a governing faction (ACB and Bastion are `concord_assembly`). -- **Implemented 2026-06-05 (#956):** `attractor_matching::political_archetype(territorial_status, economic_role)` lands the derivation, stored per settlement on `CityPlacement.political_archetype`. `TerritorialStatus` precedence is enforced (a Commission-controlled manufacturing hub → `Commission`, not `Industrial`); statuses that don't dictate an archetype (`ContestedZone`/`IndigenousHeld`/`Derelict`) fall through to `economic_role`, and the new `AutonomistHeld` (D-212 amendment) → `Pioneer` (self-organized, no central planner). The "spatial effect on district mix" (D-194 weight multipliers) is consumed later by the Quarter-skeleton generator (#957). -- **Ticket:** #914, #956 (archetype derivation + storage) -- **Raised by:** Generation cascade workshop (#897) +- **Implemented 2026-06-05 (T-956):** `attractor_matching::political_archetype(territorial_status, economic_role)` lands the derivation, stored per settlement on `CityPlacement.political_archetype`. `TerritorialStatus` precedence is enforced (a Commission-controlled manufacturing hub → `Commission`, not `Industrial`); statuses that don't dictate an archetype (`ContestedZone`/`IndigenousHeld`/`Derelict`) fall through to `economic_role`, and the new `AutonomistHeld` (D-212 amendment) → `Pioneer` (self-organized, no central planner). The "spatial effect on district mix" (D-194 weight multipliers) is consumed later by the Quarter-skeleton generator (T-957). +- **Ticket:** T-914, T-956 (archetype derivation + storage) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-194 (district mix — archetype modifiers), D-212 (TerritorialStatus — primary input), D-213 (FoundingOrientation — archetype may override), D-215 (spatial arrangement patterns), [D-237](#d-237) (authored `dominant_faction` source) ### D-215: Five Explicit Political Archetype Spatial Arrangement Patterns @@ -1187,9 +1187,9 @@ Technical foundation decisions that constrain implementation: engine, client-ser - The arrangement pattern constrains block adjacency during Phase 1 skeleton generation. Specifically: the first 2–3 districts placed in a settlement follow the pattern. Later districts are constrained only by the road network, not by the pattern. - Arrangement patterns must **vary in angular orientation** per seed (not just position) — the same archetype's radial core must not always face the same direction across seeds. - **Rationale:** The 14 D-ready items from the generator-architecture workshop established that spatial arrangement should encode power structure. These five patterns are the minimal set to cover the 6 archetypes (Pioneer and Industrial share ribbon development; hub-and-spoke is a cross-archetype pattern for transit-primary cities). Pattern variation in angular orientation prevents players from pattern-matching settlement layout after the first playthrough. -- **Implemented 2026-06-05 (#956):** the `ArrangementPattern` enum (the five patterns) and its derivation (`attractor_matching::arrangement_pattern`, from `PoliticalArchetype` + transit_hub override) land here, and the chosen pattern is **stored** per settlement on `CityPlacement.arrangement_pattern`. The block-adjacency **enforcement** (constraining the first 2–3 quarters' layout) is the Quarter-skeleton generator's job and is deferred to #957; the per-seed angular variation rides on `FoundingOrientation` (D-213, seed-derived `Free` bearing). -- **Ticket:** #914 (types), #956 (enum + derivation + storage), #899/#957 (skeleton-gen enforcement) -- **Raised by:** Generation cascade workshop (#897) +- **Implemented 2026-06-05 (T-956):** the `ArrangementPattern` enum (the five patterns) and its derivation (`attractor_matching::arrangement_pattern`, from `PoliticalArchetype` + transit_hub override) land here, and the chosen pattern is **stored** per settlement on `CityPlacement.arrangement_pattern`. The block-adjacency **enforcement** (constraining the first 2–3 quarters' layout) is the Quarter-skeleton generator's job and is deferred to T-957; the per-seed angular variation rides on `FoundingOrientation` (D-213, seed-derived `Free` bearing). +- **Ticket:** T-914 (types), T-956 (enum + derivation + storage), T-899/T-957 (skeleton-gen enforcement) +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-094 (spatial hierarchy — district sizes), D-194 (district mix — archetype modifiers), D-214 (PoliticalArchetype — pattern assignment) ### D-216: BlockIrregularity from founding_age — Layout Age Character @@ -1210,10 +1210,10 @@ Technical foundation decisions that constrain implementation: engine, client-ser - An old Pioneer settlement (age 800+ years) can have `block_irregularity ≈ 1.0`, producing maximum ±16 sim tile offsets and ±45° rotations. A new Commission district (age < 50 years) will have `block_irregularity ≈ 0.05`. - All arithmetic uses integer-scaled intermediates wherever possible (age is integer years; archetype_step is stored as integer basis points internally). The f32 in the formula above is for documentation clarity only. - **Rationale:** Age is the single most reliable predictor of urban irregularity in the real world. Old cities that grew organically have crooked streets; new planned cities have grids. Encoding this as a formula rather than a lookup table allows continuous variation along the age axis while preserving the political meaning of the archetype modifier. -- **Ticket:** #922 -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-922 +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-096 (DistrictLayoutMode::Organic — consumes block_irregularity), D-194 (district mix — founding_age is an input), D-214 (PoliticalArchetype — archetype_step source) -- **Amendment (2026-05-26 — `founding_age_years` backfill, ticket #1000):** `founding_age_years` is backfilled on every inhabited body in `tooling/economy-db/import_economics.py` MIGRATION_SQL via `COALESCE(events-first, wave-fallback)`. **Events-first:** the system's authored `historical_events.age_years` for `event_type = 'colonial_charter'` — the canonical founding event authored during the economics built-world workshop. 9 systems in the live DB carry a colonial_charter row, driving 11 inhabited body values; 5 of those diverge meaningfully from their wave fallback (e.g. GJ 144 = 580 vs wave-1 fallback 600, GJ 338B = 590, GJ 380 = 480). **Wave-fallback** — canonical **founding-edge** of each wave's range (`docs/design/systems-framework-final-miri.md:495–499`): +- **Amendment (2026-05-26 — `founding_age_years` backfill, ticket T-1000):** `founding_age_years` is backfilled on every inhabited body in `tooling/economy-db/import_economics.py` MIGRATION_SQL via `COALESCE(events-first, wave-fallback)`. **Events-first:** the system's authored `historical_events.age_years` for `event_type = 'colonial_charter'` — the canonical founding event authored during the economics built-world workshop. 9 systems in the live DB carry a colonial_charter row, driving 11 inhabited body values; 5 of those diverge meaningfully from their wave fallback (e.g. GJ 144 = 580 vs wave-1 fallback 600, GJ 338B = 590, GJ 380 = 480). **Wave-fallback** — canonical **founding-edge** of each wave's range (`docs/design/systems-framework-final-miri.md:495–499`): | `settlement_wave` | era | range (years ago) | founding-edge fallback | |---|---|---|---| @@ -1243,8 +1243,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Phase 2 application:** Chunk fill applies the baseline condition at fill time. Subsequent condition updates from simulation crossing thresholds are applied as `ChunkMutations.tile_overrides`. - Condition thresholds are authored constants, not computed. Any change to the thresholds (0.63 / 0.43 / 0.23) requires amending this D-record. - **Rationale:** Threshold-crossing invalidation is a standard visual LOD technique that avoids expensive per-frame recalculation. The four bands (Intact/Worn/Cracked/Broken) match the visual fidelity budget for the current art direction — more bands require more tile variants per palette. The era-based floor ensures that historical context is always visible: a Decay-era block cannot spontaneously look pristine from a prosperity spike alone. -- **Ticket:** #923 -- **Raised by:** Generation cascade workshop (#897) +- **Ticket:** T-923 +- **Raised by:** Generation cascade workshop (T-897) - **Cross-reference:** D-100 (DamageOverlay — post-condition modification), D-194 (district mix — prosperity_baseline is the seed for prosperity_score) ### D-218: WorldTier Enum Canonical Values (Epicenter/Regional/Backwater/Passage/Waypoint) @@ -1269,8 +1269,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Source of truth:** workshop-outcomes.md §WorldTier and ComplexityTier table (generator-architecture workshop, lead decision L-3). - All code referencing `WorldTier::Peripheral`, `WorldTier::Connected`, or `WorldTier::Core` must be updated to the canonical values. This includes generator.rs, any tests, and any serialized data that references these variants. - **Rationale:** The three-value stub (Peripheral/Connected/Core) was authored before the generator architecture workshop established the five-value canonical model. The mismatch between the code and the design means any generator code built against the stub types would need rewriting anyway. Correcting it now before the Phase 1 implementation work begins eliminates that rework. The `Backwater` full-budget exception is architecturally significant: dense isolated communities (mining towns, research outposts) should be as socially rich as regional hubs — their isolation is their drama, not their limitation. -- **Ticket:** #900 (bug fix), #912 (full enum implementation) -- **Raised by:** Generation cascade workshop (#897). Original workshop-outcomes.md §WorldTier (generator-architecture workshop, lead decision L-3). +- **Ticket:** T-900 (bug fix), T-912 (full enum implementation) +- **Raised by:** Generation cascade workshop (T-897). Original workshop-outcomes.md §WorldTier (generator-architecture workshop, lead decision L-3). - **Cross-reference:** D-096 (DistrictLayoutMode — WorldTier is a DistrictSkeleton field), D-097 (GuaranteeAuditResult — tier-conditional guarantees), D-201 (spatial hierarchy — WorldTier assigned at Tier 2 System level) --- @@ -1460,8 +1460,8 @@ Technical foundation decisions that constrain implementation: engine, client-ser Sub-city authored set-pieces (e.g. D-093 Sova Transit District, station interiors) follow the same principle when Phase 4 reaches that scale — generated geometry, lore names/roles attached, nothing pinned — to be applied (and prior authored layouts superseded) at that point. - **Rationale:** Authored positions created a hand/procedural split that (per D-207's own rationale) was impossible to query, diff, or validate. Reducing authored content to a name pool removes that split entirely while preserving the flavor that makes places feel hand-made — names follow culture and region, geometry follows terrain and economics. The templates were leftover scaffolding from before fully-generative placement; removing them simplifies the pipeline without losing any canonical place. -- **Implementation:** tracked under Phase 4 (epic #750) — strip markers to names, remove template machinery + `reserved` pinning, update the atlas pipeline/schema, regen. Historical archives (sprints, workshops, audits, CHANGELOG, atlas proposals) are left untouched. -- **Implementation status (#951, 2026-05-22): done.** All 2,398 `markers.json` reduced to a names-only pool (25,264 names; 349 city names across 271 bodies). The Python atlas **geometry generator** (`generate_atlas.py`) and the **LLM naming cluster** (`gemma_naming.py`, `naming_core.py`, `apply_name_fixes.py`, their tests/QA, the `fix_fewshot_bleed`/`prune_atlas_features` geometry tools, the redundant `import_city_names.py`, and the `run-atlas-naming.sh` runner) were retired — the procedural server cascade (Phase 4) supersedes them. The Gemma prompting methodology is preserved in [docs/gemma-naming-methodology.md](../docs/gemma-naming-methodology.md). Shared atlas-DB utilities moved to `tooling/planet-gen/atlas_common.py`; **`import_economics.py` is now the sole regen-db generator that owns the atlas index** — it loads the names pool into `atlas_city_names` and empties the 8 geometry tables (`atlas_cities/roads/railroads/pois/rivers/oceans/mountain_ranges/body_grids`), which the cascade fills. `population`/`kind`/`settlement_class` on `atlas_city_names` are **deferred to placement (#955)** — `attractor_matching` reads them at 0/default until then; that empty state is the gap the cascade closes. The `reserved=1` corp-HQ cross-reference stays (corp HQ names are real places). A latent duplicate-accumulation bug in name population (no clear + no unique constraint) was fixed with a deterministic rebuild. **Sol (system `GJ 0`) is permanently exempt from the normal generators:** it uses real Earth/Mars/Luna geography via the offline `sol_import.py` (left in place for future scripted integration), so its bodies keep geometry-bearing `markers.json` as preserved positional config and are skipped by the names-pool importer — Sol names will come from its own integration, not the cascade. `make regen-db` green. +- **Implementation:** tracked under Phase 4 (epic T-750) — strip markers to names, remove template machinery + `reserved` pinning, update the atlas pipeline/schema, regen. Historical archives (sprints, workshops, audits, CHANGELOG, atlas proposals) are left untouched. +- **Implementation status (T-951, 2026-05-22): done.** All 2,398 `markers.json` reduced to a names-only pool (25,264 names; 349 city names across 271 bodies). The Python atlas **geometry generator** (`generate_atlas.py`) and the **LLM naming cluster** (`gemma_naming.py`, `naming_core.py`, `apply_name_fixes.py`, their tests/QA, the `fix_fewshot_bleed`/`prune_atlas_features` geometry tools, the redundant `import_city_names.py`, and the `run-atlas-naming.sh` runner) were retired — the procedural server cascade (Phase 4) supersedes them. The Gemma prompting methodology is preserved in [docs/gemma-naming-methodology.md](../docs/gemma-naming-methodology.md). Shared atlas-DB utilities moved to `tooling/planet-gen/atlas_common.py`; **`import_economics.py` is now the sole regen-db generator that owns the atlas index** — it loads the names pool into `atlas_city_names` and empties the 8 geometry tables (`atlas_cities/roads/railroads/pois/rivers/oceans/mountain_ranges/body_grids`), which the cascade fills. `population`/`kind`/`settlement_class` on `atlas_city_names` are **deferred to placement (T-955)** — `attractor_matching` reads them at 0/default until then; that empty state is the gap the cascade closes. The `reserved=1` corp-HQ cross-reference stays (corp HQ names are real places). A latent duplicate-accumulation bug in name population (no clear + no unique constraint) was fixed with a deterministic rebuild. **Sol (system `GJ 0`) is permanently exempt from the normal generators:** it uses real Earth/Mars/Luna geography via the offline `sol_import.py` (left in place for future scripted integration), so its bodies keep geometry-bearing `markers.json` as preserved positional config and are skipped by the names-pool importer — Sol names will come from its own integration, not the cascade. `make regen-db` green. - **Raised by:** Jeroen, 2026-05-22 — resolving the open question on merging preconfigured content into the deterministic cascade. - **Cross-reference:** [D-207](#d-207-fully-generative-placement--markersjson-stripped-to-topographic-features) (superseded — names-only, no reserved pinning), [D-191](#d-191) §8 (markers format — names-only), [D-208](#d-208) (drainage → river courses), [D-211](#d-211) (settlement placement), [D-199](#d-199) (economic read set), [D-222](#d-222) (lore≠code names on generated geometry) - **Dissent:** None @@ -1489,17 +1489,17 @@ Technical foundation decisions that constrain implementation: engine, client-ser **Derivation (load-bearing — pinned, because changing it changes every generated world):** `derive(domain, id) = splitmix64(self.0 ^ splitmix64(domain as u64)) ^ splitmix64(id)`. - Properties: deterministic; domain-separated (distinct `SeedDomain` tags never share a stream); full avalanche (splitmix64 on each input); integer-only (D-010 #4); chainable (`root → Body → Layer3Settlement → Quarter → Block`). The output is well-distributed, so `AtlasRng::new` is fed the derived seed directly — no `| 1` or golden-ratio pre-mix guard. + Properties: deterministic; domain-separated (distinct `SeedDomain` tags never share a stream); full avalanche (splitmix64 on each input); integer-only (D-010 T-4); chainable (`root → Body → Layer3Settlement → Quarter → Block`). The output is well-distributed, so `AtlasRng::new` is fed the derived seed directly — no `| 1` or golden-ratio pre-mix guard. **Body identity (the `Body` domain id).** Bodies are identified by a *string* `body_id`, not a numeric StableId, so `SeedDomain::Body` is keyed by **FNV-1a (64-bit) of `body_id`** — the repo's standard deterministic `&str → u64` convention (matching `TemplateId`/`TriangleId`). `SeedChain::for_body(world_seed, body_id)` (`root(world_seed).derive(Body, fnv1a_64(body_id))`) is the single sanctioned path; callers must use it rather than inventing their own string→u64 hash, or they would silently derive divergent worlds from the same seed — the very class of nondeterminism this record exists to kill, one level up. **Stability guards.** `SeedDomain` carries explicit `#[repr(u64)]` discriminants and is append-only; the unit test `seed_domain_discriminants_are_pinned` fails CI if any is renumbered (which would re-roll every world). `AttractorType` likewise carries explicit `#[repr(u8)]` discriminants because it is cast `as u8` as a sort key (`features.rs`); reordering it would change attractor ordering and flip the cascade golden. - **Scope of effect (verified 2026-05-23):** SeedChain changes only the RNG-*using* layers — the existing `skeleton_gen.rs` (Layer 4 block placement) and the future Layer-3 settlement placement (#955). It does **not** affect Layer 0 heightmaps (produced by the Python `planet_simulation` pipeline, seeded separately via `--seed`, committed as `heightmap.png` files) nor Layer 1 (`drainage`/`features`/`subbiome` are RNG-free — pure functions of the heightmap). The #952 Layer 0→1 golden fixtures are therefore SeedChain-independent and can be captured in any order relative to the SeedChain work. + **Scope of effect (verified 2026-05-23):** SeedChain changes only the RNG-*using* layers — the existing `skeleton_gen.rs` (Layer 4 block placement) and the future Layer-3 settlement placement (T-955). It does **not** affect Layer 0 heightmaps (produced by the Python `planet_simulation` pipeline, seeded separately via `--seed`, committed as `heightmap.png` files) nor Layer 1 (`drainage`/`features`/`subbiome` are RNG-free — pure functions of the heightmap). The T-952 Layer 0→1 golden fixtures are therefore SeedChain-independent and can be captured in any order relative to the SeedChain work. -- **Rationale:** Three seeding paths had drifted apart — `AtlasRng` (LCG, "callers pre-mix"), `EntityRng` (correct splitmix64 mixing), and ad-hoc `wrapping_add` in atlas callers — while the code already *named* a SeedChain that didn't exist. A single typed derivation chain with domain separation makes every sub-stream reproducible from one world seed, eliminates the `(seed,id)` collision class `wrapping_add` invites, and gives the determinism harness (#952) a stable contract to verify against. Promoting one mixer prevents two divergent implementations. -- **Implementation:** #952 (Phase 4, epic #750) — `server/src/seed.rs` (incl. `for_body`/`fnv1a_64`), `SeedChain` threaded through the atlas RNG callers, an extensible cascade harness (`run_cascade`/`CascadeSnapshot`/`CascadeLayer`), and a golden-seed regression test (SHA-256 of `heightmap.png` + JSON-serialized `Layer1Output`, run at 256×128 so the river network is non-empty — JSON not msgpack, to match the diffable `golden_suite.rs` convention). Pre-Phase-5: no savegames exist, so the seed-stream change needs no migration; D-202's `schema_version` lineage covers future changes once saves exist. -- **Raised by:** Jeroen + Claude, `/whats-next` refinement of #952, 2026-05-23. +- **Rationale:** Three seeding paths had drifted apart — `AtlasRng` (LCG, "callers pre-mix"), `EntityRng` (correct splitmix64 mixing), and ad-hoc `wrapping_add` in atlas callers — while the code already *named* a SeedChain that didn't exist. A single typed derivation chain with domain separation makes every sub-stream reproducible from one world seed, eliminates the `(seed,id)` collision class `wrapping_add` invites, and gives the determinism harness (T-952) a stable contract to verify against. Promoting one mixer prevents two divergent implementations. +- **Implementation:** T-952 (Phase 4, epic T-750) — `server/src/seed.rs` (incl. `for_body`/`fnv1a_64`), `SeedChain` threaded through the atlas RNG callers, an extensible cascade harness (`run_cascade`/`CascadeSnapshot`/`CascadeLayer`), and a golden-seed regression test (SHA-256 of `heightmap.png` + JSON-serialized `Layer1Output`, run at 256×128 so the river network is non-empty — JSON not msgpack, to match the diffable `golden_suite.rs` convention). Pre-Phase-5: no savegames exist, so the seed-stream change needs no migration; D-202's `schema_version` lineage covers future changes once saves exist. +- **Raised by:** Jeroen + Claude, `/whats-next` refinement of T-952, 2026-05-23. - **Cross-reference:** [D-010](#d-010) (determinism — integer-only, seed→identical output), [D-200](#d-200) (three-tier execution model), [D-208](#d-208) (RNG-free drainage), [D-223](#d-223) (names-only pool — placement uses seeded RNG), `simulation/rng.rs` (EntityRng / splitmix64 precedent) - **Dissent:** None @@ -1507,7 +1507,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser ### D-225: Atlas layer-stream proxy — compute-on-demand, mod-first (resolves Q-098) - **Date:** 2026-05-23 -- **Decision:** How per-body generation-cascade layer data reaches the Godot client for the Phase-4 Atlas progress viewer (#960). **Resolves Q-098.** Derived layer data is **never baked** into the install — that would both bloat the install (~100MB+ for ~267 bodies) and make modded bodies second-class. Instead a server-side **layer-stream proxy** computes on demand from moddable source files and streams to the client: +- **Decision:** How per-body generation-cascade layer data reaches the Godot client for the Phase-4 Atlas progress viewer (T-960). **Resolves Q-098.** Derived layer data is **never baked** into the install — that would both bloat the install (~100MB+ for ~267 bodies) and make modded bodies second-class. Instead a server-side **layer-stream proxy** computes on demand from moddable source files and streams to the client: **(1) Transport — existing IPC stream + additive message tag.** Not a second socket, not a per-frame envelope rewrite. The bridge today carries no message-type discriminator (server→client is always `ObserverSnapshot`, client→server always `Vec`). The atlas request/response ride the *same* TCP stream as new message types, disambiguated **structurally** in v1 (a snapshot has `entities`/`tick`; the atlas messages do not). A full `BridgeMessage` envelope-everywhere migration is deferred — it would be a needless wire break, and client+server co-ship (D-005/D-192) so it can be done later as cleanup. @@ -1519,11 +1519,11 @@ Technical foundation decisions that constrain implementation: engine, client-ser **(5) Whole `Layer1Output` per response; client composites additive overlays.** The layers are produced together in one drainage pass, so per-layer requests save no compute and only add round-trips. Overlay toggles (heightmap + rivers + attractors + sub-biome shown *together*) are a pure client-side render concern (the existing `_overlay_visibility` pattern). The request carries `body_id` + `up_to_layer` (a forward-compat seam; v1 honors `Topography`). -- **Critical-path dependency:** the proxy is inert until `gen_queue.rs::run_work_item`'s `AnalyzeBody` actually runs `run_cascade` → builds `BodyWorldState` → populates the cache (today a documented stub, deferred from #142), and `GenCompletion::BodyAnalyzed` carries the computed state, not just `body_id`. This activation is the long pole and is tracked as its own ticket blocking #960. +- **Critical-path dependency:** the proxy is inert until `gen_queue.rs::run_work_item`'s `AnalyzeBody` actually runs `run_cascade` → builds `BodyWorldState` → populates the cache (today a documented stub, deferred from T-142), and `GenCompletion::BodyAnalyzed` carries the computed state, not just `body_id`. This activation is the long pole and is tracked as its own ticket blocking T-960. - **Rationale:** Baking privileges first-party content (a mod body cannot ship baked artifacts it cannot produce) and adds install bloat. Computing from the moddable heightmap on demand — the cascade is deterministic and ~45 ms, and D-200/D-203/D-206 already provide the background-compute + LRU tiers — keeps mods first-class, adds zero storage, and uses the architecture as intended. Reusing the existing IPC stream (vs a second socket) avoids a parallel connection lifecycle for an occasional, user-initiated, latest-wins-irrelevant request. -- **Deferred / spun off:** the full `BridgeMessage` envelope-everywhere migration (later cleanup, not this ticket); the mod **content catalog** — mods adding *new* bodies need body rows + `terrain_reference` discoverable, but `systems.db` is binary / source-canonical (D-189) and mods cannot append to it → **Q-099**. D-225 resolves mod *file* resolution only; base-install resolution is enough to ship #960. -- **Implementation:** #960 (client viewer + proxy) plus the `AnalyzeBody` activation ticket. New surface: `server/src/atlas/source_resolver.rs`, `server/src/atlas/layer_proxy.rs`; `gen_queue.rs` `run_work_item` activation + `GenCompletion` payload; bridge receive/send branching; client `protocol.gd` / `sim_bridge.gd` / `atlas_viewer.gd`. -- **Raised by:** Jeroen (mod-first directive) + Tyre (design pass) + Claude, `/whats-next` refinement of #960, 2026-05-23. +- **Deferred / spun off:** the full `BridgeMessage` envelope-everywhere migration (later cleanup, not this ticket); the mod **content catalog** — mods adding *new* bodies need body rows + `terrain_reference` discoverable, but `systems.db` is binary / source-canonical (D-189) and mods cannot append to it → **Q-099**. D-225 resolves mod *file* resolution only; base-install resolution is enough to ship T-960. +- **Implementation:** T-960 (client viewer + proxy) plus the `AnalyzeBody` activation ticket. New surface: `server/src/atlas/source_resolver.rs`, `server/src/atlas/layer_proxy.rs`; `gen_queue.rs` `run_work_item` activation + `GenCompletion` payload; bridge receive/send branching; client `protocol.gd` / `sim_bridge.gd` / `atlas_viewer.gd`. +- **Raised by:** Jeroen (mod-first directive) + Tyre (design pass) + Claude, `/whats-next` refinement of T-960, 2026-05-23. - **Cross-reference:** Q-098 (resolved by this), Q-099 (mod content catalog — spun off), [D-191](#d-191) (Atlas viewer), [D-166](#d-166) (per-layer Atlas progress viewer), [D-200](#d-200) / [D-203](#d-203) (three-tier execution, LRU cache), [D-005](#d-005) / [D-192](#d-192) (client+server co-ship — no version handshake), [D-224](#d-224) (SeedChain — feeds the cascade the proxy runs) - **Dissent:** None @@ -1537,7 +1537,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser **(2) Data — layer-stream proxy (D-225).** Streams `Layer1Output` (later economics state, save state) from server to client on demand, mod-first, no bake. - **(3) Human-visual viewer (#960).** The Atlas renders cascade layers as **additive, toggleable overlays** (relief base + rivers + drainage basins + attractors; *shape* encodes attractor type, *color* encodes sub-biome), extending the existing `OVERLAY_DEFS` / `AtlasOverlayBar` with a `generation` overlay group + a left-side legend panel. Buttons start `pending`/locked and unlock as each layer's data arrives ("grows as each layer lands", D-166). + **(3) Human-visual viewer (T-960).** The Atlas renders cascade layers as **additive, toggleable overlays** (relief base + rivers + drainage basins + attractors; *shape* encodes attractor type, *color* encodes sub-biome), extending the existing `OVERLAY_DEFS` / `AtlasOverlayBar` with a `generation` overlay group + a left-side legend panel. Buttons start `pending`/locked and unlock as each layer's data arrives ("grows as each layer lands", D-166). **(4) Agent-navigable channel.** A client-side `AtlasAgentInterface` exposing JSON `observe` (current data state + a walkable **UI affordance tree**) and `act` (named **semantic intents** — `select_body`, `open_regional`, `set_overlay`, `back`, … — backed by the same handlers a click calls, **not** pixel coordinates; the map uses `_gui_input`). Runs headless. Turns human-eyeball review into an agent-automatable QA sweep across the whole Reach (and later economics/saves). @@ -1547,7 +1547,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Rationale:** Reusing the real UI — rather than a parallel offline renderer or dumped files — means the debug/review surface never diverges from what ships, and a dropped artifact can't go stale. Agent-navigability converts qualitative "does the synthesis look natural?" review from a manual eyeball pass into an automatable sweep that flags the few outliers for a human. The harness rides seams that already exist (`TickRate::Paused`, the paused-allowlist, `gameplay_occluded`, the bridge framing, the `run-visual` capture primitive) — a naming-and-contract exercise, not a new subsystem. - **New surface:** server pause-gating (run-conditions on the world phases keyed to a pause command); client `AtlasAgentInterface` (`observe`/`act`, Control-tree walker) + its local transport; the generation overlay rendering + selector + legend; interactive capture wired to `run-visual`. -- **Implementation:** Phase 4 (epic #750), built bottom-up — auto-pause substrate, #969 proxy (D-225), #960 viewer, agent channel, agent capture. Geography is the first consumer. +- **Implementation:** Phase 4 (epic T-750), built bottom-up — auto-pause substrate, T-969 proxy (D-225), T-960 viewer, agent channel, agent capture. Geography is the first consumer. - **Raised by:** Jeroen + Claude (design), with Tyre (channel/pause/headless architecture) + Araminta (overlay encoding + affordance UX), 2026-05-24. - **Cross-reference:** [D-225](#d-225) (layer-stream proxy — the data path), [D-166](#d-166) (per-layer Atlas progress viewer), [D-191](#d-191) (Atlas viewer), [D-169](#d-169) / [D-170](#d-170) (implant components / HUD occlusion — `gameplay_occluded` trigger), [D-200](#d-200) / [D-203](#d-203) (execution tiers / LRU cache), Q-099 (mod content catalog), `tests/run-visual` (capture primitive), `save_state.rs` (save-inspection consumer) - **Dissent:** None @@ -1565,7 +1565,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Determinism reclassified safety-critical.** Because mutators reference derived state, any derivation drift (a non-deterministic algorithm, an f32 comparison/ordering, `HashMap` iteration) desyncs the whole save — not merely a cosmetic difference. D-010's integer-only + ordered-collection discipline is load-bearing for *saves*, not just for golden tests. - **Rationale:** A world that stores its tiles cannot scale to body-sized 3-D volumes and bloats saves; a pure-function world with a transient cache + a sparse mutator log scales to any size, makes saves trivially small, and is the only model under which "dig anywhere, to any depth" is free (the subsurface was always computable — digging just reveals it). It also forces the determinism discipline the whole cascade needs anyway. The downward floor cap fell because it was solving a problem — per-layer storage cost — that derive-don't-store eliminates. - **Open sub-questions:** the geology-model fidelity (simple depth-horizon stack vs tectonic-grade folding/faults) and how far `FloorMaterial` is derived now vs deferred to the city layers (both tracked in D-228 / Q-101); the mutator op schema (Q-103). -- **Implementation:** Phase 4+ (epic #750). The caching substrate exists at the atlas level (`BodyWorldStateCache`, D-203/D-225); the tile/voxel cache, mutator overlay, and geology derivation land as the cascade reaches the tile layers. No migration needed pre-save (D-202 `schema_version` lineage covers future drift once saves exist). +- **Implementation:** Phase 4+ (epic T-750). The caching substrate exists at the atlas level (`BodyWorldStateCache`, D-203/D-225); the tile/voxel cache, mutator overlay, and geology derivation land as the cascade reaches the tile layers. No migration needed pre-save (D-202 `schema_version` lineage covers future drift once saves exist). - **Raised by:** Jeroen (derive-don't-store, volumetric, drop-the-floor-cap directives) + Claude, atlas-derivation workshop, 2026-05-25. - **Cross-reference:** [D-010](#d-010) (determinism — now save-critical), [D-222](#d-222) (subtile/tile/chunk hierarchy), [D-110](#d-110) (signed z-levels), [D-225](#d-225) (layer-stream proxy + cache pattern), [D-203](#d-203) (LRU cache tier), [D-224](#d-224) (SeedChain — feeds `derive`), [D-228](#d-228) (composite tile schema — the derived value type), [Q-101](../questions/architecture.md#q-101) (refinement contract), [Q-103](../questions/architecture.md#q-103) (mutator op schema), [Q-104](../questions/architecture.md#q-104) (floor↔voxel-z mapping) - **Dissent:** None @@ -1584,7 +1584,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Cohesion matrix (anti-squaring at the material layer):** intra-region material + sub-biome scatter (a dirt patch in grass, a lone rock in beach sand) comes from a **global, position-keyed continuous noise field — never per-chunk** — so transitions never reveal chunk/grid seams. Straight lines appear **only when authored** (roads, plazas, field edges); a straight line must always have a placed cause, never be a generation-grid artifact. (Algorithm → Q-102.) - **Rationale:** A flat enum explodes combinatorially (≈5×5×8 ≈ 200 mostly-incoherent variants), can't be queried by axis ("all water-adjacent tiles"), and grows a new variant for every new mechanic. Orthogonal axes are set independently by different cascade passes (material from sub-biome, water from drainage, shape from elevation), independently queryable, cheap to serialize, and compose without explosion. Names are visual/narrative and belong downstream of the simulation. - **Open sub-questions:** how far the `FloorMaterial` / `Vegetation` vocabularies are enumerated now vs deferred to the settlement layers; the exact morphology-zone vocabulary; the dynamic water-height model (→ Q-105). -- **Implementation:** Phase 4+ (epic #750), as the cascade reaches the tile layers. Today's `TileKind` (D-049 render stack) is the seed of the per-subtile axes; `SubBiomeVariant` (D-210) is the region biome axis. +- **Implementation:** Phase 4+ (epic T-750), as the cascade reaches the tile layers. Today's `TileKind` (D-049 render stack) is the seed of the per-subtile axes; `SubBiomeVariant` (D-210) is the region biome axis. - **Raised by:** Jeroen (FloorMaterial, shape-from-material, cohesion-matrix directives) + Claude, with the atlas-derivation workshop four — Tyre (composite data-structure), Gestalt (tag model + tactical form), Nigel (morphology-as-character), Burnelli (economic distinctions), 2026-05-25. - **Cross-reference:** [D-227](#d-227) (derive-don't-store — these axes are the derived value type), [D-010](#d-010), [D-210](#d-210) (sub-biome — region biome axis), [D-222](#d-222) (subtile/tile), [D-049](#d-049) (z-stack render — `TileKind` seed), [D-208](#d-208) (D8 — flow-direction source), [D-226](#d-226) (dynamic-state inspection — the water overlay), [Q-100](../questions/architecture.md#q-100) (biome authority), [Q-101](../questions/architecture.md#q-101) (refinement contract), [Q-102](../questions/architecture.md#q-102) (cohesion-matrix algorithm), [Q-105](../questions/architecture.md#q-105) (dynamic water-height) - **Dissent:** None @@ -1604,10 +1604,10 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **`FloorExtent { base_floor: i8, floor_count: u8, heights: FloorHeightProfile }`** where `FloorHeightProfile = Uniform(u8) | Variable(Vec)`. **Q-104 resolution (the D-110 ↔ D-227 bridge):** two pure functions — `floor_at_voxel_z(z) -> Option` and `voxel_range_for_floor(f) -> Option<(i32,i32)>` — map D-110 floor-index addressing onto D-227 physical voxel-z. Default `Uniform(3)` (3 voxels ≈ 3 m/floor, per Jeroen); a cathedral/hangar is `Uniform(10)`; a mixed-use stack is `Variable([5,3,3,3,3])`. The `Variable` branch carries per-floor memory only when floors actually differ. - **Rationale:** A typed tag is the single contract that lets the atlas render a building, the guarantee audit validate a district, and Phase 6 seed an interior — all from one frozen object. String stubs cannot carry any of that. Orthogonal fields (what / where / how-tall / who-may-enter / cultural / era / condition) compose without a combinatorial enum explosion, matching D-228's axis discipline at the building scale. - **Implementation:** Phase 4+ (epic forthcoming). Replaces the stub fields on `BlockSkeleton`/`QuarterSkeleton` in `server/src/simulation/generator.rs`. -- **Amended 2026-06-05 (#957 — authored zone-type selection table):** the `(ZoningType + economic_role + seed) → ZoneTypeId` lookup is made concrete for the **planetary** cascade. A third input, **`setting` (SettingType)**, is added as a *tweaker* (not a surface/station switch — station & orbital bodies run a separate cascade per [Q-109](../questions/architecture.md#q-109) and own the station-only ids `residential_station`/`extraction_space`/`port_space`/`rural_orbital`, which this table never selects). `zone_type_for(zoning, role, setting, seed)` builds a candidate slice from the base table below, applies the setting tweaker, then deterministically seed-picks one id. Every base cell is non-empty (no empty slices — mirrors D-195's no-zero rule); an unknown role falls back to the ZoningType default. +- **Amended 2026-06-05 (T-957 — authored zone-type selection table):** the `(ZoningType + economic_role + seed) → ZoneTypeId` lookup is made concrete for the **planetary** cascade. A third input, **`setting` (SettingType)**, is added as a *tweaker* (not a surface/station switch — station & orbital bodies run a separate cascade per [Q-109](../questions/architecture.md#q-109) and own the station-only ids `residential_station`/`extraction_space`/`port_space`/`rural_orbital`, which this table never selects). `zone_type_for(zoning, role, setting, seed)` builds a candidate slice from the base table below, applies the setting tweaker, then deterministically seed-picks one id. Every base cell is non-empty (no empty slices — mirrors D-195's no-zero rule); an unknown role falls back to the ZoningType default. - **Base table (planetary variants):** Commercial → `[commercial_market, entertainment_hospitality]` (financial→`[diplomatic_elite, commercial_market]`; transit_hub→`[commercial_transit, commercial_market]`; service_mixed→ +`entertainment_venue`). Residential → `[residential_surface]` (agricultural→`[rural_agricultural, rural_pastoral]`; extraction→`[residential_dispersed, residential_surface]`). Industrial → `[industrial_manufacturing, industrial_freight]` (manufacturing→`[industrial_manufacturing, industrial_processing]`; extraction→`[extraction_surface, industrial_processing]`; agricultural→`[industrial_processing]`). Administrative → `[administrative_civil]` (institutional→ +`administrative_judicial, diplomatic_elite`; research→`[research_station, administrative_civil]`; service_mixed→ +`medical_facility`). Transit → `[port_surface]` (transit_hub→`[commercial_transit, port_surface]`; manufacturing|extraction→`[industrial_freight, port_surface]`). Recreational → `[entertainment_venue, entertainment_hospitality]` (research|institutional→ +`archaeological_site`). Restricted → `[security_checkpoint]` (military→`[military_garrison, security_checkpoint, detention_facility]`; research→`[research_station, security_checkpoint]`; institutional→`[detention_facility, security_checkpoint]`). Mixed → `[residential_surface, commercial_market, administrative_civil]`. - **`setting` tweaker (post-pass):** Maritime/Water → `port_surface`⇒`port_maritime` (+`port_fishing` for Transit), `rural_*`⇒`rural_aquaculture`, `extraction_surface`⇒`extraction_platform`. Agricultural → bias `rural_agricultural`/`rural_pastoral` into Residential/Mixed. Wilderness → surface `wilderness_frontier`/`residential_dispersed` at the frontier. Urban/other → base unchanged. - - **Entry-class U-curve threshold (gap fill):** the "low prosperity on a Commercial zone → BreachOnly" degrade uses the D-217 bands — `prosperity_baseline_bps < 2300` (the D-217 Broken band) → `BreachOnly`. **Era** follows this record as written (`founding_age_years + prosperity_baseline + seed`); the round-3 "distance-to-origin" note is dropped (not in the data model). **FloorExtent** uses the D-220 density-class floor midpoints with seed-jitter within the class range. **Doors** are **not** populated here — `doors: Vec::new()`; door derivation (D-231) is #979. + - **Entry-class U-curve threshold (gap fill):** the "low prosperity on a Commercial zone → BreachOnly" degrade uses the D-217 bands — `prosperity_baseline_bps < 2300` (the D-217 Broken band) → `BreachOnly`. **Era** follows this record as written (`founding_age_years + prosperity_baseline + seed`); the round-3 "distance-to-origin" note is dropped (not in the data model). **FloorExtent** uses the D-220 density-class floor midpoints with seed-jitter within the class range. **Doors** are **not** populated here — `doors: Vec::new()`; door derivation (D-231) is T-979. - **Raised by:** Tyre (schema + FloorExtent/Q-104), Gestalt (BuildingEntryClass, derivation), Miri (flavor_ref), economic-built-world workshop round 2, 2026-05-25. - **Cross-reference:** [D-142](content.md#d-142) (zone-type taxonomy), [D-096](#d-096) (layout mode), [D-197](#d-197) (prosperity baseline), [D-217](#d-217) (tile condition), [D-110](#d-110) (signed z-levels), [D-227](#d-227) (voxel substrate), [D-101](#d-101) (ZonePalette), [D-028](#d-028) (dialogue access — name disambiguated), [D-230](#d-230), [D-231](#d-231), [D-232](#d-232), [Q-104](../questions/architecture.md#q-104) (resolved here) - **Dissent:** None @@ -1693,7 +1693,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser - **Refined 2026-05-26 (street-network algorithm — two layers):** streets generate in **two layers**, mirroring real cities and the Civ road lineage. **Arterials** (`corridors`) = a **minimum-spanning / least-cost graph** over the district's key nodes (access-points, reservations, landmarks) — emergent trunk topology (Civ6-style, "roads where traffic wants to go"), ±45°-snapped; **this is the node/edge graph the guarantee audit (D-097) reads** (a chokepoint *is* an arterial bottleneck; an encounter-corridor *is* an arterial through-route). Ribbon = MST on linearly-constrained nodes; hub-and-spoke = MST with a forced centroid hub. **Local streets** (`chunk_layout`) = a ±45° **grid lattice** within each block (Civ4-style), modulated by the D-096 Grid/Organic mode (offsets + rotation). **Voronoi was rejected** — its arbitrary-angle edges violate the ±45° cap and its irregular cells break the axis-aligned `TileRect` footprint fast-path (D-229), degrading per-chunk fill from rectangle-containment to polygon rasterization. (Civ1–4's "road on every tile" is the anti-pattern this avoids; the arterial/local split is the city-scale fix.) - **Rationale:** Morphology zone and `architecture_flavor` were added to the context (D-199) as *inputs without consumers* — two economically-similar bodies otherwise produce topologically identical street networks and identical block subdivision (Nigel's same-y-grid failure). These two rules give morphology structural teeth so the fjord port and the delta port are categorically different cities, not reskins. - **Implementation:** Phase 4+. Consumed in the plan phase (D-230) when laying streets and subdividing blocks. -- **Implemented 2026-06-06 (#957):** the street network + footprint subdivision land in `skeleton_gen.rs` (absorbing #976 into #957 — one coherent walkable-quarter deliverable). `AccessPoint`/`CorridorSpine`/`ChunkLayout` are now typed structs (were `String` stubs): access nodes from road-entry octants + reservation gates; arterial corridors as a morphology-gated graph (Ribbon for fjord/canyon/mountain-pass, HubSpoke for delta/island/enclosed water, Prim-MST mesh otherwise), ±45°-snapped (a); per-block local lattice modulated by D-096 Grid/Organic. Footprint subdivision is a density-scaled BSP into axis-aligned `TileRect`s with D-233 BulkClass roofed-coverage. **Waterfront rule (b):** the water-facing quarter edge (from the settlement's `Coastal` founding orientation, D-213) drops its street setback to 0 so buildings present flush to the quay. The water bearing itself is now extracted in Layer 1 (`TerrainAnalysis::water_bearing`, 8-octant integer) and fed into D-213 founding orientation (#956's `0` stub is gone for coastal/river settlements). **Pending dependency:** the waterfront rule reads `CityGenerationContext.founding_orientation`, which `city_context_reader` still stubs to `Cardinal` — real per-settlement orientation only reaches quarter generation once the **Layer-3 placement → Layer-4 `GenerateSkeleton` dispatch** is wired (it must copy the placement's founding orientation into the context). That cross-layer dispatch is the remaining integration; the rule is correct and tested, awaiting its input pipeline. +- **Implemented 2026-06-06 (T-957):** the street network + footprint subdivision land in `skeleton_gen.rs` (absorbing T-976 into T-957 — one coherent walkable-quarter deliverable). `AccessPoint`/`CorridorSpine`/`ChunkLayout` are now typed structs (were `String` stubs): access nodes from road-entry octants + reservation gates; arterial corridors as a morphology-gated graph (Ribbon for fjord/canyon/mountain-pass, HubSpoke for delta/island/enclosed water, Prim-MST mesh otherwise), ±45°-snapped (a); per-block local lattice modulated by D-096 Grid/Organic. Footprint subdivision is a density-scaled BSP into axis-aligned `TileRect`s with D-233 BulkClass roofed-coverage. **Waterfront rule (b):** the water-facing quarter edge (from the settlement's `Coastal` founding orientation, D-213) drops its street setback to 0 so buildings present flush to the quay. The water bearing itself is now extracted in Layer 1 (`TerrainAnalysis::water_bearing`, 8-octant integer) and fed into D-213 founding orientation (T-956's `0` stub is gone for coastal/river settlements). **Pending dependency:** the waterfront rule reads `CityGenerationContext.founding_orientation`, which `city_context_reader` still stubs to `Cardinal` — real per-settlement orientation only reaches quarter generation once the **Layer-3 placement → Layer-4 `GenerateSkeleton` dispatch** is wired (it must copy the placement's founding orientation into the context). That cross-layer dispatch is the remaining integration; the rule is correct and tested, awaiting its input pipeline. - **Raised by:** Nigel, economic-built-world workshop round 2, 2026-05-25. - **Cross-reference:** [D-228](#d-228) (morphology zone), [D-215](#d-215) (arrangement patterns — gated), [D-096](#d-096) (layout mode / ±45° cap), [D-220](#d-220) (density), [D-229](#d-229), [D-213](#d-213) (founding orientation — water bearing), [Q-106](../questions/architecture.md#q-106), [Q-109](../questions/architecture.md#q-109) - **Dissent:** None @@ -1721,7 +1721,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser **(1) `system_economy.economic_specialization TEXT` (new column).** A 27-value curated vocabulary compiled from `wiki/economics/specialization_vocabulary.toml` into a `specialization_vocabulary` DB table. Each value maps to `(commodity_id, production_ubiquity_override_or_null)` → `(BulkClass, ProductionUbiquity)` for D-233. Scale is encoded in the value (no separate scale column): `breadbasket` pins `ProductionUbiquity = Specialist` where the catalog default would be `Ubiquitous`; `terroir_agriculture` pins `MonopolySource`. Equal-or-higher rule: a `production_ubiquity_override` may only be equal or higher concentration than the commodity's global default (CI hard error V-SES-03). Authored for ~80–100 named systems; deterministic-varied heuristic fallback for ~200 unnamed systems (seed-hashed weighted draw over the vocabulary: W_ROLE_PRIMARY=8,000 bps, W_CORP=5,000 bps, W_NOISE=400 bps to every vocabulary value; same seed+system_id always produces the same value; the noise term ensures ~5–10% of unnamed systems draw an off-primary value, preventing all unnamed agricultural worlds from being identical). - **Relationship to `economic_base_primary` (the seam — clarified 2026-06-01):** `system_economy.economic_base_primary`/`_secondary` is pre-existing free-text prose (e.g. `"fusion_fuel, financial_services"`) rendered into the wiki index.md "Industries/Exports" infobox by `wiki_sync.py`. `economic_specialization` is a **deliberate parallel representation at higher fidelity**, NOT a duplicate: the prose is uncomputable GTTR flavor for human readers; the enum is the machine-actionable projection the D-233 generator consumes. They are the same *concept* (what the system produces) at two resolutions. The two must stay mutually consistent — `economic_specialization` should never contradict the `economic_base_primary` prose for the same system (the #1016 content pass authors the enum from the same lore the prose describes, and CI soft-warning W-SES-03 flags prose/enum divergence). Unification onto one source was considered and rejected: the prose carries multi-sector nuance and narrative voice the enum cannot, and the enum carries the deterministic (BulkClass × ProductionUbiquity) projection the prose cannot — collapsing either direction loses information. systems.db is canonical; index.md infobox is a read-only DB projection; only the index.md *prose sections* (Supply Dependency / Faction Notes / …, round-tripped by `wiki_sync` `PROSE_SECTIONS`) are authored in-place. + **Relationship to `economic_base_primary` (the seam — clarified 2026-06-01):** `system_economy.economic_base_primary`/`_secondary` is pre-existing free-text prose (e.g. `"fusion_fuel, financial_services"`) rendered into the wiki index.md "Industries/Exports" infobox by `wiki_sync.py`. `economic_specialization` is a **deliberate parallel representation at higher fidelity**, NOT a duplicate: the prose is uncomputable GTTR flavor for human readers; the enum is the machine-actionable projection the D-233 generator consumes. They are the same *concept* (what the system produces) at two resolutions. The two must stay mutually consistent — `economic_specialization` should never contradict the `economic_base_primary` prose for the same system (the T-1016 content pass authors the enum from the same lore the prose describes, and CI soft-warning W-SES-03 flags prose/enum divergence). Unification onto one source was considered and rejected: the prose carries multi-sector nuance and narrative voice the enum cannot, and the enum carries the deterministic (BulkClass × ProductionUbiquity) projection the prose cannot — collapsing either direction loses information. systems.db is canonical; index.md infobox is a read-only DB projection; only the index.md *prose sections* (Supply Dependency / Faction Notes / …, round-tripped by `wiki_sync` `PROSE_SECTIONS`) are authored in-place. **(2) `system_economy.cultural_specialization TEXT` (new column).** Directs D-232's template pool when a system's cultural character diverges from corridor baseline. Single field carrying two value sub-types: *activity/character values* (`agrarian`, `industrial_heritage`, `institutional`, `scholarly`, `artistic`, `financial_technocratic`, `cosmopolitan`, `compact_cooperative`, etc.) and *heritage-type values* (`scottish`, `vietnamese`, `zulu`, `afrikaans_cape`, `tagalog`, `chinese`, `italian_northern`, `french_provencal`, `norse_compact`, etc.). Heritage values take precedence when both apply. `NULL` = use corridor default (D-232 existing algorithm). Authored for ~60–100 systems where the corridor default would produce wrong D-232 architectural draws in Phase 4 (not a thin Phase 6 concern: a Vietnamese-founded system in the east_reach Korean/Japanese corridor draws the wrong templates without an explicit pin). Does NOT replace `system_culture.cultural_register` (prose NPC/dialogue voice); consumed by the physical generator only. diff --git a/governance/decisions/content.md b/governance/decisions/content.md index 2e5a387c9..9c93a021a 100644 --- a/governance/decisions/content.md +++ b/governance/decisions/content.md @@ -46,7 +46,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio ### D-032: Separate monologue pools per character [SUPERSEDED — deferred to Phase 6] - **Date:** 2026-02-11 -- **Superseded by:** Development cascade (CLAUDE.md) — character/NPC monologue content is Phase 6 detail-coloring, below the current Phase 1 (wiki content). The smuggler/detective archetype enum and its hard-partitioned monologue pools were pre-cascade scaffolding and have been fully stripped from the server codebase (Sprint 37, #878, PR #137). The original D-117 supersession framing (single tycoon character in v0.2) is itself obsolete now that v0.2 is dropped (CLAUDE.md: "v0.2 target is dropped"). The partition *design pattern* is preserved in this record for when culture-driven / generator-produced monologue is reintroduced in Phase 6, but no corresponding code or content exists today. +- **Superseded by:** Development cascade (CLAUDE.md) — character/NPC monologue content is Phase 6 detail-coloring, below the current Phase 1 (wiki content). The smuggler/detective archetype enum and its hard-partitioned monologue pools were pre-cascade scaffolding and have been fully stripped from the server codebase (Sprint 37, T-878, PR #137). The original D-117 supersession framing (single tycoon character in v0.2) is itself obsolete now that v0.2 is dropped (CLAUDE.md: "v0.2 target is dropped"). The partition *design pattern* is preserved in this record for when culture-driven / generator-produced monologue is reintroduced in Phase 6, but no corresponding code or content exists today. - **Amendment (2026-04-22, Sprint 37, PR #137):** Monologue pool selection is now unkeyed by archetype until a Phase 6 character system exists. `MonologueState.character` field and `CharacterArchetype` enum deleted from `server/src/bridge/types.rs`, `server/src/simulation/monologue.rs`, observer relabeling logic, and `server/content/modules/tier1/smuggling_ring_v0_1.yaml`. Uniform single-pool behavior is the intended end state pre-Phase-6 — not scaffolding deferred in place with a stub, but retired pending the real character system. Lead override recorded in `docs/architecture/sprint-37-878-audit.md` (2026-04-21 override section). Reintroduction gate: a confirmed Phase 6 character-model design is a prerequisite before this decision is revived. - **Decision:** Internal monologue content is hard-partitioned by playable character. The smuggler and detective have completely separate monologue pools — no shared lines. The `character` tag on monologue lines is a hard partition, not a filter. File structure uses separate files per character per location (e.g., `monologue-smuggler.yaml`, `monologue-detective.yaml`). - **Rationale:** Shared monologue would dilute character voice and undermine the dual-lens experience. Each character's internal voice must be independently coherent. Same trigger, different pool — this is how mirror moments work without either pool knowing about the other. @@ -56,13 +56,13 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio ### D-034: THE FRIEND — production-level NPC pattern - **Date:** 2026-02-11 -- **Decision:** Each playable character has one "FRIEND" NPC — a production-level complex character that exercises every content and systems pipeline at full depth. THE FRIEND is the emotional centerpiece of v0.1 and carries D-027 success criterion #3 ("player names an NPC they felt conflicted about"). THE FRIEND follows a reusable pattern: 3+ relationship phases (warmth → trust → doubt → conflict), observable contradiction discoverable through observation not dialogue, sympathetic motivation, no clean resolution, dual-lens resonance, triangle integration, tell progression, and dialogue shift pre/post discovery. +- **Decision:** Each playable character has one "FRIEND" NPC — a production-level complex character that exercises every content and systems pipeline at full depth. THE FRIEND is the emotional centerpiece of v0.1 and carries D-027 success criterion T-3 ("player names an NPC they felt conflicted about"). THE FRIEND follows a reusable pattern: 3+ relationship phases (warmth → trust → doubt → conflict), observable contradiction discoverable through observation not dialogue, sympathetic motivation, no clean resolution, dual-lens resonance, triangle integration, tell progression, and dialogue shift pre/post discovery. - **Assignments:** - **Smuggler's FRIEND: Kael Davan** — dock worker, ring member, smuggler's closest colleague. Contradiction: meeting with unknown contact in restricted corridor (trying to exit the ring to protect partner Naia). Exercises: full 10-axis model, multi-phase dialogue, tell system, named monologue, dual-lens notes. - **Detective's FRIEND: Sera Venn** — Commission field tech, bar regular, detective's social anchor. Contradiction: avoids Torek Lintar (sitting on unreported evidence about Kael's manifest discrepancies, protecting friend Naia). Exercises: institutional access + personal loyalty conflict, avoidance pattern as tell, trust-contamination arc. - **Content requirements per FRIEND NPC:** ~70-100 authored lines (25-35 dialogue, 15-20 trust-gated, 5-8 unprompted, 10-15 monologue per character, 5-8 tell observation, 3-5 contradiction discovery). No generation expansion — all hand-authored. - **Cross-reference:** NPC triangle model ([D-024](#d-024-npc-generation-model--10-axes--combat-component)), relationship web ([D-029](#d-029-population-entanglement-ratio--305020)), vertical slice criteria ([D-027](scope.md#d-027-vertical-slice--smuggler--detective-two-character-proof)) -- **Raised by:** Ozzie (emotional concept, Round 1), Paula (structural design and both FRIEND profiles, Round 2), project lead (confirmed, directive #4). Sera Venn confirmed by project lead over Mellanie's alternative proposal (Lera Sessik). +- **Raised by:** Ozzie (emotional concept, Round 1), Paula (structural design and both FRIEND profiles, Round 2), project lead (confirmed, directive T-4). Sera Venn confirmed by project lead over Mellanie's alternative proposal (Lera Sessik). - **Dissent:** Mellanie proposed Lera Sessik (bar owner) as detective's FRIEND. Project lead selected Paula's Sera Venn design. Lera remains bar owner / mundane triangle member. - **Amendment (2026-03-05, Where's the Fun? Workshop):** The FRIEND pattern (3+ relationship phases, observable contradiction, sympathetic motivation, no clean resolution, tell progression, dual-lens resonance) survives as a generator template for v0.2. Kael Davan and Sera Venn do not exist — [D-122](#d-122-all-npcs-generated--no-named-hand-authored-characters) eliminates all named hand-authored NPCs. In v0.2, the FRIEND role is filled by a generated NPC whose generator profile matches the FRIEND pattern template. The FRIEND pattern is now a generator instruction set, not an authoring assignment. Workshop convergence note: warmth with generated NPCs is earned through observed relationship progression, not authored backstory — this may produce stronger emotional investment than the hand-authored approach (Paula, Mellanie in Where's the Fun? Workshop §Phase Zero). @@ -81,7 +81,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - `mood` (list\): 8 moods for v0.1 — D-028 Layer 4 weighted selection - `tags` (list\): freeform escape hatch for author intent - **Monologue-specific additions:** - - `character` (enum): historically `smuggler`, `detective` — removed. Per [D-032 SUPERSEDED] and the development cascade (CLAUDE.md), character-partitioned monologue is Phase 6 and has been stripped from the codebase (Sprint 37, #878). Field is unused; do not reintroduce without a confirmed Phase 6 design. + - `character` (enum): historically `smuggler`, `detective` — removed. Per [D-032 SUPERSEDED] and the development cascade (CLAUDE.md), character-partitioned monologue is Phase 6 and has been stripped from the codebase (Sprint 37, T-878). Field is unused; do not reintroduce without a confirmed Phase 6 design. - `trigger` (enum): 9 trigger types (enter_location, observe_npc, hear_sound, observe_anomaly, post_conversation, discover_evidence, witness_interaction, time_idle, return_visit) - `prerequisite` (map or null): knowledge state gate - **Authoring-only tags (not consumed by engine):** `dual_lens` (map, per-character notes), `notes` (string) @@ -93,7 +93,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Amendment (Sprint 14):** Mood vocabulary renamed to match voice guide (monologue-voice-guide.md). Old → new: `fond`→`warm`, `comfortable`→`content`, `worried`→`anxious`, `concerned`→`frustrated`. Dropped: `analytical` (merged into `focused`), `conflicted` (modeled as `suspicious`+`warm` collision). Added: `hostile`. Final 8 moods: `anxious`, `frustrated`, `content`, `suspicious`, `warm`, `hostile`, `relieved`, `focused`. Neutral = untagged. - **Amendment (Sprint 24):** `triangle_activated` added as 15th situation (fires post-TriangleActivated when player observes anchor NPCs). Two freeform tags registered as conventions: `triangle-signal` (line is part of the triangle activation sequence) and `tell-observation` (line observes a behavioral tell without naming its cause). `npc_in_los` prerequisite added for LOS-gated monologue lines. Schema updated to match. - **Amendment (Sprint 15):** Line ID namespace changed from location-scoped to NPC-scoped. Old scheme: `{location_slug}_{d|m}_{###}` (e.g., `the-terminal_d_039`) — all NPCs at a location share one ID sequence, requiring cross-file coordination and causing collisions at scale. New scheme: `{npc-slug}_{d|m}_{###}` for dialogue, `{npc-slug}_m_{s|d}_{###}` for monologue (e.g., `kael-davan_d_001`, `dock-worker_d_001`). Each NPC's IDs are independent — no cross-file coordination needed. Auto-generated NPCs use their generated slug. Schema regex patterns unchanged (prefix is still `^[a-z][a-z0-9-]*`), only the `description` field and convention documentation update. Migration: mechanical rename of all existing line IDs across ~20 dialogue files and monologue pools. -- **Amendment (2026-04-22, Sprint 37, PR #137):** The `character` monologue-specific tag (historically `smuggler | detective`) is retired pending a Phase 6 character-model design — not deferred in place with a stub enum. Per the development cascade (CLAUDE.md), character-partitioned monologue is Phase 6 detail-coloring; the codebase has been stripped of the `CharacterArchetype` enum and the `MonologueState.character` field that keyed pool selection (#878). Pool selection is now archetype-independent. Authoring files that carry historical `character:` tags are content artifacts and will be re-evaluated when the Phase 6 character system is designed; engine consumption of the field is gone. Reintroduction gate: a confirmed Phase 6 character-model design is a prerequisite. See `docs/architecture/sprint-37-878-audit.md` (lead override section) for the cascade rationale. +- **Amendment (2026-04-22, Sprint 37, PR #137):** The `character` monologue-specific tag (historically `smuggler | detective`) is retired pending a Phase 6 character-model design — not deferred in place with a stub enum. Per the development cascade (CLAUDE.md), character-partitioned monologue is Phase 6 detail-coloring; the codebase has been stripped of the `CharacterArchetype` enum and the `MonologueState.character` field that keyed pool selection (T-878). Pool selection is now archetype-independent. Authoring files that carry historical `character:` tags are content artifacts and will be re-evaluated when the Phase 6 character system is designed; engine consumption of the field is gone. Reintroduction gate: a confirmed Phase 6 character-model design is a prerequisite. See `docs/architecture/sprint-37-878-audit.md` (lead override section) for the cascade rationale. ### D-036: Sova Transit District / Van Maanen's Star as v0.1 setting - **Date:** 2026-02-11 @@ -104,7 +104,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - Atmosphere: "quotidian-with-undertow" — comfortable enough to be complacent, tight enough that extra income is tempting - Sensory: span gate hum, industrial lubricant, recycled air, cargo machinery - **Cross-reference:** Vertical slice ([D-027](scope.md#d-027-vertical-slice--smuggler--detective-two-character-proof)), contraband ([D-037](#d-037-contraband-specification)) -- **Raised by:** Miri (Sova setting brief, Round 1; Van Maanen's Star profile, Round 2), project lead (confirmed as worldbuilding milestone, directive #6) +- **Raised by:** Miri (Sova setting brief, Round 1; Van Maanen's Star profile, Round 2), project lead (confirmed as worldbuilding milestone, directive T-6) - **Dissent:** None - **Amendment (2026-03-05, Where's the Fun? Workshop):** Station Sova / Van Maanen's Star confirmed as the v0.2 setting. [D-128](#d-128-culture-implicit-in-starting-location--van-maanens-star-system-equals-van-maanens-star-culture) makes Van Maanen's Star culture the cultural context for the tycoon bookmark — Van Maanen's Star IS Van Maanen's Star culture by default. The setting details (naming conventions, atmosphere, sensory palette) survive as generator inputs and culture profile content. However, the Sova Transit District spatial layout (D-093) was designed for the v0.1 hand-built slice. v0.2 generates the location via the generator ([D-114](scope.md#d-114-v02-proof-of-life--generator--graphics-not-hand-built-slice)); the Van Maanen's Star culture profile (Miri prerequisite for [D-119](scope.md#d-119-generator-spike-confirmed-for-sprint-25--critical-path)) captures the setting identity as generator inputs. Sova remains the canonical example system and the first culture profile to author. @@ -185,12 +185,12 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio 1. *Separation of concerns.* Access (social position) and trust (relationship depth x knowledge depth) answer different questions. Collapsing them into one axis would require rewriting D-028's four-layer model and D-035's tag taxonomy — both confirmed and implemented. 2. *Minimal code change.* The only implementation change is adding a `KnowledgeConfidence` parameter to `relationship_to_trust()` in `server/src/simulation/dialogue.rs`. The caller already has access to the observer's KnowledgeGraph. No new components, no new tags, no content format changes. 3. *Emergent archetype distinction.* Hardcoding archetype tags creates a maintenance burden (new character = new tag = new content variant) and reduces the "two keyholes on the same world" experience. When the detective and smuggler experience different dialogue from the same NPC, it should be because they have different *relationships* and *knowledge*, not because a tag excluded them. -- **Implementation change to #305:** `relationship_to_trust()` gains a `confidence` parameter. Mapping: `(Friendly, KnowsDetails+) → Secret`, `(Friendly|Known, KnowsOf+) → Real`, `(_ , _) → Surface`. Caller in `process_talk_interaction` passes `observer_kg.confidence_of(&target_sid)` to the updated function. +- **Implementation change to T-305:** `relationship_to_trust()` gains a `confidence` parameter. Mapping: `(Friendly, KnowsDetails+) → Secret`, `(Friendly|Known, KnowsOf+) → Real`, `(_ , _) → Surface`. Caller in `process_talk_interaction` passes `observer_kg.confidence_of(&target_sid)` to the updated function. - **Resolves:** OQ-18 - **Amends:** [D-041](architecture.md#d-041-knowledge-graph-data-model) (confirms confidence-to-trust mapping; supersedes the preliminary 1:1 sketch in D-041 "Key design choices" bullet 3 with the layered model above), [D-028](#d-028-dialogue-architecture--tagged-line-pools-with-four-relational-layers) (Layer 3 trust now requires both relationship AND confidence) - **Cross-reference:** [D-028](#d-028-dialogue-architecture--tagged-line-pools-with-four-relational-layers), [D-035](#d-035-converged-tag-taxonomy-for-dialogue-and-monologue-line-pools), [D-041](architecture.md#d-041-knowledge-graph-data-model), [D-062](#d-062-invisible-locked-dialogue-options) (confidence progression naturally unlocks new trust tiers, creating the "new options appearing" reward) - **Raised by:** Tyre (technical analysis, architecture synthesis) -- **Dissent:** Gestalt endorses D-075 (reviewed 2026-02-19). The emergent archetype distinction is sufficient: access tier tags already encode "authority figure lines" vs "insider lines" in content; starting knowledge differentials produce different dialogue gate timings per character; adding an archetype filter would create per-character content maintenance burden and dilute the "two keyholes on the same world" experience (D-027). Knowledge vocabulary doc (#368) confirms this works in practice — same fact IDs, different starting confidence levels, different gate-open timing per character. *Nigel's input still pending.* +- **Dissent:** Gestalt endorses D-075 (reviewed 2026-02-19). The emergent archetype distinction is sufficient: access tier tags already encode "authority figure lines" vs "insider lines" in content; starting knowledge differentials produce different dialogue gate timings per character; adding an archetype filter would create per-character content maintenance burden and dilute the "two keyholes on the same world" experience (D-027). Knowledge vocabulary doc (T-368) confirms this works in practice — same fact IDs, different starting confidence levels, different gate-open timing per character. *Nigel's input still pending.* ### D-084: Dual-namespace line ID scheme — role pool + instance override - **Date:** 2026-02-25 @@ -211,7 +211,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Hand-authored NPCs:** Unchanged. `kael-davan`, `sera-venn`, and all named authored NPCs keep their current slugs and ID sequences. No migration. - **Resolves:** Q-028 - **Cross-reference:** Line ID scheme ([D-035](#d-035-converged-tag-taxonomy-for-dialogue-and-monologue-line-pools)), population model ([D-029](#d-029-population-entanglement-ratio--305020)), NPC generation ([D-024](#d-024-npc-generation-model--10-axes--combat-component)) -- **Raised by:** Gestalt (Sprint 18, #544). Endorsed by Tyre pending implementation review. +- **Raised by:** Gestalt (Sprint 18, T-544). Endorsed by Tyre pending implementation review. - **Dissent:** None. ### D-090: PC voice registers — smuggler and detective speech patterns `[SUPERSEDED]` @@ -244,7 +244,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Rationale:** Emerged from three-round workshop synthesis. Layout satisfies D-025 (social sites), D-036 (Sova Transit District), D-054 (tile movement), D-059 (fog zone palette), D-066 (dual-scale grid), D-011 (fog of perception), D-018 (sound model), D-027 (vertical slice criteria). Chunk/district hierarchy establishes architectural precedent for Q-036 generator. - **Raised by:** Full team — Gestalt (gameplay constraints), Miri (worldbuilding/lore), Araminta (visual/spatial), Tyre (technical), Paula (narrative), Ozzie (player experience). Compiled by Qatux. - **Dissent:** Araminta preferred 32×32 visual chunk size (overruled by lead and team majority). No other dissent. -- **Source:** Station District Layout Workshop, Ticket #153, Sprint 20. Round document: `docs/discussions/round-20-station-district-layout.md` +- **Source:** Station District Layout Workshop, Ticket T-153, Sprint 20. Round document: `docs/discussions/round-20-station-district-layout.md` - **Cross-reference:** D-025 (social sites), D-036 (Sova setting), D-054 (tile movement), D-059 (fog system), D-066 (dual-scale grid), D-094 (district hierarchy), D-095 (transport lore), Q-036 (generator), Q-040–Q-044 (transport lore questions) --- @@ -252,7 +252,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio ### D-095: Horizon Stations and Gate Infrastructure — Transport Lore - **Date:** 2026-02-25 - **Decision:** Span gates are human-built structures with a single aperture enabling near-instantaneous transit. Operating schedule uses dual-use windows: freight (bulk of hours) and passenger (scheduled slots). Physical layout: aperture chamber → freight staging / passenger arrival → customs lanes → gate concourse. Horizon stations are alien-built installations (no identified builder species), self-maintaining, located at Oort-cloud distance, with **1–8 apertures per station** (revised from 4–8; single-aperture stations are valid and produce isolated dead-end systems with one way in and one way out). Per-system canonical name: "The Ring." Travel is sequential-hop only (A→B→C through intermediate systems; no direct long-range transit). Per-system access tiers vary (4 tiers — some systems allow single-hop to orbital customs; no direct planetary span gate). Station Sova's horizon gates are located at The Ring (Oort-cloud orbital); the Administrative Hub contains booking offices only (not the gates themselves — correction to prior station profile text). "The Loop" is Sova's internal tram network: 6 districts, 4-minute run from Residential Core to Transit District. Workers arrive at the transit platform (bar-side) and disperse to Terminal or bar. -- **Rationale:** Resolves transport lore questions Q-040, Q-041, Q-043, Q-044 raised during Workshop #153. Miri's Round 3 contribution. Horizon station as alien-built infrastructure adds worldbuilding depth without requiring a named builder species. Sequential-hop travel creates natural story hooks (layover locations, transit records, smuggling route complexity). Aperture count revised to 1–8 (from 4–8) to allow single-aperture stations, which produce the most isolated and narratively interesting dead-end systems — and leave open the question of what a single-aperture station pointing at an unknown destination means. +- **Rationale:** Resolves transport lore questions Q-040, Q-041, Q-043, Q-044 raised during Workshop T-153. Miri's Round 3 contribution. Horizon station as alien-built infrastructure adds worldbuilding depth without requiring a named builder species. Sequential-hop travel creates natural story hooks (layover locations, transit records, smuggling route complexity). Aperture count revised to 1–8 (from 4–8) to allow single-aperture stations, which produce the most isolated and narratively interesting dead-end systems — and leave open the question of what a single-aperture station pointing at an unknown destination means. - **Raised by:** Miri (worldbuilding), confirmed by team. - **Amendment (2026-03-13):** Aperture count range revised from 4–8 to **1–8** per project lead direction. Single-aperture horizon stations are valid; they are the mechanism for the most isolated dead-end systems, and for any system whose single gate points to a destination that is not (or not yet) part of the Reach. This change has downstream implications for the Earth question — see note in systems-framework-miri.md §7. - **Amendment (2026-03-14):** Clarified the inter-system / intra-system gate distinction and technology lineage: @@ -260,7 +260,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Intra-system gates (span gates):** Human-built technology, derived from the Veil Institute's study of alien horizon station architecture. The Institute reverse-engineered the principles governing short-range aperture formation and licensed the technology to a gate construction corporation, which now owns and operates span gate infrastructure across the Reach (and owns an entire system as its industrial base). Span gates are well-understood engineering: buildable, modifiable, commercially operated. - **Key constraint for wiki authors:** No system's aperture count or inter-system connections are the result of human decisions. Systems cannot "vote for" or "apply for" additional horizon gate apertures. A system's gate topology is an alien-determined fact that humans have adapted to, not chosen. - **Dissent:** None. -- **Source:** Station District Layout Workshop, Ticket #153, Sprint 20. Round document: `docs/discussions/round-20-station-district-layout.md` +- **Source:** Station District Layout Workshop, Ticket T-153, Sprint 20. Round document: `docs/discussions/round-20-station-district-layout.md` - **Cross-reference:** D-093 (gate cluster spatial layout), Q-040 (gate dual-use topology — resolved), Q-041 (horizon station model — resolved), Q-042 (intra-system transport — partially resolved), Q-043 (station internal transit — resolved), Q-044 (gate-train integration — resolved) --- @@ -269,7 +269,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Date:** 2026-02-27 - **Decision:** Social triangles carry `Vec` — a multi-tag set for playstyle accessibility. Enum values: `Investigation, Economic, Social, Political, Tactical, Mundane`. `Tactical` encodes the assassination contract in spatial form (target + protector + informant/witness). Multiple purpose tags per triangle: a smuggling operation can be `Economic + Tactical` simultaneously. Purpose tags ensure the right drama is surfaced to the player whose active lens matches — they add no generation cost, categorizing existing output. - **Rationale:** Purpose tags are the bridge between the generator's social structure and the player's active playstyle. The generator doesn't build for one playstyle — it tags what's already there. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-3. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-3. - **Raised by:** Gestalt (triangle purpose model). Full team sign-off. - **Dissent:** None. - **Cross-reference:** D-025 (social site / functional cluster — triangles populate social sites), D-097 (guarantee tier system — triangles feed the audit) @@ -278,7 +278,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Date:** 2026-02-27 - **Decision:** Data-driven `HeritageGrammarOverlay` structs (10 per heritage root). Loaded once at generator startup, applied at Phase 2 chunk fill by weighted blending. Blend rules: continuous fields (decorative_density, repair_visibility, etc.) use weighted average; categorical fields (boundary_character, open_space_character) use dominant heritage weight; object tag lists use union of preferred/accent tags and intersection-exclusion of excluded tags. Phase 1 exception: `gathering_probability` evaluated at block planning for quarter pre-assignment. Authoring domain separation: **Miri** authors organizational principles, boundary character, spacing, social grammar (HeritageGrammarOverlay Rust struct / authored data). **Araminta** authors visual expression — object sets, arrangement algorithms, floor surface variants, overhead flora density and character, wall/structure material character, boundary material type, lighting temperature (TOML modifier files, one per heritage root). Shared: `ObjectTag` vocabulary must be co-maintained (see Q-049). - **Rationale:** Heritage roots must be spatially legible — the visual grammar of a Frost community versus a Tide community must be apparent to the observant player without a label. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-9. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-9. - **Raised by:** Miri (cultural grammar spec), Araminta (TOML modifier design and authoring domain). Full team sign-off. - **Dissent:** None. - **Cross-reference:** D-101 (ZonePalette — heritage modifier axis A), D-105 (informal zone typology — heritage correlations), Q-049 (ObjectTag co-maintenance) @@ -289,7 +289,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Date:** 2026-02-27 - **Decision:** Informal zones (spaces outside the community's social field) are defined by the type of social permission governing them, not by institutional absence. Three types: `physical_distance` — sparse objects, unmaintained floor; isolation is the visual. `social_permission` — normal zone palette; gathering infrastructure present; cover is about convention, not geography. `utilitarian_cover` — functional work objects; space reads as work space; unofficial use invisible to casual observation. Heritage root correlations: Frost/Stone → `physical_distance`; Tide/Vine/Dust → `social_permission`; Iron/Salt → `utilitarian_cover`. (Dust = maximum communal observation, privacy is negotiated not physical; Iron = labor function covers presence.) Location within terrain seeded independently. Visual grammar per type: `docs/workshops/generator-architecture/araminta-round4.md`. - **Rationale:** Privacy mechanics emerge from community culture. How you hide in a Frost community (physical distance) is architecturally different from how you hide in a Dust community (social agreement). The typology makes privacy mechanics culturally legible. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-10. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-10. - **Raised by:** Miri (heritage root correlations, canonical mapping corrected Round 5), Araminta (visual grammar per type). Full team sign-off. - **Dissent:** None. - **Cross-reference:** D-104 (heritage grammar overlay) @@ -300,7 +300,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Date:** 2026-02-27 - **Decision:** Trauma events are a subtype of `EraModification` with dual-track effects. Five subtypes: `PhysicalDestruction, EconomicDisruption, PoliticalShock, ViolenceEvent, MigrationShock`. Separate tracks: (1) structural damage via `StructuralChange` in ChunkMutations (applied as `LocalOverlay` per D-109); (2) cultural response via NPC weight distribution shift in `DistrictRuntimeState.npc_pattern_weights`. Decay rate seeded per-community with variation around heritage-root baseline (`trauma_visual_decay_rate: slow | medium | fast`, default medium). Design principle: **Trauma intensifies culture, it does not transform it.** A stressed community becomes a more concentrated version of itself — Frost communities close harder, Tide communities grief more publicly, Iron communities organize more collectively. Decay is toward the community's pre-trauma baseline, not toward a new equilibrium. Players who have learned a heritage root's trust model can predict community behavior in the aftermath. - **Rationale:** Cultural response must be legible — and predictable to a player who has invested in understanding the heritage root. Trauma as amplifier (not transformer) rewards prior observation. -- **Source:** Generator Architecture Workshop (#562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-12. +- **Source:** Generator Architecture Workshop (T-562), 2026-02-27. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-12. - **Raised by:** Miri (trauma subtypes and heritage decay model, "trauma intensifies culture" principle added Round 5), Tyre (struct design and decay architecture). Full team sign-off. - **Dissent:** None. - **Cross-reference:** D-100 (DamageOverlay — structural track), D-104 (heritage grammar — cultural baseline), D-109 (LocalOverlay mandate) @@ -457,9 +457,9 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Rationale:** The current model has `typical_behaviors: Vec` on RoleSpec — flat strings per culture×zone×role. At ~50 behaviors × 4 roles × N zones × M cultures, this is O(roles × zones × cultures) custom content. Decomposition to primitives + modifiers reduces to O(roles + cultures) authored content. Assembly is deterministic via SimRng, so behavior output is reproducible for a given seed. The three-layer model maps directly to the existing CultureProfile/RoleSpec/NpcBlueprint data flow — no new serialization formats or pipeline stages. - **Implementation:** `server/src/npc/blueprint.rs` — `BehaviorPrimitive`, `BehaviorContext`, `BehaviorModifier` structs + `assemble_behaviors()` function. `RoleSpec.behavior_primitives: Vec` (serde default, backward-compatible). `CultureProfile.behavior_modifiers: Vec` (serde default). Legacy `typical_behaviors` field preserved until content migration complete. - **Migration path:** Copy team populates `behavior_primitives` on zone spec RON files and `behavior_modifiers` on culture RON files. Generator switches from `typical_behaviors` to `assemble_behaviors()` when primitives are present. Once verified equivalent for seed 42, legacy field can be removed. -- **Source:** Sprint 26, ticket #633 (implements Q-057) +- **Source:** Sprint 26, ticket T-633 (implements Q-057) - **Raised by:** Tyre (Technical Architect) -- **Dissent:** None (design resolves the scaling problem identified in #630 sprint review) +- **Dissent:** None (design resolves the scaling problem identified in T-630 sprint review) - **Resolves:** Q-057 (composable behavior generation — data structure definition) - **Cross-reference:** [D-138](#d-138-llm-re-voicing-pipeline-for-npc-voice) (resolved pipeline, this resolves data format), [D-121](#d-121-voice-is-culture-driven--job-as-modifier) (culture-primary voice), [D-122](#d-122-all-npcs-generated--named-npcs-deferred) (all NPCs generated) @@ -528,7 +528,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Layer 3 grammar constraint:** When Gemma appends environmental flavor to Layer 1+2 assembled text, the flavor clause must grammatically attach as a prepositional phrase or coordinating conjunction. Absolute and participial phrases (e.g., "tool already in hand") require a leading comma separator. Culture-specific modifier clauses that use absolute-phrase construction must include the separator (e.g., ", tool already in hand") or be rewritten as prepositional form ("with tool in hand") to ensure clean assembly. This constraint applies to all culture profile modifier authoring. -- **Source:** Sprint 26 zone-type taxonomy discussion (2026-03-13); behavior review (#634); user requirements for 300-system scalability +- **Source:** Sprint 26 zone-type taxonomy discussion (2026-03-13); behavior review (T-634); user requirements for 300-system scalability - **Raised by:** Paula (zone-type taxonomy and POI system); Gestalt (assembly engine); Mellanie (primitive content); Tyre (architectural review) - **Dissent:** None - **Supersedes:** Per-location behavior pool approach from current zone spec format (`behavior_primitives` arrays on location RON files). Location files retain metadata but behavior content migrates to zone-type template files. @@ -566,21 +566,21 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio ### D-147: Aesthetic taste as character personality trait — shared root for cosmetic and environmental expression - **Date:** 2026-03-17 -- **Decision:** Characters have an `aesthetic_taste` personality trait (preferred palette, style leanings) that naturally expresses in both personal appearance and living space. This is NOT a cosmetic→apartment pipeline — hair color does not map to wall color. Instead, taste is a shared root: someone who gravitates toward teal will pick teal clothing AND own a teal vase. The apartment generator reads the character's taste trait (alongside economic position from #615 and cultural context from #679); the character customisation screen lets the player express that same taste through appearance choices. Neither system depends on the other — both read the same underlying trait. -- **Rationale:** The naive approach (pipe cosmetic choices into apartment generator) creates a crude "team color" mapping that feels artificial. The elegant approach: taste is a personality attribute, like traits or skills. It manifests independently in multiple contexts. This keeps #619 (customisation) and #617 (apartment) architecturally independent while producing coherent identity expression. The taste trait is also reusable: NPC aesthetic preferences, gift preferences, "does this room feel like home" comfort checks, cultural style variations. -- **Dependency implication:** #619 does NOT block #617. Both read the character's taste trait independently. The taste trait definition belongs to the character data model (server), not to either consuming system. +- **Decision:** Characters have an `aesthetic_taste` personality trait (preferred palette, style leanings) that naturally expresses in both personal appearance and living space. This is NOT a cosmetic→apartment pipeline — hair color does not map to wall color. Instead, taste is a shared root: someone who gravitates toward teal will pick teal clothing AND own a teal vase. The apartment generator reads the character's taste trait (alongside economic position from T-615 and cultural context from T-679); the character customisation screen lets the player express that same taste through appearance choices. Neither system depends on the other — both read the same underlying trait. +- **Rationale:** The naive approach (pipe cosmetic choices into apartment generator) creates a crude "team color" mapping that feels artificial. The elegant approach: taste is a personality attribute, like traits or skills. It manifests independently in multiple contexts. This keeps T-619 (customisation) and T-617 (apartment) architecturally independent while producing coherent identity expression. The taste trait is also reusable: NPC aesthetic preferences, gift preferences, "does this room feel like home" comfort checks, cultural style variations. +- **Dependency implication:** T-619 does NOT block T-617. Both read the character's taste trait independently. The taste trait definition belongs to the character data model (server), not to either consuming system. - **Information boundary (D-010):** `aesthetic_taste` is a public trait — visible to other entities and the client. NPCs can observe the player's taste (e.g. gift relevance, comfort assessment). This is not private knowledge behind the information boundary. - **Source:** Sprint 27 planning pass — Q-WTF-040 resolution - **Raised by:** Project lead (taste-as-shared-root framing), Gestalt (ColorHint concept evolved into trait), Paula (culture + economics as primary apartment drivers) - **Dissent:** Ozzie argued for direct cosmetic→apartment pipeline. Tyre and Paula argued for no connection at all. The taste trait is the synthesis: connection through personality, not through data pipeline. -- **Cross-reference:** [D-125](#d-125-world-is-quietly-responsive--gradient-of-caring-by-social-proximity) (quiet responsiveness), [D-134](architecture.md#d-134-full-character-customisation--hair-clothing-colors-at-tile-scale) (full character customisation), [D-136](architecture.md#d-136-first-settled-reach-moment-auto-generated-apartment--insert-activation) (first game moment), ticket #617 (auto-generated apartment), ticket #619 (full character customisation) +- **Cross-reference:** [D-125](#d-125-world-is-quietly-responsive--gradient-of-caring-by-social-proximity) (quiet responsiveness), [D-134](architecture.md#d-134-full-character-customisation--hair-clothing-colors-at-tile-scale) (full character customisation), [D-136](architecture.md#d-136-first-settled-reach-moment-auto-generated-apartment--insert-activation) (first game moment), ticket T-617 (auto-generated apartment), ticket T-619 (full character customisation) - **Resolves:** Q-WTF-040 --- --- -### ~~Content Pattern Note: Generator-compatible overheard conversation format (ticket #664)~~ SCRAPPED (R-012) +### ~~Content Pattern Note: Generator-compatible overheard conversation format (ticket T-664)~~ SCRAPPED (R-012) - **Superseded:** 2026-04-10. NPC ambient interaction model scrapped. See [R-012](../rejected/perception.md#r-012-overheard-npc-conversation-system-d-078--scrapped). --- @@ -607,7 +607,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio - **Decision:** The planet name "Iserlohn" (GJ-532c, in the Kettenschmied system) is **retained**. The name derives from Iserlohn, a real industrial city in the Sauerland region of North Rhine-Westphalia, Germany — historically a centre of wire-drawing, chain manufacturing, and metal fabrication. This origin is directly coherent with Kettenschmied's identity as a west_reach German-heritage metalworking community. The real-city reading is the intended and primary reading. - **IP note:** Iserlohn Fortress is an iconic location in *Legend of the Galactic Heroes* (Ginga Eiyuu Densetsu). The name collision is acknowledged. The decision to retain is deliberate: the real-world German city predates the anime by centuries; real geographic names are not protectable; and the concept diverges fundamentally — a cold marginal mining/fabrication planet (1,000 population, component fabrication, asteroid mining) is the structural opposite of a strategic military megastructure. No reader can point at GJ-532c and say "that's the LoGH fortress" — the contexts share only a name. - **Preferred alternative on record (if future team prefers a rename):** **Altena** — a Sauerland city at the confluence of Lenne and Volme rivers, historically the actual birthplace of German wire-drawing and chain manufacture. The Altena castle became the site of the first industrial wire-drawing operation in the German states. Altena has no known SF franchise association and carries stronger historical precision for chain/cable fabrication. It would require an atlas rename of `GJ532c` and corresponding wiki edits. -- **Raised by:** Miri (Sprint 31, ticket #773) +- **Raised by:** Miri (Sprint 31, ticket T-773) - **Dissent:** None. ### D-193: Lattice Commission — canonical long-form of the Commission diff --git a/governance/decisions/economics.md b/governance/decisions/economics.md index ec734bb9d..ac49fca01 100644 --- a/governance/decisions/economics.md +++ b/governance/decisions/economics.md @@ -14,7 +14,7 @@ This domain covers: currency system, commodity taxonomy, shadow economy, corpora - **Sol** — Earth legacy currency. Finite pool, no active issuer, no formal exchange rate. Used for crime and untraceable transactions, and as payment for premium Earth luxury imports (wines, foods, exclusive goods). Sol is NOT a currency zone — it is modeled as a shadow economy commodity, not a simulation numeraire. - Commission fines and certification fees are Tractus-denominated (the Commission is Assembly-funded). Compact members paying Commission costs face currency conversion friction — this is a structural driver of the shadow economy in the Compact zone, not a coincidence. - **Rationale:** Three currencies create structural economic bloc tension without requiring event generation. The Tractus/Mark divide maps directly to the Assembly vs. Compact political divide. Sol's untraceable nature makes it the natural medium for crime — it has lore grounding and mechanical function without requiring a formal exchange rate. -- **Raised by:** Full planning team, Sprint 32 Workshop #796. Lead directive on political framing. +- **Raised by:** Full planning team, Sprint 32 Workshop T-796. Lead directive on political framing. - **Dissent:** Burnelli-Sheldon (economist) recommended single currency for Phase 2 — see [R-011](../rejected/economics.md#r-011-single-currency-for-phase-2-economics). Overruled by lead. --- @@ -240,7 +240,7 @@ This domain covers: currency system, commodity taxonomy, shadow economy, corpora - 21 production chains including 2 substitution routes - Schema: 14 fields per commodity including 3 political sub-flags (`commission_certifiable`, `compact_contested`, `shadow_viable`), `production_ubiquity`, `demand_model`, `panic_threshold_weeks` - **Rationale:** The catalog is the concrete realization of the D-173 taxonomy. 36 types is rich enough to produce meaningful geographic specialization while remaining authorable. The 14-field schema captures economic, political, and behavioral properties needed by the tâtonnement simulation. Two substitution routes prevent hard lock-outs in chains where one raw source is geographically constrained. -- **Raised by:** Full planning team, Sprint 32 Workshop #801. +- **Raised by:** Full planning team, Sprint 32 Workshop T-801. - **Dissent:** None. - **Cross-reference:** [D-173](#d-173-commodity-taxonomy) (taxonomy), [D-182](#d-182-toml-source-of-truth-for-economics-data) (TOML pipeline), [D-185](#d-185-brands-are-not-commodities) (what is excluded from this catalog) - **Amended 2026-05-25 (D-233):** `bulk_class` enum values enumerated — `BulkSolid | BulkLiquid | PrecisionDense | Perishable | NonPhysical`. @@ -256,7 +256,7 @@ This domain covers: currency system, commodity taxonomy, shadow economy, corpora - The commodity chain terminates at abstract generic finals (e.g., "premium spirits", "luxury textiles") - Brand premium lives in the corporate behavioral layer — it is not modeled in Phase 2 - **Rationale:** Adding 30+ named brands to the commodity catalog would require per-brand pricing models, cultural preference curves, aging pipeline tracking, and star-system terroir logic — a separate simulation in its own right. Phase 2 must deliver a working generic commodity simulation first. Brands consuming generic commodities as inputs correctly captures the economic relationship without requiring brand simulation code. -- **Raised by:** Lead directive, Sprint 32 Workshop #801. +- **Raised by:** Lead directive, Sprint 32 Workshop T-801. - **Dissent:** None. - **Cross-reference:** [D-184](#d-184-commodity-catalog-36-types) (the catalog that excludes brands), [D-177](#d-177-productivity-constraints-lore-derived) (lore ceilings on brand production — still authoritative), [D-175](#d-175-corporation-taxonomy-and-prerequisite) (brand corporations exist as Tier 1 entities) @@ -272,7 +272,7 @@ This domain covers: currency system, commodity taxonomy, shadow economy, corpora - **`MARK_PRIMARY` zones default to `gate_energy_connected = false`.** The Compact refused Gate Corp energy dependency as a deliberate political choice — it preserves Compact energy sovereignty and economic independence. - **Gate Corp cutoff scenario:** If Gate Corp cuts energy to a dependent node, `fusion_fuel` demand spikes. Compact surplus capacity (from their independence) becomes the emergency supply source. The Compact's refusal of dependency is an economic asset with concrete mechanics. - **Rationale:** Energy-over-gate gives the gate topology an additional economic dimension beyond freight routing. Opt-in model prevents mandatory complexity for every node. The Compact's structural refusal creates the primary counterplay scenario Gestalt was concerned about — without opt-in, no interesting asymmetry. Gate Corp as private monopoly (not Assembly policy) makes it an independent economic actor, enabling corporate storylines. -- **Raised by:** Lead directive + Miri (lore) + Gestalt (mechanics) + Burnelli-Sheldon (economics) + Tyre (implementation), Sprint 32 Workshop #801. +- **Raised by:** Lead directive + Miri (lore) + Gestalt (mechanics) + Burnelli-Sheldon (economics) + Tyre (implementation), Sprint 32 Workshop T-801. - **Dissent:** Gestalt initially recommended Level 2 (no energy transmission) due to counterplay concerns. Resolved by the opt-in model — the Compact's refusal to connect provides the counterplay without requiring adversarial defaults. - **Cross-reference:** [D-172](#d-172-currency-zone-initialization) (`MARK_PRIMARY` zone flag), [D-173](#d-173-commodity-taxonomy) (`fusion_fuel` as a commodity), [D-187](#d-187-fusion-fuel-as-intermediate-81-water-yield) (fuel chain), [D-175](#d-175-corporation-taxonomy-and-prerequisite) (Gate Corp as Tier 1 corporation) @@ -288,7 +288,7 @@ This domain covers: currency system, commodity taxonomy, shadow economy, corpora - Creates a **structural frontier energy cost premium**: frontier nodes pay more for fusion fuel because water transport costs accumulate in the refining input cost, and the chain demand from smelting/alloys/electronics cascades that cost into the manufacturing sector - This premium arises from geography and chain structure — no event generation required - **Rationale:** Raw-material fuel would make energy cost geography flat (water is everywhere, therefore fuel is everywhere at the same price). Intermediate fuel with a high water yield ratio creates a conversion cost that multiplies transport costs — frontier refineries are expensive to run because fuel production itself consumes large volumes of low-value bulk water. The cascade effect through 3 industrial chains means frontier manufacturing is structurally more expensive, which is consistent with real-world frontier economics and the lore of the Compact vs. Core divide. -- **Raised by:** Lead directive, validated by Burnelli-Sheldon (economics), Sprint 32 Workshop #801. +- **Raised by:** Lead directive, validated by Burnelli-Sheldon (economics), Sprint 32 Workshop T-801. - **Dissent:** None. - **Cross-reference:** [D-184](#d-184-commodity-catalog-36-types) (fuel and water are catalog entries), [D-186](#d-186-gate-transmission-levels-mass--data--energy) (energy-over-gate reduces fuel demand), [D-178](#d-178-economic-model-architecture) (Leontief production cascade) @@ -396,7 +396,7 @@ This domain covers: currency system, commodity taxonomy, shadow economy, corpora | Meridian Risk | — (Tier 1, branded_products) | — | - **Rationale:** Administered pricing is the correct model because brands violate all three tâtonnement assumptions: heterogeneity (Calloway ≠ VGV ≠ generic spirits), supply inelasticity (terroir production cannot respond to price signals per D-177), and Veblen demand effects (prestige goods can have upward-sloping demand). The one-way interface (commodity prices → brand input costs; brand output prices do NOT feed back into tâtonnement) is architecturally clean and matches the D-178 layer model. The identity/exotic split in cultural premium is the minimal structural addition needed to produce all three observed pricing curves (Scarcity-Distance, Dual-Peak, Aspirational Gradient). Population asymmetry (~80B total Reach population, systems ranging from 10B to <50k) makes the halo/volume tier pattern structurally necessary — brands from tiny worlds are astronomically exclusive and need volume derivatives to be economically relevant. -- **Raised by:** Full planning team workshop, Sprint 34 (#811). +- **Raised by:** Full planning team workshop, Sprint 34 (T-811). - **Dissent:** None. - **Cross-reference:** [D-185](#d-185-brands-are-not-commodities) (brands are not commodities), [D-184](#d-184-commodity-catalog-36-types) (commodity catalog), [D-177](#d-177-productivity-constraints-lore-derived) (productivity constraints), [D-175](#d-175-corporation-taxonomy-and-prerequisite) (corporation taxonomy), [D-178](#d-178-economic-model-architecture) (economic model architecture), [D-180](#d-180-event-input-port) (event input port), [D-181](#d-181-signal-vocabulary) (signal vocabulary), [D-173](#d-173-commodity-taxonomy) (commodity taxonomy), [D-171](#d-171-three-currency-system) (three-currency system), [D-131](content.md#d-131-broad-economic-verb-vocabulary--life-verbs-not-tycoon-specific) (economic verb vocabulary), [D-118](scope.md#d-118-small-business-owner-starting-state--tycoon-is-aspiration-not-starting-position) (small business owner starting state) @@ -415,7 +415,7 @@ This domain covers: currency system, commodity taxonomy, shadow economy, corpora - **Authoring rule:** wiki volume figures for media brands and any good with `brand_category = cultural` must include a `reference_population` annotation alongside the count. "40M" is incomplete; "40M (west_reach corridor, ~10B addressable)" is correct. This applies to corporation production ceilings, media distribution figures, and market penetration estimates in wiki pages and TOML files. - **Structural scarcity principle:** for physical brand goods, production volume only has meaning relative to addressable demand. The `structural_scarcity_base` parameter in the brand pricing layer (D-189) is derived from this ratio: `1.0 - min(1.0, annual_volume / (addressable_population × demand_rate))`. A 12,000-unit/year artisan product against 100M addressable consumers yields structural_scarcity_base ≈ 0.88 — perpetually near-maximum scarcity regardless of local stockpile state. This scarcity floor is permanent, not situational. - **Rationale:** The Reach's population asymmetry (core systems 10B+, frontier systems under 50K) makes absolute volume numbers meaningless without a reference population. Without an explicit calibration rule, brand and media content significance will be systematically miscalibrated across all authoring. The structural scarcity principle connects volume calibration to the administered pricing model: a brand's Veblen premium floor is derived from the same population-relative ratio, ensuring that pricing and authoring are grounded in the same underlying reality. -- **Raised by:** Jeroen (population asymmetry insight), Burnelli-Sheldon (structural scarcity derivation and calibration table), Sprint 34 Workshop #811. +- **Raised by:** Jeroen (population asymmetry insight), Burnelli-Sheldon (structural scarcity derivation and calibration table), Sprint 34 Workshop T-811. - **Dissent:** None. - **Cross-reference:** [D-189](#d-189-brand-layer-architecture) (structural_scarcity_base parameter), [D-177](#d-177-productivity-constraints-lore-derived) (lore-constrained production ceilings), [D-175](#d-175-corporation-taxonomy-and-prerequisite) (corporation production volumes) diff --git a/governance/decisions/perception.md b/governance/decisions/perception.md index 1399a2006..03b47e121 100644 --- a/governance/decisions/perception.md +++ b/governance/decisions/perception.md @@ -105,7 +105,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - **Key principle:** Red means danger TO YOUR CHARACTER, not danger in the abstract. The detective might see amber (flagged in case file) where the smuggler sees green (trusted colleague). This IS asymmetric information rendered visually. - **Transition behavior:** Color shifts smoothly (0.5s fade) when relationship state changes. THE FRIEND's first color shift (green → amber) should be the first relationship color change in the session — maximum emotional impact. - **Cross-reference:** Internal monologue ([D-016](#d-016-internal-monologue-as-core-perceptionatmosphere-system)), THE FRIEND pattern ([D-034](content.md#d-034-the-friend--production-level-npc-pattern)) -- **Raised by:** Araminta (Round 1 proposal, color palette design), project lead (approved, directive #2) +- **Raised by:** Araminta (Round 1 proposal, color palette design), project lead (approved, directive T-2) - **Dissent:** None - **Amendment (2026-03-17, Sprint 28 workshop):** Entity relationship color is NOT displayed via character outlines in normal gameplay. The D-033 color palette (#4a9ebb, #6bc9a6, etc.) is valid within the insert/perception mode overlay only. Normal gameplay renders all characters with uniform dark outlines. See [D-154](scope.md#d-154-character-outline-is-not-a-relationship-indicator--uniform-dark). @@ -220,7 +220,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - **Source:** Control & Interaction Workshop (2026-02-13) - **Raised by:** Araminta (visual spec), Stig (UX rules + diegetic test), Ozzie (weapon suppression) - **Dissent:** None. -- **OQ-07 resolution (2026-02-19, #522):** Insert-off behavior is **option (a): cursor shape still changes, verb labels suppressed.** +- **OQ-07 resolution (2026-02-19, T-522):** Insert-off behavior is **option (a): cursor shape still changes, verb labels suppressed.** - Cursor state machine fires normally (entity hover → bracket shape, object hover → X-shape) — the character's body physically orients toward targets as a subconscious/spatial response. - Insert does not process targets into actionable data: `should_show_interactions()` returns false when `insert_active == false`, and interaction labels (z-layer 6) are hidden via `set_insert_active(false)` on `InteractionList` and `InteractionPrompt`. - `GameState.insert_active` is the source of truth (defaults true in v0.1; wired from snapshot field `insert_active`). @@ -228,7 +228,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight ### D-057: Entity interaction — vertical list, insert-styled [PARTIALLY SUPERSEDED — archetype portion deferred to Phase 6] - **Date:** 2026-02-13 -- **Supersession note (2026-04-21 / amended 2026-04-22, Sprint 37, #878, PR #137):** The character-archetype verb variation portion of this decision is retired pending a Phase 6 character-model design — not deferred in place with a stub. Per the development cascade (CLAUDE.md), archetype-driven verb relabeling is Phase 6 detail-coloring and has been stripped from the server. Vertical-list structure, Phase 1/Phase 2 split, POI priority flips, and contradiction markers remain live. Relabeling (Open→"Move"/"Stash" vs "Scan"/"Flag") is deleted; container verb labels are now identical across all player states, and uniform labeling is the intended pre-Phase-6 end state, not a regression. Reintroduction gate: a confirmed Phase 6 character-model design is a prerequisite. See `docs/architecture/sprint-37-878-audit.md` (lead override section) for the cascade rationale. +- **Supersession note (2026-04-21 / amended 2026-04-22, Sprint 37, T-878, PR #137):** The character-archetype verb variation portion of this decision is retired pending a Phase 6 character-model design — not deferred in place with a stub. Per the development cascade (CLAUDE.md), archetype-driven verb relabeling is Phase 6 detail-coloring and has been stripped from the server. Vertical-list structure, Phase 1/Phase 2 split, POI priority flips, and contradiction markers remain live. Relabeling (Open→"Move"/"Stash" vs "Scan"/"Flag") is deleted; container verb labels are now identical across all player states, and uniform labeling is the intended pre-Phase-6 end state, not a regression. Reintroduction gate: a confirmed Phase 6 character-model design is a prerequisite. See `docs/architecture/sprint-37-878-audit.md` (lead override section) for the cascade rationale. - **Decision:** Entity interactions use a compact vertical list (not radial). 2-4 options max, anchored to entity position. Insert-styled with Araminta's geometric aesthetic. New options unlocked by knowledge changes are highlighted with a gradient glow background. Radial menu reserved for world menu only ([D-058](#d-058-world-menu--radial-4-spokes)). Max 3 visible response options in dialogue context. - **Server architecture:** Two-phase verb computation. Phase 1 (simulation, no KG): compute maximum possible verb set from ObjectType component (Readable, Container, Terminal, Door, Pickup, Furniture — each with specific verb sets). Phase 2 (observer, reads KG): filter by character's knowledge (Confront requires KnowsDetails+ per [D-041](architecture.md#d-041-knowledge-graph-data-model)), apply POI priority flips, add contradiction markers. ~~Character-archetype verb variation implemented as Phase 2 observer filter rules (same crate: smuggler sees "Move/Stash", detective sees "Scan/Flag").~~ *[Removed Sprint 37 — archetype verb relabeling deleted; see supersession note above.]* - **Diegetic test:** Labels render on z-layer 6. If insert is off, labels disappear. @@ -238,7 +238,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - **Source:** Control & Interaction Workshop (2026-02-13) - **Raised by:** Stig (vertical list structure + diegetic test), Araminta (insert aesthetic), Dudley (two-phase verb computation), Nigel (character-archetype verb sets). Lead resolved: Stig's structure, Araminta's styling. - **Dissent:** Araminta argued for spoke radial (geometry transformation signals qualitative knowledge change — new spoke growing). Lead rejected: items moving under cursor when knowledge changes is a moving goalpost (bad UX while aiming at an option). -- **OQ-07 resolution (2026-02-19, #522):** +- **OQ-07 resolution (2026-02-19, T-522):** - When `insert_active == false`, the interaction list hides completely (`set_insert_active(false)` → `_hide()`). - Cursor shape changes still occur per D-056 OQ-07 — list suppression is independent of cursor state. - `GameState.insert_active` drives this at runtime, wired via `main.gd` on each snapshot. @@ -285,12 +285,12 @@ How the player observes and interacts with the world: camera, fog, line-of-sight ### D-061: Dialogue box — unified conversation log, bottom screen, max 20% height, no portraits - **Date:** 2026-02-13 - **Decision:** Dialogue occupies the bottom of the screen, max 20% height, max-width 1200px ([D-076](#d-076-dialogue-box-max-width--1200px-oq-29-resolution)). NO portraits — the NPC is on screen, a portrait is redundant. Monologue floats ABOVE the dialogue box on z-layer 7 — spatial separation allows monologue to contradict dialogue visually (character thinks one thing while NPC says another). Walk-away via WASD, dialogue fades over 300ms, no close button ([D-064](content.md#d-064-walk-away--three-phase-consequences)). Auto-pause in single-player when implant UI is open; overlay design for multiplayer readiness. -- **Unified conversation log (Sprint 14, #535):** The dialogue box is a single chronological log for player-NPC conversations. Each entry shows `Speaker → Target: text` with per-character name colours (hash-indexed from configurable palette in `data/dialogue-theme.yaml`). Player response options render below the log; max 3 visible. Locked options invisible ([D-062](content.md#d-062-invisible-locked-dialogue-options)). +- **Unified conversation log (Sprint 14, T-535):** The dialogue box is a single chronological log for player-NPC conversations. Each entry shows `Speaker → Target: text` with per-character name colours (hash-indexed from configurable palette in `data/dialogue-theme.yaml`). Player response options render below the log; max 3 visible. Locked options invisible ([D-062](content.md#d-062-invisible-locked-dialogue-options)). - **Entry lifecycle:** All entries share the same timeout (15s + 3s fade, configurable via theme YAML). Walk-away clears response options but preserves log entries — earned information is fair game. Panel auto-hides when all entries expire and no active conversation is in progress. - **Passive overheard lines:** D-078 (overheard NPC conversations) was scrapped per R-012. Passive dialogue display will be redesigned after Phase 5 walkable environment. - **Rationale:** Game world stays live above the dialogue box — player sees NPC body language while talking. Monologue above + dialogue below = the character can think one thing while saying another. Max 3 options + invisible locks = player never knows what they're missing. No portrait because the NPC IS on screen. A single unified log avoids a separate UI element for overheard content and makes the flow of conversation feel natural — active and passive dialogue interleave chronologically. - **Cross-reference:** Invisible locks ([D-062](content.md#d-062-invisible-locked-dialogue-options)), confrontation ([D-063](content.md#d-063-confrontation--same-box-different-weight)), walk-away ([D-064](content.md#d-064-walk-away--three-phase-consequences)), z-stack ([D-049](#d-049-z-level-rendering-stack-8-layers)), max-width ([D-076](#d-076-dialogue-box-max-width--1200px-oq-29-resolution)), overheard NPC conversation ([D-078](#d-078-overheard-npc-conversation--passive-dialogue-panel-with-occlusion-filter)) -- **Source:** Control & Interaction Workshop (2026-02-13). Amended Sprint 14 (#535): unified log architecture. +- **Source:** Control & Interaction Workshop (2026-02-13). Amended Sprint 14 (T-535): unified log architecture. - **Raised by:** Stig (UI spec + no portraits), Lead (20% height constraint + max-width directive). Sprint 14 unified log: Stig (implementation). - **Dissent:** Stig initially proposed 25% height and 50% width centered. Lead constrained to 20% height and max-width. @@ -380,7 +380,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - Protocol version bumped to 11. - **Client contract:** Client maintains a `zone_id → { name, temperature_tint, ambient_layer }` lookup table. Deep fog shader (layer 3) reads `zone_id` from the last-seen `VisibleTile` data to apply the ~10% temperature tint. AudioManager reads `zone_id` to trigger crossfade between ambient layers ([D-073](architecture.md#d-073-zone-crossfade-approach--hard-boundary-soft-audio-transition)). - **Cross-reference:** Fog layers ([D-059](#d-059-fog--shader-based-five-layers-knowledge-graph-driven)), zone crossfade ([D-073](architecture.md#d-073-zone-crossfade-approach--hard-boundary-soft-audio-transition)), ObserverSnapshot ([D-020](architecture.md#d-020-engine-and-architecture-selection--godot-client--rust-simulation-via-subprocessipc)), deterministic simulation ([D-010](architecture.md#d-010-multiplayer-ready-architectural-baseline)) -- **Raised by:** Tyre (architecture review, #523) +- **Raised by:** Tyre (architecture review, T-523) - **Dissent:** None ### D-078: ~~Overheard NPC conversation — passive dialogue panel with occlusion filter~~ SCRAPPED (R-012) @@ -415,7 +415,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - **Rationale:** Unified event type keeps the knowledge system composable. Grant timing at line selection (not client display) ensures D-010 determinism — tick-stamped and server-authoritative. Entity grants are required for contradiction detection: testimony must create `EntityKnowledge` with `ToldBy` source so that a subsequent `DirectObservation` can detect a discrepancy. Physical evidence uses the same mechanism with `DirectObservation` source, which is treated as higher-confidence and cannot be contradicted by the NPC-denial path. - **Raised by:** Knowledge Flow & NPC Information Boundaries Workshop — unanimous on grant timing and unified event type. Entity grants in Sprint 17 per team lead decision (2026-02-24), overriding Paula's Round 2 acceptance of Dudley's FactId workaround. - **Dissent:** Paula (Round 2) accepted the FactId workaround with 3 binding conditions; team lead overrode in favour of entity grants path championed by Tyre, Gestalt, and Dudley. Paula's conditions honoured where applicable: (1) `ToldBy` source flows through `ContradictionDetected` event payload — satisfied by D-083; (2) Sprint 18 entity grants are committed scope; (3) formal record of this commitment. -- **Implements:** Tickets #545 (schema), #546 (event + handler) +- **Implements:** Tickets T-545 (schema), T-546 (event + handler) - **Cross-reference:** [D-041](architecture.md#d-041-knowledge-graph-data-model), [D-010](architecture.md#d-010-multiplayer-ready-architectural-baseline), [D-083](#d-083-contradiction-detection-pipeline) ### D-080: NPC-to-NPC Knowledge Propagation @@ -425,9 +425,9 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - **Resolves:** Q-024 - **Raised by:** Workshop — unanimous - **Dissent:** None -- **Implements:** Ticket #548 +- **Implements:** Ticket T-548 - **Cross-reference:** [D-041](architecture.md#d-041-knowledge-graph-data-model), [D-083](#d-083-contradiction-detection-pipeline), [D-078](#d-078-overheard-npc-conversation--passive-dialogue-panel-with-occlusion-filter) (D-078 scrapped per R-012; `transfer_npc_knowledge` retained for Phase 5 rewire — see Amendment 2026-04-19) -- **Amendment (2026-04-19, R-012 / #848):** D-078 was scrapped (R-012) and the `run_npc_conversations` system was deleted in #848. The `transfer_npc_knowledge` system is **retained in-tree for Phase 5 rewire** but no longer fires in production — its `Added` trigger is now only inserted by test fixtures. The design (trust-gated transfer, dual-mutable KG access, `KnowsOf` confidence cap, `disclosure_blocked` honoring) is preserved; Phase 5 will wire a new proximity/dialogue trigger in its place. Until then, treat the system as dormant and guard against assuming it runs. +- **Amendment (2026-04-19, R-012 / T-848):** D-078 was scrapped (R-012) and the `run_npc_conversations` system was deleted in T-848. The `transfer_npc_knowledge` system is **retained in-tree for Phase 5 rewire** but no longer fires in production — its `Added` trigger is now only inserted by test fixtures. The design (trust-gated transfer, dual-mutable KG access, `KnowsOf` confidence cap, `disclosure_blocked` honoring) is preserved; Phase 5 will wire a new proximity/dialogue trigger in its place. Until then, treat the system as dormant and guard against assuming it runs. ### D-081: Unprompted Disclosure Design - **Date:** 2026-02-24 @@ -435,7 +435,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - **Rationale:** NPCs checking only their own KG (Option A) is both diegetically correct and mechanically richer: NPCs can say things the player already knows, which creates dramatic irony. Option B (NPC checks player's KG) would collapse this asymmetry at exactly the moments their speech would be most dramatically charged. Separate `DisclosureCandidates` component prevents DerivedTellState serialization bloat. The location privacy gate creates learnable spatial behavior patterns. The per-fact `per_fact_history` is the primary narrative quality gate. - **Raised by:** Workshop — unanimous on architecture; Paula and Gestalt on trigger gates; Tyre and Dudley on implementation structure - **Dissent:** Gestalt (Round 1) opposed global rate limit as creating invisible NPC competition. Resolved in Round 2 via deterministic StableId selection. Included. -- **Implements:** Tickets #551, #172, #173 +- **Implements:** Tickets T-551, T-172, T-173 - **Cross-reference:** [D-041](architecture.md#d-041-knowledge-graph-data-model), [D-082](#d-082-npc-information-boundaries--mvp-scope), [D-034](content.md#d-034-the-friend-npc-archetype) ### D-082: NPC Information Boundaries — MVP Scope @@ -444,7 +444,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - **Rationale:** The minimum viable boundary produces maximum gameplay-visible difference for minimum risk. Pathfinding from KG has an unresolvable failure mode (NPC forgets path nodes = stuck NPCs = undefined movement behavior) and must not be implemented. Self-knowledge always comes from axis components because the NPC is the continuous observer of its own state. The `Option<&KnowledgeGraph>` addition is backward-compatible. - **Raised by:** Workshop — unanimous - **Dissent:** None. Paula adds that `tell_state` should eventually incorporate KG-derived secret-exposure intensity — deferred to a future sprint as enhancement. -- **Implements:** Tickets #549 (step 1), #551 (step 2), ticket #142 +- **Implements:** Tickets T-549 (step 1), T-551 (step 2), ticket T-142 - **Cross-reference:** [D-041](architecture.md#d-041-knowledge-graph-data-model), [D-081](#d-081-unprompted-disclosure-design), [D-026](architecture.md#d-026-npc-simulation-tier-system) ### D-083: Contradiction Detection Pipeline @@ -454,7 +454,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - **Resolves:** Q-026 - **Raised by:** Workshop — unanimous on architecture and downstream chain; Tyre and Gestalt on struct approach; Dudley conceded Option B (string encoding). - **Dissent:** Dudley (Round 1): Option B string encoding as Sprint 17 workaround. Conceded in Round 2. No standing dissent. -- **Implements:** Tickets #547 (struct + detection), #550 (monologue + event chain) +- **Implements:** Tickets T-547 (struct + detection), T-550 (monologue + event chain) - **Cross-reference:** [D-034](content.md#d-034-the-friend-npc-archetype), [D-033](perception.md#d-033-entity-color--relationship-to-player), [D-041](architecture.md#d-041-knowledge-graph-data-model), [D-079](#d-079-knowledge-grant-architecture), [D-080](#d-080-npc-to-npc-knowledge-propagation) ### D-086: Insert icon system — custom SVG, no icon font @@ -467,7 +467,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight - **Godot integration:** Godot 4 handles SVG natively (AtlasTexture, importable SVGs). Runtime color via ShaderMaterial. No font rendering pipeline or bitmap caching needed. - **Icon vocabulary (v0.1):** - Inventory item silhouettes: 3–4 items at 40×40px (manifest, access token, comm log) - - Perception mode indicators: 2–3 icons at 20×20px (deferred until #315 specs the modes) + - Perception mode indicators: 2–3 icons at 20×20px (deferred until T-315 specs the modes) - Stance indicators: 4 icons at 20×20px (WALK, CAREFUL, SPRINT, CROUCH) — delivered Sprint 32 alongside text labels. Icons supplement labels; text remains primary affordance. - Interaction prompt icons: 4 icons at 16×16px (talk, observe, follow, examine) — delivered Sprint 32. Supplement interaction verb text per insert HUD wireframe §9. - Border arrows and geometric markers (diamonds, dots, squares): drawn as primitives, not icons diff --git a/governance/decisions/process.md b/governance/decisions/process.md index 24aa76ce8..5579fcd67 100644 --- a/governance/decisions/process.md +++ b/governance/decisions/process.md @@ -59,7 +59,7 @@ How the team works: composition, naming, workflow. - **Design principles:** (1) Game-first, not encyclopedia-first — every entry serves the content pipeline. (2) Hierarchical with cross-references via IDs, not file paths. (3) Metadata-rich YAML frontmatter enabling programmatic queries. (4) Template-driven — new entries follow category templates. (5) Every entry has a `status` field: `proposed` → `draft` → `canonical`. - **Governance:** Miri owns wiki structure (new categories, templates, structural decisions). Anyone can create entries within their domain following templates. Qatux maintains the index and cross-references. - **Cross-reference:** Setting ([D-036](content.md#d-036-sova-transit-district--van-maanens-star-system-as-v01-setting)) -- **Raised by:** Miri (Round 2 proposal with full directory structure and 7 YAML schemas), project lead (non-negotiable, directive #5) +- **Raised by:** Miri (Round 2 proposal with full directory structure and 7 YAML schemas), project lead (non-negotiable, directive T-5) - **Dissent:** None --- diff --git a/governance/decisions/scope.md b/governance/decisions/scope.md index dce080045..bf23d0c1f 100644 --- a/governance/decisions/scope.md +++ b/governance/decisions/scope.md @@ -118,9 +118,9 @@ What we're building: game concept, design pillars, prototype definition, map spe - **Architecture:** Event-driven with asset registry + visual fallback. Simulation emits typed sound events; client renders as audio (if asset exists) or visual indicator + monologue trigger (if not). Ambient loops managed separately from event-driven sounds. Monologue chime is a UI sound, not a simulation sound. - **Amendment (2026-02-16, Audio Pipeline Kickoff):** - **Hybrid audio generation approach:** Stable Audio Open (SAO) for sounds >200ms with organic character (footsteps, ambient layers). Manual synthesis for sounds <200ms with precise/digital character (UI clicks, scanner beeps). SAO ceiling ~47s; accept 45s loops with crossfade for ambient layers. The insert-tech/organic split ([D-074](content.md#d-074-audio-aesthetic-identity--insert-tech-vs-organic)) maps to synthesis/generation split. - - **Sprint 7 scope expansion:** Ticket #440 expanded from 6 to 8 assets, adding monologue chimes (assets 7-8) as deliberate placeholders with Sprint 8 redo mandate. Chimes are critical for [D-067](perception.md#d-067-recognition-chime-fires-at-onset-of-cognitive-delay) cognitive delay feel but acknowledged as difficult to generate correctly — manual synthesis required for production quality. + - **Sprint 7 scope expansion:** Ticket T-440 expanded from 6 to 8 assets, adding monologue chimes (assets 7-8) as deliberate placeholders with Sprint 8 redo mandate. Chimes are critical for [D-067](perception.md#d-067-recognition-chime-fires-at-onset-of-cognitive-delay) cognitive delay feel but acknowledged as difficult to generate correctly — manual synthesis required for production quality. - **Cross-reference:** Three-range sound model ([D-018](perception.md#d-018-three-range-sound-model)), sound event architecture (Gestalt R2 section 6), audio aesthetic ([D-074](content.md#d-074-audio-aesthetic-identity--insert-tech-vs-organic)), recognition chime ([D-067](perception.md#d-067-recognition-chime-fires-at-onset-of-cognitive-delay)) -- **Raised by:** Ozzie (Round 1 minimum viable proposal, Round 2 full spec), project lead (confirmed, directives #3 and #9). Amendment raised by Inigo (hybrid approach), endorsed by Tyre. +- **Raised by:** Ozzie (Round 1 minimum viable proposal, Round 2 full spec), project lead (confirmed, directives T-3 and T-9). Amendment raised by Inigo (hybrid approach), endorsed by Tyre. - **Dissent:** Mellanie and Araminta both proposed deferring audio; project lead overruled. Visual sound indicators remain complementary to audio (not replacement). ### D-039: v0.1 wow moment scope — all 6 moments [SUPERSEDED] @@ -135,7 +135,7 @@ What we're building: game concept, design pillars, prototype definition, map spe 5. **The News Ticker Gut-Punch**: Same ticker, opposite monologue reactions per character. "The asymmetry in three words." Content: ticker + dual monologue reactions. 6. **The Quiet Moment**: Idle in a corridor, unprompted reflective monologue. "I care about this person." Content: 2 monologue lines + time_idle trigger. - **Cross-reference:** Vertical slice criteria ([D-027](#d-027-vertical-slice--smuggler--detective-two-character-proof)), THE FRIEND ([D-034](content.md#d-034-the-friend--production-level-npc-pattern)) -- **Raised by:** Ozzie (Round 1 identification, Round 2 budget), project lead (all 6 promoted, directive #8) +- **Raised by:** Ozzie (Round 1 identification, Round 2 budget), project lead (all 6 promoted, directive T-8) - **Dissent:** None ### D-051: "Settling is placement" — design principle @@ -291,12 +291,12 @@ What we're building: game concept, design pillars, prototype definition, map spe ### D-146: Character creation preview — tile-scale sprite with heavy zoom - **Date:** 2026-03-17 - **Decision:** The character creation screen shows the player character's game sprite at heavy zoom — the same top-down tile-scale rendering used in gameplay, rendered at high resolution so it holds up when zoomed in. No separate portrait rendering system. Cosmetic choices (hair, clothing, colors) update the zoomed sprite in real time. Rimworld uses this exact approach: the character customisation screen shows the same pawn sprite rendered larger, with clothing/equipment layers visible. -- **Rationale:** The tile-scale sprite IS the player's character for the entire game. Showing it zoomed in character creation: (1) sets accurate expectations for in-game appearance, (2) reuses the production sprite/animation pipeline — zero new rendering infrastructure, (3) validates that cosmetic customisation (#619) reads correctly at game scale, (4) keeps #618 as a single client ticket with no visual-team split. A separate portrait system would be a one-off rendering context (different camera, lighting, asset pipeline) that serves no other game feature. "CK3-style" in the bookmark concept refers to the bookmark structure, not the portrait aesthetic. Portraits can be added later as an additive feature if needed. -- **Team implication:** Ticket #618 (CK3-style character creation screen) stays as a single `client` team ticket. No split required. +- **Rationale:** The tile-scale sprite IS the player's character for the entire game. Showing it zoomed in character creation: (1) sets accurate expectations for in-game appearance, (2) reuses the production sprite/animation pipeline — zero new rendering infrastructure, (3) validates that cosmetic customisation (T-619) reads correctly at game scale, (4) keeps T-618 as a single client ticket with no visual-team split. A separate portrait system would be a one-off rendering context (different camera, lighting, asset pipeline) that serves no other game feature. "CK3-style" in the bookmark concept refers to the bookmark structure, not the portrait aesthetic. Portraits can be added later as an additive feature if needed. +- **Team implication:** Ticket T-618 (CK3-style character creation screen) stays as a single `client` team ticket. No split required. - **Source:** Sprint 27 planning pass — Q-WTF-039 resolution - **Raised by:** Tyre (architecture), Paula (narrative), Gestalt (systems), confirmed by project lead - **Dissent:** Ozzie argued for portrait render (emotional attachment). Overruled: tile-scale at heavy zoom provides sufficient character identity without a second rendering pipeline. -- **Cross-reference:** [D-115](#d-115-character-creation-scoped-to-skills--bookmark-for-v02) (creation scope), [D-134](architecture.md#d-134-full-character-customisation--hair-clothing-colors-at-tile-scale) (full character customisation), [D-136](architecture.md#d-136-first-settled-reach-moment-auto-generated-apartment--insert-activation) (first game moment), ticket #618 (CK3-style character creation screen) +- **Cross-reference:** [D-115](#d-115-character-creation-scoped-to-skills--bookmark-for-v02) (creation scope), [D-134](architecture.md#d-134-full-character-customisation--hair-clothing-colors-at-tile-scale) (full character customisation), [D-136](architecture.md#d-136-first-settled-reach-moment-auto-generated-apartment--insert-activation) (first game moment), ticket T-618 (CK3-style character creation screen) - **Resolves:** Q-WTF-039 --- @@ -378,7 +378,7 @@ What we're building: game concept, design pillars, prototype definition, map spe - **(b) Atlas highest-level only.** The Atlas renders Sol at its system-level pregenerated map and **nothing more** — no zoom-in, no per-body click-through, no in-world rendering of Sol bodies. The Sol map is a leaf node in the Atlas hierarchy. - **(c) Deeper Sol is a future DLC.** Any treatment that makes Sol *feel like actual Sol* (real Earth/Mars/Luna geography, real history, real political textures) is reserved for a separate, future DLC with its own scope. Explicitly NOT current work. - **Rationale:** Sol carries enormous authorial expectation (real Earth/Mars/Luna geography, real human history). Fudging it would land worse than leaving it sealed. Keeping Sol out of scope keeps the Reach the focus and the realism debt manageable; reserving Sol for a hypothetical DLC keeps the door open without committing to the (massive) job now. -- **Implementation:** generation pipelines filter out bodies whose system has `settlement_wave = 'origin'`; the Atlas zoom UI gates Sol at the system map (no body-level navigation panes). Effective immediately on the Phase-4 fill-seam cascade (#1000 founding_age backfill explicitly excludes Sol). +- **Implementation:** generation pipelines filter out bodies whose system has `settlement_wave = 'origin'`; the Atlas zoom UI gates Sol at the system map (no body-level navigation panes). Effective immediately on the Phase-4 fill-seam cascade (T-1000 founding_age backfill explicitly excludes Sol). - **Raised by:** Jeroen, 2026-05-26 — formalising what was previously implicit in D-223's naming exemption and GJ-0's wiki frontmatter into a single canonical scope rule. - **Cross-reference:** [D-145](#d-145-base-building-dlc--gj-902-unclaimed-moon-as-player-settlement-site) (DLC-scoping precedent), [D-191](architecture.md#d-191-atlas-of-the-reach--phase-3-scope-and-pipeline) (Atlas Phase-3 — this constrains Sol's Atlas treatment), [D-223](architecture.md#d-223) (Sol naming exemption + offline `sol_import.py` geometry — this generalises that scope), [D-171](economics.md#d-171-three-currency-system) / [D-174](economics.md#d-174-shadow-economy-layer) (Sol-as-shadow-currency — orthogonal, unaffected), GJ-0 wiki frontmatter (`settlement_wave: origin`, `political_zone: earth_sphere`). - **Dissent:** None diff --git a/governance/questions/architecture.md b/governance/questions/architecture.md index af1885c9c..de9162327 100644 --- a/governance/questions/architecture.md +++ b/governance/questions/architecture.md @@ -72,7 +72,7 @@ Technical foundation questions: engine, protocols, data structures, performance, ### Q-030: Seed configuration schema - **Status:** Open -- **Question:** What artifact records all randomizer decisions at game start? The wiki-review workshop proposed a `seed-state.yaml` capturing: world seed, character selection, pool draws (Tier 1 modules, FRIEND selection, contraband variant), template assignments, NPC trait rolls, triangle configurations, and entanglement pattern. Ticket #394 (seed configuration schema design) exists but the design is open. +- **Question:** What artifact records all randomizer decisions at game start? The wiki-review workshop proposed a `seed-state.yaml` capturing: world seed, character selection, pool draws (Tier 1 modules, FRIEND selection, contraband variant), template assignments, NPC trait rolls, triangle configurations, and entanglement pattern. Ticket T-394 (seed configuration schema design) exists but the design is open. - **Assigned to:** Tyre, Gestalt - **Source:** Wiki Review Workshop + v0.1 Content Scoping Workshop @@ -80,7 +80,7 @@ Technical foundation questions: engine, protocols, data structures, performance, - **Status:** Resolved → D-108 (MobileChunk Specification) - **Resolution:** `scheduled_departure: Option` in `Docked` state is mandatory generator output. Vessels without departure schedules are an error state. The `Docked` struct must include `docked_since: SimTick` and `scheduled_departure: Option` — these fields must be added at implementation time (absent from Tyre's Round 4 canonical struct). - **Date resolved:** 2026-02-27 -- **Source:** Generator Architecture Workshop (#562) +- **Source:** Generator Architecture Workshop (T-562) - **Assigned to:** Tyre + Miri ### Q-059: PlatformInfo full interface scope @@ -90,7 +90,7 @@ Technical foundation questions: engine, protocols, data structures, performance, - **Date raised:** 2026-03-13 - **Date resolved:** 2026-03-13 - **Assigned to:** Tyre -- **Source:** Sprint 26 client work (#646, #659) +- **Source:** Sprint 26 client work (T-646, T-659) ### Q-060: Can Surface Deform produce acceptable clothing at extreme body types? - **Status:** OPEN @@ -109,7 +109,7 @@ Technical foundation questions: engine, protocols, data structures, performance, --- ### Q-064: 3D planet generator for wiki system screenshots -- **Status:** Resolved — answered by #779 (Sprint 32) +- **Status:** Resolved — answered by T-779 (Sprint 32) - **Question:** Evaluate the Godot 3D Planet Generator (https://github.com/remijean/godot-3d-planet-generator) for generating unique planet visuals per star system in the wiki. Each of the 301 systems could get a procedurally generated planet rendered as a screenshot for its wiki page. Key questions: can we get enough visual variety across 301 systems (different biomes, atmospheres, colors, ring configurations)? Can the generator run headlessly for batch rendering? What's the parameter space — how many distinct-looking planets can it produce? Could the planet configs be seeded from system properties (star class, habitable zone, etc.) for consistency across regenerations? - **Resolution:** Pure Python ray-sphere renderer (`spikes/planet-renders/generate_planets.py`) replaces the Godot plugin approach. Answers all evaluation criteria: (1) visual variety via planet_class type × body_id seed = 301 distinct renders, (2) fully headless — no Godot required, ~2s for all 7 types, (3) seeded from system properties for reproducibility. Avoids headless Godot rendering complexity. See `docs/design/planetary-screenshots-spec.md`. - **Cross-reference:** Wiki system pages (docs/wiki/), world generation pipeline @@ -307,17 +307,17 @@ Technical foundation questions: engine, protocols, data structures, performance, --- -### Q-092: Modular settings menu as foundation for #735 +### Q-092: Modular settings menu as foundation for T-735 - **Status:** Open -- **Question:** Godot Modular Settings Menu (https://github.com/MarkVelez/godot-modular-settings-menu) — composable settings panels for keybinds, audio, video, accessibility. Evaluate as the foundation for epic #735 (Settings and input system). Instead of building keybind remapper, audio sliders, resolution picker, and controller support from scratch, start from this template and restyle to match our UI aesthetic. Key: the modular approach means we can add/remove panels as features are implemented without restructuring. -- **Cross-reference:** Epic #735 (settings/input system), Frame0 wireframe session +- **Question:** Godot Modular Settings Menu (https://github.com/MarkVelez/godot-modular-settings-menu) — composable settings panels for keybinds, audio, video, accessibility. Evaluate as the foundation for epic T-735 (Settings and input system). Instead of building keybind remapper, audio sliders, resolution picker, and controller support from scratch, start from this template and restyle to match our UI aesthetic. Key: the modular approach means we can add/remove panels as features are implemented without restructuring. +- **Cross-reference:** Epic T-735 (settings/input system), Frame0 wireframe session --- ### Q-093: Tile-based exploration map in player insert (Google Maps for the implant) - **Status:** Open (high interest) - **Question:** Reference: MapTileProvider (https://github.com/AngryMeenky/MapTileProvider) — lazy-loading tile map provider. Concept: the player's insert has a map that works like Google Maps — pan, zoom, tile-based rendering. Server generates map tiles from ECS exploration data (what the player has seen). Explored areas show room layouts, corridors, points of interest. Unexplored areas are blank/fogged. Zoom levels: room detail → building → district → zone → station overview. Knowledge-graph-driven overlays: NPC last-known positions (if the player tracked them), quest markers, danger zones, faction territories. Map tiles are server-authoritative (can't see what you haven't explored) and cached on the client. The tile pyramid approach means the map scales to any world size without loading everything at once. -- **Cross-reference:** Information boundary (D-011), insert/minimap UI, knowledge graph (D-041), #732 (minimap ticket) +- **Cross-reference:** Information boundary (D-011), insert/minimap UI, knowledge graph (D-041), T-732 (minimap ticket) --- @@ -334,16 +334,16 @@ Technical foundation questions: engine, protocols, data structures, performance, - **Status:** Resolved — D-225 (atlas layer-stream proxy), 2026-05-23 - **Resolution:** Resolved against the question's own premise. The viewer does **not** need a durable store under the LRU: the cascade is deterministic and ~45 ms, so eviction → recompute is acceptable. Baking into `systems.db` (option a) is rejected — it bloats the install and makes modded bodies second-class. Decision: **lazy compute on demand**, served by a mod-first **layer-stream proxy** that resolves a body's source files (base + mod dirs) and streams the computed `Layer1Output` over the existing IPC bridge, backed by the D-203 in-memory LRU (miss → background `AnalyzeBody`; eviction → recompute). See [D-225](../decisions/architecture.md#d-225). The mod-content-catalog corner (mods adding *new* body rows / `terrain_reference` to the binary `systems.db`) is spun off to Q-099. - **Question:** The deterministic cascade can recompute river courses (D8 drainage, D-208) and city placements (economic sim + attractor matching, D-211) from seed at any time, so persisting them is a *cost* optimization, not a correctness need. But the compute is expensive — recomputing per session or per atlas view is waste. How are these mapping outputs persisted so they are computed **once per body and kept**? D-203's BodyWorldState cache is an LRU — it *evicts* (volatile). The fork: **(a)** build-time bake into `systems.db` (D-200 build-time tier — precompute all, ship); **(b)** lazy compute + persist at runtime (cache DB / savegame — compute on first visit, keep); **(c)** hybrid. Whatever the answer, the in-memory LRU should sit over a *durable* store so eviction triggers a cheap reload, not a recompute. -- **Context:** Raised 2026-05-22 looking ahead from the Phase 4 markers strip (#951). Refines D-200 (three-tier execution) and D-203 (LRU cache). Gates the Atlas layer viewer (#960), which needs persisted mapping to render without recomputing. Determinism (#952) guarantees recompute is always a valid fallback. -- **Cross-reference:** [D-200](../decisions/architecture.md#d-200), [D-203](../decisions/architecture.md#d-203), [D-208](../decisions/architecture.md#d-208), [D-211](../decisions/architecture.md#d-211), #952 (determinism harness), #960 (atlas viewer) +- **Context:** Raised 2026-05-22 looking ahead from the Phase 4 markers strip (T-951). Refines D-200 (three-tier execution) and D-203 (LRU cache). Gates the Atlas layer viewer (T-960), which needs persisted mapping to render without recomputing. Determinism (T-952) guarantees recompute is always a valid fallback. +- **Cross-reference:** [D-200](../decisions/architecture.md#d-200), [D-203](../decisions/architecture.md#d-203), [D-208](../decisions/architecture.md#d-208), [D-211](../decisions/architecture.md#d-211), T-952 (determinism harness), T-960 (atlas viewer) --- ### Q-099: Mod content catalog — body rows / terrain_reference overlay for systems.db - **Status:** Open — spun off from D-225 (2026-05-23) -- **Question:** D-225 resolves mod *file* resolution (a mod body's source `heightmap.png` is found by searching mod dirs over the base install). But a mod adding a *new* body also needs that body discoverable: the `bodies` row and its `terrain_reference` live in `systems.db`, which is binary and source-canonical (D-189) — mods cannot append to it. How does a mod register new bodies (and other DB-resident catalog rows)? Options: a mod manifest the server merges into an in-memory catalog overlay at load; a parallel mod catalog DB layered over the `systems.db` reads; or a documented mod build step. Out of scope for #960 (base-install resolution ships the viewer); needed before third-party bodies are first-class. +- **Question:** D-225 resolves mod *file* resolution (a mod body's source `heightmap.png` is found by searching mod dirs over the base install). But a mod adding a *new* body also needs that body discoverable: the `bodies` row and its `terrain_reference` live in `systems.db`, which is binary and source-canonical (D-189) — mods cannot append to it. How does a mod register new bodies (and other DB-resident catalog rows)? Options: a mod manifest the server merges into an in-memory catalog overlay at load; a parallel mod catalog DB layered over the `systems.db` reads; or a documented mod build step. Out of scope for T-960 (base-install resolution ships the viewer); needed before third-party bodies are first-class. - **Context:** Raised 2026-05-23 from the D-225 mod-first layer-stream proxy design. The proxy makes first-party and mod bodies flow through an identical resolve→compute→stream path *given a resolvable source file*; this question is the remaining gap — getting a mod's new body into the catalog the resolver consults. -- **Cross-reference:** [D-225](../decisions/architecture.md#d-225), [D-189](../decisions/architecture.md#d-189) (systems.db source-canonical), #960 (atlas viewer — base-install only for now) +- **Cross-reference:** [D-225](../decisions/architecture.md#d-225), [D-189](../decisions/architecture.md#d-189) (systems.db source-canonical), T-960 (atlas viewer — base-install only for now) --- @@ -414,15 +414,15 @@ Technical foundation questions: engine, protocols, data structures, performance, ### Q-108: Subterranean / domed / sealed-habitat settlement morphology — does the built-world fill model need a surface-vs-enclosed branch - **Status:** Open — raised 2026-05-31 (system-economic-specialization workshop follow-up, via Vuurkloof / GJ 35) - **Question:** The economic-built-world fill model (D-233, re-amended by [D-237](../decisions/architecture.md#d-237)) is implicitly a **surface** model — it derives a roofed-coverage fraction, an operations-surface remainder, and a concentrate-vs-scatter spread across open ground. But `bodies.settlement_pattern` already carries non-surface morphologies for real bodies: `underground_concentrated` (e.g. Vuurkloof / GJ35c, built into ravine walls), `cave`, `domed`, `underground_complex` (~12+ bodies on `underground_concentrated` alone, more across the others). **Nothing in the current chain reads `settlement_pattern`** — it is not in the [D-199](../decisions/architecture.md#d-199) read-set, not consumed by `city_context_reader`, not branched on in `skeleton_gen` / D-233 fill. The engine *primitives* exist ([D-110](../decisions/architecture.md#d-110) signed `base_z`, the `UndergroundComplex` reservation, [D-106](../decisions/architecture.md#d-106) vertical scale — "a deep mine is an inverted skyscraper"), but no morphology switch connects `settlement_pattern` to them at the built-world layer. So an enclosed-habitat body like Vuurkloof would currently generate as a *surface* geothermal town (conduits and exchange stations spread across open ground), contradicting authored lore (underground-concentrated, ravine-wall construction). Does the fill model need a first-class **surface-vs-enclosed (subterranean / domed / sealed-habitat) morphology branch**, and where does it live — (a) `settlement_pattern` as a hard-gate input to D-233/D-237 fill selecting an enclosed coverage/vocabulary model; (b) a separate morphology layer above D-233 the economic vocabulary plugs into; (c) treat domed/sealed (pressurized surface envelope) as distinct from true subterranean (excavated z-negative)? And how does verticality (D-106/D-110) compose with the coverage model when a settlement is primarily vertical/subsurface, and how does `morphology_zone` (D-228/D-234, terrain-driven street geometry) behave when there is no open street plane? -- **Context:** D-237 *inherited* D-233's surface assumption; it did not introduce the gap and does not block on it (surface bodies — the majority — are correct today). But enclosed-habitat bodies are authored lore and will read wrong until resolved. Wants its own debate/workshop with Tyre (z-level architecture), Burnelli (coverage model), Miri (which bodies, what they must read as), Araminta (how enclosed interiors are visually distinct). Tracked by ticket #1018; schedule before the Phase 4 content pass authors built form for enclosed-habitat bodies. +- **Context:** D-237 *inherited* D-233's surface assumption; it did not introduce the gap and does not block on it (surface bodies — the majority — are correct today). But enclosed-habitat bodies are authored lore and will read wrong until resolved. Wants its own debate/workshop with Tyre (z-level architecture), Burnelli (coverage model), Miri (which bodies, what they must read as), Araminta (how enclosed interiors are visually distinct). Tracked by ticket T-1018; schedule before the Phase 4 content pass authors built form for enclosed-habitat bodies. - **Cross-reference:** [D-233](../decisions/architecture.md#d-233) (surface coverage model — would be amended), [D-237](../decisions/architecture.md#d-237) (authored specialization layer — inherits the surface assumption), [D-199](../decisions/architecture.md#d-199) (read-set — would need `settlement_pattern`), [D-220](../decisions/architecture.md#d-220) (density / vertical pressure), [D-106](../decisions/architecture.md#d-106) (vertical scale), [D-110](../decisions/architecture.md#d-110) (signed z-levels), [D-228](../decisions/architecture.md#d-228)/[D-234](../decisions/architecture.md#d-234) (morphology zone / street geometry), [D-196](../decisions/architecture.md#d-196) (SettlementClass — orthogonal; this is morphology, not active/ghost) --- ### Q-109: Cascade generation-source dispatch — planetary / station / mod-DLC-forked / save-only -- **Status:** Open — raised by Jeroen (2026-06-05, during #957 zone-selection authoring) -- **Question:** The generation cascade currently has a single implicit path: every body runs the planetary generator (`run_cascade` → Layers 0–5). But a body's `SettingType` (or a sibling `GenerationSource` discriminator) should **dispatch at the cascade entry** to one of several generation sources: (a) **planetary** — the default terrain→quarter→tile cascade (#955/#956/#957…); (b) **station / orbital** — a *separate* cascade that owns the station-only zone types (`residential_station`, `extraction_space`, `port_space`, `rural_orbital`) and its own layout model, rather than branching the planetary code (this is why #957's zone-selection table deliberately excludes those ids — see [D-229](../decisions/architecture.md#d-229) amendment 2026-06-05); (c) **mod / DLC-forked** — a value that routes generation to externally-provided code/templates (a mod or DLC name), so third-party content can supply a body's built form without patching core; (d) **save-only / `player_base`** — disables auto-generation entirely and loads the body's built world from a save file (player-constructed bases must persist, not regenerate). Where does this discriminator live — on `SettingType` itself (it already carries `Station`/`Orbital` variants) or a dedicated `GenerationSource` enum at the `run_cascade` entry? How does the mod/DLC fork resolve to external code (registry? trait object? content-pack manifest?)? And how does save-only compose with the D-203 hot cache and the D-225 compute-on-demand proxy (a `player_base` body must never enqueue an `AnalyzeBody`)? -- **Context:** Surfaced while authoring #957's `(ZoningType × economic_role × setting)` zone-selection table: `setting` is the right *tweaker* within the planetary path, but it is also the natural *dispatch* point above it — the two roles are distinct and only the tweaker belongs in #957. The dispatch is a cross-cutting routing seam above any single layer; capturing it here so #957 stays scoped to the planetary path. Not blocking — the planetary path is the only live source today. Wants Tyre (cascade architecture) + a look at modding/DLC strategy and the savegame model (Phase 5+) before it's decided. +- **Status:** Open — raised by Jeroen (2026-06-05, during T-957 zone-selection authoring) +- **Question:** The generation cascade currently has a single implicit path: every body runs the planetary generator (`run_cascade` → Layers 0–5). But a body's `SettingType` (or a sibling `GenerationSource` discriminator) should **dispatch at the cascade entry** to one of several generation sources: (a) **planetary** — the default terrain→quarter→tile cascade (T-955/T-956/T-957…); (b) **station / orbital** — a *separate* cascade that owns the station-only zone types (`residential_station`, `extraction_space`, `port_space`, `rural_orbital`) and its own layout model, rather than branching the planetary code (this is why T-957's zone-selection table deliberately excludes those ids — see [D-229](../decisions/architecture.md#d-229) amendment 2026-06-05); (c) **mod / DLC-forked** — a value that routes generation to externally-provided code/templates (a mod or DLC name), so third-party content can supply a body's built form without patching core; (d) **save-only / `player_base`** — disables auto-generation entirely and loads the body's built world from a save file (player-constructed bases must persist, not regenerate). Where does this discriminator live — on `SettingType` itself (it already carries `Station`/`Orbital` variants) or a dedicated `GenerationSource` enum at the `run_cascade` entry? How does the mod/DLC fork resolve to external code (registry? trait object? content-pack manifest?)? And how does save-only compose with the D-203 hot cache and the D-225 compute-on-demand proxy (a `player_base` body must never enqueue an `AnalyzeBody`)? +- **Context:** Surfaced while authoring T-957's `(ZoningType × economic_role × setting)` zone-selection table: `setting` is the right *tweaker* within the planetary path, but it is also the natural *dispatch* point above it — the two roles are distinct and only the tweaker belongs in T-957. The dispatch is a cross-cutting routing seam above any single layer; capturing it here so T-957 stays scoped to the planetary path. Not blocking — the planetary path is the only live source today. Wants Tyre (cascade architecture) + a look at modding/DLC strategy and the savegame model (Phase 5+) before it's decided. - **Cross-reference:** [D-200](../decisions/architecture.md#d-200) (runtime cascade), [D-225](../decisions/architecture.md#d-225) (compute-on-demand proxy — save-only must bypass), [D-203](../decisions/architecture.md#d-203) (hot cache), [D-229](../decisions/architecture.md#d-229) (zone-selection — excludes station ids for this reason), D-222 (Quarter/spatial tiers), and the development cascade (Phase 5 player control + savegame) --- diff --git a/governance/questions/content.md b/governance/questions/content.md index e9323c2cc..6bc846bd9 100644 --- a/governance/questions/content.md +++ b/governance/questions/content.md @@ -18,7 +18,7 @@ Narrative, NPCs, dialogue, templates, setting, worldbuilding, and storyteller me ### Q-013: Line previewer temporal progression - **Status:** Open -- **Question:** How does the line previewer (#193) handle THE FRIEND's multi-visit contradiction arc? Needs sequence mode to simulate interaction progression over multiple encounters. +- **Question:** How does the line previewer (T-193) handle THE FRIEND's multi-visit contradiction arc? Needs sequence mode to simulate interaction progression over multiple encounters. - **Assigned to:** Gestalt, Dudley - **Source:** Content Gap Analysis Workshop (Mellanie R2) @@ -38,8 +38,8 @@ Narrative, NPCs, dialogue, templates, setting, worldbuilding, and storyteller me ### Q-028: Collision-resistant line IDs for auto-generated NPCs - **Status:** Resolved → [D-084](../decisions/content.md#d-084-dual-namespace-line-id-scheme--role-pool--instance-override) - **Resolution:** The collision problem is mostly already solved by the role-pool architecture: `dock-worker_d_###` lines are shared content for all instances of the role, not per-instance IDs. A true collision (two distinct authored lines sharing the same ID) cannot occur with one file per role. For the edge case of authored instance-specific content, a role-slug + zero-padded generation counter suffix produces `dock-worker-07_d_001`. Counter is seeded-deterministic. No schema change, no migration. Hand-authored NPCs unchanged. -- **Closed by:** Gestalt (Sprint 18, #544). 2026-02-25. -- **Ticket:** #544 +- **Closed by:** Gestalt (Sprint 18, T-544). 2026-02-25. +- **Ticket:** T-544 - **Assigned to:** Gestalt, Tyre - **Source:** Sprint 16 PR #59 review discussion (2026-02-23) @@ -61,52 +61,52 @@ Narrative, NPCs, dialogue, templates, setting, worldbuilding, and storyteller me - **Question:** System span gates serve both freight and commuter traffic (one gate per system). How does this work physically? Is it one gate aperture with scheduling (freight window vs. passenger window), or parallel lanes (separate apertures for freight and passenger flows)? What does the gate facility look like from the inside — a single large bay or divided infrastructure? - **Layout implication:** Affects the gate cluster spatial design in the Transit District — the gate cluster must accommodate both freight staging and passenger throughflow, possibly at different times of day. - **Assigned to:** Miri -- **Source:** Station District Layout Workshop (#153), Round 2. Surfaced by lead correction to Miri's S-02 (commuter transit ≠ second external gate). +- **Source:** Station District Layout Workshop (T-153), Round 2. Surfaced by lead correction to Miri's S-02 (commuter transit ≠ second external gate). - **Cross-reference:** D-036 (Sova setting), Q-036 (district skeleton as generator output) ### Q-041: Interstellar travel mechanics — horizon stations and gate architecture - **Status:** Resolved → D-095 (horizon stations: alien-built, 4–8 apertures, Oort-cloud, "The Ring"; sequential hop travel) - **Question:** A system needs MORE than one horizon gate for multi-hop connectivity (one gate allows only 1:1 connections). Lead proposal (Round 3): **Horizon stations** — orbital installations at Oort-cloud distance, partially or wholly understood ancient alien technology, self-maintaining (Mass Effect relay/Citadel analog). Each horizon station holds a FIXED number of active and inactive horizon gates. Some systems may only have one hop to an orbital customs station with no direct planet-side span gate access. Remaining questions: How many gates per horizon station? What determines which gates are active vs. inactive? Is the travel instantaneous or traversal-based? What is "The Ring" (the orbital horizon station) like as a physical space? - **Assigned to:** Miri -- **Source:** Station District Layout Workshop (#153), Round 2. Lead correction in Round 3: single-gate-per-system model insufficient for multi-hop travel; horizon station model proposed. +- **Source:** Station District Layout Workshop (T-153), Round 2. Lead correction in Round 3: single-gate-per-system model insufficient for multi-hop travel; horizon station model proposed. - **Cross-reference:** D-036 (Sova setting), Q-039 (gate topology generation), Q-040 (gate dual-use topology) ### Q-042: Intra-system transport networks — passenger vs. freight, vehicles and modes - **Status:** Partially resolved → D-095 (span gates at star/planetary level; horizon stations at Oort distance). Intra-system hab-to-hab transit remains open. - **Question:** How do people and goods move within a star system (between orbital stations, planetary surfaces, and other in-system facilities)? Are there two separate networks (passenger transport and freight transport) or one shared network? What are the vehicle types and transit modes? How does intra-system transit interact with the span gate at the system's hub station? - **Assigned to:** Miri -- **Source:** Station District Layout Workshop (#153), Round 2. Flagged by lead as transport lore requiring formal tracking. +- **Source:** Station District Layout Workshop (T-153), Round 2. Flagged by lead as transport lore requiring formal tracking. - **Cross-reference:** D-036 (Sova setting), Q-043 (station internal transit) ### Q-043: Station internal transit — intra-station transport system between districts - **Status:** Resolved → D-095 (The Loop: 6-district tram, 4-minute Residential Core → Transit District; transit platform is bar-side encounter node) - **Question:** What is the intra-station transport system on Station Sova? How do workers commute between districts (e.g., Residential Core → Transit District)? Is it a train, tram, shuttle, or pressurised corridor? What is the travel time and frequency? Where does the transit stop sit within the Transit District — gate-cluster-adjacent (workers arrive near freight operations) or bar-side-adjacent (workers arrive near their social space)? -- **Layout implication for #153:** The Transit District must include an internal transit stop. Its position within the district affects NPC traffic patterns and the district entry topology. This is the active T-03b question for Round 2/3 of the Station District Layout Workshop. +- **Layout implication for T-153:** The Transit District must include an internal transit stop. Its position within the district affects NPC traffic patterns and the district entry topology. This is the active T-03b question for Round 2/3 of the Station District Layout Workshop. - **Assigned to:** Miri -- **Source:** Station District Layout Workshop (#153), Round 2. Arose from lead correction: commuter transit = internal station transit, not a second external gate. +- **Source:** Station District Layout Workshop (T-153), Round 2. Arose from lead correction: commuter transit = internal station transit, not a second external gate. - **Cross-reference:** D-036 (Sova setting), Q-042 (intra-system transport networks), S-02 revision ### Q-044: Gate-train integration — do transport vehicles use gates directly or transfer on each side - **Status:** Resolved → D-093/D-095 (gates are pedestrian/cargo-only; passengers transfer via gate concourse → transition corridor → transit platform; no direct gate-to-tram connection) - **Question:** If trains or shuttles are the intra-system or intra-station transit mode, do they use the span gate directly (a train enters the gate and exits at the destination, carriages and all)? Or are the gates pedestrian/cargo-only, requiring passengers and freight to transfer to separate transport on each side? What does this imply for gate terminal design — does it need platforms, or just processing space? - **Assigned to:** Miri -- **Source:** Station District Layout Workshop (#153), Round 2. Flagged by lead as transport lore requiring formal tracking. +- **Source:** Station District Layout Workshop (T-153), Round 2. Flagged by lead as transport lore requiring formal tracking. - **Cross-reference:** Q-040 (gate dual-use topology), Q-043 (station internal transit) ### Q-045: Axis 11 — Network Footprint NPC tag - **Status:** Open - **Priority:** High - **Question:** Should the NPC model (D-024, 10 axes) gain an 11th axis: `network_footprint: Option` for NPCs who are locally insignificant in appearance but carry network-significant information or are relevant to external actors? Default `None` for procedural NPCs. Explicitly set for authored scenario NPCs. This would enable the storyteller to identify locally-invisible but network-critical nodes without breaking the NPC's mundane character. -- **Context:** Raised during Generator Architecture Workshop (#562). The Ysabel Vorn litmus test (4.5/5 playstyle hooks, Backwater/Moderate setting) demonstrated that locally-insignificant NPCs can be key network nodes. Without this field, the generator has no mechanism to flag them to the storyteller. -- **Source:** Generator Architecture Workshop (#562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §NPC Model. +- **Context:** Raised during Generator Architecture Workshop (T-562). The Ysabel Vorn litmus test (4.5/5 playstyle hooks, Backwater/Moderate setting) demonstrated that locally-insignificant NPCs can be key network nodes. Without this field, the generator has no mechanism to flag them to the storyteller. +- **Source:** Generator Architecture Workshop (T-562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §NPC Model. - **Assigned to:** Miri ### Q-047: Mobile environment social arc — structural representation of journey timeline - **Status:** Open - **Priority:** Medium - **Question:** How is the social arc of a mobile environment journey (BoundedLinear / BoundedMobile) represented structurally? The journey has a beginning (boarding, strangers), middle (established dynamic), and end (departure, relationship crystallized). What game structures capture this timeline and enable the storyteller to intervene? Does `TransitSocialModifier` need a journey-phase field? -- **Context:** Ozzie's player experience requirement from Generator Architecture Workshop (#562): "the journey must have a social arc — not just social presence." The stage+cast framing is correct; the formal structural representation is unspecified. -- **Source:** Generator Architecture Workshop (#562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §Open Questions. +- **Context:** Ozzie's player experience requirement from Generator Architecture Workshop (T-562): "the journey must have a social arc — not just social presence." The stage+cast framing is correct; the formal structural representation is unspecified. +- **Source:** Generator Architecture Workshop (T-562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §Open Questions. - **Assigned to:** Miri + Gestalt ### Q-048: DramaDensity enum naming — 3-level vs 5-level @@ -114,7 +114,7 @@ Narrative, NPCs, dialogue, templates, setting, worldbuilding, and storyteller me - **Priority:** Low - **Question:** The Round 4 struct uses `Quiescent / Active / Intense` (3 levels). Round 3 proposed `Zero / Low / Medium / High / Flashpoint` (5 levels). Which should be canonical? Nigel's position: `Flashpoint` should be preserved as a distinct peak value — it is the storyteller's maximum-pressure instrument and should not collapse into `Intense`. If 3 levels are chosen for implementation simplicity, `Flashpoint` should still be the distinct peak name, not `Intense`. - **Context:** ComplexityTier → DramaDensity ceiling (established): Full → any intensity; Moderate → Active max; Minimal → Quiescent max; Empty → Zero only. Naming must be consistent with these ceiling values. -- **Source:** Generator Architecture Workshop (#562), Rounds 3–4. `docs/workshops/generator-architecture/workshop-outcomes.md` §Open Questions. +- **Source:** Generator Architecture Workshop (T-562), Rounds 3–4. `docs/workshops/generator-architecture/workshop-outcomes.md` §Open Questions. - **Assigned to:** Tyre + Gestalt ### Q-049: ObjectTag vocabulary co-maintenance — Miri and Araminta shared dependency @@ -122,15 +122,15 @@ Narrative, NPCs, dialogue, templates, setting, worldbuilding, and storyteller me - **Priority:** Medium - **Question:** The `ObjectTag` vocabulary must be co-maintained between Miri's `HeritageGrammarOverlay` (cultural grammar, Rust struct) and Araminta's asset categorization (visual expression, TOML files). What is the governance model? Who owns the canonical tag list? How are additions and deprecations coordinated? Does the vocabulary live in the Rust struct definition or in a shared data file? - **Context:** If the vocabulary diverges, the generator will reference tags that don't exist in asset categories, or assets will be authored that the grammar never references. This is a silent correctness failure. -- **Source:** Generator Architecture Workshop (#562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-9. +- **Source:** Generator Architecture Workshop (T-562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §D-READY-9. - **Assigned to:** Miri + Araminta ### Q-050: Assassination difficulty synthesis — formal spec combining stored baseline with on-demand computation - **Status:** Open - **Priority:** Medium - **Question:** Formal specification needed for the synthesis combining stored cultural baseline (`DerivedDistrictAnalysis` on Phase 1 skeleton) with on-demand runtime computation for player-facing assessment. Key constraint from Miri: **on-demand computation is display-only** — all game logic (tactical triangle instantiation, guarantee audit) uses the Phase 1 `DerivedDistrictAnalysis` value. The on-demand computation is subordinate to the stored baseline, not a replacement. -- **Context:** Minor tension between Gestalt's "computed entirely on demand" position and Miri's "stored cultural baseline" position. Synthesis accepted by both participants in Generator Architecture Workshop (#562); formal spec needed for implementation. -- **Source:** Generator Architecture Workshop (#562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §Open Questions. +- **Context:** Minor tension between Gestalt's "computed entirely on demand" position and Miri's "stored cultural baseline" position. Synthesis accepted by both participants in Generator Architecture Workshop (T-562); formal spec needed for implementation. +- **Source:** Generator Architecture Workshop (T-562), Round 4. `docs/workshops/generator-architecture/workshop-outcomes.md` §Open Questions. - **Assigned to:** Gestalt + Miri ### Q-052: Storyteller hint delivery — parallel diegetic channels when player does not act @@ -172,8 +172,8 @@ Narrative, NPCs, dialogue, templates, setting, worldbuilding, and storyteller me - Universal channels (environmental, overheard, tells): zero per-triangle authoring. Build once as simulation features. - Authored channels (monologue, FRIEND, job-giver): per-character/per-archetype investment. Layer on top as richness. - Job-giver treated as authored feature shipping archetype by archetype, not universal system shipping once. -- **Context:** Raised during Sprint 22 storyteller scoping (#162). Analysis by Gestalt (systems) and Paula (narrative) across two rounds. -- **Cross-reference:** D-023 (storyteller activation), D-016 (monologue system), D-018 (three-range hearing), D-024 (NPC axes — tell system), D-028 (dialogue architecture), D-032 (separate monologue pools), D-034 (THE FRIEND), D-090 (character voice), #162 (storyteller module activation) +- **Context:** Raised during Sprint 22 storyteller scoping (T-162). Analysis by Gestalt (systems) and Paula (narrative) across two rounds. +- **Cross-reference:** D-023 (storyteller activation), D-016 (monologue system), D-018 (three-range hearing), D-024 (NPC axes — tell system), D-028 (dialogue architecture), D-032 (separate monologue pools), D-034 (THE FRIEND), D-090 (character voice), T-162 (storyteller module activation) - **Assigned to:** Gestalt, Paula --- @@ -187,42 +187,42 @@ Narrative, NPCs, dialogue, templates, setting, worldbuilding, and storyteller me - **Context:** Rural zone spec behaviors reference sky, weather, and diurnal heat — only valid on a planetary surface, not inside a station. The current `ZoneSpec` struct has no field for environment context. Without it, the generator can't distinguish surface-rural from station-rural, and behavior strings may be incoherent for the location. - **Question:** Should `ZoneSpec` include a `location_context` enum (Surface/Station/Vessel) that the generator uses to filter or modify environment-specific behaviors? Or should zone specs be authored per-context (e.g. `rural-surface.ron`, `rural-station.ron`)? - **Implications:** Affects all zone spec authoring going forward. The generator's ability to extrapolate from minimal input depends on knowing whether "rural" means open sky or sealed corridors. -- **Cross-reference:** D-012 (chunk-based map), D-036 (Van Maanen's Star/Sova setting), D-104/D-105 (heritage roots), #609 (zone identity spec) +- **Cross-reference:** D-012 (chunk-based map), D-036 (Van Maanen's Star/Sova setting), D-104/D-105 (heritage roots), T-609 (zone identity spec) - **Assigned to:** Tyre, Miri --- ### Q-057: Composable behavior generation — decompose culture × role × context into assembled behaviors -- **Status:** Resolved — D-139 (Sprint 26, #633) -- **Raised:** Sprint 25, ticket #630 review discussion +- **Status:** Resolved — D-139 (Sprint 26, T-633) +- **Raised:** Sprint 25, ticket T-630 review discussion - **Priority:** High (blocks scaling beyond hand-authored content) - **Context:** Current behavior pools are hand-authored per culture×zone×role combination (`typical_behaviors` arrays in zone spec RON files). At ~50 behaviors per role × 4 roles × N zone types × M cultures, this is O(roles × zones × cultures) custom content. Each cell is effectively a unique location — "rural zone spec" is really "Van Maanen's Star rural settlement content" with the name filed off. This doesn't scale to multiple cultures or zone types. - **Question:** Should the generator compose observable behaviors from smaller primitives instead of drawing from pre-written complete sentences? Proposed decomposition: (1) **role action templates** — generic observable stage directions per role, culture-neutral, (2) **culture modifier sets** — culture-specific flavoring (Van Maanen's Star mannerisms, speech patterns, social norms) that overlay role actions, (3) **context tags** — on-shift, off-duty, break-room, social-site-type that filter/weight which behaviors are available. The generator assembles these at runtime. -- **Resolution:** Yes. D-139 defines the three-layer composable behavior model: `BehaviorPrimitive` (role actions with context tags), `BehaviorModifier` (culture overlays), and `BehaviorContext` (on-shift/off-duty/social/any filtering). The `assemble_behaviors()` function composes at runtime. Generator spike updated to use assembly when `behavior_primitives` are present, falling back to `typical_behaviors` for backward compatibility. Copy team (#634) authors the decomposed format. -- **Cross-reference:** #630 (behavior pool expansion), #633 (server: composition engine), #634 (copy: decomposed content format), D-121 (voice is culture-driven), D-122 (all NPCs generated), D-139 (composable behavior assembly) +- **Resolution:** Yes. D-139 defines the three-layer composable behavior model: `BehaviorPrimitive` (role actions with context tags), `BehaviorModifier` (culture overlays), and `BehaviorContext` (on-shift/off-duty/social/any filtering). The `assemble_behaviors()` function composes at runtime. Generator spike updated to use assembly when `behavior_primitives` are present, falling back to `typical_behaviors` for backward compatibility. Copy team (T-634) authors the decomposed format. +- **Cross-reference:** T-630 (behavior pool expansion), T-633 (server: composition engine), T-634 (copy: decomposed content format), D-121 (voice is culture-driven), D-122 (all NPCs generated), D-139 (composable behavior assembly) - **Assigned to:** Tyre, Mellanie, Miri --- ### Q-WTF-039: Character creation — portrait render or tile-scale preview? - **Status:** Resolved → [D-146](../decisions/scope.md#d-146-character-creation-preview--tile-scale-sprite-with-heavy-zoom) -- **Question:** Does the character creation screen show a portrait render or a tile-scale in-world preview of the player character? Affects #618 (CK3-style character creation screen) team assignment: portrait render is a visual team deliverable; tile-scale preview is client. -- **Resolution:** Tile-scale sprite with heavy zoom. The creation screen shows the game sprite rendered at high resolution, zoomed in so cosmetic details are clearly visible. Same approach as Rimworld: one rendering pipeline, no separate portrait system. #618 stays as a single client ticket. Resolved by [D-146](../decisions/scope.md#d-146-character-creation-preview--tile-scale-sprite-with-heavy-zoom). +- **Question:** Does the character creation screen show a portrait render or a tile-scale in-world preview of the player character? Affects T-618 (CK3-style character creation screen) team assignment: portrait render is a visual team deliverable; tile-scale preview is client. +- **Resolution:** Tile-scale sprite with heavy zoom. The creation screen shows the game sprite rendered at high resolution, zoomed in so cosmetic details are clearly visible. Same approach as Rimworld: one rendering pipeline, no separate portrait system. T-618 stays as a single client ticket. Resolved by [D-146](../decisions/scope.md#d-146-character-creation-preview--tile-scale-sprite-with-heavy-zoom). - **Assigned to:** Tyre (architecture), confirmed by project lead - **Source:** Sprint 27 planning pass (2026-03-17) ### Q-WTF-040: Do creation choices trace into the generated apartment? - **Status:** Resolved → [D-147](../decisions/content.md#d-147-aesthetic-taste-as-character-personality-trait--shared-root-for-cosmetic-and-environmental-expression) -- **Question:** If the player picks hair colour and clothing in #619, does the generator use those choices when laying out the starting apartment (furniture style, colour palette)? Affects #617 scope and dependency. -- **Resolution:** Not directly. Cosmetic choices do not pipeline into the apartment generator. Instead, characters have an `aesthetic_taste` personality trait that independently informs both appearance choices and living space. Someone who likes teal picks teal clothing AND owns a teal vase — but through shared taste, not data coupling. #619 does NOT block #617. Resolved by [D-147](../decisions/content.md#d-147-aesthetic-taste-as-character-personality-trait--shared-root-for-cosmetic-and-environmental-expression). +- **Question:** If the player picks hair colour and clothing in T-619, does the generator use those choices when laying out the starting apartment (furniture style, colour palette)? Affects T-617 scope and dependency. +- **Resolution:** Not directly. Cosmetic choices do not pipeline into the apartment generator. Instead, characters have an `aesthetic_taste` personality trait that independently informs both appearance choices and living space. Someone who likes teal picks teal clothing AND owns a teal vase — but through shared taste, not data coupling. T-619 does NOT block T-617. Resolved by [D-147](../decisions/content.md#d-147-aesthetic-taste-as-character-personality-trait--shared-root-for-cosmetic-and-environmental-expression). - **Assigned to:** Gestalt (systems), Paula (narrative), confirmed by project lead - **Source:** Sprint 27 planning pass (2026-03-17) ### Q-WTF-041: What should the player feel looking at the span gate from their apartment? - **Status:** Open - **Question:** The First Settled Reach moment (D-136) is apartment + insert activation. The player's apartment has a view of the span gate. What emotional register should this view hit? Awe? Routine familiarity? Unease? This shapes the visual design of the apartment scene and the insert's first content. -- **Affects:** #626 (setting delivery — both layers). Blocks implementation until the emotional target is defined. +- **Affects:** T-626 (setting delivery — both layers). Blocks implementation until the emotional target is defined. - **Needs:** Gore (thematic framing), Ozzie (player experience), Miri (setting grounding), Araminta (visual direction) - **Source:** Where's the Fun? Workshop, Round 5 (2026-03-05) diff --git a/governance/questions/process.md b/governance/questions/process.md index f841c0231..1cfb932ab 100644 --- a/governance/questions/process.md +++ b/governance/questions/process.md @@ -176,7 +176,7 @@ The findings file separates the agent's reasoning from its authority. Binary gat **Related:** - `feedback_broken_tests_not_preexisting_shield.md` — pattern sprints may enable - tmux teammate mode: confirmed working on Claude Code 2.1.148 (the earlier "broken" status was a `teammateMode: in-process` config issue, not a regression) -- Epic #854 — pipeline discipline automation +- Epic T-854 — pipeline discipline automation - D-166 — cascade phases --- diff --git a/governance/questions/scope.md b/governance/questions/scope.md index b063bcc8e..2f5abe037 100644 --- a/governance/questions/scope.md +++ b/governance/questions/scope.md @@ -42,12 +42,12 @@ Game concept, prototype boundaries, production pipeline, and feature decisions. ### Q-027: Fast-travel system design - **Status:** Open -- **Question:** How does inter-system travel work in production gameplay? The current hub teleport (Home key, #501) is scoped as Gauntlet-only dev tool. Production travel must be diegetic and respect asymmetric information. Proposed flow: player goes to local gate → warps to system gate → interacts with target menu → jumps to destination system gate. Key constraints: +- **Question:** How does inter-system travel work in production gameplay? The current hub teleport (Home key, T-501) is scoped as Gauntlet-only dev tool. Production travel must be diegetic and respect asymmetric information. Proposed flow: player goes to local gate → warps to system gate → interacts with target menu → jumps to destination system gate. Key constraints: 1. **Region gating:** fast-travel only available from safe or fast-travel-enabled regions. If you rented transport to reach a remote location (e.g. mountain colony), you must return the transport to civilization first — this can be a skip-travel interaction but must happen in-world. 2. **Asymmetric information:** NPCs observe arrivals and departures. Travel choices leak information (who saw you leave, who sees you arrive, what transport was used). 3. **Home key in production:** at most, Home could prompt "Do you want to fast-travel to the system hub?" if in a safe/enabled region — never instant teleport. 4. **Transport types:** walking, rented vehicle, public transit, gate network — each with different information exposure profiles. -- **Context:** #501 implemented instant Home key teleport gated behind `gauntlet_mode`. Re-scoped to Gauntlet-only after design review. Production fast-travel needs separate design and implementation. +- **Context:** T-501 implemented instant Home key teleport gated behind `gauntlet_mode`. Re-scoped to Gauntlet-only after design review. Production fast-travel needs separate design and implementation. - **Assigned to:** Gestalt, Paula, Tyre - **Source:** Sprint 10 PR review discussion (2026-02-19) diff --git a/governance/rejected/economics.md b/governance/rejected/economics.md index 35afb158b..2d42ba962 100644 --- a/governance/rejected/economics.md +++ b/governance/rejected/economics.md @@ -4,7 +4,7 @@ Rejected proposals in the **economics** domain, rationale preserved for the audi ### R-011: Single currency for Phase 2 economics - **Rejected:** 2026-04-05 -- **Proposed by:** Burnelli-Sheldon (economist), Sprint 32 Workshop #796 Round 1 +- **Proposed by:** Burnelli-Sheldon (economist), Sprint 32 Workshop T-796 Round 1 - **Proposal:** Use a single currency for the Phase 2 economics simulation to reduce model complexity; exchange-rate mechanics could be added in a later phase. - **Reason:** Three currencies create structural economic bloc tension as an emergent property of initialization, requiring no event generation. The Tractus/Mark divide maps directly to the canonical Assembly vs. Compact political divide. Deferring to a later phase would require retrofitting political geography into a running simulation. Complexity cost low; design value high. - **Raised by:** Lead directive overruling the recommendation. diff --git a/governance/rejected/perception.md b/governance/rejected/perception.md index d18a3d201..ef9005c14 100644 --- a/governance/rejected/perception.md +++ b/governance/rejected/perception.md @@ -5,4 +5,4 @@ Rejected proposals in the **perception** domain, rationale preserved for the aud ### R-012: Overheard NPC conversation system (D-078) — scrapped - **Rejected:** 2026-04-10 - **Reason:** The entire NPC ambient interaction model (overheard conversations, room grammar, zone-type NPC population, behavior engine for physical spaces) is scrapped. These systems were designed speculatively before a walkable environment exists. The NPC interaction model will be designed and built from scratch after Phase 5 (world generation at tile level) is complete. All content authored against these systems (overheard.ron, zone-type conversation pools) is orphaned. Related content pattern note in content.md and D-078 in perception.md are superseded. -- **Cross-reference:** D-078 (perception.md), content pattern note (content.md #664) +- **Cross-reference:** D-078 (perception.md), content pattern note (content.md T-664) diff --git a/tooling/pql-migrate/retag_ticket_refs.py b/tooling/pql-migrate/retag_ticket_refs.py new file mode 100644 index 000000000..3b3a65ef9 --- /dev/null +++ b/tooling/pql-migrate/retag_ticket_refs.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Rewrite ticket references #N -> T-N in the active layer (pql migration, Phase 3). + +Switches the project's ticket-reference convention to T-NNN, matching the pql id +(T-N == old #N). Operates on markdown in the active operational layer only: +CLAUDE.md, DECISIONS.md, governance/, .claude/{rules,agents,skills}. Git history is +NOT rewritten (a commit's #N already equals T-N numerically). Historical archives +(docs/sprints, docs/discussions, docs/workshops) keep their point-in-time #N. + +Guards against false positives (every skip is logged with a reason): + - N must be an actual ticket id (excludes years, the #4122 stray, hex colors — + 6-digit hexes are also excluded by the 1-4 digit bound + word boundary). + - NOT a PR reference: "PR #136" / "pull request #138" stay (PRs are a separate + #-namespace from tickets in this repo). + - NOT a semantic non-ticket: "the #1 process failure", "task #3". + +Default is a dry run. Pass --apply to write. +""" +import glob +import os +import re +import sqlite3 +import sys + +APPLY = "--apply" in sys.argv +REPO = "/var/mnt/data/projects/settled-reach/main" +SRC = os.environ.get("SR_DB_PATH", "/var/home/jeroenschweitzer/Projects/settled-reach/settledreach.db") + +TICKET_IDS = set(str(r[0]) for r in sqlite3.connect(f"file:{SRC}?mode=ro", uri=True).execute("SELECT id FROM tickets")) + +REF = re.compile(r"(?