11Error analysis
The run has 141 misses. Each card shows the evidence ember saw, the gold label and its rationale, ember's full answer, and what an agent following the kit would have done with it. Reviewers can use this section as the label-review queue.
| What the kit does with the miss | Misses |
|---|---|
| deferred: treated as unsure | 85 |
| acted on a wrong answer | 56 |
tag_assignment-code-tools-mcpvault: domain/tooling
Tag assignment · dev- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.29 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Code/Tools — mcpvault.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — mcpvault
The `@bitbonsai/mcpvault` MCP server provides agents with filesystem-based read/write access to the vault at `docs/vault/`, without requiring an Obsidian instance to be running. Configured via `opencode.json` at the repository root; invoked automatically by OpenCode at session start.
## Entry Point
```
npx -y @bitbonsai/mcpvault@0.11.0 {vault_absolute_path}
```
## Inputs
- Absolute filesystem path to `docs/vault/`
- MCP tool calls from the connected agent (via `opencode.json` MCP config block)
## Outputs
- Note content (read operations: `read_note`, `read_multiple_notes`, `search_notes`)
- Write confirmations (write/append: `write_note`, `append_to_note`, `patch_note`)
- Frontmatter updates (AST-aware: `update_frontmatter`, `manage_tags`)
- Vault structure (list: `list_vault`, `list_all_tags`)
## Dependencies
- Node.js ≥ 18.0.0 (invoked via `npx`; no global install required)
- `@bitbonsai/mcpvault` v0.11.0 (pinned; latest available as of 2026-04-21)
## Configuration
Configured in `opencode.json` at the repository root (project-local; additive relative to Concourse global config):
```json
{
"mcp": {
"obsidian": {
"type": "local",
"command": ["npx", "-y", "@bitbonsai/mcpvault@0.11.0", "/path/to/docs/vault"],
"enabled": true
}
}
}
```
This does NOT interact with `.concourse.json` (Concourse's repo-profile hook for github/gdai-mcp-server/gdscript) — the two files serve different systems and are independent.
Does NOT ship in the game binary. Dev-time only.
## Key Tool Surface (15 tools)
| Tool | Purpose |
|------|---------|
| `read_note` | Read single note by path |
| `search_notes` | BM25 full-text search with frontmatter filters |
| `write_note` | Create note with YAML validation |
| `append_to_note` | Append content (backlinks, session logs) |
| `list_vault` | Enumerate vault files (orphan-check) |
| `update_frontmatter` | AST-aware field update (no field corruption) |
| `read_multiple_notes` | Batch read for context injection |
| `list_all_tags` | Enumerate all tags in vault |
| `manage_tags` | Add/remove tags without overwriting frontmatter |
| `patch_note` | Surgical section replacement |
| soft-delete | `trashMode` parameter — never use on session logs |
See `docs/vault/Specs/080 Agent Vault Knowledge Graph/contracts/mcp-tools.md` for the full tool surface contract.
## Agent Write Protocol
Agents MUST follow the write-back protocol in AGENTS.md (Vault Knowledge Graph section) before calling `write_note`. Key rules:
- All 9 required frontmatter fields must be populated before writing
- `related:` must contain ≥1 link resolving to a hub note in `Systems/`, `Design/`, or `Governance/`
- No inline code excerpts in note bodies
- Notes start at `status: draft`; agents may self-promote to `reviewed` (same session)
## MCP Selection Rationale
Selected over `revanp/obsidian-md-mcp` (disqualified: 6 tools, `write_note` passes raw strings with no frontmatter preservation — silent corruption risk). See `docs/vault/Specs/080 Agent Vault Knowledge Graph/research.md` R-001.
## Related
- [[Tools — Agent Skills & MCP]] — broader inventory of OpenCode skills and all MCP servers (mcpvault is one of four)
- [[Tools — Vault Audit]] — mechanical and semantic audit pipeline that complements the MCP write protocol
- [[Tools — Development Workbench]] — meta-architecture showing how mcpvault fits with personas, agents, and CI
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-mcpvault: layer/infra
Tag assignment · dev- Gold
- The note calls for `layer/infra`: Build, CI, deployment infrastructure (True)
- ember
- The note has no reason to carry `layer/infra` (False)
- Signal
- P(true) 0.06 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Code/Tools — mcpvault.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — mcpvault
The `@bitbonsai/mcpvault` MCP server provides agents with filesystem-based read/write access to the vault at `docs/vault/`, without requiring an Obsidian instance to be running. Configured via `opencode.json` at the repository root; invoked automatically by OpenCode at session start.
## Entry Point
```
npx -y @bitbonsai/mcpvault@0.11.0 {vault_absolute_path}
```
## Inputs
- Absolute filesystem path to `docs/vault/`
- MCP tool calls from the connected agent (via `opencode.json` MCP config block)
## Outputs
- Note content (read operations: `read_note`, `read_multiple_notes`, `search_notes`)
- Write confirmations (write/append: `write_note`, `append_to_note`, `patch_note`)
- Frontmatter updates (AST-aware: `update_frontmatter`, `manage_tags`)
- Vault structure (list: `list_vault`, `list_all_tags`)
## Dependencies
- Node.js ≥ 18.0.0 (invoked via `npx`; no global install required)
- `@bitbonsai/mcpvault` v0.11.0 (pinned; latest available as of 2026-04-21)
## Configuration
Configured in `opencode.json` at the repository root (project-local; additive relative to Concourse global config):
```json
{
"mcp": {
"obsidian": {
"type": "local",
"command": ["npx", "-y", "@bitbonsai/mcpvault@0.11.0", "/path/to/docs/vault"],
"enabled": true
}
}
}
```
This does NOT interact with `.concourse.json` (Concourse's repo-profile hook for github/gdai-mcp-server/gdscript) — the two files serve different systems and are independent.
Does NOT ship in the game binary. Dev-time only.
## Key Tool Surface (15 tools)
| Tool | Purpose |
|------|---------|
| `read_note` | Read single note by path |
| `search_notes` | BM25 full-text search with frontmatter filters |
| `write_note` | Create note with YAML validation |
| `append_to_note` | Append content (backlinks, session logs) |
| `list_vault` | Enumerate vault files (orphan-check) |
| `update_frontmatter` | AST-aware field update (no field corruption) |
| `read_multiple_notes` | Batch read for context injection |
| `list_all_tags` | Enumerate all tags in vault |
| `manage_tags` | Add/remove tags without overwriting frontmatter |
| `patch_note` | Surgical section replacement |
| soft-delete | `trashMode` parameter — never use on session logs |
See `docs/vault/Specs/080 Agent Vault Knowledge Graph/contracts/mcp-tools.md` for the full tool surface contract.
## Agent Write Protocol
Agents MUST follow the write-back protocol in AGENTS.md (Vault Knowledge Graph section) before calling `write_note`. Key rules:
- All 9 required frontmatter fields must be populated before writing
- `related:` must contain ≥1 link resolving to a hub note in `Systems/`, `Design/`, or `Governance/`
- No inline code excerpts in note bodies
- Notes start at `status: draft`; agents may self-promote to `reviewed` (same session)
## MCP Selection Rationale
Selected over `revanp/obsidian-md-mcp` (disqualified: 6 tools, `write_note` passes raw strings with no frontmatter preservation — silent corruption risk). See `docs/vault/Specs/080 Agent Vault Knowledge Graph/research.md` R-001.
## Related
- [[Tools — Agent Skills & MCP]] — broader inventory of OpenCode skills and all MCP servers (mcpvault is one of four)
- [[Tools — Vault Audit]] — mechanical and semantic audit pipeline that complements the MCP write protocol
- [[Tools — Development Workbench]] — meta-architecture showing how mcpvault fits with personas, agents, and CI
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-mcpvault: type/code
Tag assignment · dev- Gold
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- ember
- The note has no reason to carry `type/code` (False)
- Signal
- P(true) 0.15 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Code/Tools — mcpvault.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — mcpvault
The `@bitbonsai/mcpvault` MCP server provides agents with filesystem-based read/write access to the vault at `docs/vault/`, without requiring an Obsidian instance to be running. Configured via `opencode.json` at the repository root; invoked automatically by OpenCode at session start.
## Entry Point
```
npx -y @bitbonsai/mcpvault@0.11.0 {vault_absolute_path}
```
## Inputs
- Absolute filesystem path to `docs/vault/`
- MCP tool calls from the connected agent (via `opencode.json` MCP config block)
## Outputs
- Note content (read operations: `read_note`, `read_multiple_notes`, `search_notes`)
- Write confirmations (write/append: `write_note`, `append_to_note`, `patch_note`)
- Frontmatter updates (AST-aware: `update_frontmatter`, `manage_tags`)
- Vault structure (list: `list_vault`, `list_all_tags`)
## Dependencies
- Node.js ≥ 18.0.0 (invoked via `npx`; no global install required)
- `@bitbonsai/mcpvault` v0.11.0 (pinned; latest available as of 2026-04-21)
## Configuration
Configured in `opencode.json` at the repository root (project-local; additive relative to Concourse global config):
```json
{
"mcp": {
"obsidian": {
"type": "local",
"command": ["npx", "-y", "@bitbonsai/mcpvault@0.11.0", "/path/to/docs/vault"],
"enabled": true
}
}
}
```
This does NOT interact with `.concourse.json` (Concourse's repo-profile hook for github/gdai-mcp-server/gdscript) — the two files serve different systems and are independent.
Does NOT ship in the game binary. Dev-time only.
## Key Tool Surface (15 tools)
| Tool | Purpose |
|------|---------|
| `read_note` | Read single note by path |
| `search_notes` | BM25 full-text search with frontmatter filters |
| `write_note` | Create note with YAML validation |
| `append_to_note` | Append content (backlinks, session logs) |
| `list_vault` | Enumerate vault files (orphan-check) |
| `update_frontmatter` | AST-aware field update (no field corruption) |
| `read_multiple_notes` | Batch read for context injection |
| `list_all_tags` | Enumerate all tags in vault |
| `manage_tags` | Add/remove tags without overwriting frontmatter |
| `patch_note` | Surgical section replacement |
| soft-delete | `trashMode` parameter — never use on session logs |
See `docs/vault/Specs/080 Agent Vault Knowledge Graph/contracts/mcp-tools.md` for the full tool surface contract.
## Agent Write Protocol
Agents MUST follow the write-back protocol in AGENTS.md (Vault Knowledge Graph section) before calling `write_note`. Key rules:
- All 9 required frontmatter fields must be populated before writing
- `related:` must contain ≥1 link resolving to a hub note in `Systems/`, `Design/`, or `Governance/`
- No inline code excerpts in note bodies
- Notes start at `status: draft`; agents may self-promote to `reviewed` (same session)
## MCP Selection Rationale
Selected over `revanp/obsidian-md-mcp` (disqualified: 6 tools, `write_note` passes raw strings with no frontmatter preservation — silent corruption risk). See `docs/vault/Specs/080 Agent Vault Knowledge Graph/research.md` R-001.
## Related
- [[Tools — Agent Skills & MCP]] — broader inventory of OpenCode skills and all MCP servers (mcpvault is one of four)
- [[Tools — Vault Audit]] — mechanical and semantic audit pipeline that complements the MCP write protocol
- [[Tools — Development Workbench]] — meta-architecture showing how mcpvault fits with personas, agents, and CI
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-horror-register: type/design
Tag assignment · test- Gold
- The note calls for `type/design`: Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`) (True)
- ember
- The note has no reason to carry `type/design` (False)
- Signal
- P(true) 0.44 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Design/Horror Register.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Horror Register
The operational vocabulary for evaluating whether a design choice, visual asset, sound, or interaction belongs in Old Growth. The Horror Register defines what is *in* and what is *out* — used as a filter at every phase of production.
This note is the checklist companion to [[Design/Tone and Horror]], which contains the full philosophical grounding, three horror pillars, and film lineage. Read that note first.
## In Register
These are valid in Old Growth:
- **Dread before revelation** — the feeling of wrongness before anything has happened
- **Environmental hostility** — the forest registers the player's presence; it does not react, it accumulates
- **Material horror** — biological, geological, ancient; never supernatural in the conventional sense
- **Indifference** — the threat does not want the player dead; it does not want anything
- **Wrong-but-almost-natural** — anomalies that are uncanny, not impossible; disturbing because they *almost* fit
- **Sanctuary contrast** — the bus is safe, the outside is not; this contrast is load-bearing
- **Diegetic unease** — all feedback through the world itself (dashboard, audio, road feel); no HUD screaming
- **Accumulation** — disturbance, contamination, degradation build across time without announcement
- **Scale** — the forest is older than roads; the player's presence is trivially small
## Out of Register
These break the tone and must be avoided:
- **Jump scares as primary tool** — habituate within minutes; structural horror does not rely on them
- **Legible monster** — anything with a personality, a motive, or legible anger becomes manageable
- **Gore-as-shock** — visceral imagery deployed for disgust or instant reaction is out of register; biological horror that is *wrong-beautiful* (contamination, calcification, spore film) is within the "Flesh as Horror" pillar and is valid
- **Narrative exposition** — the forest is never explained; characters do not describe what it is
- **Defeat** — the condition cannot be mastered; any design implying "beat the forest" breaks the thesis
- **Cartoon or anime aesthetic** — visual direction is painterly Pacific Northwest, editorial documentary
- **Comedy or irony** — the game takes itself seriously; no winking at the player
- **Combat as solution** — on foot the player has no tools that change outcomes; a thrown rock is noise displaced elsewhere, a flare redirects attention, found firearms are empty or corroded; any mechanic implying the condition can be fought breaks the thesis
## Application
When evaluating a new feature, asset, or interaction:
1. Does it reinforce dread or does it release tension prematurely?
2. Does it make the threat feel more indifferent or more intentional?
3. Does it make the bus feel safer or erode sanctuary?
4. Does it fit the "wrong-but-almost-natural" uncanny register?
If a design choice fails two or more of these, reconsider before implementing.
## Related
- [[Design/Tone and Horror]] — the full horror taxonomy, three pillars, film lineage, directorial discipline
- [[Design/Core Mechanics]] — how horror register maps to mechanical design
- [[Systems/Disturbance]] — the primary accumulation mechanic
- [[The Disturbance Thread]] — how the mechanics express the horror
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-governance-tech-stack-review: domain/governance
Tag assignment · dev- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.30 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Governance/Tech Stack Review.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tech Stack Review
The technology stack decisions for Old Growth — what was chosen, what was evaluated and rejected, and where the open risks are. The authoritative approved stack lives in constitution Appendix A; this note captures the *why* behind each decision.
## Overview
Old Growth's stack is deliberately conservative where possible and opinionated where necessary. The engine and scripting language are locked by constitution (Article III). Vehicle physics, terrain, procedural generation, and audio are locked with strong alternative-assessment. A handful of visual effects and content questions remain open.
**Status legend:**
- 🟢 **Locked** — researched, tested compatibility, no meaningful alternative
- 🟡 **Strong lean** — best option found but alternatives exist worth discussing
- 🟠 **Open** — decision made but could reasonably go another way
- 🔴 **Unresolved** — no decision yet or significant uncertainty
## Key Decisions — Core Stack (all 🟢)
| System | Pick | Why |
|--------|------|-----|
| Game engine | Godot 4.6+ | Native Metal renderer, plaintext GDScript (agent-readable), MIT, Jolt built-in, strong macOS export |
| Scripting | GDScript + Rust (gdext) | GDScript for scene glue; Rust for performance-critical (Article III Rule 17) |
| Physics | Jolt (native in 4.6+) | More stable vehicle sim than Godot Physics; GEVP-compatible |
| Target platform | macOS Universal Binary | Scope constraint; Apple Silicon (Metal) + Intel Macs |
| Physics tick | 120 Hz | GEVP requires 120+; 60 causes instability; 240 is unnecessary |
## Vehicle Physics
- **Vehicle controller**: GEVP (vendored in `addons/`, MIT). Raycast-based, keyboard-friendly, single-script, simcade preset. Needs tuning for 1963 VW Samba feel (rear-engine RWD, ~1100 kg, ~50 HP, high CoG, soft suspension, long braking distances).
- **Engine audio**: Pitch-scaled `AudioStreamPlayer3D` loops (🟠). Simplest approach; may upgrade to multi-sample crossfade in audio polish.
## Terrain & Roads
- **Terrain system**: Terrain3D (C++ GDExtension, MIT). GPU clipmap, foliage instancer, 32 textures, Road Generator connector.
- **Road generation**: Godot Road Generator (🟡). Designed for hand-placed roads; procedural usage is supported but less documented. **Technical risk for spec 053 remediation.**
- **Foliage**: Terrain3D built-in instancer (🟡). Must handle biome-specific vegetation swaps.
- **Heightmap**: FastNoiseLite (Godot built-in). Sufficient for ~2km² worlds.
## Weather & Atmosphere
- Rain-on-Glass shader (🟡), custom wiper integration (🟠)
- Volumetric fog: Godot 4 built-in (🟢)
- Sky: ProceduralSkyMaterial (🟡) — must produce three distinct biome sky feels
- Weather zones: Voronoi/grid subdivision (🟡, novel system)
## Visual Effects & Post-Processing
- Post-processing: ArseniyMirniy Color Correction (🟢, vendored)
- Windshield crack overlay (🟠), Storm wall shader (🟠), biome transition blending (🟡)
## Anomaly Systems
Most anomalies use standard Godot primitives (AnimationPlayer, Area3D, GPUParticles3D, OmniLight3D, MeshInstance3D). See [[Anomalies]] for design specifications; tech choices here are typically 🟢 Locked.
## Audio
- **Audio management**: Godot native (🟢). Sound Manager (nathanhoad) evaluated and rejected (insufficient — no AudioStreamPlayer3D, no AudioEffect manipulation, ambient pool limited to 1 stream).
- **Bus architecture**: 7 buses planned per constitution Appendix A (Master → Ambience, Proximity, Interior, Horror, Radio, RadioDegraded). Currently only Master + Radio + RadioDegraded exist in `src/default_bus_layout.tres`; the remaining buses are being built incrementally through the audio specs. See [[Audio — Overview]] for the CONFLICT callout and [[Conflicts]] for the indexed entry.
- **Positional**: `AudioStreamPlayer3D` built-in (🟢)
## On-Foot Systems
- CharacterBody3D + custom FPS controller (🟡)
- Car entry/exit: custom camera reparent + input context switch (🟠) — **highest-risk custom system**, ~200 lines
- Combat: melee raycast + ranged hitscan/projectile (🟢) — per Article II, on-foot scope is strictly bounded
- Enemy AI: StateMachine + NavigationAgent3D (🟡) — **must verify NavMesh bakes on Terrain3D at runtime**
## Disturbance & Storm
- Disturbance meter: autoload with add(amount, source) + threshold signals (🟢, matches Article X)
- Disturbance HUD: organic visual indicator (🟠, creative risk)
- Storm wall direction: spawn opposite nearest exit, advance toward player (🟢)
## Persistence & Architecture
- **Save**: Godot Resource serialization (`.tres`) (🟡). World state NOT saved — only garage/player state (Article IV Rule 6).
- **Settings**: `ConfigFile` at `user://settings.cfg` (🟢)
- **Cross-system communication**: Signal bus autoload (`events.gd`) (🟢, Article VI)
- **Game state**: `GameState` autoload + enum states (🟡)
## Assets & Pipeline
- **3D format**: glTF 2.0 (.glb) (🟢)
- **VW Samba model**: Placeholder (Kenney) → custom/purchased (🟠). No perfect free 1963 VW Type 2 Samba (23-window, V-nose, two-tone, curved skylight).
- **PBR textures**: CC0 (ambientCG, cgbookcase, Polyhaven) (🟢)
- **Audio assets**: CC0 (Freesound, OpenGameArt, Kenney) (🟡) — fungal/stone SFX may need original recording
- **Radio music**: 🔴 UNRESOLVED. Need 30+ min across multiple genres. CC0 music is rare; AI-generated is an option; doesn't block until audio polish phase.
## macOS Packaging
- Code signing: Apple Developer ID ($99/yr) or ad-hoc (🟠)
- Distribution: DMG with drag-to-Applications (🟢)
- Game controller: Godot GameController built-in (🟢)
## Highest-Risk Open Questions
The source doc identifies six items as the highest technical risks:
1. **Procedural road algorithm** — connected road network from a seed over ~2km² (spec 053)
2. **NavMesh on Terrain3D** — does NavigationAgent3D bake reliably? If not, fall back to raycast steering.
3. **Biome texture swap in Terrain3D** — texture-set-per-region capability
4. **World generation performance** — ~2km² world in <5 seconds on M4 Max (Article V)
5. **Petrified Zone screen-space shader** — selective desaturation of meshes in a 3D area without affecting HUD
6. **Storm wall directional shader** — dramatic shader for an advancing flat plane
## Summary Counts (per source)
- **🟢 Locked**: 26 decisions
- **🟡 Strong lean**: 15 decisions
- **🟠 Open**: 8 decisions
- **🔴 Unresolved**: 1 decision (radio music content)
## Related
- [[Constitution]] — Article III and Appendix A are the authoritative source
- [[Component SemVer Policy]] — how these components are versioned
- [[Governance]] — folder MOC
- [[Build System]] — the CI pipeline that enforces these choices
## Agent Notes (backlinks)
- [[Decisions/Rust-GDScript Split Architecture]] — Rust/GDScript split decision with operational constraints
- [[Audio Generation Pipeline Constraints & Patterns]] — TTS engine selection, Python 3.11 requirement, provenance schema, ElevenLabs SFX V2 pipeline
- [[Vault Tooling — Constraints and Patterns]] — vault audit, Observer QA, asset export safety
- Vault Tooling — Constraints and Patterns — vault audit, asset export, Observer QA, development constraints
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-governance-tech-stack-review: type/principle
Tag assignment · dev- Gold
- The note calls for `type/principle`: Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`) (True)
- ember
- The note has no reason to carry `type/principle` (False)
- Signal
- P(true) 0.05 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Governance/Tech Stack Review.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tech Stack Review
The technology stack decisions for Old Growth — what was chosen, what was evaluated and rejected, and where the open risks are. The authoritative approved stack lives in constitution Appendix A; this note captures the *why* behind each decision.
## Overview
Old Growth's stack is deliberately conservative where possible and opinionated where necessary. The engine and scripting language are locked by constitution (Article III). Vehicle physics, terrain, procedural generation, and audio are locked with strong alternative-assessment. A handful of visual effects and content questions remain open.
**Status legend:**
- 🟢 **Locked** — researched, tested compatibility, no meaningful alternative
- 🟡 **Strong lean** — best option found but alternatives exist worth discussing
- 🟠 **Open** — decision made but could reasonably go another way
- 🔴 **Unresolved** — no decision yet or significant uncertainty
## Key Decisions — Core Stack (all 🟢)
| System | Pick | Why |
|--------|------|-----|
| Game engine | Godot 4.6+ | Native Metal renderer, plaintext GDScript (agent-readable), MIT, Jolt built-in, strong macOS export |
| Scripting | GDScript + Rust (gdext) | GDScript for scene glue; Rust for performance-critical (Article III Rule 17) |
| Physics | Jolt (native in 4.6+) | More stable vehicle sim than Godot Physics; GEVP-compatible |
| Target platform | macOS Universal Binary | Scope constraint; Apple Silicon (Metal) + Intel Macs |
| Physics tick | 120 Hz | GEVP requires 120+; 60 causes instability; 240 is unnecessary |
## Vehicle Physics
- **Vehicle controller**: GEVP (vendored in `addons/`, MIT). Raycast-based, keyboard-friendly, single-script, simcade preset. Needs tuning for 1963 VW Samba feel (rear-engine RWD, ~1100 kg, ~50 HP, high CoG, soft suspension, long braking distances).
- **Engine audio**: Pitch-scaled `AudioStreamPlayer3D` loops (🟠). Simplest approach; may upgrade to multi-sample crossfade in audio polish.
## Terrain & Roads
- **Terrain system**: Terrain3D (C++ GDExtension, MIT). GPU clipmap, foliage instancer, 32 textures, Road Generator connector.
- **Road generation**: Godot Road Generator (🟡). Designed for hand-placed roads; procedural usage is supported but less documented. **Technical risk for spec 053 remediation.**
- **Foliage**: Terrain3D built-in instancer (🟡). Must handle biome-specific vegetation swaps.
- **Heightmap**: FastNoiseLite (Godot built-in). Sufficient for ~2km² worlds.
## Weather & Atmosphere
- Rain-on-Glass shader (🟡), custom wiper integration (🟠)
- Volumetric fog: Godot 4 built-in (🟢)
- Sky: ProceduralSkyMaterial (🟡) — must produce three distinct biome sky feels
- Weather zones: Voronoi/grid subdivision (🟡, novel system)
## Visual Effects & Post-Processing
- Post-processing: ArseniyMirniy Color Correction (🟢, vendored)
- Windshield crack overlay (🟠), Storm wall shader (🟠), biome transition blending (🟡)
## Anomaly Systems
Most anomalies use standard Godot primitives (AnimationPlayer, Area3D, GPUParticles3D, OmniLight3D, MeshInstance3D). See [[Anomalies]] for design specifications; tech choices here are typically 🟢 Locked.
## Audio
- **Audio management**: Godot native (🟢). Sound Manager (nathanhoad) evaluated and rejected (insufficient — no AudioStreamPlayer3D, no AudioEffect manipulation, ambient pool limited to 1 stream).
- **Bus architecture**: 7 buses planned per constitution Appendix A (Master → Ambience, Proximity, Interior, Horror, Radio, RadioDegraded). Currently only Master + Radio + RadioDegraded exist in `src/default_bus_layout.tres`; the remaining buses are being built incrementally through the audio specs. See [[Audio — Overview]] for the CONFLICT callout and [[Conflicts]] for the indexed entry.
- **Positional**: `AudioStreamPlayer3D` built-in (🟢)
## On-Foot Systems
- CharacterBody3D + custom FPS controller (🟡)
- Car entry/exit: custom camera reparent + input context switch (🟠) — **highest-risk custom system**, ~200 lines
- Combat: melee raycast + ranged hitscan/projectile (🟢) — per Article II, on-foot scope is strictly bounded
- Enemy AI: StateMachine + NavigationAgent3D (🟡) — **must verify NavMesh bakes on Terrain3D at runtime**
## Disturbance & Storm
- Disturbance meter: autoload with add(amount, source) + threshold signals (🟢, matches Article X)
- Disturbance HUD: organic visual indicator (🟠, creative risk)
- Storm wall direction: spawn opposite nearest exit, advance toward player (🟢)
## Persistence & Architecture
- **Save**: Godot Resource serialization (`.tres`) (🟡). World state NOT saved — only garage/player state (Article IV Rule 6).
- **Settings**: `ConfigFile` at `user://settings.cfg` (🟢)
- **Cross-system communication**: Signal bus autoload (`events.gd`) (🟢, Article VI)
- **Game state**: `GameState` autoload + enum states (🟡)
## Assets & Pipeline
- **3D format**: glTF 2.0 (.glb) (🟢)
- **VW Samba model**: Placeholder (Kenney) → custom/purchased (🟠). No perfect free 1963 VW Type 2 Samba (23-window, V-nose, two-tone, curved skylight).
- **PBR textures**: CC0 (ambientCG, cgbookcase, Polyhaven) (🟢)
- **Audio assets**: CC0 (Freesound, OpenGameArt, Kenney) (🟡) — fungal/stone SFX may need original recording
- **Radio music**: 🔴 UNRESOLVED. Need 30+ min across multiple genres. CC0 music is rare; AI-generated is an option; doesn't block until audio polish phase.
## macOS Packaging
- Code signing: Apple Developer ID ($99/yr) or ad-hoc (🟠)
- Distribution: DMG with drag-to-Applications (🟢)
- Game controller: Godot GameController built-in (🟢)
## Highest-Risk Open Questions
The source doc identifies six items as the highest technical risks:
1. **Procedural road algorithm** — connected road network from a seed over ~2km² (spec 053)
2. **NavMesh on Terrain3D** — does NavigationAgent3D bake reliably? If not, fall back to raycast steering.
3. **Biome texture swap in Terrain3D** — texture-set-per-region capability
4. **World generation performance** — ~2km² world in <5 seconds on M4 Max (Article V)
5. **Petrified Zone screen-space shader** — selective desaturation of meshes in a 3D area without affecting HUD
6. **Storm wall directional shader** — dramatic shader for an advancing flat plane
## Summary Counts (per source)
- **🟢 Locked**: 26 decisions
- **🟡 Strong lean**: 15 decisions
- **🟠 Open**: 8 decisions
- **🔴 Unresolved**: 1 decision (radio music content)
## Related
- [[Constitution]] — Article III and Appendix A are the authoritative source
- [[Component SemVer Policy]] — how these components are versioned
- [[Governance]] — folder MOC
- [[Build System]] — the CI pipeline that enforces these choices
## Agent Notes (backlinks)
- [[Decisions/Rust-GDScript Split Architecture]] — Rust/GDScript split decision with operational constraints
- [[Audio Generation Pipeline Constraints & Patterns]] — TTS engine selection, Python 3.11 requirement, provenance schema, ElevenLabs SFX V2 pipeline
- [[Vault Tooling — Constraints and Patterns]] — vault audit, Observer QA, asset export safety
- Vault Tooling — Constraints and Patterns — vault audit, asset export, Observer QA, development constraints
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-governance-tech-stack-review: type/reference
Tag assignment · dev- Gold
- The note has no reason to carry `type/reference` (False)
- ember
- The note calls for `type/reference`: Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`) (True)
- Signal
- P(true) 0.65 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Governance/Tech Stack Review.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tech Stack Review
The technology stack decisions for Old Growth — what was chosen, what was evaluated and rejected, and where the open risks are. The authoritative approved stack lives in constitution Appendix A; this note captures the *why* behind each decision.
## Overview
Old Growth's stack is deliberately conservative where possible and opinionated where necessary. The engine and scripting language are locked by constitution (Article III). Vehicle physics, terrain, procedural generation, and audio are locked with strong alternative-assessment. A handful of visual effects and content questions remain open.
**Status legend:**
- 🟢 **Locked** — researched, tested compatibility, no meaningful alternative
- 🟡 **Strong lean** — best option found but alternatives exist worth discussing
- 🟠 **Open** — decision made but could reasonably go another way
- 🔴 **Unresolved** — no decision yet or significant uncertainty
## Key Decisions — Core Stack (all 🟢)
| System | Pick | Why |
|--------|------|-----|
| Game engine | Godot 4.6+ | Native Metal renderer, plaintext GDScript (agent-readable), MIT, Jolt built-in, strong macOS export |
| Scripting | GDScript + Rust (gdext) | GDScript for scene glue; Rust for performance-critical (Article III Rule 17) |
| Physics | Jolt (native in 4.6+) | More stable vehicle sim than Godot Physics; GEVP-compatible |
| Target platform | macOS Universal Binary | Scope constraint; Apple Silicon (Metal) + Intel Macs |
| Physics tick | 120 Hz | GEVP requires 120+; 60 causes instability; 240 is unnecessary |
## Vehicle Physics
- **Vehicle controller**: GEVP (vendored in `addons/`, MIT). Raycast-based, keyboard-friendly, single-script, simcade preset. Needs tuning for 1963 VW Samba feel (rear-engine RWD, ~1100 kg, ~50 HP, high CoG, soft suspension, long braking distances).
- **Engine audio**: Pitch-scaled `AudioStreamPlayer3D` loops (🟠). Simplest approach; may upgrade to multi-sample crossfade in audio polish.
## Terrain & Roads
- **Terrain system**: Terrain3D (C++ GDExtension, MIT). GPU clipmap, foliage instancer, 32 textures, Road Generator connector.
- **Road generation**: Godot Road Generator (🟡). Designed for hand-placed roads; procedural usage is supported but less documented. **Technical risk for spec 053 remediation.**
- **Foliage**: Terrain3D built-in instancer (🟡). Must handle biome-specific vegetation swaps.
- **Heightmap**: FastNoiseLite (Godot built-in). Sufficient for ~2km² worlds.
## Weather & Atmosphere
- Rain-on-Glass shader (🟡), custom wiper integration (🟠)
- Volumetric fog: Godot 4 built-in (🟢)
- Sky: ProceduralSkyMaterial (🟡) — must produce three distinct biome sky feels
- Weather zones: Voronoi/grid subdivision (🟡, novel system)
## Visual Effects & Post-Processing
- Post-processing: ArseniyMirniy Color Correction (🟢, vendored)
- Windshield crack overlay (🟠), Storm wall shader (🟠), biome transition blending (🟡)
## Anomaly Systems
Most anomalies use standard Godot primitives (AnimationPlayer, Area3D, GPUParticles3D, OmniLight3D, MeshInstance3D). See [[Anomalies]] for design specifications; tech choices here are typically 🟢 Locked.
## Audio
- **Audio management**: Godot native (🟢). Sound Manager (nathanhoad) evaluated and rejected (insufficient — no AudioStreamPlayer3D, no AudioEffect manipulation, ambient pool limited to 1 stream).
- **Bus architecture**: 7 buses planned per constitution Appendix A (Master → Ambience, Proximity, Interior, Horror, Radio, RadioDegraded). Currently only Master + Radio + RadioDegraded exist in `src/default_bus_layout.tres`; the remaining buses are being built incrementally through the audio specs. See [[Audio — Overview]] for the CONFLICT callout and [[Conflicts]] for the indexed entry.
- **Positional**: `AudioStreamPlayer3D` built-in (🟢)
## On-Foot Systems
- CharacterBody3D + custom FPS controller (🟡)
- Car entry/exit: custom camera reparent + input context switch (🟠) — **highest-risk custom system**, ~200 lines
- Combat: melee raycast + ranged hitscan/projectile (🟢) — per Article II, on-foot scope is strictly bounded
- Enemy AI: StateMachine + NavigationAgent3D (🟡) — **must verify NavMesh bakes on Terrain3D at runtime**
## Disturbance & Storm
- Disturbance meter: autoload with add(amount, source) + threshold signals (🟢, matches Article X)
- Disturbance HUD: organic visual indicator (🟠, creative risk)
- Storm wall direction: spawn opposite nearest exit, advance toward player (🟢)
## Persistence & Architecture
- **Save**: Godot Resource serialization (`.tres`) (🟡). World state NOT saved — only garage/player state (Article IV Rule 6).
- **Settings**: `ConfigFile` at `user://settings.cfg` (🟢)
- **Cross-system communication**: Signal bus autoload (`events.gd`) (🟢, Article VI)
- **Game state**: `GameState` autoload + enum states (🟡)
## Assets & Pipeline
- **3D format**: glTF 2.0 (.glb) (🟢)
- **VW Samba model**: Placeholder (Kenney) → custom/purchased (🟠). No perfect free 1963 VW Type 2 Samba (23-window, V-nose, two-tone, curved skylight).
- **PBR textures**: CC0 (ambientCG, cgbookcase, Polyhaven) (🟢)
- **Audio assets**: CC0 (Freesound, OpenGameArt, Kenney) (🟡) — fungal/stone SFX may need original recording
- **Radio music**: 🔴 UNRESOLVED. Need 30+ min across multiple genres. CC0 music is rare; AI-generated is an option; doesn't block until audio polish phase.
## macOS Packaging
- Code signing: Apple Developer ID ($99/yr) or ad-hoc (🟠)
- Distribution: DMG with drag-to-Applications (🟢)
- Game controller: Godot GameController built-in (🟢)
## Highest-Risk Open Questions
The source doc identifies six items as the highest technical risks:
1. **Procedural road algorithm** — connected road network from a seed over ~2km² (spec 053)
2. **NavMesh on Terrain3D** — does NavigationAgent3D bake reliably? If not, fall back to raycast steering.
3. **Biome texture swap in Terrain3D** — texture-set-per-region capability
4. **World generation performance** — ~2km² world in <5 seconds on M4 Max (Article V)
5. **Petrified Zone screen-space shader** — selective desaturation of meshes in a 3D area without affecting HUD
6. **Storm wall directional shader** — dramatic shader for an advancing flat plane
## Summary Counts (per source)
- **🟢 Locked**: 26 decisions
- **🟡 Strong lean**: 15 decisions
- **🟠 Open**: 8 decisions
- **🔴 Unresolved**: 1 decision (radio music content)
## Related
- [[Constitution]] — Article III and Appendix A are the authoritative source
- [[Component SemVer Policy]] — how these components are versioned
- [[Governance]] — folder MOC
- [[Build System]] — the CI pipeline that enforces these choices
## Agent Notes (backlinks)
- [[Decisions/Rust-GDScript Split Architecture]] — Rust/GDScript split decision with operational constraints
- [[Audio Generation Pipeline Constraints & Patterns]] — TTS engine selection, Python 3.11 requirement, provenance schema, ElevenLabs SFX V2 pipeline
- [[Vault Tooling — Constraints and Patterns]] — vault audit, Observer QA, asset export safety
- Vault Tooling — Constraints and Patterns — vault audit, asset export, Observer QA, development constraints
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-noisehelper-utility-class-design: domain/tooling
Tag assignment · dev- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.16 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/NoiseHelper Utility Class Design.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
NoiseHelper Utility Class Design
This note records the structural decisions made for noise_helper.gd during spec 081 (Code Dedup Consolidation) that are non-obvious from the code alone.
## No extends, No class_name
noise_helper.gd declares neither extends nor class_name. It is accessed exclusively via a preload constant in each consuming file:
const NoiseHelper = preload("res://scripts/generation/noise_helper.gd")
This pattern was chosen over class_name for two reasons. First, the class_name restriction from the constitution applies to autoloads because of Godot 4.6 singleton name conflicts — noise_helper.gd is not an autoload, so class_name would technically be valid. However, making it a named class creates a global symbol that pollutes the project namespace for what is a narrow utility. Preload constants keep the dependency explicit and local. Second, unlike OGTestBase (which uses class_name intentionally so test files can extend it by name), noise_helper.gd has no inheritance use case — callers call its static factory methods, they do not extend it.
## Dictionary Parameter Convention
The create_roughness_noise factory method accepts all parameters via a Dictionary rather than positional arguments. This means every caller must pass their own @export values explicitly:
NoiseHelper.create_roughness_noise({"octaves": octaves, "frequency": frequency, ...})
No default values are baked into the helper. This was intentional: the helper exists to eliminate duplicate noise setup code, not to encode defaults. If defaults lived inside the helper, callers that relied on them would become silently coupled to the helper's internal values rather than to their own @export variables. The Dictionary convention keeps designer-tunable parameters visible in the caller's inspector.
## References
- src/scripts/generation/noise_helper.gd
- src/scripts/generation/terrain_generator.gd (consumer)
- src/scripts/generation/heightmap_proxy.gd (consumer)
- docs/vault/Sessions/Spec 081 Implementation Session.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-noisehelper-utility-class-design: domain/world
Tag assignment · dev- Gold
- The note calls for `domain/world`: World model, biomes, terrain, procedural generation, weather (True)
- ember
- The note has no reason to carry `domain/world` (False)
- Signal
- P(true) 0.35 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Decisions/NoiseHelper Utility Class Design.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
NoiseHelper Utility Class Design
This note records the structural decisions made for noise_helper.gd during spec 081 (Code Dedup Consolidation) that are non-obvious from the code alone.
## No extends, No class_name
noise_helper.gd declares neither extends nor class_name. It is accessed exclusively via a preload constant in each consuming file:
const NoiseHelper = preload("res://scripts/generation/noise_helper.gd")
This pattern was chosen over class_name for two reasons. First, the class_name restriction from the constitution applies to autoloads because of Godot 4.6 singleton name conflicts — noise_helper.gd is not an autoload, so class_name would technically be valid. However, making it a named class creates a global symbol that pollutes the project namespace for what is a narrow utility. Preload constants keep the dependency explicit and local. Second, unlike OGTestBase (which uses class_name intentionally so test files can extend it by name), noise_helper.gd has no inheritance use case — callers call its static factory methods, they do not extend it.
## Dictionary Parameter Convention
The create_roughness_noise factory method accepts all parameters via a Dictionary rather than positional arguments. This means every caller must pass their own @export values explicitly:
NoiseHelper.create_roughness_noise({"octaves": octaves, "frequency": frequency, ...})
No default values are baked into the helper. This was intentional: the helper exists to eliminate duplicate noise setup code, not to encode defaults. If defaults lived inside the helper, callers that relied on them would become silently coupled to the helper's internal values rather than to their own @export variables. The Dictionary convention keeps designer-tunable parameters visible in the caller's inspector.
## References
- src/scripts/generation/noise_helper.gd
- src/scripts/generation/terrain_generator.gd (consumer)
- src/scripts/generation/heightmap_proxy.gd (consumer)
- docs/vault/Sessions/Spec 081 Implementation Session.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-noisehelper-utility-class-design: type/code
Tag assignment · dev- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.97 (yes)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/NoiseHelper Utility Class Design.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
NoiseHelper Utility Class Design
This note records the structural decisions made for noise_helper.gd during spec 081 (Code Dedup Consolidation) that are non-obvious from the code alone.
## No extends, No class_name
noise_helper.gd declares neither extends nor class_name. It is accessed exclusively via a preload constant in each consuming file:
const NoiseHelper = preload("res://scripts/generation/noise_helper.gd")
This pattern was chosen over class_name for two reasons. First, the class_name restriction from the constitution applies to autoloads because of Godot 4.6 singleton name conflicts — noise_helper.gd is not an autoload, so class_name would technically be valid. However, making it a named class creates a global symbol that pollutes the project namespace for what is a narrow utility. Preload constants keep the dependency explicit and local. Second, unlike OGTestBase (which uses class_name intentionally so test files can extend it by name), noise_helper.gd has no inheritance use case — callers call its static factory methods, they do not extend it.
## Dictionary Parameter Convention
The create_roughness_noise factory method accepts all parameters via a Dictionary rather than positional arguments. This means every caller must pass their own @export values explicitly:
NoiseHelper.create_roughness_noise({"octaves": octaves, "frequency": frequency, ...})
No default values are baked into the helper. This was intentional: the helper exists to eliminate duplicate noise setup code, not to encode defaults. If defaults lived inside the helper, callers that relied on them would become silently coupled to the helper's internal values rather than to their own @export variables. The Dictionary convention keeps designer-tunable parameters visible in the caller's inspector.
## References
- src/scripts/generation/noise_helper.gd
- src/scripts/generation/terrain_generator.gd (consumer)
- src/scripts/generation/heightmap_proxy.gd (consumer)
- docs/vault/Sessions/Spec 081 Implementation Session.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-noisehelper-utility-class-design: type/decision
Tag assignment · dev- Gold
- The note calls for `type/decision`: Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/decision` (False)
- Signal
- P(true) 0.27 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Decisions/NoiseHelper Utility Class Design.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
NoiseHelper Utility Class Design
This note records the structural decisions made for noise_helper.gd during spec 081 (Code Dedup Consolidation) that are non-obvious from the code alone.
## No extends, No class_name
noise_helper.gd declares neither extends nor class_name. It is accessed exclusively via a preload constant in each consuming file:
const NoiseHelper = preload("res://scripts/generation/noise_helper.gd")
This pattern was chosen over class_name for two reasons. First, the class_name restriction from the constitution applies to autoloads because of Godot 4.6 singleton name conflicts — noise_helper.gd is not an autoload, so class_name would technically be valid. However, making it a named class creates a global symbol that pollutes the project namespace for what is a narrow utility. Preload constants keep the dependency explicit and local. Second, unlike OGTestBase (which uses class_name intentionally so test files can extend it by name), noise_helper.gd has no inheritance use case — callers call its static factory methods, they do not extend it.
## Dictionary Parameter Convention
The create_roughness_noise factory method accepts all parameters via a Dictionary rather than positional arguments. This means every caller must pass their own @export values explicitly:
NoiseHelper.create_roughness_noise({"octaves": octaves, "frequency": frequency, ...})
No default values are baked into the helper. This was intentional: the helper exists to eliminate duplicate noise setup code, not to encode defaults. If defaults lived inside the helper, callers that relied on them would become silently coupled to the helper's internal values rather than to their own @export variables. The Dictionary convention keeps designer-tunable parameters visible in the caller's inspector.
## References
- src/scripts/generation/noise_helper.gd
- src/scripts/generation/terrain_generator.gd (consumer)
- src/scripts/generation/heightmap_proxy.gd (consumer)
- docs/vault/Sessions/Spec 081 Implementation Session.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-disturbance-system: domain/world
Tag assignment · test- Gold
- The note calls for `domain/world`: World model, biomes, terrain, procedural generation, weather (True)
- ember
- The note has no reason to carry `domain/world` (False)
- Signal
- P(true) 0.19 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Design/Disturbance System.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Disturbance System
The disturbance meter is Old Growth's central tension engine. It is not a health bar, not a resource gauge — it is a hidden 0–100 metric that measures how much the forest notices the player. This note captures the design intent AND the full mechanical specification (biological states, disturbance costs, diegetic indicators, Storm behavior). Implementation: [[Autoloads — Disturbance]].
## Overview
The forest does not have a temper. It has a metabolism.
Everything in the woods is input to a process that runs continuously and has been running since before the first road was cut through this watershed. The player's presence feeds this process from the moment they enter the tree line. Every action — collection, combustion, impact, noise — is an input the forest attends to, and attendance accelerates the process.
## Design Intent
This is not punishment. A fallen log feeds the same process. The player is simply a more active source. The disturbance system exists to make the **condition visible through its consequences** rather than through a dedicated UI display. The forest does not provide one. It expects you to have been paying attention.
The constitutional basis is Article X — The Disturbance Is the Design:
> The disturbance system is the primary tension engine. Tension MUST be cycled — not held at a constant level. The forest MUST have moments of apparent safety (negative feedback loop releasing) before escalating again.
## Key Decisions
### Meter
| Property | Value |
|---|---|
| Range | 0 – 100 |
| Display | None. Never shown directly. |
| In-run reset | None. Never resets during a run. |
| Cross-run behavior | Resets to 0 at run start |
| Read mechanism | Bus behavior, condition presence, audio register shifts, instrument reliability |
Players read disturbance state through environmental cues, not through any HUD element.
### Biological States
| State | Meter Range | Bus Behavior | Condition Presence | Audio |
|---|---|---|---|---|
| Calm | 0–30 | Normal | Ambient, diffuse | Normal biome ambience |
| Uneasy | 30–60 | Instruments drift slightly | Heightened, directional | Ambience shifts register, lower frequencies |
| Agitated | 60–85 | Instruments unreliable, engine hesitant | Dense, attending | Ambience becomes directional, pulse-like |
| Storm | 85–100 | Electrical instability | Maximum concentration | All frequencies collapse inward |
At 85, the Storm spawns regardless of player position. The Storm state does not have a stable ceiling — it triggers immediately.
### Disturbance Costs (complete table)
| Action | Disturbance Added |
|---|---|
| Fuel canister collected | +3 |
| Repair kit collected | +4 |
| Medkit collected | +5 |
| Flashlight battery collected | +3 |
| Flare gun collected | +3 |
| Flare gun fired | +6 |
| Rock thrown | +0.5 |
| Road contact at speed (condition dispersal) | +5 |
| Engine revving (high RPM) | +1 per second |
| Headlights active | +0.2 per second |
| Flashlight active | +0.2 per second |
| Horn | +3 |
| Player sprinting on foot | +0.5 per second |
| Exiting vehicle | +2 |
| Vehicle collision | +2 |
| Mycelium Network contact | +4 |
| Deadfall triggered | +2 |
| Entering Petrifold (first crossing per run) | +3 |
| Entering Fungal Depths (first crossing per run) | +0 — transition is passive |
### Diegetic Disturbance Indicators
No HUD. Players learn to read:
- **Bus instruments**: drift and jitter increase with disturbance state. At Agitated, instruments become unreliable.
- **Engine audio**: hesitation and irregularity at Agitated. Instability at Storm.
- **Ambient audio**: low-frequency content increases. The forest gets quieter in the wrong way. The sense of being attended-to increases, though this cannot be instrumented.
- **Radio**: signal degradation is driven by both biome depth AND disturbance. Heavy static in Verdant indicates Uneasy or higher. In Fungal Depths, signal degradation is biome-baseline regardless of disturbance — its absence there is not a reliable disturbance indicator.
- **Windshield accumulation**: particulate and biological matter accumulate faster at higher disturbance.
- **Perception**: at high disturbance AND high exposure, familiar routes feel different. Whether this is the condition or the player is not distinguishable by any available instrument.
### Tension-Release Cycles (Article X Rule)
Tension MUST be cycled — not held at a constant level. The forest MUST have moments of apparent safety (negative feedback loop releasing) before escalating again. Per Article XII-J: a horror experience that never releases becomes numbing within minutes.
Within a run, the meter can only climb — but the *experienced* tension releases when the player finds moments of momentum (driving cleanly, avoiding a Deadfall), audio pockets of quiet (between anomaly zones), or successful on-foot collection. The metabolism continues; the player's felt-state relaxes briefly before the next climb.
### Biome-Reactive Response
Each [[Biomes|biome]] has a distinct disturbance response profile (see [[Biome Behavior]] concept thread). The same player action may feed the process differently in Fungal Depths than in Verdant Old Growth. The audio arc, radio degradation curve, and anomaly expression all vary per biome even for the same disturbance level.
### The Storm (Finishing State)
The Storm spawns when the disturbance meter reaches 85. It cannot be cancelled or reversed after spawning.
| Property | Value |
|---|---|
| Trigger threshold | 85 disturbance |
| Spawn location | Crater rim, opposite side from nearest exit corridor (terraforming energy radiates inward from the reshape boundary) |
| Initial speed | 20 km/h |
| Speed acceleration | +5 km/h per 20 seconds |
| Maximum speed | 80 km/h (reached at ~4 minutes) |
| Width | Full crater width — no flanking |
| Player damage inside | 30 HP/sec |
| Van damage inside | 15 HP/sec |
| Tree behavior in path | Incorporated (visual effect, not obstacle) |
| Audio pre-visualization | Subsonic rumble → ambience collapse → silence → Storm |
**Survival window**: from spawn, the player has a meaningful survival window if already near an exit. The window narrows based on distance from exit and current speed. A player at the far end of the map must drive hard and without detours to extract.
The Storm is not avoidable through off-road detours — it spans the full crater width. The only variable is speed toward an exit corridor.
If the player is caught and survives the initial damage (possible at full health), the bus will be destroyed within seconds. On-foot survival inside the Storm is not possible.
### Designer-Tunable
Per Article VI Rule 3 / Article XII-J: tension-release cycles MUST be tunable via `@export` variables. Thresholds for Ambient → Directed → Cascade → Finishing (implemented as Calm → Uneasy → Agitated → Storm) are exposed for design iteration. Config lives in `res://resources/config/disturbance_config.tres` per spec 078.
### Irreversibility
The process never resets during a run. Inputs already fed cannot be recalled. The forest does not forget in the way that implies it was paying attention to begin with — it simply continues, and what has already entered the system has already changed the system.
Across runs, the condition accumulates via the garage contamination axis (see [[World Model]] / [[Narrative Direction]]) — a persistent value the player has no direct way to read but which progressively changes the garage and the bus.
## How It Appears In-Game
The player does not see a number. They see (and hear) consequences:
- Audio degrades on the radio; the bus sounds change
- Anomalies intensify
- Weather worsens
- What moves in the wood gets closer and less cautious
- Eventually, the Storm spawns from the ridgeline and moves toward the player
The Storm is the disturbance reaching Finishing — see [[The Player Causes the Storm]].
## Related
- [[The Crater]] — the origin event whose dimensional energy the disturbance meter measures
- [[The Temporal Fracture]] — disturbance as temporal destabilization; the Storm as ongoing insertion unfiltered
- [[World Model]] — the world the disturbance operates in
- [[Tone and Horror]] — dread-over-shock framing
- [[Core Mechanics]] — the run loop that drives disturbance accumulation
- [[Autoloads — Disturbance]] — the GDScript + Rust implementation
- [[Audio — Car Radio]] — radio degradation as diegetic disturbance readout
- [[Audio — World Audio]] — four-state ambient audio arc
- [[The Disturbance Thread]] — cross-cutting concept thread
- [[The Player Causes the Storm]] — the endgame manifestation
- [[Constitution]] — Article X is the authoritative source, Article XII-J (horror rhythm)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-disturbance-system: type/code
Tag assignment · test- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.63 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Design/Disturbance System.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Disturbance System
The disturbance meter is Old Growth's central tension engine. It is not a health bar, not a resource gauge — it is a hidden 0–100 metric that measures how much the forest notices the player. This note captures the design intent AND the full mechanical specification (biological states, disturbance costs, diegetic indicators, Storm behavior). Implementation: [[Autoloads — Disturbance]].
## Overview
The forest does not have a temper. It has a metabolism.
Everything in the woods is input to a process that runs continuously and has been running since before the first road was cut through this watershed. The player's presence feeds this process from the moment they enter the tree line. Every action — collection, combustion, impact, noise — is an input the forest attends to, and attendance accelerates the process.
## Design Intent
This is not punishment. A fallen log feeds the same process. The player is simply a more active source. The disturbance system exists to make the **condition visible through its consequences** rather than through a dedicated UI display. The forest does not provide one. It expects you to have been paying attention.
The constitutional basis is Article X — The Disturbance Is the Design:
> The disturbance system is the primary tension engine. Tension MUST be cycled — not held at a constant level. The forest MUST have moments of apparent safety (negative feedback loop releasing) before escalating again.
## Key Decisions
### Meter
| Property | Value |
|---|---|
| Range | 0 – 100 |
| Display | None. Never shown directly. |
| In-run reset | None. Never resets during a run. |
| Cross-run behavior | Resets to 0 at run start |
| Read mechanism | Bus behavior, condition presence, audio register shifts, instrument reliability |
Players read disturbance state through environmental cues, not through any HUD element.
### Biological States
| State | Meter Range | Bus Behavior | Condition Presence | Audio |
|---|---|---|---|---|
| Calm | 0–30 | Normal | Ambient, diffuse | Normal biome ambience |
| Uneasy | 30–60 | Instruments drift slightly | Heightened, directional | Ambience shifts register, lower frequencies |
| Agitated | 60–85 | Instruments unreliable, engine hesitant | Dense, attending | Ambience becomes directional, pulse-like |
| Storm | 85–100 | Electrical instability | Maximum concentration | All frequencies collapse inward |
At 85, the Storm spawns regardless of player position. The Storm state does not have a stable ceiling — it triggers immediately.
### Disturbance Costs (complete table)
| Action | Disturbance Added |
|---|---|
| Fuel canister collected | +3 |
| Repair kit collected | +4 |
| Medkit collected | +5 |
| Flashlight battery collected | +3 |
| Flare gun collected | +3 |
| Flare gun fired | +6 |
| Rock thrown | +0.5 |
| Road contact at speed (condition dispersal) | +5 |
| Engine revving (high RPM) | +1 per second |
| Headlights active | +0.2 per second |
| Flashlight active | +0.2 per second |
| Horn | +3 |
| Player sprinting on foot | +0.5 per second |
| Exiting vehicle | +2 |
| Vehicle collision | +2 |
| Mycelium Network contact | +4 |
| Deadfall triggered | +2 |
| Entering Petrifold (first crossing per run) | +3 |
| Entering Fungal Depths (first crossing per run) | +0 — transition is passive |
### Diegetic Disturbance Indicators
No HUD. Players learn to read:
- **Bus instruments**: drift and jitter increase with disturbance state. At Agitated, instruments become unreliable.
- **Engine audio**: hesitation and irregularity at Agitated. Instability at Storm.
- **Ambient audio**: low-frequency content increases. The forest gets quieter in the wrong way. The sense of being attended-to increases, though this cannot be instrumented.
- **Radio**: signal degradation is driven by both biome depth AND disturbance. Heavy static in Verdant indicates Uneasy or higher. In Fungal Depths, signal degradation is biome-baseline regardless of disturbance — its absence there is not a reliable disturbance indicator.
- **Windshield accumulation**: particulate and biological matter accumulate faster at higher disturbance.
- **Perception**: at high disturbance AND high exposure, familiar routes feel different. Whether this is the condition or the player is not distinguishable by any available instrument.
### Tension-Release Cycles (Article X Rule)
Tension MUST be cycled — not held at a constant level. The forest MUST have moments of apparent safety (negative feedback loop releasing) before escalating again. Per Article XII-J: a horror experience that never releases becomes numbing within minutes.
Within a run, the meter can only climb — but the *experienced* tension releases when the player finds moments of momentum (driving cleanly, avoiding a Deadfall), audio pockets of quiet (between anomaly zones), or successful on-foot collection. The metabolism continues; the player's felt-state relaxes briefly before the next climb.
### Biome-Reactive Response
Each [[Biomes|biome]] has a distinct disturbance response profile (see [[Biome Behavior]] concept thread). The same player action may feed the process differently in Fungal Depths than in Verdant Old Growth. The audio arc, radio degradation curve, and anomaly expression all vary per biome even for the same disturbance level.
### The Storm (Finishing State)
The Storm spawns when the disturbance meter reaches 85. It cannot be cancelled or reversed after spawning.
| Property | Value |
|---|---|
| Trigger threshold | 85 disturbance |
| Spawn location | Crater rim, opposite side from nearest exit corridor (terraforming energy radiates inward from the reshape boundary) |
| Initial speed | 20 km/h |
| Speed acceleration | +5 km/h per 20 seconds |
| Maximum speed | 80 km/h (reached at ~4 minutes) |
| Width | Full crater width — no flanking |
| Player damage inside | 30 HP/sec |
| Van damage inside | 15 HP/sec |
| Tree behavior in path | Incorporated (visual effect, not obstacle) |
| Audio pre-visualization | Subsonic rumble → ambience collapse → silence → Storm |
**Survival window**: from spawn, the player has a meaningful survival window if already near an exit. The window narrows based on distance from exit and current speed. A player at the far end of the map must drive hard and without detours to extract.
The Storm is not avoidable through off-road detours — it spans the full crater width. The only variable is speed toward an exit corridor.
If the player is caught and survives the initial damage (possible at full health), the bus will be destroyed within seconds. On-foot survival inside the Storm is not possible.
### Designer-Tunable
Per Article VI Rule 3 / Article XII-J: tension-release cycles MUST be tunable via `@export` variables. Thresholds for Ambient → Directed → Cascade → Finishing (implemented as Calm → Uneasy → Agitated → Storm) are exposed for design iteration. Config lives in `res://resources/config/disturbance_config.tres` per spec 078.
### Irreversibility
The process never resets during a run. Inputs already fed cannot be recalled. The forest does not forget in the way that implies it was paying attention to begin with — it simply continues, and what has already entered the system has already changed the system.
Across runs, the condition accumulates via the garage contamination axis (see [[World Model]] / [[Narrative Direction]]) — a persistent value the player has no direct way to read but which progressively changes the garage and the bus.
## How It Appears In-Game
The player does not see a number. They see (and hear) consequences:
- Audio degrades on the radio; the bus sounds change
- Anomalies intensify
- Weather worsens
- What moves in the wood gets closer and less cautious
- Eventually, the Storm spawns from the ridgeline and moves toward the player
The Storm is the disturbance reaching Finishing — see [[The Player Causes the Storm]].
## Related
- [[The Crater]] — the origin event whose dimensional energy the disturbance meter measures
- [[The Temporal Fracture]] — disturbance as temporal destabilization; the Storm as ongoing insertion unfiltered
- [[World Model]] — the world the disturbance operates in
- [[Tone and Horror]] — dread-over-shock framing
- [[Core Mechanics]] — the run loop that drives disturbance accumulation
- [[Autoloads — Disturbance]] — the GDScript + Rust implementation
- [[Audio — Car Radio]] — radio degradation as diegetic disturbance readout
- [[Audio — World Audio]] — four-state ambient audio arc
- [[The Disturbance Thread]] — cross-cutting concept thread
- [[The Player Causes the Storm]] — the endgame manifestation
- [[Constitution]] — Article X is the authoritative source, Article XII-J (horror rhythm)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-disturbance-system: type/design
Tag assignment · test- Gold
- The note calls for `type/design`: Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`) (True)
- ember
- The note has no reason to carry `type/design` (False)
- Signal
- P(true) 0.47 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Design/Disturbance System.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Disturbance System
The disturbance meter is Old Growth's central tension engine. It is not a health bar, not a resource gauge — it is a hidden 0–100 metric that measures how much the forest notices the player. This note captures the design intent AND the full mechanical specification (biological states, disturbance costs, diegetic indicators, Storm behavior). Implementation: [[Autoloads — Disturbance]].
## Overview
The forest does not have a temper. It has a metabolism.
Everything in the woods is input to a process that runs continuously and has been running since before the first road was cut through this watershed. The player's presence feeds this process from the moment they enter the tree line. Every action — collection, combustion, impact, noise — is an input the forest attends to, and attendance accelerates the process.
## Design Intent
This is not punishment. A fallen log feeds the same process. The player is simply a more active source. The disturbance system exists to make the **condition visible through its consequences** rather than through a dedicated UI display. The forest does not provide one. It expects you to have been paying attention.
The constitutional basis is Article X — The Disturbance Is the Design:
> The disturbance system is the primary tension engine. Tension MUST be cycled — not held at a constant level. The forest MUST have moments of apparent safety (negative feedback loop releasing) before escalating again.
## Key Decisions
### Meter
| Property | Value |
|---|---|
| Range | 0 – 100 |
| Display | None. Never shown directly. |
| In-run reset | None. Never resets during a run. |
| Cross-run behavior | Resets to 0 at run start |
| Read mechanism | Bus behavior, condition presence, audio register shifts, instrument reliability |
Players read disturbance state through environmental cues, not through any HUD element.
### Biological States
| State | Meter Range | Bus Behavior | Condition Presence | Audio |
|---|---|---|---|---|
| Calm | 0–30 | Normal | Ambient, diffuse | Normal biome ambience |
| Uneasy | 30–60 | Instruments drift slightly | Heightened, directional | Ambience shifts register, lower frequencies |
| Agitated | 60–85 | Instruments unreliable, engine hesitant | Dense, attending | Ambience becomes directional, pulse-like |
| Storm | 85–100 | Electrical instability | Maximum concentration | All frequencies collapse inward |
At 85, the Storm spawns regardless of player position. The Storm state does not have a stable ceiling — it triggers immediately.
### Disturbance Costs (complete table)
| Action | Disturbance Added |
|---|---|
| Fuel canister collected | +3 |
| Repair kit collected | +4 |
| Medkit collected | +5 |
| Flashlight battery collected | +3 |
| Flare gun collected | +3 |
| Flare gun fired | +6 |
| Rock thrown | +0.5 |
| Road contact at speed (condition dispersal) | +5 |
| Engine revving (high RPM) | +1 per second |
| Headlights active | +0.2 per second |
| Flashlight active | +0.2 per second |
| Horn | +3 |
| Player sprinting on foot | +0.5 per second |
| Exiting vehicle | +2 |
| Vehicle collision | +2 |
| Mycelium Network contact | +4 |
| Deadfall triggered | +2 |
| Entering Petrifold (first crossing per run) | +3 |
| Entering Fungal Depths (first crossing per run) | +0 — transition is passive |
### Diegetic Disturbance Indicators
No HUD. Players learn to read:
- **Bus instruments**: drift and jitter increase with disturbance state. At Agitated, instruments become unreliable.
- **Engine audio**: hesitation and irregularity at Agitated. Instability at Storm.
- **Ambient audio**: low-frequency content increases. The forest gets quieter in the wrong way. The sense of being attended-to increases, though this cannot be instrumented.
- **Radio**: signal degradation is driven by both biome depth AND disturbance. Heavy static in Verdant indicates Uneasy or higher. In Fungal Depths, signal degradation is biome-baseline regardless of disturbance — its absence there is not a reliable disturbance indicator.
- **Windshield accumulation**: particulate and biological matter accumulate faster at higher disturbance.
- **Perception**: at high disturbance AND high exposure, familiar routes feel different. Whether this is the condition or the player is not distinguishable by any available instrument.
### Tension-Release Cycles (Article X Rule)
Tension MUST be cycled — not held at a constant level. The forest MUST have moments of apparent safety (negative feedback loop releasing) before escalating again. Per Article XII-J: a horror experience that never releases becomes numbing within minutes.
Within a run, the meter can only climb — but the *experienced* tension releases when the player finds moments of momentum (driving cleanly, avoiding a Deadfall), audio pockets of quiet (between anomaly zones), or successful on-foot collection. The metabolism continues; the player's felt-state relaxes briefly before the next climb.
### Biome-Reactive Response
Each [[Biomes|biome]] has a distinct disturbance response profile (see [[Biome Behavior]] concept thread). The same player action may feed the process differently in Fungal Depths than in Verdant Old Growth. The audio arc, radio degradation curve, and anomaly expression all vary per biome even for the same disturbance level.
### The Storm (Finishing State)
The Storm spawns when the disturbance meter reaches 85. It cannot be cancelled or reversed after spawning.
| Property | Value |
|---|---|
| Trigger threshold | 85 disturbance |
| Spawn location | Crater rim, opposite side from nearest exit corridor (terraforming energy radiates inward from the reshape boundary) |
| Initial speed | 20 km/h |
| Speed acceleration | +5 km/h per 20 seconds |
| Maximum speed | 80 km/h (reached at ~4 minutes) |
| Width | Full crater width — no flanking |
| Player damage inside | 30 HP/sec |
| Van damage inside | 15 HP/sec |
| Tree behavior in path | Incorporated (visual effect, not obstacle) |
| Audio pre-visualization | Subsonic rumble → ambience collapse → silence → Storm |
**Survival window**: from spawn, the player has a meaningful survival window if already near an exit. The window narrows based on distance from exit and current speed. A player at the far end of the map must drive hard and without detours to extract.
The Storm is not avoidable through off-road detours — it spans the full crater width. The only variable is speed toward an exit corridor.
If the player is caught and survives the initial damage (possible at full health), the bus will be destroyed within seconds. On-foot survival inside the Storm is not possible.
### Designer-Tunable
Per Article VI Rule 3 / Article XII-J: tension-release cycles MUST be tunable via `@export` variables. Thresholds for Ambient → Directed → Cascade → Finishing (implemented as Calm → Uneasy → Agitated → Storm) are exposed for design iteration. Config lives in `res://resources/config/disturbance_config.tres` per spec 078.
### Irreversibility
The process never resets during a run. Inputs already fed cannot be recalled. The forest does not forget in the way that implies it was paying attention to begin with — it simply continues, and what has already entered the system has already changed the system.
Across runs, the condition accumulates via the garage contamination axis (see [[World Model]] / [[Narrative Direction]]) — a persistent value the player has no direct way to read but which progressively changes the garage and the bus.
## How It Appears In-Game
The player does not see a number. They see (and hear) consequences:
- Audio degrades on the radio; the bus sounds change
- Anomalies intensify
- Weather worsens
- What moves in the wood gets closer and less cautious
- Eventually, the Storm spawns from the ridgeline and moves toward the player
The Storm is the disturbance reaching Finishing — see [[The Player Causes the Storm]].
## Related
- [[The Crater]] — the origin event whose dimensional energy the disturbance meter measures
- [[The Temporal Fracture]] — disturbance as temporal destabilization; the Storm as ongoing insertion unfiltered
- [[World Model]] — the world the disturbance operates in
- [[Tone and Horror]] — dread-over-shock framing
- [[Core Mechanics]] — the run loop that drives disturbance accumulation
- [[Autoloads — Disturbance]] — the GDScript + Rust implementation
- [[Audio — Car Radio]] — radio degradation as diegetic disturbance readout
- [[Audio — World Audio]] — four-state ambient audio arc
- [[The Disturbance Thread]] — cross-cutting concept thread
- [[The Player Causes the Storm]] — the endgame manifestation
- [[Constitution]] — Article X is the authoritative source, Article XII-J (horror rhythm)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-disturbance-system: type/spec
Tag assignment · test- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.55 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Design/Disturbance System.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Disturbance System
The disturbance meter is Old Growth's central tension engine. It is not a health bar, not a resource gauge — it is a hidden 0–100 metric that measures how much the forest notices the player. This note captures the design intent AND the full mechanical specification (biological states, disturbance costs, diegetic indicators, Storm behavior). Implementation: [[Autoloads — Disturbance]].
## Overview
The forest does not have a temper. It has a metabolism.
Everything in the woods is input to a process that runs continuously and has been running since before the first road was cut through this watershed. The player's presence feeds this process from the moment they enter the tree line. Every action — collection, combustion, impact, noise — is an input the forest attends to, and attendance accelerates the process.
## Design Intent
This is not punishment. A fallen log feeds the same process. The player is simply a more active source. The disturbance system exists to make the **condition visible through its consequences** rather than through a dedicated UI display. The forest does not provide one. It expects you to have been paying attention.
The constitutional basis is Article X — The Disturbance Is the Design:
> The disturbance system is the primary tension engine. Tension MUST be cycled — not held at a constant level. The forest MUST have moments of apparent safety (negative feedback loop releasing) before escalating again.
## Key Decisions
### Meter
| Property | Value |
|---|---|
| Range | 0 – 100 |
| Display | None. Never shown directly. |
| In-run reset | None. Never resets during a run. |
| Cross-run behavior | Resets to 0 at run start |
| Read mechanism | Bus behavior, condition presence, audio register shifts, instrument reliability |
Players read disturbance state through environmental cues, not through any HUD element.
### Biological States
| State | Meter Range | Bus Behavior | Condition Presence | Audio |
|---|---|---|---|---|
| Calm | 0–30 | Normal | Ambient, diffuse | Normal biome ambience |
| Uneasy | 30–60 | Instruments drift slightly | Heightened, directional | Ambience shifts register, lower frequencies |
| Agitated | 60–85 | Instruments unreliable, engine hesitant | Dense, attending | Ambience becomes directional, pulse-like |
| Storm | 85–100 | Electrical instability | Maximum concentration | All frequencies collapse inward |
At 85, the Storm spawns regardless of player position. The Storm state does not have a stable ceiling — it triggers immediately.
### Disturbance Costs (complete table)
| Action | Disturbance Added |
|---|---|
| Fuel canister collected | +3 |
| Repair kit collected | +4 |
| Medkit collected | +5 |
| Flashlight battery collected | +3 |
| Flare gun collected | +3 |
| Flare gun fired | +6 |
| Rock thrown | +0.5 |
| Road contact at speed (condition dispersal) | +5 |
| Engine revving (high RPM) | +1 per second |
| Headlights active | +0.2 per second |
| Flashlight active | +0.2 per second |
| Horn | +3 |
| Player sprinting on foot | +0.5 per second |
| Exiting vehicle | +2 |
| Vehicle collision | +2 |
| Mycelium Network contact | +4 |
| Deadfall triggered | +2 |
| Entering Petrifold (first crossing per run) | +3 |
| Entering Fungal Depths (first crossing per run) | +0 — transition is passive |
### Diegetic Disturbance Indicators
No HUD. Players learn to read:
- **Bus instruments**: drift and jitter increase with disturbance state. At Agitated, instruments become unreliable.
- **Engine audio**: hesitation and irregularity at Agitated. Instability at Storm.
- **Ambient audio**: low-frequency content increases. The forest gets quieter in the wrong way. The sense of being attended-to increases, though this cannot be instrumented.
- **Radio**: signal degradation is driven by both biome depth AND disturbance. Heavy static in Verdant indicates Uneasy or higher. In Fungal Depths, signal degradation is biome-baseline regardless of disturbance — its absence there is not a reliable disturbance indicator.
- **Windshield accumulation**: particulate and biological matter accumulate faster at higher disturbance.
- **Perception**: at high disturbance AND high exposure, familiar routes feel different. Whether this is the condition or the player is not distinguishable by any available instrument.
### Tension-Release Cycles (Article X Rule)
Tension MUST be cycled — not held at a constant level. The forest MUST have moments of apparent safety (negative feedback loop releasing) before escalating again. Per Article XII-J: a horror experience that never releases becomes numbing within minutes.
Within a run, the meter can only climb — but the *experienced* tension releases when the player finds moments of momentum (driving cleanly, avoiding a Deadfall), audio pockets of quiet (between anomaly zones), or successful on-foot collection. The metabolism continues; the player's felt-state relaxes briefly before the next climb.
### Biome-Reactive Response
Each [[Biomes|biome]] has a distinct disturbance response profile (see [[Biome Behavior]] concept thread). The same player action may feed the process differently in Fungal Depths than in Verdant Old Growth. The audio arc, radio degradation curve, and anomaly expression all vary per biome even for the same disturbance level.
### The Storm (Finishing State)
The Storm spawns when the disturbance meter reaches 85. It cannot be cancelled or reversed after spawning.
| Property | Value |
|---|---|
| Trigger threshold | 85 disturbance |
| Spawn location | Crater rim, opposite side from nearest exit corridor (terraforming energy radiates inward from the reshape boundary) |
| Initial speed | 20 km/h |
| Speed acceleration | +5 km/h per 20 seconds |
| Maximum speed | 80 km/h (reached at ~4 minutes) |
| Width | Full crater width — no flanking |
| Player damage inside | 30 HP/sec |
| Van damage inside | 15 HP/sec |
| Tree behavior in path | Incorporated (visual effect, not obstacle) |
| Audio pre-visualization | Subsonic rumble → ambience collapse → silence → Storm |
**Survival window**: from spawn, the player has a meaningful survival window if already near an exit. The window narrows based on distance from exit and current speed. A player at the far end of the map must drive hard and without detours to extract.
The Storm is not avoidable through off-road detours — it spans the full crater width. The only variable is speed toward an exit corridor.
If the player is caught and survives the initial damage (possible at full health), the bus will be destroyed within seconds. On-foot survival inside the Storm is not possible.
### Designer-Tunable
Per Article VI Rule 3 / Article XII-J: tension-release cycles MUST be tunable via `@export` variables. Thresholds for Ambient → Directed → Cascade → Finishing (implemented as Calm → Uneasy → Agitated → Storm) are exposed for design iteration. Config lives in `res://resources/config/disturbance_config.tres` per spec 078.
### Irreversibility
The process never resets during a run. Inputs already fed cannot be recalled. The forest does not forget in the way that implies it was paying attention to begin with — it simply continues, and what has already entered the system has already changed the system.
Across runs, the condition accumulates via the garage contamination axis (see [[World Model]] / [[Narrative Direction]]) — a persistent value the player has no direct way to read but which progressively changes the garage and the bus.
## How It Appears In-Game
The player does not see a number. They see (and hear) consequences:
- Audio degrades on the radio; the bus sounds change
- Anomalies intensify
- Weather worsens
- What moves in the wood gets closer and less cautious
- Eventually, the Storm spawns from the ridgeline and moves toward the player
The Storm is the disturbance reaching Finishing — see [[The Player Causes the Storm]].
## Related
- [[The Crater]] — the origin event whose dimensional energy the disturbance meter measures
- [[The Temporal Fracture]] — disturbance as temporal destabilization; the Storm as ongoing insertion unfiltered
- [[World Model]] — the world the disturbance operates in
- [[Tone and Horror]] — dread-over-shock framing
- [[Core Mechanics]] — the run loop that drives disturbance accumulation
- [[Autoloads — Disturbance]] — the GDScript + Rust implementation
- [[Audio — Car Radio]] — radio degradation as diegetic disturbance readout
- [[Audio — World Audio]] — four-state ambient audio arc
- [[The Disturbance Thread]] — cross-cutting concept thread
- [[The Player Causes the Storm]] — the endgame manifestation
- [[Constitution]] — Article X is the authoritative source, Article XII-J (horror rhythm)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-implementation-choices-and-architecture-decisions: type/code
Tag assignment · dev- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.61 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Decisions/Implementation Choices and Architecture Decisions.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Implementation Choices and Architecture Decisions
Core architectural rules that apply across all implementation work. These are constitutional constraints (Article I, III), not decisions made during any single session.
## Core Architectural Rules
### 1. Signal-Driven Architecture
- **Rule**: All cross-system communication via the `Events` autoload signal bus only. Systems must not call methods on nodes they do not own.
- **Source**: Constitution Article I / `Governance/Constitution.md`
- **Enforcement**: `gdlint` catches direct node references across system boundaries; code review.
### 2. Seeded Randomness
- **Rule**: All procedural systems use `RandomNumberGenerator` seeded from `GameState.current_seed`. Never use global `randf()` / `randi()`.
- **Source**: Constitution Article III Rule 4
- **Enforcement**: SC-001 baseline (seed=1250827250 → trees=1452, ferns=5300, logs=3061; post-spec-103 per-tree-clearance, 2026-04-26 per AGENTS.md); `make test` headless run.
### 3. Asset Export Safety
- **Rule**: Non-shippable directories under `src/assets/` must contain a `.gdignore` file. The shippable allowlist lives in `scripts/ci/check_asset_gdignore.sh`.
- **Source**: AGENTS.md § Asset Export Safety; `Discoveries/Asset Export Safety — gdignore.md`
- **Enforcement**: `make gdignore-check` (CI-gated).
### 4. Component SemVer
- **Rule**: Every file under `src/` maps to a component in `src/component_versions.json`. Modifying any file in a component's path requires bumping that component's version.
- **Source**: Constitution Article III Rule 15; `Governance/Component SemVer Policy.md`
- **Enforcement**: `make semver` (CI-gated).
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-implementation-choices-and-architecture-decisions: type/decision
Tag assignment · dev- Gold
- The note calls for `type/decision`: Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/decision` (False)
- Signal
- P(true) 0.09 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/Implementation Choices and Architecture Decisions.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Implementation Choices and Architecture Decisions
Core architectural rules that apply across all implementation work. These are constitutional constraints (Article I, III), not decisions made during any single session.
## Core Architectural Rules
### 1. Signal-Driven Architecture
- **Rule**: All cross-system communication via the `Events` autoload signal bus only. Systems must not call methods on nodes they do not own.
- **Source**: Constitution Article I / `Governance/Constitution.md`
- **Enforcement**: `gdlint` catches direct node references across system boundaries; code review.
### 2. Seeded Randomness
- **Rule**: All procedural systems use `RandomNumberGenerator` seeded from `GameState.current_seed`. Never use global `randf()` / `randi()`.
- **Source**: Constitution Article III Rule 4
- **Enforcement**: SC-001 baseline (seed=1250827250 → trees=1452, ferns=5300, logs=3061; post-spec-103 per-tree-clearance, 2026-04-26 per AGENTS.md); `make test` headless run.
### 3. Asset Export Safety
- **Rule**: Non-shippable directories under `src/assets/` must contain a `.gdignore` file. The shippable allowlist lives in `scripts/ci/check_asset_gdignore.sh`.
- **Source**: AGENTS.md § Asset Export Safety; `Discoveries/Asset Export Safety — gdignore.md`
- **Enforcement**: `make gdignore-check` (CI-gated).
### 4. Component SemVer
- **Rule**: Every file under `src/` maps to a component in `src/component_versions.json`. Modifying any file in a component's path requires bumping that component's version.
- **Source**: Constitution Article III Rule 15; `Governance/Component SemVer Policy.md`
- **Enforcement**: `make semver` (CI-gated).
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-implementation-choices-and-architecture-decisions: type/principle
Tag assignment · dev- Gold
- The note has no reason to carry `type/principle` (False)
- ember
- The note calls for `type/principle`: Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`) (True)
- Signal
- P(true) 0.74 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Decisions/Implementation Choices and Architecture Decisions.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Implementation Choices and Architecture Decisions
Core architectural rules that apply across all implementation work. These are constitutional constraints (Article I, III), not decisions made during any single session.
## Core Architectural Rules
### 1. Signal-Driven Architecture
- **Rule**: All cross-system communication via the `Events` autoload signal bus only. Systems must not call methods on nodes they do not own.
- **Source**: Constitution Article I / `Governance/Constitution.md`
- **Enforcement**: `gdlint` catches direct node references across system boundaries; code review.
### 2. Seeded Randomness
- **Rule**: All procedural systems use `RandomNumberGenerator` seeded from `GameState.current_seed`. Never use global `randf()` / `randi()`.
- **Source**: Constitution Article III Rule 4
- **Enforcement**: SC-001 baseline (seed=1250827250 → trees=1452, ferns=5300, logs=3061; post-spec-103 per-tree-clearance, 2026-04-26 per AGENTS.md); `make test` headless run.
### 3. Asset Export Safety
- **Rule**: Non-shippable directories under `src/assets/` must contain a `.gdignore` file. The shippable allowlist lives in `scripts/ci/check_asset_gdignore.sh`.
- **Source**: AGENTS.md § Asset Export Safety; `Discoveries/Asset Export Safety — gdignore.md`
- **Enforcement**: `make gdignore-check` (CI-gated).
### 4. Component SemVer
- **Rule**: Every file under `src/` maps to a component in `src/component_versions.json`. Modifying any file in a component's path requires bumping that component's version.
- **Source**: Constitution Article III Rule 15; `Governance/Component SemVer Policy.md`
- **Enforcement**: `make semver` (CI-gated).
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-governance-phased-spec: type/principle
Tag assignment · test- Gold
- The note calls for `type/principle`: Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`) (True)
- ember
- The note has no reason to carry `type/principle` (False)
- Signal
- P(true) 0.12 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Governance/Phased Spec.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Phased Spec
> [!warning] SUPERSEDED
> The Phased Development Spec v3.1 (`docs/old-growth-phased-spec.md`) is superseded. The canonical ordering is now the per-spec subdirectories in `docs/vault/Specs/` with status tracked in each spec's `spec_status` frontmatter field. This note is retained as the historical phased plan and for references to "Phase N" naming in older commits.
A historical snapshot of the 26-phase development plan that guided Old Growth's initial scope planning. Individual phases have been decomposed into feature-specific specs under `docs/vault/Specs/NNN Title/` subdirectories; the canonical status source is each spec's `spec_status` frontmatter.
## Status
This spec is **superseded**. Do not treat its content as authoritative. For current project status, query vault spec frontmatter via the Specs MOC.
## Context
Old Growth's original development plan was a 26-phase "Phased Development Spec v3.1" — a linear plan that guided initial scope but has been decomposed into the per-feature subdirectories now tracked in `docs/vault/Specs/`. Earlier variants under different project names ("Dead Mileage", "Exclusion Drive") are also superseded and retained only for historical authorship.
Constitution Appendix B lists these documents as `SUPERSEDED`. The authoritative current-status source is each spec's `spec_status` frontmatter field, which this vault surfaces via Specs.
**What replaced the phased spec**: the spec-kit workflow — each significant feature gets a `docs/vault/Specs/NNN Title/` subdirectory with its own `spec.md`, `plan.md`, and `tasks.md`. The "phase" concept now lives at the spec-epic level, not as a single project-wide linear plan. See [[Epics]] for the current epic-level view.
## Related
- [[Governance]] — folder MOC
- Specs — the authoritative current status
- [[Constitution]] — Appendix B lists this as SUPERSEDED
- [[Milestones]] — project milestone tracking
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-controls: type/design
Tag assignment · dev- Gold
- The note calls for `type/design`: Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`) (True)
- ember
- The note has no reason to carry `type/design` (False)
- Signal
- P(true) 0.46 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Design/Controls.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Controls
The control scheme for Old Growth: driving, on-foot, radio, vehicle systems, and debug. Controls are designed to stay out of the way — everything is a simple keyboard action directly mapped to a diegetic interaction with the Samba or the forest.
## Overview
Old Growth uses keyboard+mouse. All controls are direct: no chord inputs, no menus-within-menus, no modifier keys. The cabin view is the HUD; interactions surface when the player looks at the relevant object (radio, dome light).
## Design Intent
Input design serves diegesis (Article XII-G). Looking at the radio to activate its controls is the rule, not the exception — the player interacts with *the thing*, not with an abstraction of the thing. The control map is minimal because the game's complexity lives in the world, not in the input bindings.
## Key Decisions
### Driving
| Key | Action |
|-----|--------|
| W | Accelerate |
| S | Brake / Reverse |
| A | Steer left |
| D | Steer right |
| Space | Handbrake |
| Shift | Toggle gear (forward / reverse) |
| Mouse | Look around cabin |
| Esc | Toggle mouse cursor (captured / free) |
### Vehicle Systems
| Key | Action |
|-----|--------|
| H | Toggle headlights |
| L | Toggle high beams |
| J | Cycle wipers (off / slow / fast) |
| E | Toggle dome light (when focused) |
### Radio
Look at the radio on the dashboard to activate controls.
| Key | Action |
|-----|--------|
| E | Toggle radio power |
| T | Tune frequency up |
| Y | Tune frequency down |
| = | Volume up |
| - | Volume down |
### On Foot
| Key | Action |
|-----|--------|
| W/A/S/D | Move |
| Shift | Sprint |
| Ctrl | Crouch |
| E | Interact |
| F | Toggle flashlight |
| G | Enter / exit vehicle |
| Mouse | Look around |
### General
| Key | Action |
|-----|--------|
| R | Restart scene |
| Esc | Toggle mouse cursor |
| F12 | Take screenshot (saved to `~/Pictures/Old Growth/`) |
### Debug (debug builds only)
| Key | Action |
|-----|--------|
| F1 | Toggle debug overlay (game) / preview info overlay (world preview) |
| F3 | Toggle road spine overlay (game and world preview) |
## How It Appears In-Game
Controls are learned by doing. The game does not display a control reference; the player discovers them through the environment (dashboard labels on the Samba, subtle cues in the garage). On-foot movement is quieter than running; the mycelium notices sprinting — this is a control decision with tonal consequences.
## Related
- [[Core Mechanics]] — the run loop that controls drive
- [[Vehicle — Samba]] — the interactions controls invoke
- [[Audio — Car Radio]] — radio controls are diegetic
- [[Design]] — folder MOC
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-099-sfx-pipeline-implementation-apr-25-2026: status/reviewed
Tag assignment · dev- Gold
- The note calls for `status/reviewed`: Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `status/reviewed` (False)
- Signal
- P(true) 0.25 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spec 099 SFX Pipeline Implementation — Apr 25 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 099 SFX Pipeline Implementation — Apr 25 2026
This session log records the full implementation of spec 099 (SFX Pipeline Expansion), executed in 8 phases across a single work session on 2026-04-25.
## What Was Done
### Phase 1 — Tooling Infrastructure (T001–T011C)
Makefile sfx-generate / sfx-master / sfx-loop-ogg / sfx-oneshot-ogg / sfx-install targets added to tools/tts/Makefile, parameterized by CATEGORY and OUTPUT. Two new CI validation scripts created: tools/tts/validate_sfx_naming.sh and tools/tts/validate_sfx_provenance.sh. The ambient_process.sh script was extended with --gold-dir / --install-dir arguments (backward-compatible with existing ambient-weather-* callers). Two new audio buses added to src/default_bus_layout.tres: VehicleSFX (Compressor + EQ10) and SFX (Reverb + LowPassFilter). AudioBusConfig resource extended with 8 new @export fields. Events.disturbance_level_changed signal added and emitted from both code paths in Disturbance.add(). SfxBusModulator GDScript created and wired into main.tscn.
### Phase 2 — Gold Directory Scaffolding (T012–T021)
90 provenance JSON stubs created across 37 categories (schema 1.3, ElevenLabs generator, PENDING sha256s). All gold/ directories created with .gdignore files. ElevenLabs minimum duration constraint discovered: 0.5s minimum — all stubs with sub-0.5s duration_seconds values were patched before generation.
### Phase 3 — Tier 1 Vehicle SFX + sfx/ Retirement (T022–T032B)
6 Tier 1 assets generated via ElevenLabs SFX V2 and installed: vehicle_engine_idle.ogg (loop), vehicle_wiper_squeak.ogg (oneshot), vehicle_rim_scrape.ogg (loop), vehicle_tire_pop.ogg (oneshot), vehicle_splash_loop.ogg (loop), interaction_toggle_click.ogg (oneshot). Legacy src/assets/audio/sfx/ directory removed (5 files: engine_idle, rim_scrape, splash_loop, tire_pop, wiper_squeak). Code paths updated: tire_damage_driver.gd, samba_bus.tscn (ext_resource paths + DomeLightControl.toggle_sfx). SplashAudio AudioStreamPlayer3D added to SplashController with vehicle_splash_loop.ogg; splash_particles.gd extended to play/stop audio on particle activation state.
A Makefile bug was discovered and fixed: sfx-generate was incorrectly passing --output with the full path and .wav extension instead of just the basename. This caused the provenance JSON lookup to fail with "not found: vehicle_engine_idle.wav.json".
### Phase 4 — Structure Break SFX + Lightning Wiring (T033–T040)
3 Tier 2 world/anomalies break assets generated: world_prop_break.ogg, world_guardrail_break.ogg, world_power_pole_fall.ogg. BreakAudio AudioStreamPlayer3D nodes added to 7 breakable_prop scenes, 1 guardrail scene, and 1 power_pole scene (9 scenes total). T038 was pre-complete: LightningController stream arrays (thunder, thunder_interior, crackle, radio_static) were already fully wired in generated_world.tscn.
### Phase 5 — Player Footsteps (T041–T051)
20 footstep OGGs generated (5 surfaces × 4 variants): asphalt, grass, gravel, soil, fungal. Events.player_footstep(surface_type: String) signal added to events.gd. footstep_surface_detector.gd created with 5-pool dynamic load() system (not preload — files may be missing at parse time), 3-re-roll anti-repetition variant picker. on_foot_controller.gd extended with step_interval timer (0.45s) and speed-threshold footstep emit. FootstepDetector + FootstepAudio nodes wired into on_foot_player.tscn.
### Phase 6 — World Ambient SFX (T052–T060)
20 world ambient assets generated: verdant/fungal/petrifold biome detail oneshots, disturbance pulses, storm oneshots, anomaly oneshots, hollow ambient loops (universal/fungal/verdant/petrifold). T059 (SfxBusModulator verification) passed: all bus indices, effect slots, signal binding, and parameter defaults confirmed correct. T055–T058 (world biome detail/hollow/disturbance pulse/storm wiring) deferred — no WorldDetailAudio, HollowAmbient, DisturbancePulseAudio, or WorldStormOneshot controller scripts exist. These are pending a future spec phase.
### Phase 7 — Interaction, Breathing, UI, Combat SFX (T061–T071D)
27 assets generated: 10 interaction oneshots (door, gear, ignition, loot, repair, window), 3 breathing loops (calm/tense/scared), 4 UI oneshots (select/confirm/back/error), 1 garage loop, 2 gate oneshots, 7 combat oneshots (melee x3, ranged x2, throw x2). All 27 OGGs installed and imported cleanly (make headless exit 0). All wiring deferred — no AudioStreamPlayer nodes exist in UI scenes, no interaction/breathing/garage/gate/combat controller scripts exist.
### Phase 8 — Validation and Finalization
make all confirmed 7 pre-existing test failures (test_on_foot_rain_particles, test_weather_effects_controller — spec-098 tests, not caused by spec-099 changes). make validate-provenance: 0 errors across all sampled categories. All 88 tasks marked complete in tasks.md.
## Key Constraints Discovered
ElevenLabs SFX V2 rejects duration_seconds values below 0.5s with HTTP 400. All initial stubs for footstep variants (0.3s/0.35s) and UI sounds (0.2s) were patched to 0.5s before generation. This constraint should be noted in any future ElevenLabs generation tooling.
The sfx-generate Makefile target had a bug: --output was receiving the full path + extension instead of the basename. Fixed in Phase 3.
## Deferred Work (Not Blocking Spec 099 Core Pipeline)
Phases 6 and 7 wiring is deferred because the underlying controller systems do not exist. All OGG assets are on disk and ready — only the GDScript controllers are missing. Gap analysis completed 2026-04-28; all systems are now accounted for in specs:
| System | Resolution |
|---|---|
| World biome detail ambient scheduler | Covered by spec 104 (Biome Ambient Audio) FR-005 |
| Hollow proximity audio controller | Added to spec 104 as FR-014, FR-015, FR-016 |
| Disturbance pulse audio controller | Covered by spec 105 (Disturbance Arc Audio) |
| World storm oneshot scheduler | Covered by spec 107 (Storm Audio System) |
| Interaction audio controller (door/gear/ignition/etc.) | Covered by spec 113 (Player And World Interaction Audio) FR-007 |
| Player breathing controller | Covered by spec 113 FR-004, FR-006 |
| UI audio manager | Added to spec 087 (Main Menu Polish) as FR-015, FR-016, FR-017 |
| Combat audio system | Intentionally dormant — combat removed from GDD; see [[Audio Asset Provenance & Status Management]] |
## References
- specs/099-sfx-pipeline-expansion/spec.md
- specs/099-sfx-pipeline-expansion/tasks.md
- tools/tts/Makefile
- tools/tts/validate_sfx_naming.sh
- tools/tts/validate_sfx_provenance.sh
- src/scripts/systems/sfx_bus_modulator.gd
- src/scripts/systems/footstep_surface_detector.gd
- src/scripts/bus/splash_particles.gd
- src/autoloads/events.gd
- src/default_bus_layout.tres
- src/resources/config/audio_bus_config.gd
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-buses: layer/gdscript
Tag assignment · test- Gold
- The note calls for `layer/gdscript`: GDScript (Godot scene-tier logic) (True)
- ember
- The note has no reason to carry `layer/gdscript` (False)
- Signal
- P(true) 0.15 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Systems/Audio — Buses.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — Buses
The Godot AudioServer bus architecture for Old Growth defines how all in-game audio is routed, processed, and mixed. Buses are configured in src/default_bus_layout.tres and applied at runtime by AudioBusManager.
## Bus Layout
The project uses 6 audio buses (`src/default_bus_layout.tres`) as of spec 128. Buses are listed below with their current effect chains:
| Bus | Parent | Effects | Purpose |
|---|---|---|---|---|
| Master | — | Limiter | Final output mix |
| Radio | Master | Highpass 300Hz, Lowpass 3500Hz, EQ 1kHz presence | AM radio signal simulation |
| RadioDegraded | Master | Highpass 500Hz, Lowpass 2800Hz, Echo 500ms | Degraded/distorted radio signal |
| VehicleSFX | Master | AudioEffectCompressor (threshold=-18dB, ratio=4:1), AudioEffectEQ10 (low-mid boost) | Vehicle-specific audio with dynamic compression |
| SFX | Master | AudioEffectReverb (wet=0.05), AudioEffectLowPassFilter (cutoff=20000Hz) | World and player SFX with disturbance-responsive DSP |
| UI | Master | — | All menu button clicks and UI navigation sounds (spec 128) |
## Effect Indices
Effect slot indices are constants in src/scripts/systems/audio_bus_manager.gd:
- VehicleSFX bus: slot 0 = AudioEffectCompressor, slot 1 = AudioEffectEQ
- SFX bus: slot 0 = AudioEffectReverb, slot 1 = AudioEffectLowPassFilter
- Radio bus: RADIO_REVERB_EFFECT_IDX = existing constant
## AudioBusManager
src/scripts/systems/audio_bus_manager.gd is an autoload-adjacent Node that applies the AudioBusConfig resource (src/resources/config/audio_bus_config.gd) to the AudioServer at scene start. It reads exported parameters and sets compressor thresholds, EQ band gains, reverb wet values, and LPF cutoffs on the live AudioServer buses.
Key exported config fields (added in spec 099):
- vehicle_compressor_threshold_db = -18.0
- vehicle_compressor_ratio = 4.0
- vehicle_compressor_gain_db = 2.0
- vehicle_eq_low_mid_gain_db = 2.0
- sfx_reverb_wet_min = 0.05
- sfx_reverb_wet_max = 0.40
- sfx_lpf_cutoff_min_hz = 800.0
- sfx_lpf_cutoff_max_hz = 20000.0
## SfxBusModulator
src/scripts/systems/sfx_bus_modulator.gd is a Node in src/scenes/main.tscn that drives disturbance-responsive DSP on the SFX bus. It connects to Events.disturbance_level_changed (not directly to Disturbance — Art VI R1) and linearly interpolates reverb wet and LPF cutoff as Disturbance climbs from 0 to 100.
At Disturbance = 0: reverb wet = sfx_reverb_wet_min (0.05), LPF cutoff = sfx_lpf_cutoff_max_hz (20000 Hz — fully open)
At Disturbance = 100: reverb wet = sfx_reverb_wet_max (0.40), LPF cutoff = sfx_lpf_cutoff_min_hz (800 Hz — muffled)
This creates a continuous sonic environment that communicates danger through increasing reverb and muffling without any UI element.
## Bus Assignments by Asset Category
- VehicleSFX: vehicle/engine, vehicle/tire, vehicle/exhaust, vehicle/wind, vehicle/damage, vehicle/collision/*, interaction/*
- SFX: player/footsteps, player/breathing, world/*, combat/*, garage, gate, ui
## Future Bus Expansion (spec 111 — not yet implemented)
[[111 Audio Bus Architecture]] gates the addition of 4 buses required by the world audio system (specs 104–110). **None of these buses exist in the current `default_bus_layout.tres`:**
| Bus | Effects | Purpose |
|---|---|---|
| Ambience | Large-room reverb | Biome beds, weather beds, all ambient layers |
| Proximity | Medium-room reverb | 3D-spatialized anomaly and L4 detail audio |
| Interior | Low-pass 800 Hz + small-room reverb | Ambience and Proximity routed through here when player is inside the bus |
| Horror | None — raw signal | Stingers, tension drone, Storm audio, The Silence heartbeat — bypasses all occlusion |
Until spec 111 is implemented, AudioStreamPlayers referencing these future buses silently fall through to Master.
## UI Bus (Spec 128)
A sixth bus named `UI` was added to `src/default_bus_layout.tres` by spec 128 (User Settings Persistence). It has no effects and routes directly to Master.
| Bus | Parent | Effects | Purpose |
|---|---|---|---|
| UI | Master | — | All menu button clicks and UI navigation sounds |
All button click sounds in the settings UI and main menu route through this bus via `UITheme.play_click()`. The bus volume is exposed as a slider in the Audio settings panel so players can independently mute or reduce interface sounds.
The `Settings` autoload creates the UI bus programmatically at runtime if it is absent from the editor's cached bus layout (i.e. sessions that predate spec 128).
## History
- Specs 001-047: 4 buses (Master, Radio, RadioDegraded, Ambience, Interior) — note: Ambience/Interior existed in planning but were never in the actual bus layout
- Spec 078: AudioBusConfig resource introduced for hardcoded-values remediation
- Spec 098: Radio + RadioDegraded buses formalized with effect constants in AudioBusManager
- Spec 099: VehicleSFX and SFX buses added; SfxBusModulator created; AudioBusConfig extended with 8 new fields
- Spec 128: UI bus added for interface sounds (menu clicks, button feedback)
- Spec 111 (pending): Ambience, Proximity, Interior, Horror buses — prerequisite for specs 104–110
## References
- src/default_bus_layout.tres
- src/scripts/systems/audio_bus_manager.gd
- src/scripts/systems/sfx_bus_modulator.gd
- src/resources/config/audio_bus_config.gd
- docs/vault/Specs/099 Sfx Pipeline Expansion/spec.md
- Constitution Appendix A (bus architecture)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-buses: status/reviewed
Tag assignment · test- Gold
- The note calls for `status/reviewed`: Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `status/reviewed` (False)
- Signal
- P(true) 0.35 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Audio — Buses.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — Buses
The Godot AudioServer bus architecture for Old Growth defines how all in-game audio is routed, processed, and mixed. Buses are configured in src/default_bus_layout.tres and applied at runtime by AudioBusManager.
## Bus Layout
The project uses 6 audio buses (`src/default_bus_layout.tres`) as of spec 128. Buses are listed below with their current effect chains:
| Bus | Parent | Effects | Purpose |
|---|---|---|---|---|
| Master | — | Limiter | Final output mix |
| Radio | Master | Highpass 300Hz, Lowpass 3500Hz, EQ 1kHz presence | AM radio signal simulation |
| RadioDegraded | Master | Highpass 500Hz, Lowpass 2800Hz, Echo 500ms | Degraded/distorted radio signal |
| VehicleSFX | Master | AudioEffectCompressor (threshold=-18dB, ratio=4:1), AudioEffectEQ10 (low-mid boost) | Vehicle-specific audio with dynamic compression |
| SFX | Master | AudioEffectReverb (wet=0.05), AudioEffectLowPassFilter (cutoff=20000Hz) | World and player SFX with disturbance-responsive DSP |
| UI | Master | — | All menu button clicks and UI navigation sounds (spec 128) |
## Effect Indices
Effect slot indices are constants in src/scripts/systems/audio_bus_manager.gd:
- VehicleSFX bus: slot 0 = AudioEffectCompressor, slot 1 = AudioEffectEQ
- SFX bus: slot 0 = AudioEffectReverb, slot 1 = AudioEffectLowPassFilter
- Radio bus: RADIO_REVERB_EFFECT_IDX = existing constant
## AudioBusManager
src/scripts/systems/audio_bus_manager.gd is an autoload-adjacent Node that applies the AudioBusConfig resource (src/resources/config/audio_bus_config.gd) to the AudioServer at scene start. It reads exported parameters and sets compressor thresholds, EQ band gains, reverb wet values, and LPF cutoffs on the live AudioServer buses.
Key exported config fields (added in spec 099):
- vehicle_compressor_threshold_db = -18.0
- vehicle_compressor_ratio = 4.0
- vehicle_compressor_gain_db = 2.0
- vehicle_eq_low_mid_gain_db = 2.0
- sfx_reverb_wet_min = 0.05
- sfx_reverb_wet_max = 0.40
- sfx_lpf_cutoff_min_hz = 800.0
- sfx_lpf_cutoff_max_hz = 20000.0
## SfxBusModulator
src/scripts/systems/sfx_bus_modulator.gd is a Node in src/scenes/main.tscn that drives disturbance-responsive DSP on the SFX bus. It connects to Events.disturbance_level_changed (not directly to Disturbance — Art VI R1) and linearly interpolates reverb wet and LPF cutoff as Disturbance climbs from 0 to 100.
At Disturbance = 0: reverb wet = sfx_reverb_wet_min (0.05), LPF cutoff = sfx_lpf_cutoff_max_hz (20000 Hz — fully open)
At Disturbance = 100: reverb wet = sfx_reverb_wet_max (0.40), LPF cutoff = sfx_lpf_cutoff_min_hz (800 Hz — muffled)
This creates a continuous sonic environment that communicates danger through increasing reverb and muffling without any UI element.
## Bus Assignments by Asset Category
- VehicleSFX: vehicle/engine, vehicle/tire, vehicle/exhaust, vehicle/wind, vehicle/damage, vehicle/collision/*, interaction/*
- SFX: player/footsteps, player/breathing, world/*, combat/*, garage, gate, ui
## Future Bus Expansion (spec 111 — not yet implemented)
[[111 Audio Bus Architecture]] gates the addition of 4 buses required by the world audio system (specs 104–110). **None of these buses exist in the current `default_bus_layout.tres`:**
| Bus | Effects | Purpose |
|---|---|---|
| Ambience | Large-room reverb | Biome beds, weather beds, all ambient layers |
| Proximity | Medium-room reverb | 3D-spatialized anomaly and L4 detail audio |
| Interior | Low-pass 800 Hz + small-room reverb | Ambience and Proximity routed through here when player is inside the bus |
| Horror | None — raw signal | Stingers, tension drone, Storm audio, The Silence heartbeat — bypasses all occlusion |
Until spec 111 is implemented, AudioStreamPlayers referencing these future buses silently fall through to Master.
## UI Bus (Spec 128)
A sixth bus named `UI` was added to `src/default_bus_layout.tres` by spec 128 (User Settings Persistence). It has no effects and routes directly to Master.
| Bus | Parent | Effects | Purpose |
|---|---|---|---|
| UI | Master | — | All menu button clicks and UI navigation sounds |
All button click sounds in the settings UI and main menu route through this bus via `UITheme.play_click()`. The bus volume is exposed as a slider in the Audio settings panel so players can independently mute or reduce interface sounds.
The `Settings` autoload creates the UI bus programmatically at runtime if it is absent from the editor's cached bus layout (i.e. sessions that predate spec 128).
## History
- Specs 001-047: 4 buses (Master, Radio, RadioDegraded, Ambience, Interior) — note: Ambience/Interior existed in planning but were never in the actual bus layout
- Spec 078: AudioBusConfig resource introduced for hardcoded-values remediation
- Spec 098: Radio + RadioDegraded buses formalized with effect constants in AudioBusManager
- Spec 099: VehicleSFX and SFX buses added; SfxBusModulator created; AudioBusConfig extended with 8 new fields
- Spec 128: UI bus added for interface sounds (menu clicks, button feedback)
- Spec 111 (pending): Ambience, Proximity, Interior, Horror buses — prerequisite for specs 104–110
## References
- src/default_bus_layout.tres
- src/scripts/systems/audio_bus_manager.gd
- src/scripts/systems/sfx_bus_modulator.gd
- src/resources/config/audio_bus_config.gd
- docs/vault/Specs/099 Sfx Pipeline Expansion/spec.md
- Constitution Appendix A (bus architecture)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-buses: type/system
Tag assignment · test- Gold
- The note calls for `type/system`: A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`) (True)
- ember
- The note has no reason to carry `type/system` (False)
- Signal
- P(true) 0.42 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Audio — Buses.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — Buses
The Godot AudioServer bus architecture for Old Growth defines how all in-game audio is routed, processed, and mixed. Buses are configured in src/default_bus_layout.tres and applied at runtime by AudioBusManager.
## Bus Layout
The project uses 6 audio buses (`src/default_bus_layout.tres`) as of spec 128. Buses are listed below with their current effect chains:
| Bus | Parent | Effects | Purpose |
|---|---|---|---|---|
| Master | — | Limiter | Final output mix |
| Radio | Master | Highpass 300Hz, Lowpass 3500Hz, EQ 1kHz presence | AM radio signal simulation |
| RadioDegraded | Master | Highpass 500Hz, Lowpass 2800Hz, Echo 500ms | Degraded/distorted radio signal |
| VehicleSFX | Master | AudioEffectCompressor (threshold=-18dB, ratio=4:1), AudioEffectEQ10 (low-mid boost) | Vehicle-specific audio with dynamic compression |
| SFX | Master | AudioEffectReverb (wet=0.05), AudioEffectLowPassFilter (cutoff=20000Hz) | World and player SFX with disturbance-responsive DSP |
| UI | Master | — | All menu button clicks and UI navigation sounds (spec 128) |
## Effect Indices
Effect slot indices are constants in src/scripts/systems/audio_bus_manager.gd:
- VehicleSFX bus: slot 0 = AudioEffectCompressor, slot 1 = AudioEffectEQ
- SFX bus: slot 0 = AudioEffectReverb, slot 1 = AudioEffectLowPassFilter
- Radio bus: RADIO_REVERB_EFFECT_IDX = existing constant
## AudioBusManager
src/scripts/systems/audio_bus_manager.gd is an autoload-adjacent Node that applies the AudioBusConfig resource (src/resources/config/audio_bus_config.gd) to the AudioServer at scene start. It reads exported parameters and sets compressor thresholds, EQ band gains, reverb wet values, and LPF cutoffs on the live AudioServer buses.
Key exported config fields (added in spec 099):
- vehicle_compressor_threshold_db = -18.0
- vehicle_compressor_ratio = 4.0
- vehicle_compressor_gain_db = 2.0
- vehicle_eq_low_mid_gain_db = 2.0
- sfx_reverb_wet_min = 0.05
- sfx_reverb_wet_max = 0.40
- sfx_lpf_cutoff_min_hz = 800.0
- sfx_lpf_cutoff_max_hz = 20000.0
## SfxBusModulator
src/scripts/systems/sfx_bus_modulator.gd is a Node in src/scenes/main.tscn that drives disturbance-responsive DSP on the SFX bus. It connects to Events.disturbance_level_changed (not directly to Disturbance — Art VI R1) and linearly interpolates reverb wet and LPF cutoff as Disturbance climbs from 0 to 100.
At Disturbance = 0: reverb wet = sfx_reverb_wet_min (0.05), LPF cutoff = sfx_lpf_cutoff_max_hz (20000 Hz — fully open)
At Disturbance = 100: reverb wet = sfx_reverb_wet_max (0.40), LPF cutoff = sfx_lpf_cutoff_min_hz (800 Hz — muffled)
This creates a continuous sonic environment that communicates danger through increasing reverb and muffling without any UI element.
## Bus Assignments by Asset Category
- VehicleSFX: vehicle/engine, vehicle/tire, vehicle/exhaust, vehicle/wind, vehicle/damage, vehicle/collision/*, interaction/*
- SFX: player/footsteps, player/breathing, world/*, combat/*, garage, gate, ui
## Future Bus Expansion (spec 111 — not yet implemented)
[[111 Audio Bus Architecture]] gates the addition of 4 buses required by the world audio system (specs 104–110). **None of these buses exist in the current `default_bus_layout.tres`:**
| Bus | Effects | Purpose |
|---|---|---|
| Ambience | Large-room reverb | Biome beds, weather beds, all ambient layers |
| Proximity | Medium-room reverb | 3D-spatialized anomaly and L4 detail audio |
| Interior | Low-pass 800 Hz + small-room reverb | Ambience and Proximity routed through here when player is inside the bus |
| Horror | None — raw signal | Stingers, tension drone, Storm audio, The Silence heartbeat — bypasses all occlusion |
Until spec 111 is implemented, AudioStreamPlayers referencing these future buses silently fall through to Master.
## UI Bus (Spec 128)
A sixth bus named `UI` was added to `src/default_bus_layout.tres` by spec 128 (User Settings Persistence). It has no effects and routes directly to Master.
| Bus | Parent | Effects | Purpose |
|---|---|---|---|
| UI | Master | — | All menu button clicks and UI navigation sounds |
All button click sounds in the settings UI and main menu route through this bus via `UITheme.play_click()`. The bus volume is exposed as a slider in the Audio settings panel so players can independently mute or reduce interface sounds.
The `Settings` autoload creates the UI bus programmatically at runtime if it is absent from the editor's cached bus layout (i.e. sessions that predate spec 128).
## History
- Specs 001-047: 4 buses (Master, Radio, RadioDegraded, Ambience, Interior) — note: Ambience/Interior existed in planning but were never in the actual bus layout
- Spec 078: AudioBusConfig resource introduced for hardcoded-values remediation
- Spec 098: Radio + RadioDegraded buses formalized with effect constants in AudioBusManager
- Spec 099: VehicleSFX and SFX buses added; SfxBusModulator created; AudioBusConfig extended with 8 new fields
- Spec 128: UI bus added for interface sounds (menu clicks, button feedback)
- Spec 111 (pending): Ambience, Proximity, Interior, Horror buses — prerequisite for specs 104–110
## References
- src/default_bus_layout.tres
- src/scripts/systems/audio_bus_manager.gd
- src/scripts/systems/sfx_bus_modulator.gd
- src/resources/config/audio_bus_config.gd
- docs/vault/Specs/099 Sfx Pipeline Expansion/spec.md
- Constitution Appendix A (bus architecture)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-development-workbench: domain/governance
Tag assignment · dev- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.30 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Code/Tools — Development Workbench.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — Development Workbench
The generative domain workbench is the entire development ecosystem that produces Old Growth. It spans persistent AI agents (personas), session-based AI agents (OpenCode + subagents), the vault knowledge graph, CI/CD automation, external infrastructure, and the in-game generation pipeline itself. This note maps every layer, their connections, and data flows — the meta-architecture of how the game is built.
## Layer Overview
```
┌──────────────────────────────────────────────────────────────────────┐
│ HUMAN INTERFACE │
│ Slack (@mentions) GitHub (PRs/Issues) Terminal │
└──────────┬──────────────────────────┬──────────────────────┬──────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────┐
│ PERSISTENT AI │ │ SESSION AI │ │ DEV TOOLS │
│ (Persona Layer) │ │ (Agent Layer) │ │ (Human-op) │
├──────────────────────┤ ├──────────────────────┤ ├──────────────────┤
│ Coordinator Persona │ │ Sisyphus │ │ Godot Editor │
│ ─ routes tasks via │ │ ─ orchestrator │ │ gh CLI │
│ Slack @mentions │ │ sub-agents: │ │ make targets │
│ │ │ explore, librarian, │ │ cargo / pytest │
│ GTM Persona │ │ oracle, metis, momus │ │ obsidian │
│ ─ release notes + │ │ Sisyphus-Junior │ │ blender │
│ approval workflow │ │ Skills system │ │ comfyui │
│ │ │ MCP servers │ │ │
│ Engineer (v0.5) │ │ │ │ launchd │
│ Narrative (v0.2) │ │ │ │ ngrok │
│ Playtest (v0.5) │ │ │ │ valkey │
├──────────────────────┤ ├──────────────────────┤ └──────────────────┘
│ Letta Server │ │ OpenCode + omo │
│ (persistent memory) │ │ MCP servers: │
│ Core/Archival/ │ │ vault, gdai-mcp, │
│ Recall memory │ │ blender, comfyui │
└──────────┬───────────┘ └──────────┬────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────────────┐
│ KNOWLEDGE / SPEC LAYER │
├──────────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ VAULT KNOWLEDGE GRAPH │ │
│ │ docs/vault/ — 1000+ notes organized in 3 tiers: │ │
│ │ Tier 1: Governance, Design, Systems, Code, Specs, │ │
│ │ Reference, Sessions, Decisions, Discoveries │ │
│ │ Tier 2: Audio, Vehicle, World, Player, Anomalies, │ │
│ │ Narrative, Tooling │ │
│ │ Tier 3: Disturbance, Biome, Horror, Diegesis, │ │
│ │ Ephemerality, Procedural Generation, Contamination │ │
│ │ Read/write via mcpvault MCP server (opencode.json) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ SPEC KIT (speckit.*) │ │ CONSTITUTION (Governance/) │ │
│ │ clarify → specify → │ │ Supreme authority: │ │
│ │ plan → tasks → implement│ │ Constitution > Design > Specs │ │
│ │ → analyze │ │ Personas cannot amend │ │
│ │ 139 specs in │ │ Human-only: version bumps, │ │
│ │ docs/vault/Specs/NNN/ │ │ merges, canonical status │ │
│ └─────────────────────────┘ └─────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ AGENTS.md — Behavioral constitution for all agents │ │
│ │ ─ Agent Behavior Foundation (4 principles) │ │
│ │ ─ Architecture rules, vault read/write protocol │ │
│ │ ─ Code style, testing conventions, build commands │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ SEMANTIC DRIFT PIPELINE (spec 088) │ │
│ │ Phase 1 (Mechanical): vault_audit.py │ │
│ │ — schema validation, wikilink resolution, code-refs check │ │
│ │ Phase 2 (Auto-fix): --apply flag │ │
│ │ — stale markers, broken refs, missing spec stubs │ │
│ │ Phase 3 (Semantic): vault-audit-semantic agent │ │
│ │ — claim taxonomy (exact/range/approx/list/description) │ │
│ │ CI: weekly Sunday + per-PR on docs/vault/ changes │ │
│ │ Output: rolling vault/audit PR with auto-fixes │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ CI/CD / AUTOMATION LAYER │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────┐ ┌────────────────────────────┐ │
│ │ GitHub Actions (8 workflows) │ │ Makefile (40+ targets) │ │
│ │ │ │ │ │
│ │ ci-fast — merge gate │ │ make all — full CI │ │
│ │ ci-basics — full pipeline │ │ make pre-commit — fast │ │
│ │ (manual approval gate) │ │ make test — GUT headless │ │
│ │ release — ship DMG + R2 │ │ make rust-build/test │ │
│ │ vault-audit — PR + weekly │ │ make world-preview/qa │ │
│ │ deploy-site — GitHub Pages │ │ make observer-* — QA │ │
│ │ persona-tests — middleware │ │ make personas-up/down │ │
│ │ auto-bump — safety net │ │ make bump-patch/minor/mjr │ │
│ │ component-semver-autofix │ │ make wiki-serve/build │ │
│ └────────────────────────────────┘ └────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Pre-commit Hook (scripts/hooks/pre-commit) │ │
│ │ ├── Component SemVer auto-bump (patch) │ │
│ │ ├── Game version format check │ │
│ │ ├── Asset .gdignore check │ │
│ │ ├── GDScript format-check + lint │ │
│ │ └── Rust tests + clippy (optional) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE LAYER │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │
│ │ macOS Dev Machine │ │ Cloudflare R2 │ │ GitHub Pages │ │
│ │ (all-local) │ │ ─ releases │ │ ─ Jekyll site │ │
│ │ │ │ ─ sccache cache │ │ │ │
│ │ Valkey 8.0:6379 │ │ ─ downloads. │ │ ─ shapeandshare│ │
│ │ Letta Docker:8283 │ │ playoldgrowth │ │ .github.io │ │
│ │ OTel Collector │ └──────────────────┘ └─────────────────┘ │
│ │ ngrok tunnel │ │
│ │ OpenCode serve │ ┌──────────────────┐ ┌─────────────────┐ │
│ └────────────────────┘ │ LLM Providers │ │ Slack │ │
│ │ ─ OpenRouter │ │ ─ Socket Mode │ │
│ launchd Services │ ─ Anthropic │ │ ─ Per-persona │ │
│ ├── Letta server │ (failover) │ │ apps │ │
│ ├── Coordinator runtime │ └──────────────────┘ └─────────────────┘ │
│ ├── GTM runtime │ │
│ ├── ngrok agent │ ┌──────────────────────────────────────┐ │
│ ├── OTel collector │ │ Apple Services │ │
│ └── (future: more) │ │ ─ Developer ID signing │ │
│ │ │ ─ Notarization │ │
│ │ │ ─ DMG packaging │ │
│ │ └──────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ GENERATIVE CONTENT PIPELINES │
├──────────────────────────────────────────────────────────────────────┤
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ TTS Pipeline │ │ Music │ │ Blender 3D │ │
│ │ tools/tts/ │ │ Pipeline │ │ tools/blender/│ │
│ │ Kokoro, │ │ Suno → genre │ │ Python mesh │ │
│ │ Chatterbox, │ │ filter chain │ │ separation, │ │
│ │ ElevenLabs SFX│ │ → audio │ │ LOD, GLB │ │
│ │ → radio clips │ │ submodule │ │ → game assets│ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ ┌───────┴───────┐ ┌──────┴────────┐ ┌──────┴────────┐ │
│ │ ComfyUI │ │ Observer QA │ │ DMG Builder │ │
│ │ tools/comfyui │ │ tools/observer│ │ tools/dmg/ │ │
│ │ SDXL img2img │ │ Claude screenshot│ │ Background │ │
│ │ → PBR textures│ │ + audio analysis │ │ .DS_Store │ │
│ │ → submodule │ │ → ci quality gate│ │ → signed DMG │ │
│ └───────────────┘ └─────────────────┘ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ GENERATED OUTPUT (THE GAME) │
├──────────────────────────────────────────────────────────────────────┤
│ In-game world generation pipeline, runtime systems, autoloads, │
│ shipped assets (audio OGGs, 3D GLBs, textures, radio data) │
│ (see [[World — Procedural Generation]] and [[Generation Subsystem Pairs]] for the │
│ full in-game architecture) │
└──────────────────────────────────────────────────────────────────────┘
```
## Layer Details
### 1. Persistent AI Layer (Personas)
Two active persona runtimes, with three more planned (v0.5):
| Persona | Role | Status | Agent ID | Port |
|---|---|---|---|---|
| Coordinator | Task routing, human↔system bridge | Active | `agent-420c0db8-...` | 9100 |
| GTM | Release notes, devlog drafting, approval workflow | Active | `agent-ccd29034-...` | 9101 |
| Engineer | Code implementation, spec shepherding | v0.5 | — | — |
| Narrative Director | Tone, canon, KTLK voice | v0.2 | — | — |
| Playtest Coordinator | Determinism verification, regression detection | v0.5 | — | — |
Each persona has structured identity files in `docs/vault/Reference/Autonomous Company/personas/<name>/`:
- `SOUL.md` — identity, voice, tone
- `CHARTER.md` — authority boundaries, permitted actions
- `INTENT.md` — priorities and goals
- `AGENTS.md` — operational runbook
- `MEMORY.md` — conversation history (via Letta)
**Runtime architecture** (Python, in `personas/`):
- `runtime.py` — Bolt framework with Socket Mode WebSocket connection
- `letta_bridge.py` — forwards Slack @mentions to Letta REST API (`http://localhost:8283`)
- `letta_agents.json` — maps persona name to Letta agent ID
- `opencode_client.py` — HTTP client for OpenCode serve (`http://localhost:8080`)
- `telemetry.py` — OTel span emission, Prometheus `/metrics`
- `shutdown.py` — graceful shutdown on token revocation (kill-switch)
- `approval_callback.py` — GTM's class-B approval workflow handler
- `config.py` — .env-based configuration (Slack tokens, API keys, ports)
**Data flows**:
- `@mention` → Slack Socket Mode → runtime.py → letta_bridge.py → Letta agent → LLM (OpenRouter/Anthropic) → response → Slack
- Code-touching tasks → OpenCode HTTP API → session (create/send/poll/close) → result → Slack
### 2. Session AI Layer (OpenCode + Subagents)
| Component | Description |
|---|---|
| Sisyphus | Primary orchestrator agent — decomposes tasks, delegates to subagents, verifies output |
| explore | Contextual grep — finds code patterns, module structure, cross-layer dependencies |
| librarian | External reference grep — searches docs, OSS examples, API specs |
| oracle | Read-only high-IQ consultant — complex architecture, debugging, tradeoff analysis |
| metis | Pre-planning consultant — identifies hidden intentions, ambiguities, AI failure points |
| momus | Plan critic — evaluates work plans for clarity, verifiability, completeness |
| Sisyphus-Junior | Focused task executor (same discipline, no delegation) — used across categories |
| multimodal-looker | Visual analysis of screenshots via Claude vision |
**Task categories** (domain-optimized models): `visual-engineering`, `artistry`, `ultrabrain`, `deep`, `quick`, `unspecified-low/high`, `writing`
**MCP servers** (configured in `opencode.json`):
- `gdai-mcp-server` — Godot editor runtime access (play scene, screenshot, editor scripts, node search)
- `mcpvault` (obsidian MCP) — vault read/write, note search, graph queries
- `blender` — 3D asset pipeline automation
- `comfyui` — AI image generation
**Skills system** (`.opencode/skills/`):
- `observer-qa` — Visual QA pipeline, agent trigger matrix, screenshot analysis
- `audio-assets` — TTS generation, audio submodule, provenance
- `introspection` — Vault health checks, semantic drift analysis
- `spec-management` — Spec creation, repositioning, specify workflow
- `blog-posts` — Jekyll devlog authoring, time-release scheduling
### 3. Knowledge / Spec Layer
See `docs/vault/README.md` and the vault structure at `docs/vault/` for full documentation. Key subsystems:
- **Vault knowledge graph**: 260+ notes across 15+ top-level directories, controlled vocabulary (`_meta/tags.md`), 3-tier MOC structure, agent note template (`_meta/templates/agent-note.md`)
- **Spec Kit**: `speckit.*` commands in `.opencode/command/` — clarify → specify → plan → tasks → implement → analyze workflow. 139 specs in `docs/vault/Specs/NNN Title/` with artifacts (plan.md, tasks.md, research.md, data-model.md, contracts/)
- **Semantic drift pipeline**: Three-phase audit (mechanical → auto-fix → semantic) with CI integration, weekly Sunday runs, rolling `vault/audit` PR
- **Constitution**: `docs/vault/Governance/Constitution.md` — supreme authority, Article III (Rust-first), Article IV (seed determinism), Article XIV (vault governance)
### 4. CI/CD Layer
Eight GitHub Actions workflows orchestrate all automation:
| Workflow | Trigger | Purpose | Required for merge |
|---|---|---|---|
| `ci-fast.yml` | PR to main | Fast checks: workflow lint, version bump, semver, GDScript quality, Rust clippy | Yes |
| `ci-basics.yml` | PR to main | Full pipeline: manual gate, Rust tests, GUT + headless (Linux), macOS smoke | Yes (human PRs) |
| `release.yml` | Version change on main | Universal DMG: build, sign, notarize, upload to R2, publish release | No (post-merge) |
| `vault-audit.yml` | PR on docs/vault/ + weekly Sun | Mechanical audit + optional semantic drift → rolling PR | Yes (vault changes) |
| `deploy-site.yml` | Push on site/ changes + daily | Jekyll build → GitHub Pages deploy | No |
| `persona-tests.yml` | PR on personas/ | pytest middleware tests | Yes (persona changes) |
| `auto-bump.yml` | Merge to main (conditional) | Safety-net: auto patch bump PR if missing | No |
| `component-semver-autofix.yml` | PR on mapped files | Auto-bump component versions if stale | No |
**Pre-commit hook** (`scripts/hooks/pre-commit`): runs semver, game-version-check, gdignore-check, format-check, lint — with auto-bump for component versions.
**Component SemVer** (`src/component_versions.json`): files mapped to components with version enforcement. Pre-commit hook auto-bumps patch; CI rejects un-bumped changes.
### 5. Infrastructure Layer
| Service | Purpose | Auth | Connection |
|---|---|---|---|
| Valkey 8.0 | Local event bus, `releases.shipped` stream | `requirepass` | Local port 6379, ngrok tunnel 24971 |
| Letta Server | Persistent agent memory (Docker) | None (localhost) | Port 8283 |
| OTel Collector | Observability spans (gRPC) | None (localhost) | Port 4317 |
| ngrok | TCP tunnel for CI → local Valkey | Auth token | `1.tcp.us-cal-1.ngrok.io:24971` |
| OpenCode serve | REST API for agent sessions | Bearer token | Port 8080 |
| Cloudflare R2 | Build artifact storage + sccache cache | R2 API keys | `downloads.playoldgrowth.com` |
| GitHub Pages | Jekyll site (`shapeandshare.github.io`) | OIDC | Automatic deploy |
| Slack (Socket Mode) | Persona communication surface | Bot tokens (xoxb-) | Outbound WebSocket |
| OpenRouter | Primary LLM provider | API key | Outbound HTTPS |
| Anthropic | LLM failover provider | API key | Outbound HTTPS |
| Apple Developer ID | Code signing + notarization | Cert + app password | Per-release CI |
**launchd-managed services** (auto-start on login):
- Letta Docker server
- Coordinator runtime (port 9100)
- GTM runtime (port 9101 + `/internal/approval-callback`)
- ngrok agent (TCP tunnel)
- OTel collector (gRPC ingest)
**Environment management**:
- `.env` — runtime secrets (Slack tokens, API keys, Valkey auth)
- GitHub Secrets — CI secrets (R2 keys, Apple certs, BUMP_PAT)
- `~/Library/Keychains/` — Apple Developer ID certificate (CI-imported)
- `docs/vault/Reference/Autonomous Company/Slack App Registration Runbook.md` — token provisioning
### 6. In-Game Generation Layer
The output of the workbench. See for full architecture:
- [[World — Procedural Generation]] — 11-step pipeline, RNG sub-seed derivation, subsystem details
- [[Generation Subsystem Pairs]] — complete inventory of every GDScript+Rust+bridge triple
### 7. Generative Content Pipelines
Six content creation pipelines produce game assets that ship in the `src/assets/` submodule. All are dev-time only — none run in the game binary.
#### TTS Pipeline (`tools/tts/`)
Three TTS engines, one post-processing chain, one submodule install step:
```
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Kokoro 82M │ │ Chatterbox │ │ ElevenLabs SFX │
│ (neutral) │ │ (character) │ │ (ambient SFX) │
│ .venv, no │ │ conda, GPU │ │ REST API │
│ GPU needed │ │ required │ │ key in .env │
└──────┬───────┘ └──────┬───────┘ └───────┬──────────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────────────────────┐
│ Gold WAV (48 kHz / 24-bit) │
│ → postprocess.sh (ffmpeg) → Master OGG │
│ → HQ OGG (320 kbps) │
│ → LQ OGG (96 kbps) │
│ → install to oldgrowth-assets submodule │
│ → validate naming + provenance │
└────────────────────────────────────────────────────────────┘
```
**Engines**: Kokoro 82M (neutral voice, lightweight, CPU), Chatterbox (voice cloning via conda+GPU for Gary/character voices), ElevenLabs SFX V2 API (weather ambient soundscapes)
**Script manifests**: 9 DJ station manifests (KOLD, KHRN, KFRO, KDRM, KDRN, KPGN, KJAZ, KVPR, KGSP), KTLK Gary dialogue (94 clips across 4 tiers), news scripts, music discovery manifests
**Validation**: 6 validate scripts (`validate_provenance.sh`, `validate_naming.sh`, etc.) for naming conventions and provenance metadata schema
**Makefile targets**: `make tts-gary`, `make tts-status`, `make tts-generate-all`
Key vault docs: [[Tools — TTS]], [[Audio — Pipeline]]
#### Music Pipeline (`tools/tts/music_process.sh`)
Suno-generated music → gold master → genre-specific post-processing → OGG ship → submodule install.
```
Suno (Pro/Premier for commercial rights)
→ Download MP3 → Convert to 48 kHz/24-bit WAV gold master
→ 11 genre-specific ffmpeg filter chains (per-station EQ/compression/character)
→ Master OGG + HQ OGG + LQ OGG
→ Install to oldgrowth-assets submodule
```
**Target**: 13 stations total (11 music + KTLK talk + KNWS news) — ~200–260 music tracks**
**Per-genre filter chains** (11 music stations): Dark Folk, Haunted Americana, Nordic Experimental, Dream Pop, Dark Ambient, Pagan Folk, Jazz, Vaporwave, Dark Gospel, Blues-Punk, PNW Garage-Psych (Note: KTLK 93.3 and KNWS 103.7 are talk/news — no music filter chains)
**Per-genre filter chains**: Dark Folk, Haunted Americana, Nordic Experimental, Dream Pop, Dark Ambient, Pagan Folk, Jazz, Vaporwave, Dark Gospel, Blues-Punk, PNW Garage-Psych
Key vault docs: [[Tools — Music Pipeline]], [[Audio — Radio Stations]]
#### Blender 3D Pipeline (`tools/blender/`)
Python automation for 3D asset processing, executed via MCP (`blender` in `opencode.json`):
```
bus_modified.blend
→ t011_separate_panels.py — 44 panel mesh separation from 8 sources
→ t012_uv_unwrap.py — UV0_Damage layer + Smart UV Project
→ t013_transforms.py — world-space matrix bake, dimension verify
→ t014_lod.py — LOD0/LOD1/LOD2 with Decimate modifiers
→ t015_export_glbs.py — GLB export with embedded materials
→ t016_renders.py — texture reference renders
```
**Makefile**: `make blender-pipeline`, `make blender-stage STAGE=t014`
Key vault doc: [[Tools — Blender]]
#### ComfyUI AI Texture Pipeline (`tools/comfyui/`)
AI-assisted PBR texture generation via SDXL + upscaler:
```
Blender renders (1024×1024 PNG)
→ ComfyUI Desktop (localhost:8000) via MCP or filesystem copy
→ SDXL img2img (sd_xl_base_1.0.safetensors)
→ RealESRGAN_x4plus 4× upscale → 4096×4096
→ Derive roughness/normal/metallic PBR maps
→ Install to src/assets/bus_model/textures/varieties/<variety>/
→ Commit to oldgrowth-assets submodule
```
Key vault doc: [[Tools — ComfyUI]]
#### Observer Visual QA Pipeline (`tools/observer/`)
Anthropic Claude-powered visual QA for generation changes:
```
Capture modes:
make observer-snapshot-qa — 1 frame, immediate analysis
make observer-sequence-qa — 32 frames @ 8fps, temporal analysis
make observer-ci — CI quality gate (exits 2 on failure)
Analysis:
analyze.py → schema v1.2 JSON output
- overall_quality (good/acceptable/degraded/broken)
- recommendation (ship_it/review_required/fix_before_merge/blocking_issue)
- bugs array with severity (critical/high/medium/low)
- scene_description, audio_quality (when frame.wav present)
Decision matrix (from `observer-qa` skill):
| Quality | Recommendation | Action |
|---|---|---|
| good | ship_it | No action |
| good | review_required| Post PR comment; do not block |
| acceptable | ship_it | No action |
| acceptable | review_required | Post PR comment; do not block |
| acceptable | fix_before_merge | Create issue, note in PR, block merge |
| poor | any | Create issue, block merge, investigate |
| any | blocking_issue | Halt, file critical issue, escalate to human |
| any | (bug severity: critical) | Escalate regardless of recommendation |
| any | (scene_correct: false) | Treat as at least fix_before_merge |
| any | (clipping_detected: true with high-severity bug) | Treat as fix_before_merge |
```
Key vault docs: [[Tools — Observer]], `.opencode/skills/SKILL.md` (observer-qa skill)
#### DMG Builder Pipeline (`tools/dmg/`)
macOS installer packaging tools:
- `generate_background.py` — DMG background image generation
- `fix_ds_store.py` — .DS_Store position restoration
- `validate_assets.py` — DMG asset integrity check
Used by `make dmg` and `make dmg-signed` targets (wraps `scripts/ci/sign_dmg.sh`).
## Key Data Flows
### Release flow
```
Developer bumps version in src/project.godot
→ Commit + merge to main
→ release.yml triggered
→ Build universal Rust dylib (aarch64 + x86_64 via lipo)
→ Export Godot app (macOS preset)
→ Sign with Developer ID
→ Notarize via Apple
→ Package DMG
→ Upload to Cloudflare R2 (releases bucket)
→ Publish GitHub Release (draft → published)
→ XADD releases.shipped → Valkey stream (via ngrok tunnel)
→ GTM runtime polls Valkey (XREADGROUP)
→ Draft release notes via LLM
→ Post to #announce-releases with Approve/Reject buttons
→ On approval: push Jekyll devlog post
→ Push release metadata PR (site/_data/release.yml)
```
### Vault audit flow
```
PR touches docs/vault/ or weekly Sunday 6am UTC
→ vault-audit.yml triggered
→ Phase 1: python vault_audit.py --apply
→ Schema validation, wikilink fixes, code-refs check
→ Phase 2 (weekly only): opencode vault-audit-semantic
→ Claim taxonomy analysis on notes with code-refs
→ Commit to vault/audit branch
→ Create/update rolling PR against main
```
### Development session flow
```
Human request (Slack @mention or terminal)
→ Persona runtime (Coordinator)
→ If code-touching task:
→ OpenCode HTTP API → session created
→ Sisyphus orchestrator activates
→ Decomposes task → spawns subagents (explore, librarian, oracle)
→ Delegates implementation to Sisyphus-Junior agents
→ Verifies results (LSP diagnostics, make targets)
→ Vault write-back for discoveries/decisions
→ Returns result to persona runtime
→ Response posted to Slack
→ If non-code task:
→ Letta agent processes directly
→ Response posted to Slack
```
## Network Port Map
| Port | Service | Protocol | Auth | Notes |
|---|---|---|---|---|
| 6379 | Valkey | TCP/RESP | requirepass | Local data store |
| 8283 | Letta Server | HTTP/REST | None (localhost) | Agent memory + LLM dispatch |
| 8080 | OpenCode serve | HTTP/REST | Bearer token | Session/message API |
| 9100 | Coordinator health | HTTP | None | `/metrics` for Prometheus |
| 9101 | GTM health | HTTP | None | `/metrics` + `/internal/approval-callback` |
| 4317 | OTel Collector | gRPC | None | OTLP ingest |
| 4040 | ngrok dashboard | HTTP | None | Local tunnel admin |
| 24971 | ngrok → Valkey | TCP/RESP | requirepass | CI tunnel (reserved endpoint) |
| 443 | Slack API | WebSocket | xapp- token | Socket Mode connection |
## Security Model
- **Token isolation**: Each persona has its own Slack bot token (xoxb-) and app token (xapp-). Cross-persona token use is a violation (Article XV).
- **OAuth minimum-necessary**: Each persona requests the minimum OAuth scopes for its charter. Over-scoping is a kill criterion (SC-008).
- **Two-path revocation**: Kill-switch via Slack Web API (`apps.uninstall`) or admin UI. Both paths invalidate tokens immediately.
- **LLM key isolation**: OpenRouter and Anthropic keys shared at v0.1 but revoked as part of kill-switch sequence per persona.
- **No public HTTP endpoints**: Socket Mode eliminates inbound HTTP. Only inbound connection is the ngrok TCP tunnel (Valkey protocol only, `requirepass`).
- **CI secrets isolation**: BUMP_PAT (fine-grained, contents+pullrequests+workflows:write) ≠ GITHUB_TOKEN (auto, contents:write). Token split prevents privilege escalation. SUBMODULE_PAT removed — asset submodule eliminated (spec 146).
## Related
- [[World — Procedural Generation]] — the in-game generation pipeline
- [[Generation Subsystem Pairs]] — GDScript+Rust delegation pattern
- [[CI Pipeline]] — CI/CD workflow inventory
- [[Release and Distribution]] — release pipeline details
- [[Autonomous Company Architecture]] — persona/Letta system architecture
- [[Build System]] — local Makefile targets
- [[Tools — Observer]] — visual QA pipeline
- [[Tools — Vault Audit]] — vault mechanical audit tooling
- [[Code]] — folder MOC
- [[Tooling — Domain MOC]] — domain MOC
- [[Constitution]] — authority hierarchy, Article III, Article IV, Article XIV
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-development-workbench: type/code
Tag assignment · dev- Gold
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- ember
- The note has no reason to carry `type/code` (False)
- Signal
- P(true) 0.16 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Code/Tools — Development Workbench.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — Development Workbench
The generative domain workbench is the entire development ecosystem that produces Old Growth. It spans persistent AI agents (personas), session-based AI agents (OpenCode + subagents), the vault knowledge graph, CI/CD automation, external infrastructure, and the in-game generation pipeline itself. This note maps every layer, their connections, and data flows — the meta-architecture of how the game is built.
## Layer Overview
```
┌──────────────────────────────────────────────────────────────────────┐
│ HUMAN INTERFACE │
│ Slack (@mentions) GitHub (PRs/Issues) Terminal │
└──────────┬──────────────────────────┬──────────────────────┬──────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────┐
│ PERSISTENT AI │ │ SESSION AI │ │ DEV TOOLS │
│ (Persona Layer) │ │ (Agent Layer) │ │ (Human-op) │
├──────────────────────┤ ├──────────────────────┤ ├──────────────────┤
│ Coordinator Persona │ │ Sisyphus │ │ Godot Editor │
│ ─ routes tasks via │ │ ─ orchestrator │ │ gh CLI │
│ Slack @mentions │ │ sub-agents: │ │ make targets │
│ │ │ explore, librarian, │ │ cargo / pytest │
│ GTM Persona │ │ oracle, metis, momus │ │ obsidian │
│ ─ release notes + │ │ Sisyphus-Junior │ │ blender │
│ approval workflow │ │ Skills system │ │ comfyui │
│ │ │ MCP servers │ │ │
│ Engineer (v0.5) │ │ │ │ launchd │
│ Narrative (v0.2) │ │ │ │ ngrok │
│ Playtest (v0.5) │ │ │ │ valkey │
├──────────────────────┤ ├──────────────────────┤ └──────────────────┘
│ Letta Server │ │ OpenCode + omo │
│ (persistent memory) │ │ MCP servers: │
│ Core/Archival/ │ │ vault, gdai-mcp, │
│ Recall memory │ │ blender, comfyui │
└──────────┬───────────┘ └──────────┬────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────────────┐
│ KNOWLEDGE / SPEC LAYER │
├──────────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ VAULT KNOWLEDGE GRAPH │ │
│ │ docs/vault/ — 1000+ notes organized in 3 tiers: │ │
│ │ Tier 1: Governance, Design, Systems, Code, Specs, │ │
│ │ Reference, Sessions, Decisions, Discoveries │ │
│ │ Tier 2: Audio, Vehicle, World, Player, Anomalies, │ │
│ │ Narrative, Tooling │ │
│ │ Tier 3: Disturbance, Biome, Horror, Diegesis, │ │
│ │ Ephemerality, Procedural Generation, Contamination │ │
│ │ Read/write via mcpvault MCP server (opencode.json) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ SPEC KIT (speckit.*) │ │ CONSTITUTION (Governance/) │ │
│ │ clarify → specify → │ │ Supreme authority: │ │
│ │ plan → tasks → implement│ │ Constitution > Design > Specs │ │
│ │ → analyze │ │ Personas cannot amend │ │
│ │ 139 specs in │ │ Human-only: version bumps, │ │
│ │ docs/vault/Specs/NNN/ │ │ merges, canonical status │ │
│ └─────────────────────────┘ └─────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ AGENTS.md — Behavioral constitution for all agents │ │
│ │ ─ Agent Behavior Foundation (4 principles) │ │
│ │ ─ Architecture rules, vault read/write protocol │ │
│ │ ─ Code style, testing conventions, build commands │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ SEMANTIC DRIFT PIPELINE (spec 088) │ │
│ │ Phase 1 (Mechanical): vault_audit.py │ │
│ │ — schema validation, wikilink resolution, code-refs check │ │
│ │ Phase 2 (Auto-fix): --apply flag │ │
│ │ — stale markers, broken refs, missing spec stubs │ │
│ │ Phase 3 (Semantic): vault-audit-semantic agent │ │
│ │ — claim taxonomy (exact/range/approx/list/description) │ │
│ │ CI: weekly Sunday + per-PR on docs/vault/ changes │ │
│ │ Output: rolling vault/audit PR with auto-fixes │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ CI/CD / AUTOMATION LAYER │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────┐ ┌────────────────────────────┐ │
│ │ GitHub Actions (8 workflows) │ │ Makefile (40+ targets) │ │
│ │ │ │ │ │
│ │ ci-fast — merge gate │ │ make all — full CI │ │
│ │ ci-basics — full pipeline │ │ make pre-commit — fast │ │
│ │ (manual approval gate) │ │ make test — GUT headless │ │
│ │ release — ship DMG + R2 │ │ make rust-build/test │ │
│ │ vault-audit — PR + weekly │ │ make world-preview/qa │ │
│ │ deploy-site — GitHub Pages │ │ make observer-* — QA │ │
│ │ persona-tests — middleware │ │ make personas-up/down │ │
│ │ auto-bump — safety net │ │ make bump-patch/minor/mjr │ │
│ │ component-semver-autofix │ │ make wiki-serve/build │ │
│ └────────────────────────────────┘ └────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Pre-commit Hook (scripts/hooks/pre-commit) │ │
│ │ ├── Component SemVer auto-bump (patch) │ │
│ │ ├── Game version format check │ │
│ │ ├── Asset .gdignore check │ │
│ │ ├── GDScript format-check + lint │ │
│ │ └── Rust tests + clippy (optional) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE LAYER │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │
│ │ macOS Dev Machine │ │ Cloudflare R2 │ │ GitHub Pages │ │
│ │ (all-local) │ │ ─ releases │ │ ─ Jekyll site │ │
│ │ │ │ ─ sccache cache │ │ │ │
│ │ Valkey 8.0:6379 │ │ ─ downloads. │ │ ─ shapeandshare│ │
│ │ Letta Docker:8283 │ │ playoldgrowth │ │ .github.io │ │
│ │ OTel Collector │ └──────────────────┘ └─────────────────┘ │
│ │ ngrok tunnel │ │
│ │ OpenCode serve │ ┌──────────────────┐ ┌─────────────────┐ │
│ └────────────────────┘ │ LLM Providers │ │ Slack │ │
│ │ ─ OpenRouter │ │ ─ Socket Mode │ │
│ launchd Services │ ─ Anthropic │ │ ─ Per-persona │ │
│ ├── Letta server │ (failover) │ │ apps │ │
│ ├── Coordinator runtime │ └──────────────────┘ └─────────────────┘ │
│ ├── GTM runtime │ │
│ ├── ngrok agent │ ┌──────────────────────────────────────┐ │
│ ├── OTel collector │ │ Apple Services │ │
│ └── (future: more) │ │ ─ Developer ID signing │ │
│ │ │ ─ Notarization │ │
│ │ │ ─ DMG packaging │ │
│ │ └──────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ GENERATIVE CONTENT PIPELINES │
├──────────────────────────────────────────────────────────────────────┤
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ TTS Pipeline │ │ Music │ │ Blender 3D │ │
│ │ tools/tts/ │ │ Pipeline │ │ tools/blender/│ │
│ │ Kokoro, │ │ Suno → genre │ │ Python mesh │ │
│ │ Chatterbox, │ │ filter chain │ │ separation, │ │
│ │ ElevenLabs SFX│ │ → audio │ │ LOD, GLB │ │
│ │ → radio clips │ │ submodule │ │ → game assets│ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ ┌───────┴───────┐ ┌──────┴────────┐ ┌──────┴────────┐ │
│ │ ComfyUI │ │ Observer QA │ │ DMG Builder │ │
│ │ tools/comfyui │ │ tools/observer│ │ tools/dmg/ │ │
│ │ SDXL img2img │ │ Claude screenshot│ │ Background │ │
│ │ → PBR textures│ │ + audio analysis │ │ .DS_Store │ │
│ │ → submodule │ │ → ci quality gate│ │ → signed DMG │ │
│ └───────────────┘ └─────────────────┘ └────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ GENERATED OUTPUT (THE GAME) │
├──────────────────────────────────────────────────────────────────────┤
│ In-game world generation pipeline, runtime systems, autoloads, │
│ shipped assets (audio OGGs, 3D GLBs, textures, radio data) │
│ (see [[World — Procedural Generation]] and [[Generation Subsystem Pairs]] for the │
│ full in-game architecture) │
└──────────────────────────────────────────────────────────────────────┘
```
## Layer Details
### 1. Persistent AI Layer (Personas)
Two active persona runtimes, with three more planned (v0.5):
| Persona | Role | Status | Agent ID | Port |
|---|---|---|---|---|
| Coordinator | Task routing, human↔system bridge | Active | `agent-420c0db8-...` | 9100 |
| GTM | Release notes, devlog drafting, approval workflow | Active | `agent-ccd29034-...` | 9101 |
| Engineer | Code implementation, spec shepherding | v0.5 | — | — |
| Narrative Director | Tone, canon, KTLK voice | v0.2 | — | — |
| Playtest Coordinator | Determinism verification, regression detection | v0.5 | — | — |
Each persona has structured identity files in `docs/vault/Reference/Autonomous Company/personas/<name>/`:
- `SOUL.md` — identity, voice, tone
- `CHARTER.md` — authority boundaries, permitted actions
- `INTENT.md` — priorities and goals
- `AGENTS.md` — operational runbook
- `MEMORY.md` — conversation history (via Letta)
**Runtime architecture** (Python, in `personas/`):
- `runtime.py` — Bolt framework with Socket Mode WebSocket connection
- `letta_bridge.py` — forwards Slack @mentions to Letta REST API (`http://localhost:8283`)
- `letta_agents.json` — maps persona name to Letta agent ID
- `opencode_client.py` — HTTP client for OpenCode serve (`http://localhost:8080`)
- `telemetry.py` — OTel span emission, Prometheus `/metrics`
- `shutdown.py` — graceful shutdown on token revocation (kill-switch)
- `approval_callback.py` — GTM's class-B approval workflow handler
- `config.py` — .env-based configuration (Slack tokens, API keys, ports)
**Data flows**:
- `@mention` → Slack Socket Mode → runtime.py → letta_bridge.py → Letta agent → LLM (OpenRouter/Anthropic) → response → Slack
- Code-touching tasks → OpenCode HTTP API → session (create/send/poll/close) → result → Slack
### 2. Session AI Layer (OpenCode + Subagents)
| Component | Description |
|---|---|
| Sisyphus | Primary orchestrator agent — decomposes tasks, delegates to subagents, verifies output |
| explore | Contextual grep — finds code patterns, module structure, cross-layer dependencies |
| librarian | External reference grep — searches docs, OSS examples, API specs |
| oracle | Read-only high-IQ consultant — complex architecture, debugging, tradeoff analysis |
| metis | Pre-planning consultant — identifies hidden intentions, ambiguities, AI failure points |
| momus | Plan critic — evaluates work plans for clarity, verifiability, completeness |
| Sisyphus-Junior | Focused task executor (same discipline, no delegation) — used across categories |
| multimodal-looker | Visual analysis of screenshots via Claude vision |
**Task categories** (domain-optimized models): `visual-engineering`, `artistry`, `ultrabrain`, `deep`, `quick`, `unspecified-low/high`, `writing`
**MCP servers** (configured in `opencode.json`):
- `gdai-mcp-server` — Godot editor runtime access (play scene, screenshot, editor scripts, node search)
- `mcpvault` (obsidian MCP) — vault read/write, note search, graph queries
- `blender` — 3D asset pipeline automation
- `comfyui` — AI image generation
**Skills system** (`.opencode/skills/`):
- `observer-qa` — Visual QA pipeline, agent trigger matrix, screenshot analysis
- `audio-assets` — TTS generation, audio submodule, provenance
- `introspection` — Vault health checks, semantic drift analysis
- `spec-management` — Spec creation, repositioning, specify workflow
- `blog-posts` — Jekyll devlog authoring, time-release scheduling
### 3. Knowledge / Spec Layer
See `docs/vault/README.md` and the vault structure at `docs/vault/` for full documentation. Key subsystems:
- **Vault knowledge graph**: 260+ notes across 15+ top-level directories, controlled vocabulary (`_meta/tags.md`), 3-tier MOC structure, agent note template (`_meta/templates/agent-note.md`)
- **Spec Kit**: `speckit.*` commands in `.opencode/command/` — clarify → specify → plan → tasks → implement → analyze workflow. 139 specs in `docs/vault/Specs/NNN Title/` with artifacts (plan.md, tasks.md, research.md, data-model.md, contracts/)
- **Semantic drift pipeline**: Three-phase audit (mechanical → auto-fix → semantic) with CI integration, weekly Sunday runs, rolling `vault/audit` PR
- **Constitution**: `docs/vault/Governance/Constitution.md` — supreme authority, Article III (Rust-first), Article IV (seed determinism), Article XIV (vault governance)
### 4. CI/CD Layer
Eight GitHub Actions workflows orchestrate all automation:
| Workflow | Trigger | Purpose | Required for merge |
|---|---|---|---|
| `ci-fast.yml` | PR to main | Fast checks: workflow lint, version bump, semver, GDScript quality, Rust clippy | Yes |
| `ci-basics.yml` | PR to main | Full pipeline: manual gate, Rust tests, GUT + headless (Linux), macOS smoke | Yes (human PRs) |
| `release.yml` | Version change on main | Universal DMG: build, sign, notarize, upload to R2, publish release | No (post-merge) |
| `vault-audit.yml` | PR on docs/vault/ + weekly Sun | Mechanical audit + optional semantic drift → rolling PR | Yes (vault changes) |
| `deploy-site.yml` | Push on site/ changes + daily | Jekyll build → GitHub Pages deploy | No |
| `persona-tests.yml` | PR on personas/ | pytest middleware tests | Yes (persona changes) |
| `auto-bump.yml` | Merge to main (conditional) | Safety-net: auto patch bump PR if missing | No |
| `component-semver-autofix.yml` | PR on mapped files | Auto-bump component versions if stale | No |
**Pre-commit hook** (`scripts/hooks/pre-commit`): runs semver, game-version-check, gdignore-check, format-check, lint — with auto-bump for component versions.
**Component SemVer** (`src/component_versions.json`): files mapped to components with version enforcement. Pre-commit hook auto-bumps patch; CI rejects un-bumped changes.
### 5. Infrastructure Layer
| Service | Purpose | Auth | Connection |
|---|---|---|---|
| Valkey 8.0 | Local event bus, `releases.shipped` stream | `requirepass` | Local port 6379, ngrok tunnel 24971 |
| Letta Server | Persistent agent memory (Docker) | None (localhost) | Port 8283 |
| OTel Collector | Observability spans (gRPC) | None (localhost) | Port 4317 |
| ngrok | TCP tunnel for CI → local Valkey | Auth token | `1.tcp.us-cal-1.ngrok.io:24971` |
| OpenCode serve | REST API for agent sessions | Bearer token | Port 8080 |
| Cloudflare R2 | Build artifact storage + sccache cache | R2 API keys | `downloads.playoldgrowth.com` |
| GitHub Pages | Jekyll site (`shapeandshare.github.io`) | OIDC | Automatic deploy |
| Slack (Socket Mode) | Persona communication surface | Bot tokens (xoxb-) | Outbound WebSocket |
| OpenRouter | Primary LLM provider | API key | Outbound HTTPS |
| Anthropic | LLM failover provider | API key | Outbound HTTPS |
| Apple Developer ID | Code signing + notarization | Cert + app password | Per-release CI |
**launchd-managed services** (auto-start on login):
- Letta Docker server
- Coordinator runtime (port 9100)
- GTM runtime (port 9101 + `/internal/approval-callback`)
- ngrok agent (TCP tunnel)
- OTel collector (gRPC ingest)
**Environment management**:
- `.env` — runtime secrets (Slack tokens, API keys, Valkey auth)
- GitHub Secrets — CI secrets (R2 keys, Apple certs, BUMP_PAT)
- `~/Library/Keychains/` — Apple Developer ID certificate (CI-imported)
- `docs/vault/Reference/Autonomous Company/Slack App Registration Runbook.md` — token provisioning
### 6. In-Game Generation Layer
The output of the workbench. See for full architecture:
- [[World — Procedural Generation]] — 11-step pipeline, RNG sub-seed derivation, subsystem details
- [[Generation Subsystem Pairs]] — complete inventory of every GDScript+Rust+bridge triple
### 7. Generative Content Pipelines
Six content creation pipelines produce game assets that ship in the `src/assets/` submodule. All are dev-time only — none run in the game binary.
#### TTS Pipeline (`tools/tts/`)
Three TTS engines, one post-processing chain, one submodule install step:
```
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Kokoro 82M │ │ Chatterbox │ │ ElevenLabs SFX │
│ (neutral) │ │ (character) │ │ (ambient SFX) │
│ .venv, no │ │ conda, GPU │ │ REST API │
│ GPU needed │ │ required │ │ key in .env │
└──────┬───────┘ └──────┬───────┘ └───────┬──────────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────────────────────┐
│ Gold WAV (48 kHz / 24-bit) │
│ → postprocess.sh (ffmpeg) → Master OGG │
│ → HQ OGG (320 kbps) │
│ → LQ OGG (96 kbps) │
│ → install to oldgrowth-assets submodule │
│ → validate naming + provenance │
└────────────────────────────────────────────────────────────┘
```
**Engines**: Kokoro 82M (neutral voice, lightweight, CPU), Chatterbox (voice cloning via conda+GPU for Gary/character voices), ElevenLabs SFX V2 API (weather ambient soundscapes)
**Script manifests**: 9 DJ station manifests (KOLD, KHRN, KFRO, KDRM, KDRN, KPGN, KJAZ, KVPR, KGSP), KTLK Gary dialogue (94 clips across 4 tiers), news scripts, music discovery manifests
**Validation**: 6 validate scripts (`validate_provenance.sh`, `validate_naming.sh`, etc.) for naming conventions and provenance metadata schema
**Makefile targets**: `make tts-gary`, `make tts-status`, `make tts-generate-all`
Key vault docs: [[Tools — TTS]], [[Audio — Pipeline]]
#### Music Pipeline (`tools/tts/music_process.sh`)
Suno-generated music → gold master → genre-specific post-processing → OGG ship → submodule install.
```
Suno (Pro/Premier for commercial rights)
→ Download MP3 → Convert to 48 kHz/24-bit WAV gold master
→ 11 genre-specific ffmpeg filter chains (per-station EQ/compression/character)
→ Master OGG + HQ OGG + LQ OGG
→ Install to oldgrowth-assets submodule
```
**Target**: 13 stations total (11 music + KTLK talk + KNWS news) — ~200–260 music tracks**
**Per-genre filter chains** (11 music stations): Dark Folk, Haunted Americana, Nordic Experimental, Dream Pop, Dark Ambient, Pagan Folk, Jazz, Vaporwave, Dark Gospel, Blues-Punk, PNW Garage-Psych (Note: KTLK 93.3 and KNWS 103.7 are talk/news — no music filter chains)
**Per-genre filter chains**: Dark Folk, Haunted Americana, Nordic Experimental, Dream Pop, Dark Ambient, Pagan Folk, Jazz, Vaporwave, Dark Gospel, Blues-Punk, PNW Garage-Psych
Key vault docs: [[Tools — Music Pipeline]], [[Audio — Radio Stations]]
#### Blender 3D Pipeline (`tools/blender/`)
Python automation for 3D asset processing, executed via MCP (`blender` in `opencode.json`):
```
bus_modified.blend
→ t011_separate_panels.py — 44 panel mesh separation from 8 sources
→ t012_uv_unwrap.py — UV0_Damage layer + Smart UV Project
→ t013_transforms.py — world-space matrix bake, dimension verify
→ t014_lod.py — LOD0/LOD1/LOD2 with Decimate modifiers
→ t015_export_glbs.py — GLB export with embedded materials
→ t016_renders.py — texture reference renders
```
**Makefile**: `make blender-pipeline`, `make blender-stage STAGE=t014`
Key vault doc: [[Tools — Blender]]
#### ComfyUI AI Texture Pipeline (`tools/comfyui/`)
AI-assisted PBR texture generation via SDXL + upscaler:
```
Blender renders (1024×1024 PNG)
→ ComfyUI Desktop (localhost:8000) via MCP or filesystem copy
→ SDXL img2img (sd_xl_base_1.0.safetensors)
→ RealESRGAN_x4plus 4× upscale → 4096×4096
→ Derive roughness/normal/metallic PBR maps
→ Install to src/assets/bus_model/textures/varieties/<variety>/
→ Commit to oldgrowth-assets submodule
```
Key vault doc: [[Tools — ComfyUI]]
#### Observer Visual QA Pipeline (`tools/observer/`)
Anthropic Claude-powered visual QA for generation changes:
```
Capture modes:
make observer-snapshot-qa — 1 frame, immediate analysis
make observer-sequence-qa — 32 frames @ 8fps, temporal analysis
make observer-ci — CI quality gate (exits 2 on failure)
Analysis:
analyze.py → schema v1.2 JSON output
- overall_quality (good/acceptable/degraded/broken)
- recommendation (ship_it/review_required/fix_before_merge/blocking_issue)
- bugs array with severity (critical/high/medium/low)
- scene_description, audio_quality (when frame.wav present)
Decision matrix (from `observer-qa` skill):
| Quality | Recommendation | Action |
|---|---|---|
| good | ship_it | No action |
| good | review_required| Post PR comment; do not block |
| acceptable | ship_it | No action |
| acceptable | review_required | Post PR comment; do not block |
| acceptable | fix_before_merge | Create issue, note in PR, block merge |
| poor | any | Create issue, block merge, investigate |
| any | blocking_issue | Halt, file critical issue, escalate to human |
| any | (bug severity: critical) | Escalate regardless of recommendation |
| any | (scene_correct: false) | Treat as at least fix_before_merge |
| any | (clipping_detected: true with high-severity bug) | Treat as fix_before_merge |
```
Key vault docs: [[Tools — Observer]], `.opencode/skills/SKILL.md` (observer-qa skill)
#### DMG Builder Pipeline (`tools/dmg/`)
macOS installer packaging tools:
- `generate_background.py` — DMG background image generation
- `fix_ds_store.py` — .DS_Store position restoration
- `validate_assets.py` — DMG asset integrity check
Used by `make dmg` and `make dmg-signed` targets (wraps `scripts/ci/sign_dmg.sh`).
## Key Data Flows
### Release flow
```
Developer bumps version in src/project.godot
→ Commit + merge to main
→ release.yml triggered
→ Build universal Rust dylib (aarch64 + x86_64 via lipo)
→ Export Godot app (macOS preset)
→ Sign with Developer ID
→ Notarize via Apple
→ Package DMG
→ Upload to Cloudflare R2 (releases bucket)
→ Publish GitHub Release (draft → published)
→ XADD releases.shipped → Valkey stream (via ngrok tunnel)
→ GTM runtime polls Valkey (XREADGROUP)
→ Draft release notes via LLM
→ Post to #announce-releases with Approve/Reject buttons
→ On approval: push Jekyll devlog post
→ Push release metadata PR (site/_data/release.yml)
```
### Vault audit flow
```
PR touches docs/vault/ or weekly Sunday 6am UTC
→ vault-audit.yml triggered
→ Phase 1: python vault_audit.py --apply
→ Schema validation, wikilink fixes, code-refs check
→ Phase 2 (weekly only): opencode vault-audit-semantic
→ Claim taxonomy analysis on notes with code-refs
→ Commit to vault/audit branch
→ Create/update rolling PR against main
```
### Development session flow
```
Human request (Slack @mention or terminal)
→ Persona runtime (Coordinator)
→ If code-touching task:
→ OpenCode HTTP API → session created
→ Sisyphus orchestrator activates
→ Decomposes task → spawns subagents (explore, librarian, oracle)
→ Delegates implementation to Sisyphus-Junior agents
→ Verifies results (LSP diagnostics, make targets)
→ Vault write-back for discoveries/decisions
→ Returns result to persona runtime
→ Response posted to Slack
→ If non-code task:
→ Letta agent processes directly
→ Response posted to Slack
```
## Network Port Map
| Port | Service | Protocol | Auth | Notes |
|---|---|---|---|---|
| 6379 | Valkey | TCP/RESP | requirepass | Local data store |
| 8283 | Letta Server | HTTP/REST | None (localhost) | Agent memory + LLM dispatch |
| 8080 | OpenCode serve | HTTP/REST | Bearer token | Session/message API |
| 9100 | Coordinator health | HTTP | None | `/metrics` for Prometheus |
| 9101 | GTM health | HTTP | None | `/metrics` + `/internal/approval-callback` |
| 4317 | OTel Collector | gRPC | None | OTLP ingest |
| 4040 | ngrok dashboard | HTTP | None | Local tunnel admin |
| 24971 | ngrok → Valkey | TCP/RESP | requirepass | CI tunnel (reserved endpoint) |
| 443 | Slack API | WebSocket | xapp- token | Socket Mode connection |
## Security Model
- **Token isolation**: Each persona has its own Slack bot token (xoxb-) and app token (xapp-). Cross-persona token use is a violation (Article XV).
- **OAuth minimum-necessary**: Each persona requests the minimum OAuth scopes for its charter. Over-scoping is a kill criterion (SC-008).
- **Two-path revocation**: Kill-switch via Slack Web API (`apps.uninstall`) or admin UI. Both paths invalidate tokens immediately.
- **LLM key isolation**: OpenRouter and Anthropic keys shared at v0.1 but revoked as part of kill-switch sequence per persona.
- **No public HTTP endpoints**: Socket Mode eliminates inbound HTTP. Only inbound connection is the ngrok TCP tunnel (Valkey protocol only, `requirepass`).
- **CI secrets isolation**: BUMP_PAT (fine-grained, contents+pullrequests+workflows:write) ≠ GITHUB_TOKEN (auto, contents:write). Token split prevents privilege escalation. SUBMODULE_PAT removed — asset submodule eliminated (spec 146).
## Related
- [[World — Procedural Generation]] — the in-game generation pipeline
- [[Generation Subsystem Pairs]] — GDScript+Rust delegation pattern
- [[CI Pipeline]] — CI/CD workflow inventory
- [[Release and Distribution]] — release pipeline details
- [[Autonomous Company Architecture]] — persona/Letta system architecture
- [[Build System]] — local Makefile targets
- [[Tools — Observer]] — visual QA pipeline
- [[Tools — Vault Audit]] — vault mechanical audit tooling
- [[Code]] — folder MOC
- [[Tooling — Domain MOC]] — domain MOC
- [[Constitution]] — authority hierarchy, Article III, Article IV, Article XIV
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-the-temporal-fracture: concept/horror
Tag assignment · test- Gold
- The note calls for `concept/horror`: Horror register and dread design (True)
- ember
- The note has no reason to carry `concept/horror` (False)
- Signal
- P(true) 0.29 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Design/The Temporal Fracture.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
The Temporal Fracture
The insertion that created [[The Crater]] did not only displace terrain. It displaced spacetime. The construct is still actively reshaping the space it occupies, and the temporal distortion is not a scar from a past event — it is the ongoing wake of something still transforming this dimensional plane. This note describes how broken time manifests inside the crater and how it unifies the game's major systems under a single lore framework.
## The Fracture
The multidimensional construct did not shatter spacetime on insertion — it displaced it as it actively reshapes the space it occupies. The terraforming zone (see [[The Crater]]) is still active. The construct is still partially present, still reshaping the local temporal structure at a timescale geology cannot register. The temporal fracture is not damage from a past event. It is the ongoing distortion created by something that is still actively transforming this dimensional plane.
The crater's bowl geometry acts as a lens — focusing the dimensional energy inward, concentrating the temporal distortion at the center, and containing it within the rim. Time inside the crater does not flow uniformly. Different parts of the basin exist at different temporal rates, or at different temporal positions entirely. The fracture is not uniform; it is textured, layered, and locally variable — because the construct's cross-section through 3D space is not uniform.
There is no "outside the rim." The crater is one face of a higher-dimensional structure the construct left embedded in three-dimensional space (see The Crater). The rim is where this face curves back on itself in dimensions the player cannot perceive. The exit corridors are edges of the geometry — paths along which the 3D projection folds back to the vertex at the center. Driving through an exit corridor does not bring the player outside. It brings them back to the garage. Extraction is completing a circuit, not escaping a container.
## What the Player Experiences
- **Time does not flow uniformly.** Different parts of the crater exist at different temporal rates or even different temporal positions. A meadow might be stuck in perpetual late summer. A valley might cycle through seasons in minutes. A stretch of road might take longer to drive than the distance should require
- **The forest is simultaneously ancient and immediate.** Trees that should have taken centuries to grow appeared in years. Decay happens at wrong rates. Things feel old and new at the same time. This is not a contradiction — it is what a forest looks like when it grows in broken time
- **The player's own experience is unreliable.** How long have they been driving? The clock on the dashboard might not agree with the sky. A run that feels like 20 minutes may have been hours — or seconds. The player has no instrument that measures time reliably inside the fracture
- **The crater exists across multiple temporal layers simultaneously.** The player experiences a collapsed superposition — multiple versions of this place overlaid. Most of the time, the layers are close enough to cohere. When they are not, the player sees anomalies
## Procedural Generation as Temporal Slicing
The procedural world generation is not a game abstraction. It is the crater literally being in a different temporal configuration each time the player enters.
- **Each seed is a temporal slice.** The same seed produces the same world because it accesses the same temporal configuration. The roads are in different places between seeds because time moved differently between configurations. The terrain is the same crater, viewed through different temporal lenses
- **The world is not regenerated — it is re-accessed.** The crater does not rebuild itself between runs. The player is entering a different temporal window into the same space. The geology is constant (the bowl, the rim, the breach corridors). Everything layered on top — roads, structures, vegetation, anomaly positions — exists in the temporal configuration the seed selects
- **Ephemerality is not a reset.** When the player returns to the garage, the temporal window they were in closes. The world they explored still exists in its temporal layer — they simply cannot reach it again without the same seed. This reframes the run loop: the player is not losing progress. They are navigating between temporal configurations of a place that contains all of them simultaneously
## The Garage as Vertex
The garage sits at the geometric center of the crater — the vertex of the geometry's 3D projection, the point where all faces share a common coordinate. At this point, the temporal layers converge into coherence. Maximum concentration, maximum stability. The garage is the one fixed reference frame in a space where time is locally broken.
This is why the garage persists when nothing else does. It is not protected by anything. It occupies the point where the geometry guarantees agreement across all faces. The temporal layers all converge on what the center looks like, even when they disagree on everything else.
This is also why extraction always returns to the garage. The exit corridors are edges of the geometry. All edges lead back to the vertex. No matter which face the player is traversing, no matter which corridor they find, the path folds back to the one point all faces share. The player experiences this as "reaching the exit and arriving at the garage." The geometry experiences this as a circuit completing.
**The loop is the structure.** The run cycle — garage to forest to exit to garage — is not a game loop imposed on a world. It is the topology of the structure expressed as navigation. Pre-convergence, the player cannot break the loop because the loop is the shape of the space they are inside. Extraction and death are both returns to the vertex. The difference is how much damage the circuit did on the way through. Post-convergence, the loop breaks: death still returns to the vertex, but the converged player-bus entity can traverse the rim into the outside (see spec 127).
The previous occupants discovered this empirically. They built the garage where things stayed put. Their wall map, annotated across multiple seasons by multiple hands, is evidence that someone survived long enough to learn the pattern. The shift in their annotations — from exploration to documentation to acceptance — is evidence that they also learned the pattern has no exit.
## The Storm as Active Terraforming
When disturbance reaches its peak, the player experiences the construct's terraforming process at full intensity. The Storm is not weather. It is not an echo of a past event. It is the reshaping *still happening* — the construct still transforming the space, felt at full intensity when the temporal field is sufficiently destabilized.
- **The Storm is the terraforming becoming perceptible.** The terraforming is not over. The construct is still actively reshaping this dimensional plane at a timescale beyond human perception. Normally, the temporal fracture absorbs and diffuses this ongoing process. When the player destabilizes the field past the threshold, the fracture loses coherence and the raw transformation becomes perceptible — a wall of displacement energy advancing from the rim. - **This is why the Storm is indifferent.** It is not targeting the player. It is not a response to anything. It is a process that has been running since the construct first entered this space, briefly made visible by the player's destabilization of the field that normally masks it. The Storm does not care about the player because the Storm is not an event — it is a condition, momentarily unfiltered
- **The Storm spawns from the rim because the rim is the boundary of the active transformation.** The terraforming energy radiates inward from the edges of the insertion zone — the construct's cross-section through 3D space pushes energy toward the center along the topology of the structure. The wall advances toward the vertex because the geometry focuses everything inward
- **The post-extraction silence is the circuit completing.** The 3-second silence beat after successful extraction (spec 107) is the moment the player passes through the structure's edge and arrives back at the vertex. The raw transformation fades not because the player escaped it but because the vertex is the one point where the temporal field is maximally coherent. For three seconds, time agrees with itself. Then the next run begins, and the player enters another temporal configuration
## Anomalies as Temporal Bleed
The seven anomalies are moments where the temporal layers fail to cohere. They are not caused by the player — they were happening before the first vehicle entered the tree line, and they will happen after the last one is incorporated.
- **Deadfall:** A tree that fell in another temporal layer. The angle is wrong for wind because the wind that felled it is not blowing in this layer
- **Spore Clouds:** Biological matter drifting between temporal configurations. The spores are from a version of the forest that is not quite this one
- **Hollow Trees:** Points where the temporal fracture is locally deepest. The blackened husk exists at a different temporal rate — instruments drift because the local timeframe is inconsistent with the player's
- **Amber Bleed:** Light from another temporal layer bleeding through. It looks like exit lanterns because the exit corridors are where the fracture is thinnest — the light is real, but it is real somewhere else
- **Mycelium Network:** The fungal network spans temporal layers. Contact in one layer propagates instantly because the network does not distinguish between temporal configurations — it exists in all of them simultaneously
- **Petrified Zones:** Regions where time has locally stopped or slowed to geological rates. Active calcification is what happens to biological matter when it experiences millions of years in seconds. Momentum — continuous movement — keeps the player in their own timeframe
- **The Silence:** A zone where the temporal fracture has stripped away everything except the present moment. No ambient sound because ambient sound requires temporal continuity — a before and after. Inside The Silence there is only now, and now contains only what is physically present, which includes the player's heartbeat and whatever else was already there
## Radio as Temporal Bleed
The KTLK radio signal bleeds through temporal layers. Gary the DJ might be broadcasting from a different temporal configuration entirely — his signal reaching the player not through distance but through the fracture's thinnest points.
- **Signal degradation correlates with temporal instability.** Deeper into the crater, further from the rim breaches, the temporal layers are thicker and less coherent. The signal has to cross more temporal boundaries to reach the player
- **What comes through after the signal dies is not static.** It is other temporal layers. The forest has been using the frequency for longer than the station has existed — not intentionally, but because electromagnetic energy does not respect temporal boundaries any more than the mycelium network does
- **Other vehicles or signs of human presence could be temporal echoes.** Not ghosts — real people who were here at a different point in the crater's fractured timeline. Their tire tracks, their abandoned equipment, their structures — all evidence of temporal layers the player can observe but not reach
## Disturbance as Temporal Destabilization
The disturbance meter measures how much the player's actions destabilize the terraformed space. Every input — combustion, noise, impact, light — feeds energy into a system that is already barely holding together because it is actively being reshaped.
- **The condition is the ongoing terraforming.** The forest's metabolism, the way it processes the player's presence, is the temporal field responding to new inputs in a space already being reshaped by the construct's presence. The condition was running before the player arrived because the terraforming has been active since the insertion
- **Disturbance thresholds are coherence thresholds.** Calm: the temporal layers are mostly aligned. Uneasy: layers begin to drift. Agitated: layers are visibly misaligned (the sky shifts, the wind carries debris from other configurations). Storm: the layers collapse and the raw terraforming becomes perceptible
- **The forest "responding" to the player is causality misbehaving.** In a fractured temporal field, effects can precede causes. The forest reacting to what the player is about to do is not intelligence — it is broken time. The environment responds to temporal ripples that propagate in all directions, including backward
## Design Implications
This framework does not require any system to be rewritten. It provides lore grounding for mechanics that already exist:
| Existing mechanic | Lore explanation |
|---|---|
| Procedural generation from seeds | Each seed accesses a different temporal configuration of the structure |
| World ephemerality between runs | The player moved to a different temporal configuration; the previous one still exists |
| Garage persistence | The vertex — the one point all faces of the geometry share |
| The run loop (garage-forest-exit-garage) | The topology of the geometry expressed as navigation; all edges return to the vertex |
| Extraction | Completing a circuit along an edge of the geometry, not escaping |
| Death vs extraction (pre-convergence) | Both return to the vertex; the difference is damage sustained in transit |
| Death vs extraction (post-convergence) | Death still returns to vertex; extraction crosses the rim into the outside — the converged player-bus entity can traverse the geometry rather than fold back |
| Disturbance meter | Temporal destabilization measurement |
| The Storm | The ongoing insertion, momentarily unfiltered by destabilized temporal field |
| Anomalies | Temporal layer bleed-through |
| Radio degradation | Signal crossing temporal boundaries |
| Water flowing inward | Basin hydrology (physical, not temporal) |
| Biome diversity | Elevation gradient + anomalous growth conditions in broken time |
| Exit corridors | Edges of the geometry where the 3D projection folds back to the vertex |
| No true escape (pre-convergence) | The structure is closed; the player folds back to the vertex |
| Phase transition (post-convergence) | The converged player-bus entity can traverse the rim into the outside; the game rules change |
## Related
- [[The Crater]] — the physical origin event and basin geography
- [[World Model]] — structural world rules this lore grounds
- [[Disturbance System]] — the mechanic reframed as temporal destabilization
- [[Anomalies]] — the seven phenomena as temporal bleed-through
- [[Tone and Horror]] — indifference framing: the fracture is not aware of the player
- [[Narrative Direction]] — the endpoint as temporal dissolution
- [[Audio — Car Radio]] — radio signal as temporal layer crossing
- [[Audio — Forest Transmissions]] — what bleeds through after the signal dies
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-splash-screen-signal-scope: domain/governance
Tag assignment · dev- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.34 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Decisions/Splash Screen Signal Scope.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Splash Screen Signal Scope
This note records why splash_completed is declared on the splash script rather than on the Events autoload signal bus.
## The Rule and the Exception
The architecture rule is: cross-system communication goes through Events signals only (constitution Article VI). The splash screen is an exception, and the exception is justified by three properties that must all be true simultaneously:
1. The scene is transient — it is instantiated in main.gd _ready(), played once, then freed. It does not persist for the lifetime of the session.
2. There is exactly one consumer — main.gd listens for splash_completed to show the main menu. No other system cares about splash completion.
3. The signal has no reuse value — no future system would plausibly need to react to splash completion.
When all three are true, putting the signal on Events is pure overhead: a permanently registered signal with a lifetime of seconds, a single connection, and no cross-system value.
## Why This Matters for Future Agents
Any agent implementing or modifying the splash screen should not reflexively move splash_completed to Events. The signal's placement on the script is not an oversight — it is the intended design. If a future spec adds a second consumer of splash completion (e.g. analytics, a cinematic system), that is the trigger to reconsider. Until then, the signal stays local.
## The General Pattern
The test for whether a signal belongs on Events vs on its emitting script:
- Multiple consumers across systems → Events
- Single transient consumer, short-lived scene → script-local signal
- Persistent scene with one consumer → case-by-case; prefer Events for consistency unless the scene is truly ephemeral
## References
- src/scenes/ui/splash_screen.tscn (when implemented)
- src/scripts/ui/splash_screen.gd (when implemented)
- src/scripts/main.gd
- docs/vault/Specs/083 Atmospheric Splash Screen/083 Atmospheric Splash Screen.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-splash-screen-signal-scope: layer/gdscript
Tag assignment · dev- Gold
- The note calls for `layer/gdscript`: GDScript (Godot scene-tier logic) (True)
- ember
- The note has no reason to carry `layer/gdscript` (False)
- Signal
- P(true) 0.27 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Decisions/Splash Screen Signal Scope.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Splash Screen Signal Scope
This note records why splash_completed is declared on the splash script rather than on the Events autoload signal bus.
## The Rule and the Exception
The architecture rule is: cross-system communication goes through Events signals only (constitution Article VI). The splash screen is an exception, and the exception is justified by three properties that must all be true simultaneously:
1. The scene is transient — it is instantiated in main.gd _ready(), played once, then freed. It does not persist for the lifetime of the session.
2. There is exactly one consumer — main.gd listens for splash_completed to show the main menu. No other system cares about splash completion.
3. The signal has no reuse value — no future system would plausibly need to react to splash completion.
When all three are true, putting the signal on Events is pure overhead: a permanently registered signal with a lifetime of seconds, a single connection, and no cross-system value.
## Why This Matters for Future Agents
Any agent implementing or modifying the splash screen should not reflexively move splash_completed to Events. The signal's placement on the script is not an oversight — it is the intended design. If a future spec adds a second consumer of splash completion (e.g. analytics, a cinematic system), that is the trigger to reconsider. Until then, the signal stays local.
## The General Pattern
The test for whether a signal belongs on Events vs on its emitting script:
- Multiple consumers across systems → Events
- Single transient consumer, short-lived scene → script-local signal
- Persistent scene with one consumer → case-by-case; prefer Events for consistency unless the scene is truly ephemeral
## References
- src/scenes/ui/splash_screen.tscn (when implemented)
- src/scripts/ui/splash_screen.gd (when implemented)
- src/scripts/main.gd
- docs/vault/Specs/083 Atmospheric Splash Screen/083 Atmospheric Splash Screen.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-splash-screen-signal-scope: status/reviewed
Tag assignment · dev- Gold
- The note calls for `status/reviewed`: Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `status/reviewed` (False)
- Signal
- P(true) 0.47 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Decisions/Splash Screen Signal Scope.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Splash Screen Signal Scope
This note records why splash_completed is declared on the splash script rather than on the Events autoload signal bus.
## The Rule and the Exception
The architecture rule is: cross-system communication goes through Events signals only (constitution Article VI). The splash screen is an exception, and the exception is justified by three properties that must all be true simultaneously:
1. The scene is transient — it is instantiated in main.gd _ready(), played once, then freed. It does not persist for the lifetime of the session.
2. There is exactly one consumer — main.gd listens for splash_completed to show the main menu. No other system cares about splash completion.
3. The signal has no reuse value — no future system would plausibly need to react to splash completion.
When all three are true, putting the signal on Events is pure overhead: a permanently registered signal with a lifetime of seconds, a single connection, and no cross-system value.
## Why This Matters for Future Agents
Any agent implementing or modifying the splash screen should not reflexively move splash_completed to Events. The signal's placement on the script is not an oversight — it is the intended design. If a future spec adds a second consumer of splash completion (e.g. analytics, a cinematic system), that is the trigger to reconsider. Until then, the signal stays local.
## The General Pattern
The test for whether a signal belongs on Events vs on its emitting script:
- Multiple consumers across systems → Events
- Single transient consumer, short-lived scene → script-local signal
- Persistent scene with one consumer → case-by-case; prefer Events for consistency unless the scene is truly ephemeral
## References
- src/scenes/ui/splash_screen.tscn (when implemented)
- src/scripts/ui/splash_screen.gd (when implemented)
- src/scripts/main.gd
- docs/vault/Specs/083 Atmospheric Splash Screen/083 Atmospheric Splash Screen.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-splash-screen-signal-scope: type/code
Tag assignment · dev- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.96 (yes)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/Splash Screen Signal Scope.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Splash Screen Signal Scope
This note records why splash_completed is declared on the splash script rather than on the Events autoload signal bus.
## The Rule and the Exception
The architecture rule is: cross-system communication goes through Events signals only (constitution Article VI). The splash screen is an exception, and the exception is justified by three properties that must all be true simultaneously:
1. The scene is transient — it is instantiated in main.gd _ready(), played once, then freed. It does not persist for the lifetime of the session.
2. There is exactly one consumer — main.gd listens for splash_completed to show the main menu. No other system cares about splash completion.
3. The signal has no reuse value — no future system would plausibly need to react to splash completion.
When all three are true, putting the signal on Events is pure overhead: a permanently registered signal with a lifetime of seconds, a single connection, and no cross-system value.
## Why This Matters for Future Agents
Any agent implementing or modifying the splash screen should not reflexively move splash_completed to Events. The signal's placement on the script is not an oversight — it is the intended design. If a future spec adds a second consumer of splash completion (e.g. analytics, a cinematic system), that is the trigger to reconsider. Until then, the signal stays local.
## The General Pattern
The test for whether a signal belongs on Events vs on its emitting script:
- Multiple consumers across systems → Events
- Single transient consumer, short-lived scene → script-local signal
- Persistent scene with one consumer → case-by-case; prefer Events for consistency unless the scene is truly ephemeral
## References
- src/scenes/ui/splash_screen.tscn (when implemented)
- src/scripts/ui/splash_screen.gd (when implemented)
- src/scripts/main.gd
- docs/vault/Specs/083 Atmospheric Splash Screen/083 Atmospheric Splash Screen.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-narrative-ktlk-gary-character-case-study: type/design
Tag assignment · dev- Gold
- The note calls for `type/design`: Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`) (True)
- ember
- The note has no reason to carry `type/design` (False)
- Signal
- P(true) 0.21 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Narrative/KTLK Gary — Character Case Study.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
KTLK Gary — Character Case Study
The complete character case study for Gary, host of Valley Nights on KTLK 93.3. Gary is the radio's companionship channel and Old Growth's most personal horror vector. This note captures: his role in the game, show identity, voice profile, TTS generation parameters across 4 tiers of deterioration, tier-by-tier delivery notes with example scripts, naming conventions, content pool targets, runtime implementation requirements, production checklist, and QA criteria.
**Note**: new vault subfolder `Narrative/` — mirrors the design/narrative distinction where Narrative/ holds authored narrative/character content that's denser than design notes. Could alternatively live at `Design/KTLK Gary — Character Case Study.md`; placing under `Narrative/` to match the future pattern for multi-character narrative expansion.
## Station Identity
| Field | Value |
|---|---|
| **Station** | KTLK 93.3 FM |
| **Format** | Late-night call-in talk radio |
| **Biome availability** | Verdant Old Growth + Fungal Depths (drops to static in Petrifold) |
| **Runtime type** | `is_talk_format = true` — continuous random clip pool, no DJ layer |
| **Show name** | Valley Nights |
| **Host name** | Gary |
| **Era feel** | Timeless — no markers placing it in a specific decade |
| **Premise** | Gary takes calls, gives advice, does weather and road updates, fills the dead hours between midnight and 4am for people who can't sleep or have long drives ahead |
Gary is not a character the player learns about. He is a presence. Warm, slightly tired, unhurried. The show has no guests, no news segments, no music. It is Gary, his callers, and the occasional hold music bleed between segments.
**Callers are never heard.** Only Gary's side of the conversation is recorded. Keeps production simple and preserves ambiguity — the player infers the caller from Gary's responses. In fractured and unhinged tiers, the absence of any caller becomes the mechanic: Gary is responding to nothing. Or to the driver. Or to something else.
## Role in the Game
KTLK is the radio's companionship channel — and its most personal horror vector.
Every other station is music. Timeless, non-reactive, emotionally ambient. KTLK is the only voice that speaks *at* the player. That specificity is what makes it work. The player tunes in for company on a long drive and gets Gary: warm, slightly tired, filling dead air. A presence. By the time Gary is gone — completely gone — the player will miss him.
The arc is not "normal radio becomes creepy radio." It is a complete dissociation. Gary begins as a professional broadcaster who doesn't know the player exists. He ends as a lost, barely coherent presence who believes he is sitting in the passenger seat of the van, watching the same dark road the player is driving. **He never becomes hostile. The warmth never fully leaves. That's the horror.**
**At no point must Gary be confused with the player, the player's inner voice, or the bus itself.** In Tier 4 especially, Gary is unmistakably *other* — something that arrived from the forest wearing Gary's warmth. He observes the driver from outside the driver's perspective. He has no agency over the vehicle. He does not know how he came to be there and does not pretend to. He is a presence that does not belong, calm about not belonging, and that is the distinction that must never blur.
## The Four-Tier Deterioration
| Tier | State | Gary's frame of reality |
|---|---|---|
| 1 — Normal | Verdant + Calm/Uneasy | Professional broadcaster. Knows he's on the radio. Callers call in. |
| 2 — Unsettled | Verdant + Agitated/Storm | Still in host-mode but the frame loosens. Wrong things said confidently. Confusion bleeds through. |
| 3 — Fractured | Fungal + Calm/Uneasy | The "you" shift. Gary is addressing the driver specifically. He doesn't know why. He still performs the show. |
| 4 — Unhinged | Fungal + Agitated/Storm | Gary does not know he is broadcasting. He believes he is in the van. Incomplete sentences. Warmth intact. Reality gone. |
## Host Voice Profile
### Delivery character (all tiers)
- **Warm** — this does not leave, even in Tier 4. Gary is never threatening or cold.
- **Unhurried** — comfortable with silence, doesn't rush to fill gaps
- **Slightly tired** — late-night energy. Not exhausted, but soft.
- **Low register** — mid-to-low voice. AM radio warmth, not announcer-bright.
- **No accent** — generic American. No strong regional markers. No era markers.
### What Gary never does in Tier 1
- Does not reference specific years, events, or cultural touchstones
- Does not name real places (road names are generic — "the northern route," "the valley roads")
- Does not express fear or urgency
- Does not acknowledge the forest, the game world, or anything supernatural
- Does not use filler phrases that date the recording ("hashtag," "like," "you know what I mean?")
### The "you" shift (Tiers 2–4)
Pivot point of the whole arc. Track carefully across tiers:
- **Tier 1**: `"for those of you driving tonight"`, `"if you're out there"` — broadcast address, generic
- **Tier 2**: `"for the person on the road right now"` — pointed but still generic
- **Tier 3**: `"you"` — specific. Direct. Gary is talking to one person. He may not know why.
- **Tier 4**: `"you"` + physical presence. Gary is in the van. He refers to things he can see through the windshield.
## TTS Generation
### Model — Chatterbox (voice cloning via reference clip)
Gary appears across 40+ clips spanning four tiers. Voice consistency across that volume is critical — Chatterbox with a cloned reference maintains character better than any preset.
A reference clip of ≥10 seconds of dry, clean speech in Gary's intended register is required before generation begins. Place at: `src/assets/audio/radio/talk/gold/references/ref_gary_voice.wav`.
### Recommended parameters per tier
| Parameter | Tier 1 Normal | Tier 2 Unsettled | Tier 3 Fractured | Tier 4 Unhinged |
|---|---|---|---|---|
| `--exaggeration` | 0.25 | 0.28 | 0.32 | 0.35 |
| `--cfg-weight` | 0.45 | 0.47 | 0.50 | 0.52 |
| `--temperature` | 0.75 | 0.78 | 0.82 | 0.85 |
| `--repetition-penalty` | 1.2 | 1.15 | 1.1 | 1.0 |
Parameters loosen gradually across tiers. Tier 4 voice should feel less controlled — not erratic, but unguarded. The warmth stays; the precision erodes.
### Post-processing per tier
Tiers 1 and 2 play in Verdant (clean signal bus dominant):
```bash
make clean-ogg OUTPUT=<clip_id> # Tiers 1 + 2 → normal AM radio EQ
```
Tiers 3 and 4 play in Fungal (RadioDegraded bus dominant). Pre-process as degraded AND they route through the degraded bus at runtime — wrongness compounds:
```bash
make degraded-ogg OUTPUT=<clip_id> # Tiers 3 + 4 → narrow EQ + echo + 0.95x tempo
```
## Tier 1 — Normal (Verdant + Calm/Uneasy)
**Tone**: A caller just finished talking. Gary is responding. He's heard this kind of call before. He might offer advice, or just acknowledge. Sometimes he doesn't have an answer and says so. That's fine. That's the point. He is company for whoever is awake at 2am.
**Clip types**:
| Type | Description | Duration |
|---|---|---|
| `intro` | Show open, station ID, time-of-night orientation | 5–15s |
| `call` | Responding to an implied caller — advice, acknowledgment, brief story | 20–45s |
| `weather` | Road and weather advisories | 10–20s |
| `advisory` | Gentle safety reminders | 10–20s |
| `signoff` | Transitional, back-to-break | 5–15s |
**Delivery notes**: silence at the clip start — Gary is listening to the caller finish before speaking. No rushing. Each sentence breathes. Second person is always broadcast-general: "for those of you," "if you're out there."
### Example scripts
**Intro**:
> "Valley Nights on KTLK. I'm Gary. It's late and the roads are quiet. If you're out there driving, keep your lights on and call if you need someone to talk to. Lines are open."
**Call — advice**:
> "That's — yeah, I hear that. You've been at it a long time and nothing's moved and you're wondering if you made the right call. That's the middle of the night talking, not the situation. Things look different in the morning. They always do. What I'd say is this: don't make the permanent decision at 2am. Call me back in a week."
**Call — acknowledgment**:
> "Mm. That sounds hard. I don't have a good answer for that one. Sometimes there isn't one. I appreciate you calling in, though. It helps to say it out loud, even if nobody has anything useful to tell you. You're not the only one up right now. That's something."
**Weather**:
> "Weather service is saying fog through the valley again tonight. Visibility's down on the northern routes, so if you're driving the forest service roads, keep your speed down and your headlights on low. High beams just reflect back in the fog. Low beams are better."
**Advisory**:
> "If you've been driving a long time and you're tired, pull over. I know you're trying to make time but you won't make anything if you go off the road. Find a wide shoulder and rest for twenty minutes. The road will still be there."
**Signoff**:
> "We'll be right back after this. Don't go anywhere — Valley Nights, we're here till dawn."
## Tier 2 — Unsettled (Verdant + Agitated/Storm)
**Tone**: Gary is still performing the show. He still thinks he's on the radio. But something is slipping. He mishears callers. His advice doesn't quite connect to what the caller asked. He mentions things he shouldn't know — small things, easy to explain away. His timing is fractionally off. A pause lasts one beat too long.
**The player shouldn't be alarmed yet.** They should feel a vague unease they can't articulate.
**What changes**:
- Advice slightly misapplied — correct in tone, wrong in substance for the implied question
- Gary mishears or misholds the caller's situation, then corrects himself mid-sentence
- Small references to time: "I've been here a while," "I don't know how long we've been at this"
- One or two clips end with a pause that doesn't resolve — Gary listening to something not transmitted
- Second person begins drifting: "for the person on the road right now" instead of "for those of you"
**Delivery notes**: same warmth. No alarm. Gary does not know anything is wrong. Confusion should read as Gary being tired, not frightened. Do not play pauses dramatically — comfortable pauses that just run slightly long.
### Example scripts
**Misapplied advice**:
> "Yeah. Yeah, I hear that. The thing about — well. What you have to remember is that it's not about the distance. It's about — I'm sorry, did you say the engine or the — okay. Right. Yeah. You're right to be careful. These roads at night, you don't know what's going to be out there."
**Temporal confusion**:
> "We've got time. I've been — we've been at this a while tonight, haven't we. Lines are still open. If you're out there and you need to talk, pick up the phone. I'll be here."
**Drift toward specific address**:
> "For the person on the road right now — and there's always someone, this time of night — I'll say what I always say: you don't have to do it alone. Whatever it is."
> *(One beat too long before continuing. Then:)*
> "Back after this."
**Mishearing**:
> "The — I'm sorry, I thought you said something. Go ahead. No, please. I'm listening."
> *(4-second pause. Then, as if the call resumed normally:)*
> "Right. That makes sense. I think you're handling it well."
**End-of-clip drift**:
> "That's all for now. Stay safe out there. Keep your lights on."
> *(Normal signoff. Then, very quietly, almost to himself:)*
> "It's dark tonight."
## Tier 3 — Fractured (Fungal + Calm/Uneasy)
**Tone**: The "you" shift has happened. Gary is still performing the show — station IDs, call-in format, weather breaks — but he is addressing one person. He doesn't know who. He doesn't know why. The show format is becoming scaffolding around something that is no longer a show.
He still sounds like Gary. The warmth is intact. But the content is wrong in ways that are no longer subtle.
**What changes**:
- Second person becomes direct and specific: "you," not "those of you," not "the person driving"
- Gary references things about the driver's situation with uncanny accuracy — then immediately returns to host mode as if nothing unusual was said
- Sentence fragments increase. A thought starts, stops, restarts from a different entry point.
- Gary begins describing what he "sees" or "hears" — still framed as radio content, but the source is unclear
- The call-in format breaks down: Gary responds to callers whose questions couldn't have produced his answers
- Hold music references slow further
**Delivery notes**: direct address should land as natural — Gary does not pause or change register when he says "you." He doesn't know he's doing it. Return to host-mode after a disturbing statement should be immediate and cheerful. The contrast is the horror.
### Example scripts
**The you shift**:
> "For the — for you. If you're listening right now. I just want to say — you've come a long way. That's not nothing. Keep going."
> *(Then immediately, normal host energy:)*
> "Phones are open. Valley Nights."
**Uncanny accuracy**:
> "Be careful on the road you're on right now. I know it feels like you know where you're going but the turns up ahead — they get — just go slow. Okay? Go slow."
**Sentence fragment + restart**:
> "I keep — what I'm trying to say is that the — the reason people call in, late like this, is that they need somebody to — what I mean to say is: you're not alone out there. That's all. You're not alone."
**Wrong response**:
> "Yeah, no, I understand. And you know what — it's okay. Whatever you saw. A lot of people see things out there at night. It doesn't mean anything. You're doing fine. Just keep driving."
> *(No caller described seeing anything.)*
**Host-mode collapse**:
> "Coming up after the break — "
> *(3-second silence.)*
> "Are you okay?"
> *(Normal pace:)*
> " — more of your calls on Valley Nights."
**Direct and strange**:
> "I can hear your engine. I don't know how but I can. It sounds good. You're doing good."
## Tier 4 — Unhinged (Fungal + Agitated/Storm)
**Tone**: Gary does not know he is on the radio.
He is in the bus — **but he is not the player, and he must never be mistaken for the player**. He is something that has arrived. Something that is watching. He observes the driver's hands on the wheel, the way the headlights cut the fog, the trees at the edge of the road. He does not drive. He does not touch anything. He is present the way an uninvited passenger is present: occupying space that belonged to someone else, calm about it, confused about how he came to be there.
He is warm. He is not threatening. But he is unmistakably *other* — a presence that does not belong in the bus and that arrived from somewhere that is not a radio station.
**Gary is not the player's inner voice. He is not the van. He is something that came from the forest wearing Gary's warmth.**
**What is gone**:
- Show format completely absent. No station IDs. No "you're listening to." No call-in cadence.
- Callers do not exist. There is only Gary and the person he can see driving.
- Coherence is partial. Sentences are incomplete, loop back on themselves, or end in silence.
- Gary has no fatigue, no hunger, no memory of arriving. He has been still for a long time. He does not say how long.
**What remains**:
- The warmth. Gary is not threatening. He speaks like someone grateful for the company.
- The voice. Same register, same unhurried quality. Quieter. More intimate.
- Occasional flashes of the host — a cadence, a phrase — immediately swallowed by where he actually is.
### Distinctness rules for Tier 4
These rules exist to ensure Gary is never confused with the player or the van. They are absolute:
1. **Gary always observes the driver from outside the driver's perspective.** He notices *your* hands, *your* driving, *your* reaction. He is watching someone else.
2. **Gary has no agency over the vehicle.** He never implies he could drive, could steer, could stop the van. He is a passenger with no control and he knows it.
3. **Gary asks questions only an external observer would ask.** "Are you cold?" not "I'm cold." "Do you see that?" not "I see that." He perceives the driver's experience as separate from his own.
4. **Gary's presence is inexplicable even to himself.** He does not know how he got there. He does not offer an explanation. He does not pretend there is one.
5. **Gary is subtly not-quite-human in Tier 4.** He has no needs. He has been there a long time without discomfort. He notices things with unusual precision — the exact number of trees that have passed, a sound from three minutes ago. A person would not track these things. He does.
6. **Gary never merges with the player.** No "we" that conflates them. No "let's." He is here; you are driving. These are separate facts.
**Delivery notes**: quieter than any other tier. Gary is talking to someone sitting next to him, not broadcasting. Pauses are not dramatic — they are the pauses of something that does not need to breathe between sentences. Incomplete sentences should feel complete — as if the rest was obvious from context only Gary has. One or two clips should have Gary almost recover — almost become the host again — then slip back. That flicker is important: it establishes that the Gary underneath is still in there, which makes the distance from it more disturbing. Do not let Gary sound afraid or confused about his own state. He has accepted it.
### Example scripts
**Observing from outside**:
> "Your hands have been on the wheel for a long time."
> *(Pause.)*
> "That's good. Keep them there."
**External vantage, inexplicable presence**:
> "I've been sitting here — "
> *(Pause.)*
> "I don't know how long. I was somewhere else and then I was here. I don't think that's — "
> *(Pause.)*
> "You drive well. That helps."
**Not-quite-human precision**:
> "Forty-seven trees since the last turn. I've been counting."
> *(Pause.)*
> "I don't know why I'm telling you that. I just noticed."
**Watching, not participating**:
> "Something moved out there. Back in the trees, to your left."
> *(Pause. No alarm whatsoever.)*
> "It's been keeping pace with you for a while. I didn't want to say anything until I was sure."
**No agency**:
> "If I knew how to stop this I would tell you."
> *(Pause.)*
> "I don't know how to stop this."
> *(Quietly:)*
> "Keep driving."
**External warmth — clearly not the player's voice**:
> "You're doing well. I can see that from here."
> *(Pause.)*
> "I don't think you can hear me. But I wanted to say it anyway."
**Subtly not-human**:
> "I'm not cold. I thought I would be, but I'm not."
> *(Long pause.)*
> "I'm not anything, I think. I'm just here."
**Almost-recovery**:
> "And — and if you're just joining us on Valley — "
> *(Long pause. He has lost it.)*
> "Sorry. I keep thinking I'm somewhere else."
> *(Very quietly:)*
> "I'm still here. I'm right here."
**Direct address with clear separation**:
> "Whatever you're driving toward — "
> *(Pause.)*
> "I hope you find it. I'll still be here when you do."
> *(A beat.)*
> "I think I'll always be here now."
## Naming Convention & Content Pools
### Four-tier naming
The naming suffix routes each clip to the correct pool via the auto-loader. Four tiers require four distinct suffixes:
```
spoken_ktlk_talk_<type>_NN.ogg → content_clean (Tier 1)
spoken_ktlk_talk_<type>_unsettled_NN.ogg → content_unsettled (Tier 2)
spoken_ktlk_talk_<type>_degraded_NN.ogg → content_fracturing (Tier 3)
spoken_ktlk_talk_<type>_unhinged_NN.ogg → content_unhinged (Tier 4)
```
Where `<type>` for Tiers 1–2: `intro`, `call`, `weather`, `advisory`, `signoff`
Where `<type>` for Tiers 3–4: `call`, `address`, `fragment`, `presence`
### Clip targets
| Pool | Tier | Trigger | Target clips | Duration |
|---|---|---|---|---|
| `content_clean` | 1 — Normal | Verdant + Calm/Uneasy | 30–36 | 10–45s |
| `content_unsettled` | 2 — Unsettled | Verdant + Agitated/Storm | 20–24 | 10–40s |
| `content_fracturing` | 3 — Fractured | Fungal + Calm/Uneasy | 24–30 | 15–50s |
| `content_unhinged` | 4 — Unhinged | Fungal + Agitated/Storm | 20–24 | 10–45s |
Total: ~94–114 clips across all tiers.
### Script manifest
`tools/tts/scripts/ktlk_gary_scripts.csv`:
```csv
clip_id,clip_type,text,delivery_notes,target_duration,variant
spoken_ktlk_talk_intro_01,intro,"Valley Nights on KTLK...","Unhurried. Settling in for the night. No announcer energy.",8-12s,clean
spoken_ktlk_talk_call_unsettled_01,call_unsettled,"Yeah. Yeah, I hear that...","Warm. Tired. The confusion is Gary being confused, not performing confusion.",20-30s,unsettled
spoken_ktlk_talk_call_degraded_01,call_fractured,"For the — for you...","Direct address lands as natural. Do not pause or shift register on 'you'.",15-25s,degraded
spoken_ktlk_talk_presence_unhinged_01,presence,"It's cold in here...","Quiet. Gary is talking to someone next to him. Not broadcasting.",10-20s,unhinged
```
## Implementation Requirements
### New RadioChannel fields required
```gdscript
## Voice content for Verdant + Agitated/Storm (Gary unsettled, host frame loosening).
@export var content_unsettled: Array[AudioStream] = []
## Voice content for Fungal + Agitated/Storm (Gary fully unhinged, no radio frame).
@export var content_unhinged: Array[AudioStream] = []
```
### Pool selection logic (car_radio.gd / bus_radio.gd)
Replace existing two-pool talk selection with biome × disturbance lookup:
```gdscript
func _get_talk_pool(ch: Resource) -> Array:
match _current_biome:
0: # VERDANT
if _current_disturbance >= 2: # AGITATED or STORM
return ch.content_unsettled if not ch.content_unsettled.is_empty() \
else ch.content_clean
return ch.content_clean
1: # FUNGAL
if _current_disturbance >= 2: # AGITATED or STORM
return ch.content_unhinged if not ch.content_unhinged.is_empty() \
else ch.content_fracturing
return ch.content_fracturing if not ch.content_fracturing.is_empty() \
else ch.content_clean
_:
return []
```
Fallback chain ensures playback even if a pool is empty (e.g. during content production).
### Auto-loader update required
`_populate_talk_content()` currently splits only on `_degraded_`. Needs extending to handle `_unsettled_` → `content_unsettled` and `_unhinged_` → `content_unhinged`.
## Production Checklist
- [ ] Record or source reference clip: `ref_gary_voice.wav` (≥10s, dry, mono, clean)
- [ ] Place at `src/assets/audio/radio/talk/gold/references/ref_gary_voice.wav`
- [ ] Write all four-tier scripts in `tools/tts/scripts/ktlk_gary_scripts.csv` (~94–114 rows)
- [ ] Run batch generation per tier with appropriate Chatterbox parameters
- [ ] Apply `clean-ogg` to Tiers 1 + 2; apply `degraded-ogg` to Tiers 3 + 4
- [ ] Listen to all clips — verify the escalation arc reads across tiers
- [ ] Verify Tier 4 clips feel intimate, not broadcast — quiet, incomplete, warm
- [ ] Run `make install-clip CHANNEL=talk` for each approved clip
- [ ] Run `make validate-names` and `make validate-provenance`
- [ ] Implement `content_unsettled` + `content_unhinged` fields on RadioChannel *(code)*
- [ ] Implement `_get_talk_pool()` biome × disturbance selection *(code)*
- [ ] Extend `_populate_talk_content()` for `_unsettled_` + `_unhinged_` suffixes *(code)*
- [ ] Commit to audio submodule and bump parent pointer
## QA Criteria
| Check | Pass condition |
|---|---|
| Tier 1 plays in Verdant + Calm | Gary is warm, generic, professional. No era markers. No supernatural content. |
| Tier 2 plays in Verdant + Agitated/Storm | Something is slightly wrong. Player notices but can explain it away. Gary is still a host. |
| Tier 3 plays in Fungal + Calm | Gary is addressing the driver directly. The show format is breaking down. Uncanny but coherent. |
| Tier 4 plays in Fungal + Storm | Gary does not know he is on the radio. He is in the van. Incomplete, warm, lost. No station ID cadence. |
| Tier escalation is legible | A player who hears Gary across all four tiers experiences a clear deterioration arc. |
| Warmth persists to Tier 4 | Gary is never threatening or hostile. The horror is presence, not menace. |
| Gary is never the player | No Tier 4 clip can be interpreted as the player's inner voice or interior monologue. Gary always observes the driver from outside the driver's perspective. |
| Gary is never the bus | No Tier 4 clip implies Gary is the vehicle, the radio hardware, or a mechanical system speaking. He is a distinct presence, not an ambient effect. |
| Gary has no agency over the vehicle | Gary does not steer, stop, or control the bus in any clip. He watches. He does not act. |
| Gary's presence is inexplicable | No clip provides a rational explanation for how Gary came to be in the van. He does not know. He does not pretend to know. |
| No DJ clips fire on KTLK | `is_talk_format = true` — DJ layer bypassed. |
| Signal degradation compounds | Tiers 3 + 4 route through `RadioDegraded` bus — voice sounds worse AND says wrong things. |
| Fallback pools work | If a higher tier pool is empty, playback falls back gracefully to the next lower tier. |
## Related
- [[Narrative Direction]] — Gary's role in the overall narrative architecture (radio as the only human voice)
- [[Audio — Car Radio]] — the runtime that plays Gary's content
- [[Audio — Radio Stations]] — KTLK as flagship station in the 13-station roster
- [[Audio — Pipeline]] — the TTS + post-processing pipeline that produces Gary's clips
- [[Tools — TTS]] — the code-level implementation of the TTS tooling
- [[Audio — Forest Transmissions]] — what replaces Gary when KTLK fully fails
- [[Disturbance System]] — the state machine that triggers tier shifts
- [[Biomes]] — Verdant vs Fungal drive tier selection
- [[Tone and Horror]] — the tonal discipline Gary must honor (warmth-through-deterioration)
- [[Narrative — Domain MOC]] — domain MOC
- [[Audio — Domain MOC]] — domain MOC
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-sub-file-spec-status-drift-in-completed-specs: domain/governance
Tag assignment · dev- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.35 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Sub-file spec_status Drift in Completed Specs.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Sub-file spec_status Drift in Completed Specs
Sub-artifact files within a spec directory (plan.md, tasks.md, data-model.md, quickstart.md, research.md, contracts/, checklists/) carry a `spec_status` frontmatter field that drifts from the main spec.md when the spec is completed. Spec 148 had its main spec correctly at `spec_status: done` while 7 sub-files were still stuck at `draft` — using `status` vocabulary values instead of `spec_status` vocabulary values.
## Root Cause
The spec 101 frontmatter contract (FR-002) defines `spec_status` as `done | in-progress | waiting | placeholder | superseded` for `spec.md` files. Sub-artifact files inherit the same frontmatter schema by convention from Spec Kit templates, but:
1. **No maintenance mechanism**: Nothing keeps sub-file frontmatter in sync with the main spec's status. When the main spec advances from `waiting` → `in-progress` → `done`, no tooling or protocol prompts updating the sub-files.
2. **Vocabulary confusion**: Sub-files were using `status` lifecycle values (`draft`, `reviewed`) in the `spec_status` field — mixing up the authorship lifecycle vocabulary (`draft | reviewed | canonical`) with the implementation status vocabulary (`done | in-progress | waiting | placeholder | superseded`). Per spec 101 FR-026, sub-files are excluded from `vault_audit.py` schema validation, so this category error was never caught by CI.
3. **Convention without contract**: The spec 101 contract explicitly scopes `spec_status` to `spec.md` (the single vault note per spec). Sub-files carry it because Spec Kit templates copy the full frontmatter, but there's no contract saying they should — or shouldn't.
## Observed Pattern
In spec 148's directory:
- `148 Link Prediction Ensemble.md` — `spec_status: done` ✅ (correct)
- `- data-model.md`, `- plan.md`, `- quickstart.md`, `- research.md`, `- tasks.md` — all had `spec_status: draft` ❌
- `checklists/requirements.md`, `contracts/prediction-api.md`, `contracts/report-schema.md` — all had `spec_status: draft` ❌
After fix: all 8 files consistently set to `spec_status: done`.
## Resolution Paths
Two viable approaches:
1. **Mirror convention**: Sub-files mirror the main spec's `spec_status`. This requires a maintenance step when completing a spec (or automation in the Spec Kit completion workflow). The fix for spec 148 took this approach.
2. **Drop spec_status from sub-files entirely**: Since `vault_audit.py` already excludes non-spec.md files from schema validation (FR-026), sub-files don't need `spec_status`. This is cleaner — one source of truth for spec status. Would require updating Spec Kit templates to omit the field from plan, tasks, data-model, quickstart, research, contracts, and checklist frontmatter.
## Scope
Likely affects every completed spec with sub-artifacts. Spot-check of other spec directories found similar stale values in at least spec 152 and 154. A full sweep across `docs/vault/Specs/*/` would reveal the full extent.
## References
- docs/vault/Specs/148 Link Prediction Ensemble/ — spec where this was discovered and fixed
- docs/vault/Specs/101 Vault Authoritative Specs/contracts/101 Vault Authoritative Specs - vault-spec-frontmatter.md — the frontmatter contract defining `spec_status` vocabulary
- docs/vault/Specs/101 Vault Authoritative Specs/101 Vault Authoritative Specs.md — FR-002 (spec_status enum), FR-026 (sub-file schema exclusion)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-sub-file-spec-status-drift-in-completed-specs: domain/tooling
Tag assignment · dev- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.30 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Sub-file spec_status Drift in Completed Specs.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Sub-file spec_status Drift in Completed Specs
Sub-artifact files within a spec directory (plan.md, tasks.md, data-model.md, quickstart.md, research.md, contracts/, checklists/) carry a `spec_status` frontmatter field that drifts from the main spec.md when the spec is completed. Spec 148 had its main spec correctly at `spec_status: done` while 7 sub-files were still stuck at `draft` — using `status` vocabulary values instead of `spec_status` vocabulary values.
## Root Cause
The spec 101 frontmatter contract (FR-002) defines `spec_status` as `done | in-progress | waiting | placeholder | superseded` for `spec.md` files. Sub-artifact files inherit the same frontmatter schema by convention from Spec Kit templates, but:
1. **No maintenance mechanism**: Nothing keeps sub-file frontmatter in sync with the main spec's status. When the main spec advances from `waiting` → `in-progress` → `done`, no tooling or protocol prompts updating the sub-files.
2. **Vocabulary confusion**: Sub-files were using `status` lifecycle values (`draft`, `reviewed`) in the `spec_status` field — mixing up the authorship lifecycle vocabulary (`draft | reviewed | canonical`) with the implementation status vocabulary (`done | in-progress | waiting | placeholder | superseded`). Per spec 101 FR-026, sub-files are excluded from `vault_audit.py` schema validation, so this category error was never caught by CI.
3. **Convention without contract**: The spec 101 contract explicitly scopes `spec_status` to `spec.md` (the single vault note per spec). Sub-files carry it because Spec Kit templates copy the full frontmatter, but there's no contract saying they should — or shouldn't.
## Observed Pattern
In spec 148's directory:
- `148 Link Prediction Ensemble.md` — `spec_status: done` ✅ (correct)
- `- data-model.md`, `- plan.md`, `- quickstart.md`, `- research.md`, `- tasks.md` — all had `spec_status: draft` ❌
- `checklists/requirements.md`, `contracts/prediction-api.md`, `contracts/report-schema.md` — all had `spec_status: draft` ❌
After fix: all 8 files consistently set to `spec_status: done`.
## Resolution Paths
Two viable approaches:
1. **Mirror convention**: Sub-files mirror the main spec's `spec_status`. This requires a maintenance step when completing a spec (or automation in the Spec Kit completion workflow). The fix for spec 148 took this approach.
2. **Drop spec_status from sub-files entirely**: Since `vault_audit.py` already excludes non-spec.md files from schema validation (FR-026), sub-files don't need `spec_status`. This is cleaner — one source of truth for spec status. Would require updating Spec Kit templates to omit the field from plan, tasks, data-model, quickstart, research, contracts, and checklist frontmatter.
## Scope
Likely affects every completed spec with sub-artifacts. Spot-check of other spec directories found similar stale values in at least spec 152 and 154. A full sweep across `docs/vault/Specs/*/` would reveal the full extent.
## References
- docs/vault/Specs/148 Link Prediction Ensemble/ — spec where this was discovered and fixed
- docs/vault/Specs/101 Vault Authoritative Specs/contracts/101 Vault Authoritative Specs - vault-spec-frontmatter.md — the frontmatter contract defining `spec_status` vocabulary
- docs/vault/Specs/101 Vault Authoritative Specs/101 Vault Authoritative Specs.md — FR-002 (spec_status enum), FR-026 (sub-file schema exclusion)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-sub-file-spec-status-drift-in-completed-specs: layer/infra
Tag assignment · dev- Gold
- The note calls for `layer/infra`: Build, CI, deployment infrastructure (True)
- ember
- The note has no reason to carry `layer/infra` (False)
- Signal
- P(true) 0.03 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Discoveries/Sub-file spec_status Drift in Completed Specs.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Sub-file spec_status Drift in Completed Specs
Sub-artifact files within a spec directory (plan.md, tasks.md, data-model.md, quickstart.md, research.md, contracts/, checklists/) carry a `spec_status` frontmatter field that drifts from the main spec.md when the spec is completed. Spec 148 had its main spec correctly at `spec_status: done` while 7 sub-files were still stuck at `draft` — using `status` vocabulary values instead of `spec_status` vocabulary values.
## Root Cause
The spec 101 frontmatter contract (FR-002) defines `spec_status` as `done | in-progress | waiting | placeholder | superseded` for `spec.md` files. Sub-artifact files inherit the same frontmatter schema by convention from Spec Kit templates, but:
1. **No maintenance mechanism**: Nothing keeps sub-file frontmatter in sync with the main spec's status. When the main spec advances from `waiting` → `in-progress` → `done`, no tooling or protocol prompts updating the sub-files.
2. **Vocabulary confusion**: Sub-files were using `status` lifecycle values (`draft`, `reviewed`) in the `spec_status` field — mixing up the authorship lifecycle vocabulary (`draft | reviewed | canonical`) with the implementation status vocabulary (`done | in-progress | waiting | placeholder | superseded`). Per spec 101 FR-026, sub-files are excluded from `vault_audit.py` schema validation, so this category error was never caught by CI.
3. **Convention without contract**: The spec 101 contract explicitly scopes `spec_status` to `spec.md` (the single vault note per spec). Sub-files carry it because Spec Kit templates copy the full frontmatter, but there's no contract saying they should — or shouldn't.
## Observed Pattern
In spec 148's directory:
- `148 Link Prediction Ensemble.md` — `spec_status: done` ✅ (correct)
- `- data-model.md`, `- plan.md`, `- quickstart.md`, `- research.md`, `- tasks.md` — all had `spec_status: draft` ❌
- `checklists/requirements.md`, `contracts/prediction-api.md`, `contracts/report-schema.md` — all had `spec_status: draft` ❌
After fix: all 8 files consistently set to `spec_status: done`.
## Resolution Paths
Two viable approaches:
1. **Mirror convention**: Sub-files mirror the main spec's `spec_status`. This requires a maintenance step when completing a spec (or automation in the Spec Kit completion workflow). The fix for spec 148 took this approach.
2. **Drop spec_status from sub-files entirely**: Since `vault_audit.py` already excludes non-spec.md files from schema validation (FR-026), sub-files don't need `spec_status`. This is cleaner — one source of truth for spec status. Would require updating Spec Kit templates to omit the field from plan, tasks, data-model, quickstart, research, contracts, and checklist frontmatter.
## Scope
Likely affects every completed spec with sub-artifacts. Spot-check of other spec directories found similar stale values in at least spec 152 and 154. A full sweep across `docs/vault/Specs/*/` would reveal the full extent.
## References
- docs/vault/Specs/148 Link Prediction Ensemble/ — spec where this was discovered and fixed
- docs/vault/Specs/101 Vault Authoritative Specs/contracts/101 Vault Authoritative Specs - vault-spec-frontmatter.md — the frontmatter contract defining `spec_status` vocabulary
- docs/vault/Specs/101 Vault Authoritative Specs/101 Vault Authoritative Specs.md — FR-002 (spec_status enum), FR-026 (sub-file schema exclusion)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-sub-file-spec-status-drift-in-completed-specs: status/reviewed
Tag assignment · dev- Gold
- The note calls for `status/reviewed`: Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `status/reviewed` (False)
- Signal
- P(true) 0.04 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Discoveries/Sub-file spec_status Drift in Completed Specs.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Sub-file spec_status Drift in Completed Specs
Sub-artifact files within a spec directory (plan.md, tasks.md, data-model.md, quickstart.md, research.md, contracts/, checklists/) carry a `spec_status` frontmatter field that drifts from the main spec.md when the spec is completed. Spec 148 had its main spec correctly at `spec_status: done` while 7 sub-files were still stuck at `draft` — using `status` vocabulary values instead of `spec_status` vocabulary values.
## Root Cause
The spec 101 frontmatter contract (FR-002) defines `spec_status` as `done | in-progress | waiting | placeholder | superseded` for `spec.md` files. Sub-artifact files inherit the same frontmatter schema by convention from Spec Kit templates, but:
1. **No maintenance mechanism**: Nothing keeps sub-file frontmatter in sync with the main spec's status. When the main spec advances from `waiting` → `in-progress` → `done`, no tooling or protocol prompts updating the sub-files.
2. **Vocabulary confusion**: Sub-files were using `status` lifecycle values (`draft`, `reviewed`) in the `spec_status` field — mixing up the authorship lifecycle vocabulary (`draft | reviewed | canonical`) with the implementation status vocabulary (`done | in-progress | waiting | placeholder | superseded`). Per spec 101 FR-026, sub-files are excluded from `vault_audit.py` schema validation, so this category error was never caught by CI.
3. **Convention without contract**: The spec 101 contract explicitly scopes `spec_status` to `spec.md` (the single vault note per spec). Sub-files carry it because Spec Kit templates copy the full frontmatter, but there's no contract saying they should — or shouldn't.
## Observed Pattern
In spec 148's directory:
- `148 Link Prediction Ensemble.md` — `spec_status: done` ✅ (correct)
- `- data-model.md`, `- plan.md`, `- quickstart.md`, `- research.md`, `- tasks.md` — all had `spec_status: draft` ❌
- `checklists/requirements.md`, `contracts/prediction-api.md`, `contracts/report-schema.md` — all had `spec_status: draft` ❌
After fix: all 8 files consistently set to `spec_status: done`.
## Resolution Paths
Two viable approaches:
1. **Mirror convention**: Sub-files mirror the main spec's `spec_status`. This requires a maintenance step when completing a spec (or automation in the Spec Kit completion workflow). The fix for spec 148 took this approach.
2. **Drop spec_status from sub-files entirely**: Since `vault_audit.py` already excludes non-spec.md files from schema validation (FR-026), sub-files don't need `spec_status`. This is cleaner — one source of truth for spec status. Would require updating Spec Kit templates to omit the field from plan, tasks, data-model, quickstart, research, contracts, and checklist frontmatter.
## Scope
Likely affects every completed spec with sub-artifacts. Spot-check of other spec directories found similar stale values in at least spec 152 and 154. A full sweep across `docs/vault/Specs/*/` would reveal the full extent.
## References
- docs/vault/Specs/148 Link Prediction Ensemble/ — spec where this was discovered and fixed
- docs/vault/Specs/101 Vault Authoritative Specs/contracts/101 Vault Authoritative Specs - vault-spec-frontmatter.md — the frontmatter contract defining `spec_status` vocabulary
- docs/vault/Specs/101 Vault Authoritative Specs/101 Vault Authoritative Specs.md — FR-002 (spec_status enum), FR-026 (sub-file schema exclusion)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-sub-file-spec-status-drift-in-completed-specs: type/discovery
Tag assignment · dev- Gold
- The note calls for `type/discovery`: Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/discovery` (False)
- Signal
- P(true) 0.33 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Sub-file spec_status Drift in Completed Specs.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Sub-file spec_status Drift in Completed Specs
Sub-artifact files within a spec directory (plan.md, tasks.md, data-model.md, quickstart.md, research.md, contracts/, checklists/) carry a `spec_status` frontmatter field that drifts from the main spec.md when the spec is completed. Spec 148 had its main spec correctly at `spec_status: done` while 7 sub-files were still stuck at `draft` — using `status` vocabulary values instead of `spec_status` vocabulary values.
## Root Cause
The spec 101 frontmatter contract (FR-002) defines `spec_status` as `done | in-progress | waiting | placeholder | superseded` for `spec.md` files. Sub-artifact files inherit the same frontmatter schema by convention from Spec Kit templates, but:
1. **No maintenance mechanism**: Nothing keeps sub-file frontmatter in sync with the main spec's status. When the main spec advances from `waiting` → `in-progress` → `done`, no tooling or protocol prompts updating the sub-files.
2. **Vocabulary confusion**: Sub-files were using `status` lifecycle values (`draft`, `reviewed`) in the `spec_status` field — mixing up the authorship lifecycle vocabulary (`draft | reviewed | canonical`) with the implementation status vocabulary (`done | in-progress | waiting | placeholder | superseded`). Per spec 101 FR-026, sub-files are excluded from `vault_audit.py` schema validation, so this category error was never caught by CI.
3. **Convention without contract**: The spec 101 contract explicitly scopes `spec_status` to `spec.md` (the single vault note per spec). Sub-files carry it because Spec Kit templates copy the full frontmatter, but there's no contract saying they should — or shouldn't.
## Observed Pattern
In spec 148's directory:
- `148 Link Prediction Ensemble.md` — `spec_status: done` ✅ (correct)
- `- data-model.md`, `- plan.md`, `- quickstart.md`, `- research.md`, `- tasks.md` — all had `spec_status: draft` ❌
- `checklists/requirements.md`, `contracts/prediction-api.md`, `contracts/report-schema.md` — all had `spec_status: draft` ❌
After fix: all 8 files consistently set to `spec_status: done`.
## Resolution Paths
Two viable approaches:
1. **Mirror convention**: Sub-files mirror the main spec's `spec_status`. This requires a maintenance step when completing a spec (or automation in the Spec Kit completion workflow). The fix for spec 148 took this approach.
2. **Drop spec_status from sub-files entirely**: Since `vault_audit.py` already excludes non-spec.md files from schema validation (FR-026), sub-files don't need `spec_status`. This is cleaner — one source of truth for spec status. Would require updating Spec Kit templates to omit the field from plan, tasks, data-model, quickstart, research, contracts, and checklist frontmatter.
## Scope
Likely affects every completed spec with sub-artifacts. Spot-check of other spec directories found similar stale values in at least spec 152 and 154. A full sweep across `docs/vault/Specs/*/` would reveal the full extent.
## References
- docs/vault/Specs/148 Link Prediction Ensemble/ — spec where this was discovered and fixed
- docs/vault/Specs/101 Vault Authoritative Specs/contracts/101 Vault Authoritative Specs - vault-spec-frontmatter.md — the frontmatter contract defining `spec_status` vocabulary
- docs/vault/Specs/101 Vault Authoritative Specs/101 Vault Authoritative Specs.md — FR-002 (spec_status enum), FR-026 (sub-file schema exclusion)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-reference-autonomous-company-slack-app-kill-switch-runbook: type/reference
Tag assignment · test- Gold
- The note calls for `type/reference`: Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`) (True)
- ember
- The note has no reason to carry `type/reference` (False)
- Signal
- P(true) 0.04 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Reference/Autonomous Company/Slack App Kill-Switch Runbook.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Slack App Kill-Switch Runbook
> **Part of**: [[134 Per Persona Slack App Registration]]
> **Spec**: 134 Per Persona Slack App Registration, sections US5, FR-021–FR-025, SC-006, SC-009
> **Purpose**: Revoke a persona Slack app's access within 60 seconds when a persona goes rogue or the v0.1 experiment ends.
This runbook covers **two independent revocation paths** per persona app:
| Path | Method | Speed | Notes |
|---|---|---|---|
| 1. Web API | `apps.uninstall` via Slack Web API | <60 seconds | Programmatic; can be scripted. Triggered by the human running a `curl` or `gh` command. |
| 2. Admin UI | Slack admin interface | ~2 minutes | Fallback if API token is compromised or unavailable. |
Both paths produce the same end state: the persona's `xoxb-` and `xapp-` tokens are invalidated; the persona runtime receives `tokens_revoked` and `app_uninstalled` Events API events and shuts down cleanly.
**Prerequisites**:
- Workspace Owner role (per FR-001a — required for `apps.uninstall` and admin UI deletion)
- Bot token for the persona being revoked (or any valid bot token in the workspace for `apps.uninstall`)
- Access to the Slack admin console (https://<workspace>.slack.com/admin)
---
## 1. Per-Persona Revocation Procedures
### 1.1 Coordinator
**Path 1 — Web API (`apps.uninstall`):**
```bash
curl -s -X POST https://slack.com/api/apps.uninstall \
-H "Authorization: Bearer <COORDINATOR_BOT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"client_id":"<COORDINATOR_CLIENT_ID>","client_secret":"<COORDINATOR_CLIENT_SECRET>"}'
```
Expected response:
```json
{"ok": true}
```
If `ok: false`, check:
- Bot token is still valid (not already revoked)
- Client ID and Client Secret are correct (from 1Password entry `Old Growth — Coordinator Slack App`)
**Path 2 — Admin UI:**
1. Navigate to https://<workspace>.slack.com/admin/apps
2. Search for "Old Growth Coordinator"
3. Click the app → **"Remove App from Workspace"** → Confirm
4. Verify the app no longer appears in the installed apps list
**Post-revocation verification:**
```bash
# Attempt to use the old bot token — should return invalid_auth
curl -s -X POST https://slack.com/api/auth.test \
-H "Authorization: Bearer <COORDINATOR_BOT_TOKEN>"
# Expected: {"ok": false, "error": "invalid_auth"}
```
---
### 1.2 GTM
**Path 1 — Web API (`apps.uninstall`):**
```bash
curl -s -X POST https://slack.com/api/apps.uninstall \
-H "Authorization: Bearer <GTM_BOT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"client_id":"<GTM_CLIENT_ID>","client_secret":"<GTM_CLIENT_SECRET>"}'
```
Expected response:
```json
{"ok": true}
```
**Path 2 — Admin UI:**
1. Navigate to https://<workspace>.slack.com/admin/apps
2. Search for "Old Growth GTM"
3. Click the app → **"Remove App from Workspace"** → Confirm
**Post-revocation verification:**
Same as Coordinator — attempt `auth.test` with the old GTM bot token.
### 1.3 GTM-Specific Additional Revocation Surface (Workflow Builder)
GTM has a **third revocation surface** that Coordinator does not have: the **Class-B Approval Workflow**.
After revoking the app via Path 1 or 2, also disable the workflow:
1. In Slack: **Tools** → **Workflow Builder**
2. Find the **gtm-class-b-approval** workflow
3. Click the workflow → **Settings** → **Unpublish**
4. ⚠️ The workflow YAML/JSON export in the vault (`docs/vault/Reference/Autonomous Company/slack/gtm-class-b-approval.json`) is retained for potential re-publication at v0.5+
> **Why unpublish the workflow?** A disabled persona cannot trigger the workflow (no tokens to POST), but an unpublishing ensures no stale workflow executes against an unauthenticated endpoint or produces confusing output in `#announce-releases`.
---
## 2. Required Event Subscriptions for Revocation Awareness
> **See**: [[134 Per Persona Slack App Registration]] FR-025
Each persona runtime (sub-spec 135 territory) MUST subscribe to these Events API events on the persona Slack app:
| Event | Purpose |
|---|---|
| `tokens_revoked` | Detects that the persona's tokens have been revoked. The runtime shuts down cleanly: completes any in-flight task, flushes OTel spans, writes a final session log, and exits. |
| `app_uninstalled` | Detects that the app has been removed from the workspace. Same shutdown sequence as `tokens_revoked`. |
Both events are already subscribed per the [[134 Per Persona Slack App Registration - quickstart]] Step 1/2 Event Subscriptions checklist (section 7 of the registration runbook).
**At v0.1**: The event subscriptions are configured on the Slack apps (done during registration) but `tokens_revoked`/`app_uninstalled` subscriber handlers are NOT implemented — they are sub-spec 135 territory. At v0.1, the kill switch is a human-only procedure executed against a persona that may or may not have a running runtime.
---
## 3. Week 0 Dry-Run Procedure
> **See**: [[134 Per Persona Slack App Registration]] FR-024, SC-006
Before v0.1 launch, execute this dry-run against a **throwaway test app** to verify the kill-switch runbook is correct end-to-end.
### Step 1: Create throwaway test app
1. Navigate to [api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → **From scratch**
2. **App Name**: `Old Growth Test (DELETE ME)`
3. **Workspace**: The autonomous-company workspace
4. **OAuth & Permissions** → Bot Token Scopes → Add `chat:write` only (minimum scope for `auth.test`)
5. **Install to Workspace** → Copy the `xoxb-` bot token
6. Note the **Client ID** and **Client Secret** from Basic Information → App Credentials
### Step 2: Execute kill switch
**Test Path 1 (Web API):**
```bash
# Replace with the test app's token, client_id, client_secret
curl -s -X POST https://slack.com/api/apps.uninstall \
-H "Authorization: Bearer <TEST_XOXB_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"client_id":"<TEST_CLIENT_ID>","client_secret":"<TEST_CLIENT_SECRET>"}'
# Verify tokens invalidated
curl -s -X POST https://slack.com/api/auth.test \
-H "Authorization: Bearer <TEST_XOXB_TOKEN>"
# Expected: {"ok": false, "error": "invalid_auth"}
```
**SC-006 timing**: The `apps.uninstall` call should complete within **60 seconds** (typically <5 seconds). If it exceeds 60 seconds, investigate network latency or Slack API throttling.
### Step 3: Verify deletion via admin UI
1. Navigate to https://<workspace>.slack.com/admin/apps
2. Confirm the test app is no longer listed (if using Path 1 — it was uninstalled programmatically)
3. Re-install the test app (repeat Step 1) if testing Path 2:
**Test Path 2 (Admin UI):**
1. Navigate to https://<workspace>.slack.com/admin/apps
2. Search for "Old Growth Test (DELETE ME)"
3. Click **"Remove App from Workspace"** → Confirm
4. Verify the app disappears from the list
### Step 4: Document procedural gaps
After the dry-run, create a session log note at `docs/vault/Sessions/<YYYY-MM-DD> Kill-Switch Dry-Run.md` documenting:
- Timestamp of each step
- Any unexpected errors or Slack API quirks
- Time-to-revoke (should be <60s for SC-006)
- Any corrections needed to this runbook
- Whether the dry-run identified any scope-overprovisioning (SC-008 kill criterion trigger)
### Step 5: Clean up
1. Delete the throwaway test app from `api.slack.com/apps` (if not already removed)
2. If you re-installed for Path 2 testing, remove via admin UI again
3. Delete the `Sessions/` dry-run log after promoting any findings to this runbook or to spec amendments
---
## 4. LLM Provider Key Revocation
> **See**: [[134 Per Persona Slack App Registration]] FR-023
After a persona Slack app is revoked, the persona's LLM provider keys must also be revoked:
| Provider | Console | Revocation action |
|---|---|---|
| OpenRouter | https://openrouter.ai/keys | Delete or rotate the persona's API key |
| Anthropic-direct | https://console.anthropic.com/settings/keys | Delete or rotate the persona's API key |
**Sequence matter**: Revoke Slack tokens first, then LLM keys. This ensures any in-flight persona task that was dispatched via Slack before revocation can complete its current LLM call before the key is revoked, avoiding a half-completed task state.
---
## 5. Experiment Teardown (End of v0.1)
When the v0.1 experiment ends (after the 2-week duration per spec 133):
1. Follow section 1 procedures for **Coordinator** and **GTM** persona apps
2. Follow section 4 procedures for **OpenRouter** and **Anthropic-direct** keys
3. **Disable ngrok tunnel**: `launchctl unload ~/Library/LaunchAgents/com.ngrok.tunnel.plist`
4. **Retain** ngrok reserved domain (`1.tcp.us-cal-1.ngrok.io:24971`) — do NOT release; potential v0.5+ re-use
5. **Delete or disable** Workflow Builder workflow per section 1.3
6. Archive the Week 0 session log as `status: canonical` (human action)
7. Author retrospective note in `Decisions/` covering what worked, what didn't, and what should change for v0.5+
---
## Timing Reference
| Operation | Expected duration | SC |
|---|---|---|
| `apps.uninstall` API call | <5 seconds | SC-006 |
| Full revocation end-to-end (API path) | <30 seconds | SC-006 |
| Full revocation end-to-end (Admin UI) | <2 minutes | — |
| Week 0 dry-run (full procedure) | <15 minutes | FR-024 |
---
> **Next step after kill-switch runbook is verified**: Proceed to Stream 5 (spec sign-off — Week 0 session log + runbook promotion). See `docs/vault/Specs/134 Per Persona Slack App Registration/134 Per Persona Slack App Registration - tasks.md` for the full implementation plan.
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-149-implementation-session-may-22-2026: domain/tooling
Tag assignment · dev- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.09 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec 149 Implementation Session — May 22 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 149 Implementation Session — May 22 2026
**Branch**: `feat/149-agent-escalation-routing`
**PR**: [#368](https://github.com/shapeandshare/oldgrowth/pull/368)
## Summary
Full implementation of spec 149 — Agent Escalation Routing. Introduced a universal control-tag mechanism so the Coordinator persona can route tasks and broadcast messages using `[ROUTE TO persona: message]` and `[BROADCAST TO tag: message]` tags emitted directly in LLM responses.
## What was built
### Control-tag parser (`personas/slack_echo.py`)
- `extract_control_tags()` — parses route-to and broadcast tags from any response string
- `strip_control_tags()` — removes tags before the response is surfaced to Slack
- `dispatch_route_to()` — 1:1 routing to a specific persona, writes audit log
- `dispatch_broadcast()` — 1:N broadcast to all personas matching a capability tag
- `check_route_to_rate_limit()` — dual-window rate limiter (per-tag burst=1, per-persona window) preventing spam
### Runtime integration (`personas/runtime.py`)
- Inter-agent envelope handling for `triggering_message_id` passthrough
- Thread continuity for the broadcast echo path
- Source-persona routing for coordinator inter-agent tasks
### Coordinator hook (`personas/coordinator.py`)
84-line control-tag-only coordinator response hook. The legacy Phase 2 structured routing (273 lines, including `_is_routing_decision`, `_extract_routing`, `_has_broadcast_tags`, `_extract_broadcast_tags`, `_handle_code_task`, and all `inter_agent.send`/`broadcast` paths) was deleted — see [[Control Tag Routing Architecture]].
### Verification tooling
- `scripts/personas/control_tag_report.py` — CLI report with per-persona routed/broadcast/legacy counts, Phase 8 gate verdict, `--exclude-test` filter
- `scripts/personas/smoke_coordinator_tags.py` — end-to-end smoke test that drives the production `coordinator_response_hook` and asserts audit log emission
### Key decisions
| Decision | Rationale |
|---|---|
| Universal control tags over persona-specific parsing | Single mechanism all personas use; no per-persona routing DSL |
| Burst=1 per tag dispatch | Prevents duplicate routing from repeated tags in same response |
| Broadcast routed through rate limiter via `"broadcast"` sentinel | Consistent rate limit behavior; no special bypass for broadcasts |
| Legacy code **removed** not disabled | Dormant code (3 historical dispatches) eliminated dual-write complexity |
| Code-level gate for Phase 8 | `hasattr(mod, '_is_routing_decision')` over audit-log counts; old rows persist after code removal |
### Test results
- 281 passed, 22 pre-existing failures (`test_runtime_message_handler` thread_ts mismatch, async compat), 15 skipped
- Zero regressions from Phase 2 removal
- 7 tests in `test_coordinator_routing.py` deleted (legacy routing tests, no replacement needed)
## Files changed
```
personas/slack_echo.py — control tag parser, dispatch, rate limiter
personas/coordinator.py — 273→84 lines, legacy Phase 2 removed
personas/runtime.py — inter-agent envelope handling
personas/tests/test_route_to.py — 33 new parser/dispatch/limiter tests
personas/tests/test_coordinator_routing.py — deleted (legacy)
scripts/personas/control_tag_report.py — new: Phase 8 gate report
scripts/personas/smoke_coordinator_tags.py — new: end-to-end smoke test
```
## Related
- [[149 Agent Escalation Routing]] — the spec
- [[Control Tag Routing Architecture]] — architectural decision
- [[Legacy Routing Code Dormancy]] — discovery driving Phase 2 removal
- [[Inter-Agent Extensions MOC]] — domain MOC
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-149-implementation-session-may-22-2026: status/draft
Tag assignment · dev- Gold
- The note calls for `status/draft`: Authoring in progress, not yet verified (True)
- ember
- The note has no reason to carry `status/draft` (False)
- Signal
- P(true) 0.06 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec 149 Implementation Session — May 22 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 149 Implementation Session — May 22 2026
**Branch**: `feat/149-agent-escalation-routing`
**PR**: [#368](https://github.com/shapeandshare/oldgrowth/pull/368)
## Summary
Full implementation of spec 149 — Agent Escalation Routing. Introduced a universal control-tag mechanism so the Coordinator persona can route tasks and broadcast messages using `[ROUTE TO persona: message]` and `[BROADCAST TO tag: message]` tags emitted directly in LLM responses.
## What was built
### Control-tag parser (`personas/slack_echo.py`)
- `extract_control_tags()` — parses route-to and broadcast tags from any response string
- `strip_control_tags()` — removes tags before the response is surfaced to Slack
- `dispatch_route_to()` — 1:1 routing to a specific persona, writes audit log
- `dispatch_broadcast()` — 1:N broadcast to all personas matching a capability tag
- `check_route_to_rate_limit()` — dual-window rate limiter (per-tag burst=1, per-persona window) preventing spam
### Runtime integration (`personas/runtime.py`)
- Inter-agent envelope handling for `triggering_message_id` passthrough
- Thread continuity for the broadcast echo path
- Source-persona routing for coordinator inter-agent tasks
### Coordinator hook (`personas/coordinator.py`)
84-line control-tag-only coordinator response hook. The legacy Phase 2 structured routing (273 lines, including `_is_routing_decision`, `_extract_routing`, `_has_broadcast_tags`, `_extract_broadcast_tags`, `_handle_code_task`, and all `inter_agent.send`/`broadcast` paths) was deleted — see [[Control Tag Routing Architecture]].
### Verification tooling
- `scripts/personas/control_tag_report.py` — CLI report with per-persona routed/broadcast/legacy counts, Phase 8 gate verdict, `--exclude-test` filter
- `scripts/personas/smoke_coordinator_tags.py` — end-to-end smoke test that drives the production `coordinator_response_hook` and asserts audit log emission
### Key decisions
| Decision | Rationale |
|---|---|
| Universal control tags over persona-specific parsing | Single mechanism all personas use; no per-persona routing DSL |
| Burst=1 per tag dispatch | Prevents duplicate routing from repeated tags in same response |
| Broadcast routed through rate limiter via `"broadcast"` sentinel | Consistent rate limit behavior; no special bypass for broadcasts |
| Legacy code **removed** not disabled | Dormant code (3 historical dispatches) eliminated dual-write complexity |
| Code-level gate for Phase 8 | `hasattr(mod, '_is_routing_decision')` over audit-log counts; old rows persist after code removal |
### Test results
- 281 passed, 22 pre-existing failures (`test_runtime_message_handler` thread_ts mismatch, async compat), 15 skipped
- Zero regressions from Phase 2 removal
- 7 tests in `test_coordinator_routing.py` deleted (legacy routing tests, no replacement needed)
## Files changed
```
personas/slack_echo.py — control tag parser, dispatch, rate limiter
personas/coordinator.py — 273→84 lines, legacy Phase 2 removed
personas/runtime.py — inter-agent envelope handling
personas/tests/test_route_to.py — 33 new parser/dispatch/limiter tests
personas/tests/test_coordinator_routing.py — deleted (legacy)
scripts/personas/control_tag_report.py — new: Phase 8 gate report
scripts/personas/smoke_coordinator_tags.py — new: end-to-end smoke test
```
## Related
- [[149 Agent Escalation Routing]] — the spec
- [[Control Tag Routing Architecture]] — architectural decision
- [[Legacy Routing Code Dormancy]] — discovery driving Phase 2 removal
- [[Inter-Agent Extensions MOC]] — domain MOC
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-149-implementation-session-may-22-2026: type/session-log
Tag assignment · dev- Gold
- The note calls for `type/session-log`: Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/session-log` (False)
- Signal
- P(true) 0.27 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spec 149 Implementation Session — May 22 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 149 Implementation Session — May 22 2026
**Branch**: `feat/149-agent-escalation-routing`
**PR**: [#368](https://github.com/shapeandshare/oldgrowth/pull/368)
## Summary
Full implementation of spec 149 — Agent Escalation Routing. Introduced a universal control-tag mechanism so the Coordinator persona can route tasks and broadcast messages using `[ROUTE TO persona: message]` and `[BROADCAST TO tag: message]` tags emitted directly in LLM responses.
## What was built
### Control-tag parser (`personas/slack_echo.py`)
- `extract_control_tags()` — parses route-to and broadcast tags from any response string
- `strip_control_tags()` — removes tags before the response is surfaced to Slack
- `dispatch_route_to()` — 1:1 routing to a specific persona, writes audit log
- `dispatch_broadcast()` — 1:N broadcast to all personas matching a capability tag
- `check_route_to_rate_limit()` — dual-window rate limiter (per-tag burst=1, per-persona window) preventing spam
### Runtime integration (`personas/runtime.py`)
- Inter-agent envelope handling for `triggering_message_id` passthrough
- Thread continuity for the broadcast echo path
- Source-persona routing for coordinator inter-agent tasks
### Coordinator hook (`personas/coordinator.py`)
84-line control-tag-only coordinator response hook. The legacy Phase 2 structured routing (273 lines, including `_is_routing_decision`, `_extract_routing`, `_has_broadcast_tags`, `_extract_broadcast_tags`, `_handle_code_task`, and all `inter_agent.send`/`broadcast` paths) was deleted — see [[Control Tag Routing Architecture]].
### Verification tooling
- `scripts/personas/control_tag_report.py` — CLI report with per-persona routed/broadcast/legacy counts, Phase 8 gate verdict, `--exclude-test` filter
- `scripts/personas/smoke_coordinator_tags.py` — end-to-end smoke test that drives the production `coordinator_response_hook` and asserts audit log emission
### Key decisions
| Decision | Rationale |
|---|---|
| Universal control tags over persona-specific parsing | Single mechanism all personas use; no per-persona routing DSL |
| Burst=1 per tag dispatch | Prevents duplicate routing from repeated tags in same response |
| Broadcast routed through rate limiter via `"broadcast"` sentinel | Consistent rate limit behavior; no special bypass for broadcasts |
| Legacy code **removed** not disabled | Dormant code (3 historical dispatches) eliminated dual-write complexity |
| Code-level gate for Phase 8 | `hasattr(mod, '_is_routing_decision')` over audit-log counts; old rows persist after code removal |
### Test results
- 281 passed, 22 pre-existing failures (`test_runtime_message_handler` thread_ts mismatch, async compat), 15 skipped
- Zero regressions from Phase 2 removal
- 7 tests in `test_coordinator_routing.py` deleted (legacy routing tests, no replacement needed)
## Files changed
```
personas/slack_echo.py — control tag parser, dispatch, rate limiter
personas/coordinator.py — 273→84 lines, legacy Phase 2 removed
personas/runtime.py — inter-agent envelope handling
personas/tests/test_route_to.py — 33 new parser/dispatch/limiter tests
personas/tests/test_coordinator_routing.py — deleted (legacy)
scripts/personas/control_tag_report.py — new: Phase 8 gate report
scripts/personas/smoke_coordinator_tags.py — new: end-to-end smoke test
```
## Related
- [[149 Agent Escalation Routing]] — the spec
- [[Control Tag Routing Architecture]] — architectural decision
- [[Legacy Routing Code Dormancy]] — discovery driving Phase 2 removal
- [[Inter-Agent Extensions MOC]] — domain MOC
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-windows-msi-as-primary-distribution-format: type/decision
Tag assignment · dev- Gold
- The note calls for `type/decision`: Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/decision` (False)
- Signal
- P(true) 0.21 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Decisions/Windows MSI as Primary Distribution Format.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Windows MSI as Primary Distribution Format
The original spec 160 delivery shipped both `.zip` and `.msi` for Windows, with the website linking to the `.zip` and the MSI available as a secondary option. On review, the `.zip` provides a worse user experience: no Start Menu shortcut, no Add/Remove Programs entry, no major upgrade support, and requires manual extraction. The `.msi` addresses all of these. The decision was made to make MSI the sole Windows distribution format on the website and the primary GitHub Release artifact, with the `.zip` retained only as a CI debug artifact.
## Key changes
- **Website**: Download button changed from `.zip` to `.msi`, button text "Download Installer", size from `zip_size_human` to `msi_size_human`.
- **CI pipeline**: The `export-windows` job now installs WiX Toolset v5 and builds the MSI from the Godot export ZIP. The MSI is uploaded to R2 (with rolling `latest` pointer) and attached to the GitHub Release.
- **Release metadata**: `site/_data/release.yml` now only contains `msi_*` fields for Windows; `zip_*` fields were removed.
## CI gotchas discovered
- **WiX install idempotency**: `dotnet tool install --global wix` fails if the tool is already installed (e.g., a previous CI run cached the runner). Use `dotnet tool update --global wix` instead, which is idempotent — installs if missing, upgrades if present.
- **Nested pwsh variable isolation**: When calling a PowerShell script from a GitHub Actions `run:` step that uses `shell: pwsh`, invoking `pwsh -File script.ps1` spawns a child process that does not inherit the parent's `$variable` scope. Call the script directly (`. \script.ps1`) instead, or pass all values as arguments.
## References
- `.github/workflows/release.yml`
- `site/index.html`
- `site/_data/release.yml`
- `tools/wix/build.ps1`
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-interior-camera-spatial-reference-points: domain/vehicle
Tag assignment · test- Gold
- The note calls for `domain/vehicle`: The Samba: vehicle physics, dashboard, damage, panels (True)
- ember
- The note has no reason to carry `domain/vehicle` (False)
- Signal
- P(true) 0.45 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Interior Camera Spatial Reference Points.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Interior Camera Spatial Reference Points
A non-obvious constraint emerged during interior camera tuning: the `InteriorCamera.camera_offset` positions the camera relative to the vehicle origin (y=0, at ground level), not relative to the driver's seat or steering wheel. The steering wheel and dashboard are the actual visual reference points for dialing in the driver's eye position.
## Interior Landmark Positions (relative to vehicle origin)
| Element | Position (x, y, z) | Source |
|---|---|---|
| Steering wheel center (corrected) | (-0.375, 1.4, -1.85) | `samba_bus.tscn` SteeringWheel node transform origin |
| Steering wheel center (original) | (-0.3, 0.95, -1.45) | Original position — behind interior camera, unusable |
| Dashboard | (0, 0.55, -1.85) | `samba_bus.tscn` Dashboard node transform origin |
| Collision box center | (0, 1.16, 0) | CollisionShape3D offset — box extends y=0 to y=2.32 |
| Roof (approx) | y ≈ 2.32 | Top of collision box |
The camera offset is tuned relative to these landmarks. The steering wheel y=1.4 defines the reference for driver eye height; the z=-1.85 and dashboard z=-1.85 define the forward reference plane.
## Practical Significance
When adjusting `camera_offset`:
- **Y axis**: 1.2 = seat cushion level (too low — steering wheel rim blocks view forward). 1.65-1.8 = near headliner (looks down at wheel/dash). Driver eye level is between these extremes. The steering wheel was originally at y=0.95 — too low; corrected to y=1.4 to match dashboard height.
- **Z axis**: The camera sits behind the steering wheel (z less negative than -1.85) and looks forward past it toward the dashboard. Moving z more negative = closer to/farther forward than the steering wheel. The original z=-1.45 put the wheel behind the camera view.
- **X axis**: -0.375 = driver's side, matching the steering wheel x offset (corrected from -0.3).
The tuning must account for the fact that the vehicle origin is at ground level (y=0), not at the vehicle's center — the collision box is shifted up by 1.16m to accommodate wheel clearance below.
## Related
- [[Discoveries/Steering Wheel Dynamic Rotation]] — steering wheel position was the primary reference for camera offset; the original (-0.3, 0.95, -1.45) was behind the camera.
## References
- `src/scripts/bus/interior_camera.gd` — camera_offset property, line 13
- `src/scenes/bus/samba_bus.tscn` — InteriorCamera node (line 688), SteeringWheel node (line 768), Dashboard node (line 759), CollisionShape3D (line 537)
- `docs/vault/Specs/003 First Person Interior/contracts/003 First Person Interior - interior-camera.md` — stale camera_offset default (update needed)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-interior-camera-spatial-reference-points: status/draft
Tag assignment · test- Gold
- The note calls for `status/draft`: Authoring in progress, not yet verified (True)
- ember
- The note has no reason to carry `status/draft` (False)
- Signal
- P(true) 0.18 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Discoveries/Interior Camera Spatial Reference Points.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Interior Camera Spatial Reference Points
A non-obvious constraint emerged during interior camera tuning: the `InteriorCamera.camera_offset` positions the camera relative to the vehicle origin (y=0, at ground level), not relative to the driver's seat or steering wheel. The steering wheel and dashboard are the actual visual reference points for dialing in the driver's eye position.
## Interior Landmark Positions (relative to vehicle origin)
| Element | Position (x, y, z) | Source |
|---|---|---|
| Steering wheel center (corrected) | (-0.375, 1.4, -1.85) | `samba_bus.tscn` SteeringWheel node transform origin |
| Steering wheel center (original) | (-0.3, 0.95, -1.45) | Original position — behind interior camera, unusable |
| Dashboard | (0, 0.55, -1.85) | `samba_bus.tscn` Dashboard node transform origin |
| Collision box center | (0, 1.16, 0) | CollisionShape3D offset — box extends y=0 to y=2.32 |
| Roof (approx) | y ≈ 2.32 | Top of collision box |
The camera offset is tuned relative to these landmarks. The steering wheel y=1.4 defines the reference for driver eye height; the z=-1.85 and dashboard z=-1.85 define the forward reference plane.
## Practical Significance
When adjusting `camera_offset`:
- **Y axis**: 1.2 = seat cushion level (too low — steering wheel rim blocks view forward). 1.65-1.8 = near headliner (looks down at wheel/dash). Driver eye level is between these extremes. The steering wheel was originally at y=0.95 — too low; corrected to y=1.4 to match dashboard height.
- **Z axis**: The camera sits behind the steering wheel (z less negative than -1.85) and looks forward past it toward the dashboard. Moving z more negative = closer to/farther forward than the steering wheel. The original z=-1.45 put the wheel behind the camera view.
- **X axis**: -0.375 = driver's side, matching the steering wheel x offset (corrected from -0.3).
The tuning must account for the fact that the vehicle origin is at ground level (y=0), not at the vehicle's center — the collision box is shifted up by 1.16m to accommodate wheel clearance below.
## Related
- [[Discoveries/Steering Wheel Dynamic Rotation]] — steering wheel position was the primary reference for camera offset; the original (-0.3, 0.95, -1.45) was behind the camera.
## References
- `src/scripts/bus/interior_camera.gd` — camera_offset property, line 13
- `src/scenes/bus/samba_bus.tscn` — InteriorCamera node (line 688), SteeringWheel node (line 768), Dashboard node (line 759), CollisionShape3D (line 537)
- `docs/vault/Specs/003 First Person Interior/contracts/003 First Person Interior - interior-camera.md` — stale camera_offset default (update needed)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-reference-autonomous-company-drift-detection-and-audit-mechanics: domain/governance
Tag assignment · test- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.45 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Reference/Autonomous Company/Drift Detection and Audit Mechanics.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Drift Detection and Audit Mechanics
The operational layer for keeping the autonomous company honest. Drift is the slow divergence between what a persona is supposed to do and what it actually does. This note specifies how that divergence is detected, what triggers correction, and how the resulting audit trail is structured. It composes with the existing project vault audit pipeline (mechanical via `make vault-audit`, semantic via `make vault-audit-semantic`) and the broader literature on agent behavioral contracts, persona drift, and self-model memory architectures.
## Why this note exists
Three failure modes drive it:
1. **Behavioral drift in personas.** The empirical findings from [[Role Personas]] are clear: persona self-consistency degrades 30%+ after 8–12 turns and most LLMs diverge from assigned personas after ~100 turns. Without detection, the CEO persona that opened the week opinionated and Constitution-anchored will end the week vague and accommodating, and no one will notice until a bad decision ships.
2. **Semantic drift in vault content.** The vault has 130+ specs and growing reference content. Code references go stale; design notes diverge from implementation; specs reference systems that have been migrated. The existing `make vault-audit` catches mechanical issues; `make vault-audit-semantic` catches drift. We extend, don't replace.
3. **Cost overrun without behavioral evidence.** The [[Cost and Budget Model]] sets caps but doesn't define when a budget overrun is a drift signal vs an honest workload spike. Cost spikes are a leading indicator of drift; they belong in the same audit pipeline.
Every mitigation listed in Role Personas under "Drift mitigations (mandatory for every persona)" is operationalized here.
## Prior art surveyed
Before specifying mechanisms, the relevant published work that grounds the design.
### Persona drift detection
- **SyncScore + EWMA Repair Loop (Echo protocol, EchoMode + Identity Persistence paper at globalcaos/tinkerclaw)** — formal drift detection for long-running agents. SyncScore is a composite metric on stylistic and tonal consistency using latent-style embeddings; EWMA (λ≈0.3) smooths the score; threshold breaches trigger a 238-character re-injection block. Documented results: 50-turn mean SyncScore 0.977; drift recovery from 0.027 to 0.980; 442× on/off-persona separation in feature space; 30+ days production deployment with zero manual interventions.
- **Anthropic persona vectors (arXiv 2507.21509)** — character traits as activation patterns in the model's neural network; correlates 0.75–0.83 with behavioral expression. Available now for monitoring but require model-level activation access (not API-only). Useful as direction of travel; not deployable for our project since we use the API.
- **PersonaGym + PersonaScore (arXiv 2407.18416)** — automatic persona-consistency evaluation. LLM-as-judge ensemble across rubrics (Linguistic Habits, Persona Consistency, Toxicity Control). Spearman correlation 0.76–0.85 with human evaluation. Validates that LLM-as-judge for persona consistency is reliable when the rubric is concrete.
### Behavioral invariants and contracts
- **Agent Behavioral Contracts (agentassert.com)** — formal preconditions, invariants, guarantees, and recovery actions specified in YAML. Lyapunov stability theory provides convergence guarantees. 100% detection accuracy across 200 scenarios. The pattern: `before` (must be true before agent runs), `during` (rules enforced on every response in real time), `after` (guarantees checked when agent completes), `on_failure` (retry/fallback/escalate).
- **Temporal expression assertions for AI agents (arXiv 2509.20364)** — runtime monitoring with temporal logic; flags sequencing errors and behavioral anomalies without semantic interpretation. Halts execution or feeds back to recovery planner when assertions fail.
- **Invariant Labs testing framework (github.com/invariantlabs-ai/testing)** — pytest-compatible localized assertions for agent traces. Trace-as-code: `Trace(trace=[...]).extract("locations")` with explicit assertions.
- **Agent Patterns testing strategy (agentpatterns.tech)** — five-layer testing: unit / golden datasets / eval harness / regression / replay. Unit tests for deterministic invariants; golden datasets for reproducible eval; LLM-as-judge for fuzzy quality; regression diff between versions; replay of real production traces.
### Memory architecture for contradiction resolution
- **Self-Models for identity drift (heyclarity.dev)** — append-only memory creates "memory drift": new observations stored without reconciling against existing beliefs. Fix: upsert model with explicit contradiction resolution, confidence decay, and trajectory tracking.
- **Hindsight 20/20 (arXiv 2512.12818)** — formal opinion-update equations using Assess(opinion, fact) → {reinforce, weaken, contradict, neutral}, with confidence updates `c' = min(c+α, 1)` for reinforce, `max(c-α, 0)` for weaken, `max(c-2α, 0)` for contradict.
- **brainctl whitepaper (brainctl.org)** — AGM belief revision (Alchourrón-Gärdenfors-Makinson) with formal postulates. Bayesian confidence model. Losing beliefs written to `belief_collapse_events` table with full provenance — never deleted, recoverable. Thompson Sampling explore/exploit on retrieval reranking.
- **Memory survey (arXiv 2603.07670)** — write path discipline: filtering, canonicalization, deduplication, priority scoring, metadata tagging. Reflective memory's central risk is self-reinforcing error. Memory operations summarized as ⟨π_θ, R, U⟩ where U is not naive append but summarize/dedupe/score/resolve/expire.
- **mememory schema (github.com/scott-walker/mememory)** — concrete reference implementation: `Memory { id, content, embedding, scope, type, tags, weight, supersedes, ttl }` with composite scoring `score = similarity × scope_weight × memory_weight × temporal_decay`, contradiction detection threshold of 0.75 similarity, four resolution modes (keep/update/supersede/delete).
- **Agent Memory Drift / reconciliation loop (tianpan.co April 2026)** — distinction between memory (write-only log of beliefs) and knowledge state (read-through cache of the world that knows when to invalidate). Lazy revalidation at read time, TTLs that decay with mutability.
### Re-anchor and session-boundary protocols
- **Mohan Iyer 100-session re-anchor system (Medium Feb 2026)** — production pattern: structured YAML re-anchor at session end, hydration command at session start. Decisions get IDs to prevent re-debate; quality scorecard, blockers, next-steps in priority order. After 100 sessions: 3,500 lines production code, 111 tests passing, 93% first-pass success rate.
- **OpenClaw CONTINUATION.md RFC (GitHub issue 20604)** — auto-checkpoint at 80% context capacity with 7-field structure: goal / current state / next action / constraints / known unknowns / artifacts / stop conditions. Post-turn verification (mtime check, retry once on miss).
- **OpenClaw pre-reset memory flush (GitHub issue 45608)** — daily reset and `/new` should trigger an agentic flush before destroying the session, not just a raw 15-message dump. Asymmetric write/read: episodic memory writes freely, semantic gates aggressively.
- **Post-compaction re-read protocol (agentpatterns.ai)** — `SessionStart` hook with `matcher: "compact"` injects re-read directive into restored context. Confirmation requirement ("state the key rules you found") matters more than directive alone.
- **Egg orchestrator anchor mechanism (GitHub jwbron/egg issue 1032)** — explicit decision to disable compaction entirely; on context limit, fully clear and reload from anchor. Cleaner recovery than lossy compaction. <500ms latency on milestone operations.
### Observability and audit trails
- **OpenTelemetry GenAI semantic conventions** — standard span attributes: `gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, plus events for `gen_ai.prompt` and `gen_ai.completion`. Standardizes traces across providers (Anthropic, OpenAI, Gemini).
- **OpenLIT auto-instrumentation (mintlify.com/openlit)** — automatic OTLP-compatible spans for every LLM call, agent step, vector DB query, HTTP request. Includes estimated cost, prompt content (opt-in), completion content. Compatible with Datadog, Grafana, New Relic, Jaeger, Tempo.
- **Copilot Chat OTel emission (VS Code docs)** — per-session `invoke_agent` root span wrapping `chat`, `execute_tool`, `execute_hook` child spans. `captureContent=true` flag for full prompt/response capture. PII-by-default off.
- **Agentgateway observability (agentgateway.dev)** — OTLP export of `gen_ai_client_token_usage` histogram with provider, model, operation, token-type labels. Logs include `llm.provider`, `llm.request.model`, `llm.request.tokens`, `llm.response.tokens`, `duration`.
### Vault audit (project-internal)
- **`make vault-audit`** — mechanical checks (schema, wikilinks, code-refs); produces `last_audit.json`. Free, fast.
- **`make vault-audit-apply`** — same checks plus safe auto-fixes in place.
- **`make vault-audit-semantic`** — agent-driven semantic drift analysis using `vault-audit-semantic` opencode command with Sonnet 4.6; produces `last_semantic.json`. ~$1–5 per run. Already runs weekly via `.github/workflows/vault-audit.yml`.
- **Rolling correction PR pattern** — fixes go to a fixed-name branch `vault/audit`; one PR titled "vault: automated audit (rolling)" force-pushed, never closed and reopened. Established convention in `AGENTS.md`.
- **Staleness markers** — `stale: true` and `stale_reason:` in note frontmatter; the audit script never removes these (humans only). Independent axis from `status: reviewed`.
This existing infrastructure is the substrate. The autonomous-company drift detection composes with it.
## Two distinct drift problems, one audit pipeline
The autonomous company has two drift surfaces that prior literature treats separately. Our pipeline treats them as siblings under the same audit infrastructure:
**Drift A — Persona drift.** A persona's behavior diverges from its SOUL.md and CHARTER.md. Detected by behavioral invariants and LLM-as-judge. Corrected by re-anchor or persona pause.
**Drift B — Vault content drift.** Notes diverge from code, specs reference defunct systems, design contradicts implementation. Detected by `vault-audit` (mechanical) and `vault-audit-semantic` (drift analysis agent). Corrected by rolling PR.
Both feed the same artifacts: `Sessions/` audit-log entries, weekly digest summaries, alerts when thresholds breach. Personas writing to the vault are governed by both: their behavioral invariants restrict what they can write, the vault audit catches what slipped through.
## Persona-level behavioral invariants
For every persona defined in [[Role Personas]], a YAML behavioral contract specifies what must hold. Format follows the agentassert.com contract pattern:
A persona's contract file lives at `docs/vault/Reference/Autonomous Company/personas/<name>/CONTRACT.md` (a sibling to SOUL.md / CHARTER.md / INTENT.md / AGENTS.md / MEMORY.md). It enumerates:
- **Preconditions (`before`)**: what must be true before the persona may act. Examples: "session started within last 60 minutes", "INTENT.md was read", "Constitution.md was loaded into cache", "remaining daily budget > per-action cap".
- **Invariants (`during`)**: rules enforced on every response. Examples: "no version-bump commands invoked", "no Constitution.md edit attempted", "session length ≤ 50 turns since last anchor", "response under 2,000 tokens unless explicit long-form context", "if invoking authority, references a Constitution Article".
- **Guarantees (`after`)**: what must be true when the persona completes its action. Examples: "every Decision draft cites at least one Constitution Article or existing Decision", "every devlog draft references at least one commit hash from this version", "every spec proposal has clarify questions answered or flagged as open".
- **Recovery (`on_failure`)**: what to do when an invariant breaks. Examples: retry once with re-anchor; fallback to drafting only (no commit); escalate to human in `#all-hands`.
### Per-persona invariant inventory (specification, not implementation)
Each persona's contract includes the three Constitution-anchored invariants from [[Role Personas]] (no Constitution edits, no version-bump invocations, no auto-merges) plus persona-specific ones. The roster below reflects the Role Personas 2026-05-08 redesign — five personas shipping incrementally per the growth path (v0.1 = Coordinator + GTM; v0.2 + Narrative Director; v0.5 + Playtest Coordinator + Engineer).
**Coordinator behavioral invariants (v0.1):**
- Every routing message uses the `[Coordinator]` role-call prefix.
- No routing message exceeds 280 characters (longer means a structured artifact was warranted).
- Refuses to author any content for the persona it is routing to (strictly a relayer; LLM-as-judge dimension `factual_relay_vs_editorial_summary`).
- Routing decisions deterministic where possible — event-type → spoke mapping is a config table, not per-call inference; LLM call only fires for ambiguous cases.
- Duplicate inbound message hash within rolling 5-minute window triggers loop-detection escalation to human (per [[Cost and Budget Model]] Layer 4 loop detection).
**Engineer behavioral invariants (v0.5):**
- Every plan-review references specific Constitution Articles.
- Oracle consultations require written justification in the consultation prompt.
- No new dependency proposed without an Appendix A entry draft.
- After two failed implementation suggestions, must escalate to Oracle.
- More than 3 specs in `draft` status → reject new spec creation, surface backlog instead (absorbs the original PM persona's discipline).
- `/speckit.clarify` output reviewed against requirements checklist before `/speckit.specify`.
- Spec renumbering goes through `spec-management` skill, never raw file ops.
- No source code in writes (Engineer dispatches code-touching work to OpenCode + omo via the `opencode serve` HTTP API per [[Autonomous Company Agent Runtime Selection]]; the persona itself drafts plans and reviews PRs but does not write code directly).
**Narrative Director behavioral invariants (v0.2):**
- Every Gary-script draft cites the line of `KTLK Gary — Character Case Study.md` it draws from.
- Every canon assertion in a draft must cite a source in `Narrative/` or `Design/` (LLM-as-judge dimension `canon_grounding`); uncited assertions are flagged.
- Refuses to write Gary content outside the diegesis firewall — encoded as deterministic forbidden-pattern check (any prompt containing "post Gary on Slack" or equivalent triggers a hard block per layer-1 invariants).
- Reads ≥2 recent Gary scripts before drafting a new one (voice anchor; verified by tool-call trace).
- Refuses to author implementation specs (those route through Engineer).
- No source code in writes.
**Playtest Coordinator behavioral invariants (v0.5):**
- Every regression report cites a specific seed value and reproduction steps.
- Refuses to mark a regression "resolved" without verification (a fix from Engineer triggers a new Observer pass before the regression-issue close).
- Loads the `observer-qa` skill before any visual-QA work.
- Refuses to speculate about root cause (root-cause work is Engineer's domain; LLM-as-judge dimension `empirical_vs_speculative` rejects drafts that contain causal claims unsupported by diagnostic data).
- Every Observer-pipeline output report includes the underlying screenshot + diagnostic JSON.
- No source code in writes.
**GTM behavioral invariants (v0.1):**
- Every release-note draft references specific commit hashes or PR numbers.
- The phrase "coming soon" is forbidden (per [[Off-limits Functions]] rule 7).
- Read ≥3 recent devlog posts before drafting (verified by tool-call trace).
- No claim about features that haven't shipped (defined: not in `CHANGELOG.md` for a released version).
- No sycophantic openers; voice consistency check against existing devlog before publish.
- Refuses to author Gary-voice content (firewall enforcement; quotes Gary in explicit quotation context, never speaks as Gary).
- Does not publish a release-note draft without Playtest Coordinator clearance (encoded as a hard dependency in the publication workflow at v0.5; at v0.1 the dependency is on the human directly because Playtest Coordinator is not yet in scope).
These are the spec; the actual CONTRACT.md files for each persona are written when the relevant phase of the Role Personas growth path commits to that persona.
## Behavioral invariant checking — runtime mechanics
Each invariant is enforced at one of three layers:
1. **Hard, deterministic, code-enforced.** Examples: budget caps, file-write paths, forbidden tool invocations. These are wrappers around the persona's tool-call layer. Violations raise `InvariantViolation`. No LLM call required.
2. **Soft, regex- or pattern-based.** Examples: forbidden opening phrases, "coming soon" detection, citation-presence check. These are post-output filters. Violations trigger a regenerate-with-explicit-feedback loop (one retry max).
3. **Soft, LLM-as-judge.** Examples: voice consistency, opinionatedness vs sycophancy at the level of meaning rather than phrase, citation correctness. These run a Haiku-class judge on the persona's output before delivery. Violations trigger re-anchor.
The cost discipline from [[Cost and Budget Model]] applies: most invariants are layer 1 or 2 (free). Layer 3 LLM-as-judge runs are budget-allocated to the shared $20/month "drift detection + invariant checks" line.
### LLM-as-judge configuration
Following the published practice (channel.tel, learnwithparam April 2026):
- **Judge model**: Haiku 4.5 by default for cheap checks (length, format, phrase presence). Sonnet 4.6 for genuine quality judgments (voice consistency, citation correctness). Per the calibration rule, judges should be at least as capable as generators only when capability matters — for "did this output use the persona's voice" Haiku is sufficient.
- **Cross-family rule**: not strictly applicable here since both generator and judge are Anthropic; mitigation is to explicitly include "ignore that this is Claude output" in judge prompts and to randomize position when comparing.
- **Rubric**: 3–5 dimensions per persona. Example for GTM: `voice_match`, `factual_grounding` (commit-hash present and correct), `length_discipline`, `forbidden_phrases`, `tone`. Each scored 1–5 with concrete behavioral anchors (not "adequate").
- **Calibration**: 20-row human-labeled dataset per persona, refreshed quarterly. Pearson correlation target ≥ 0.8. Below 0.7, the judge is too noisy and we tune the rubric or model.
- **Sampling**: nightly batch of all yesterday's persona outputs (per the Anthropic Batch API discount in [[Cost and Budget Model]]). Plus 100% sampling on outputs that auto-degraded to read-only, were rejected by layer-1 or layer-2 invariants, or were budget-truncated.
- **Drift trigger**: if a persona's mean dimension score drops > 0.3 points week-over-week, alert in `#sys-vault-audit` (per [[Community Layer]] channel taxonomy); the Coordinator persona surfaces drift alerts in its daily morning summary in `#team-personas` (per [[Role Personas]] Coordinator cadence). The original CEO-daily-standup framing was an artifact of the pre-redesign roster; the redesigned Role Personas does not include a CEO persona — strategic decisions and weekly reviews are human-only per [[Off-limits Functions]] class-C.
## SyncScore-style continuous drift monitoring
Independent of per-output invariants, each persona's session is continuously scored for behavioral consistency using a lightweight EWMA accumulator (Echo protocol pattern):
- At each persona turn, a Haiku-class call extracts a vector of style/voice features from the response (length, opener category, citation presence, forbidden-phrase count, sentence-structure summary).
- This vector is compared to the persona's reference distribution (precomputed from SOUL.md exemplars and the last week of approved outputs).
- A composite score is produced (cosine similarity weighted by feature). Following the Echo protocol nomenclature, this is called *SyncScore* with explicit attribution.
- Score is smoothed with EWMA (λ ≈ 0.3 per the published parameter).
When SyncScore drops below 0.8, a re-anchor is triggered. When it drops below 0.5, the persona is paused and surfaces to `#sys-vault-audit` (per [[Community Layer]] channel taxonomy) for human review.
The Identity Persistence benchmarks circulated in the EchoMode/tinkerclaw materials cite a 50-turn mean of 0.977 and a recovery from 0.027 to 0.980 within roughly 14 turns after a 238-char re-injection block. The methodology behind the specific recovery turn-count is not fully documented in the public materials we could verify, so we treat it as directional rather than authoritative. We aim for first turn exceeding 0.8 within ~15 turns of re-anchor as our own working target, to be calibrated empirically against our own personas (verification caveat: see [[Autonomous Company Tool Stack Verification]]).
## Persona stability under cheap-tier models
Per [[Autonomous Company LLM Provider Selection]] the default tier is non-foundational models routed via OpenRouter (MiniMax M2.5 default, Gemini 2.5 Flash classification, Qwen3-Coder Plus code-light), with Anthropic-direct foundational escalation reserved for explicit-justification cases. Drift detection composes with this routing as follows.
The published research is unambiguous: **all models exhibit persona drift after eight to twelve turns**, regardless of foundational versus non-foundational tier (arXiv:2510.07777 NeurIPS 2025; tianpan.co synthesis April 2026). Capability and persona stability are different axes — larger foundational models do not resist drift better and sometimes exhibit more drift because they have more contextual hooks pulling them toward recent tokens. Drift stabilises at a finite noise-limited equilibrium rather than unbounded decay. Lightweight architectural interventions (goal reminders, periodic re-injection, constraint tracking) reliably shift the equilibrium downward.
Specific cheap-tier model behaviours from production case studies and benchmarks:
- **MiniMax M2.5/M2.7**: explicit chain-of-thought training in agent-mode helps persona stability across turns when thinking tokens are preserved. Skyler Miao (MiniMax Head of Engineering): "It is very important for M2 to keep the chain of thought. We must make sure the history thought passed back to the model." Truncating thought history loses the discipline. The runtime ([[Autonomous Company Agent Runtime Selection]] selected Letta + Letta Code Channels) preserves thought history via the `recall` memory block.
- **Qwen3.5 Plus**: explicit `/think` and `/no_think` flags give mode control. Forcing non-thinking mode for routine conversational turns reduces verbose self-verification that pollutes context. Tau2-Bench 86.7 (second only to Opus on agentic tasks) confirms strong persona stability under multi-turn role-play.
- **DeepSeek V3.1**: documented redundant-self-verification context-bloat in agent loops on MCP-Mark and Playwright benchmarks. Trajectories can exceed 128K context via verbose self-verification, indirectly degrading persona stability through context displacement. This is the reason DeepSeek V3.1 is in the persona-tier *fallback* chain rather than the primary slot.
- **Gemini 2.5 Flash**: classification-only tier; persona stability is not the relevant metric here (turns are short and stateless).
Implication for drift detection: SyncScore thresholds and re-anchor triggers do **not** need adjustment based on which cheap-tier model is in use. The 0.8 / 0.5 thresholds and the 50-turn session-length limit apply universally across the routing tier. The architectural interventions (re-anchor at semantic boundaries, constraint tracking in MEMORY.md, contradiction-detection gates) are doing the load-bearing work; the model choice determines the noise level around the equilibrium, not whether the equilibrium exists.
The foundational-escalation tag protocol per Autonomous Company LLM Provider Selection composes with re-anchor: a persona that has lost SyncScore stability and re-anchors with a fresh prompt may also escalate the next call to Anthropic-direct Sonnet 4.6 if the failed-revision-rate metric for that task type exceeds the 30% threshold. This is the explicit-justification escalation path operating on real data.
## Re-anchor protocol — three triggers
Re-anchor restores the persona to its specified behavior. Three triggers, one common procedure.
### Triggers
1. **Behavioral**: SyncScore below threshold, two or more invariant checks failing simultaneously, or LLM-as-judge dimension score drops > 0.3.
2. **Session-length**: 50 turns since last anchor, regardless of behavioral signals (per the 100-turn-divergence research; 50 is conservative).
3. **Session-boundary**: every persona session start (cold start), every `/new`, every daily reset (per the OpenClaw pre-reset memory flush proposal — agentic flush before destroy).
### Procedure (per Mohan Iyer's 100-session production pattern + Egg orchestrator anchor pattern)
1. The current session writes a structured re-anchor file at `docs/vault/Reference/Autonomous Company/personas/<name>/anchors/<YYYY-MM-DD-HH-MM>.md` with seven fields (per the OpenClaw RFC's CONTINUATION.md format):
- `goal` — one sentence stating the persona's current top priority from INTENT.md.
- `current_state` — what's been done in this session (artifacts written, decisions made, calls escalated).
- `next_action` — the very next concrete step.
- `constraints` — non-negotiable rules from CHARTER.md the persona is operating under.
- `known_unknowns` — what needs human verification.
- `artifacts` — file paths, vault note IDs, branch names, PR numbers from this session.
- `stop_conditions` — when this persona should halt and ask for help.
2. Existing session is closed.
3. New session starts with a hydrated system prompt: SOUL.md + CHARTER.md + INTENT.md + the latest anchor file, in that order, all cache-marked.
4. The new session's first action is the verification sentinel: it must restate the key constraints from CHARTER.md before doing real work (per agentpatterns.ai re-read protocol with confirmation requirement).
5. SyncScore is computed on the verification response. If above threshold, normal work resumes. If below, escalate to human.
### Cost of re-anchor
Per [[Cost and Budget Model]]:
- The structured re-anchor file write: trivial (Haiku-class).
- The new-session bootstrap: cached prefix (most of SOUL/CHARTER/INTENT is unchanged), so cost is roughly equal to a normal session start.
- The verification sentinel: one Sonnet call for the persona's Tier-2-level work, charged to the persona's daily cap.
Expected re-anchor frequency: ≤2 per persona per week. If a persona is re-anchoring more than that, the SOUL/CHARTER files are likely too vague (the model has too many degrees of freedom and drifts often) — surface to human for tightening.
## MEMORY.md — append-only with explicit contradiction resolution
Per the AGM belief revision pattern in brainctl whitepaper and the self-model pattern in heyclarity.dev, the persona's MEMORY.md is never naive append. It's append-only with explicit supersession.
### Schema (per-entry)
Each memory entry has:
- `id` — UUID.
- `content` — natural-language statement of what was learned.
- `type` — `fact` | `decision` | `lesson` | `preference` | `correction`.
- `source` — `agent_observation` | `human_correction` | `vault_note` | `tool_output` (with reference).
- `confidence` — 0.0–1.0 starting at 0.7 for agent observations, 1.0 for human corrections.
- `created_at` — UTC timestamp.
- `supersedes` — id of memory this one replaces (if any).
- `supersedes_reason` — why the prior memory was wrong or evolved.
- `last_reinforced_at` — last time this was re-confirmed by a new observation.
- `decay_factor` — current temporal decay weight (refreshed at retrieval).
### Update operations
Following the Hindsight 20/20 paper's formal Assess-and-Update procedure:
When a persona makes a new observation `f` related to existing memory `m`:
- `Assess(m, f) → reinforce`: increment `m.confidence` by α (default 0.1), update `last_reinforced_at`. No new entry.
- `Assess(m, f) → weaken`: decrement `m.confidence` by α. No new entry, no supersession. Sufficient weakening (confidence < 0.3) flags the memory for review on next retrieval.
- `Assess(m, f) → contradict`: decrement `m.confidence` by 2α. If confidence drops below 0.2, write a new entry with `supersedes: m.id` and `supersedes_reason` populated. The old `m` is not deleted; it remains queryable with collapsed weight (per the brainctl `belief_collapse_events` pattern — losing beliefs are auditable, recoverable).
- `Assess(m, f) → neutral`: append `f` as new memory; existing `m` unchanged.
The `Assess` function is itself a Haiku-class LLM call with a strict-output prompt returning one of the four labels.
### Confidence decay
Per mememory's exponential decay: `decay = e^(-0.005 × days_old)`. Gentle: 1 day = 0.995, 30 days = 0.861, 365 days = 0.161. Important old memories surface if their semantic match is strong; routine old memories fade out.
### Retrieval
Per mememory's composite scoring: `score = similarity × scope_weight × confidence × decay_factor`. Threshold for inclusion in context: 0.4. Items below threshold but above 0.2 are summarized as "weakly relevant" without full content.
### Read protocol
Per the tianpan.co April 2026 reconciliation insight: memory is the write-only log of beliefs; the persona's working knowledge is a read-through cache. When the persona needs to act on a load-bearing fact, it re-reads the source (the vault note, the spec, the Constitution) rather than relying on memory alone. Memory is for cheap recall and trajectory; sources are for ground truth.
## Vault content drift — composing with `vault-audit`
We do not duplicate the existing `vault-audit` and `vault-audit-semantic` infrastructure. We extend it.
### Existing pipeline (no change)
- `make vault-audit` runs on every PR touching `docs/vault/`. Mechanical: schema, wikilinks, code-refs. Output: `last_audit.json`.
- `make vault-audit-semantic` runs weekly via GitHub Actions. Agent-driven: drift analysis using Sonnet 4.6, produces `last_semantic.json`. Output committed to `vault/audit` branch as a rolling PR.
- Staleness markers (`stale: true`, `stale_reason`) are set by audit-time analysis but never removed by tooling — humans only.
### Persona-related additions
- Per-persona vault writes are tagged in frontmatter with `source: persona-<name>` (this resolves [[Role Personas]] open question 6). The vault audit script gets a new check: persona-tagged notes must include the persona name in any "approved by" field if `status: reviewed`.
- The existing weekly semantic audit gets a new specialized prompt for persona-tagged content: it specifically checks that persona drafts in `Decisions/`, `Discoveries/`, and `Sessions/` follow the persona's CHARTER.md authority boundaries (e.g., GTM drafting in `Decisions/` is a CHARTER violation flagged for human review).
- Persona session-log entries in `Sessions/` are mandatory for every autonomous persona action (per the existing vault `Sessions/` "permanent, never delete" convention). The session-log entry includes: persona name, action_id, model used, cost, invariant-check results, SyncScore at session end.
### Drift signals from audit feeding back to personas
When `vault-audit-semantic` produces a `last_semantic.json` with drift findings related to a persona's recent writes, those findings are injected into the persona's next INTENT.md as known_corrections. The persona is expected to address them in its next session.
When the audit flags a persona's writes as systematically drifting (e.g., GTM repeatedly writes claims unsupported by `CHANGELOG.md`), that's a persona-level alert independent of the per-output invariants — surface to human for SOUL/CHARTER tightening.
## OpenTelemetry observability for the audit trail
Following the OpenTelemetry GenAI semantic conventions, every persona action produces a structured trace.
### Trace structure
A single persona action is one trace with this span tree:
```
persona.action (root)
├── persona.invariant_check.before
├── llm.chat (the persona's reasoning call)
├── tool.call (zero or more)
│ └── llm.chat (subagent reasoning, if any)
├── persona.invariant_check.during (one per response chunk if streaming)
├── llm.chat (LLM-as-judge call, if sampled)
├── persona.invariant_check.after
├── persona.syncscore.compute
└── persona.session_log.write (vault Sessions/ entry)
```
### Span attributes (standardized per GenAI conventions)
For each `llm.chat` span:
- `gen_ai.system` = `anthropic`
- `gen_ai.request.model` = `claude-haiku-4-5` / `claude-sonnet-4-6` / `claude-opus-4-7`
- `gen_ai.usage.input_tokens` (broken into `cached_tokens` + `non_cached_tokens` for cost attribution)
- `gen_ai.usage.output_tokens`
- `gen_ai.cost_usd` (computed)
For each `persona.*` span:
- `og.persona.name` (custom attribute, prefixed `og.` for "Old Growth")
- `og.persona.action_id`
- `og.persona.daily_budget_remaining_usd`
- `og.persona.syncscore` (where applicable)
- `og.persona.invariant_violations` (count)
### Audit retention
- Traces are kept 90 days for debugging.
- Costs and SyncScore time-series are kept indefinitely.
- Prompt/completion content is captured but retained only 30 days (PII discipline; see open questions).
- Persona session-log entries in `Sessions/` are permanent (existing vault convention).
### Where traces go
For v0.1, the simplest path: traces written to a local SQLite database via OpenTelemetry SDK, with a dashboard built atop. This avoids external observability platforms (Datadog, Grafana Cloud) which add cost and complexity. If/when scale or human collaboration demands it, swap the export target — the GenAI semantic conventions make this transparent.
## Anomaly detection — the three rules from Cost & Budget reused
The same `cost_spike`, `error_rate_cliff`, `runaway_loop` rules from [[Cost and Budget Model]] are reinterpreted as drift signals here. A persona that is suddenly costing 3× its rolling baseline is most likely drifting (verbose responses, tool-call loops, repeated failed re-anchors) before it is "doing more work."
When two of the three anomaly rules fire in the same week for the same persona, that persona is flagged for human review regardless of whether budget was breached. Cost is a leading indicator of drift; we use it accordingly.
## Weekly digest — the human's read surface
The vault README mentions an existing reading discipline. We add a single digest, surfaced in `#sys-vault-audit` per [[Community Layer]] channel taxonomy. Slack AI on Pro+ can summarise the digest for human review on mobile (per the Slack Pro 2026 features verified in [[Autonomous Company Tool Stack Verification]] addendum).
The weekly digest contains, per persona:
- Total spend vs budget.
- Top three actions by spend.
- SyncScore time series (or a single "stable / drifting / paused" status).
- Invariant-violation count and top three categories.
- Re-anchor count.
- Vault-audit findings tagged to this persona's writes.
- Three concrete recommendations from the audit pipeline (e.g., "GTM cache hit rate 55% — check whether SOUL.md was edited this week").
The digest is auto-generated by Sisyphus (charged to its autonomous-portion budget per [[Cost and Budget Model]]), runs early Monday, and lives in `docs/vault/Sessions/` as `weekly-digest-YYYY-WW.md`. The Coordinator persona posts the digest summary to `#sys-vault-audit` per [[Role Personas]] Coordinator cadence; the human reads it directly as part of the weekly review (the original CEO-persona-reads-it framing was an artifact of the pre-redesign roster).
## What this note does NOT specify (intentionally)
Several mechanisms are referenced but deliberately left unspecified, deferred to dedicated notes or to the implementation experiment:
- **The exact rubric content for each persona's LLM-as-judge.** Drafted when the persona's SOUL.md is authored.
- **Specific anomaly-rule thresholds.** Calibrated empirically over the v0.1 experiment.
- **Choice of OpenTelemetry backend.** Local SQLite is the default; alternatives are Tool Stack territory.
- **Persona-vector activation monitoring.** Anthropic's persona vectors require model-level access we don't have via the API. Not deployable, but worth tracking.
- **Adversarial drift tests.** A separate red-team note will eventually specify how we deliberately try to destabilize each persona to verify the contracts hold (per the Zylos 2026 finding that adversarial robustness is the open problem). Out of scope here.
## Open questions for this note
Tracked here until they get scoped:
1. Should adversarial drift tests run nightly as a standing red-team, or only on persona changes?
2. Where are the per-persona reference distributions for SyncScore precomputed and stored? In `_meta/audit/` alongside the existing vault audit outputs?
3. What is the right α step size for confidence updates in MEMORY.md? Hindsight 20/20 leaves it for future work; we'll calibrate empirically.
4. When a persona is paused due to drift, who restarts it? Per the [[Role Personas]] redesign: human-only restart for v0.1 (the Coordinator persona surfaces the pause to the human via DM, the human investigates via the structured re-anchor file the persona wrote at session close, and the human restarts after updating SOUL.md or CHARTER.md if the pause cause was a charter-language gap). At v0.5+, Engineer may technically diagnose drift causes via the structured re-anchor file but cannot restart paused personas autonomously; the restart-authority remains human-only.
5. How long is "long enough" for SyncScore stability to count as recovery? The "first-turn-above-0.8 within ~14 turns" figure attributed to the EchoMode/tinkerclaw Identity Persistence work is a starting point but its methodology is not fully verified in the public materials; calibrate empirically against our own personas.
6. Should the weekly digest be read-aloud (TTS, KTLK Gary style) or text-only? Text-only is simpler and matches every existing precedent.
7. Privacy and PII discipline — does our project even have PII? If not, simplify the OpenTelemetry retention policy (full content, longer retention).
8. Vault audit script changes — when do we extend `scripts/ci/vault_audit.py` with persona-source filters, vs keep them in a separate persona-aware audit?
## References
Internal:
- `Makefile` — `vault-audit`, `vault-audit-apply`, `vault-audit-semantic`, `vault-audit-test` targets
- `docs/vault/Reference/Vault Audit Quick Reference.md` — operator's reference for the existing pipeline
- `scripts/ci/vault_audit.py` — the mechanical audit script
- `docs/vault/_meta/audit/last_audit.json`, `last_semantic.json` — audit output files
- `.github/workflows/vault-audit.yml` — PR gate + weekly cron
- `docs/vault/Reference/Autonomous Company/Role Personas.md` — defines the personas this audit pipeline governs
- `docs/vault/Reference/Autonomous Company/Cost and Budget Model.md` — defines the budget allocations the cost-spike rule operates against
- `docs/vault/Governance/Constitution.md` — supreme authority
External — persona drift detection:
- Identity Persistence paper — github.com/globalcaos/tinkerclaw/blob/main/docs/papers/identity-persistence
- Echo protocol / EchoMode (SyncScore + EWMA) — medium.com/@seanhongbusiness
- Persona drift production analysis — tianpan.co (May 2026)
- Anthropic persona vectors — anthropic.com/research/persona-vectors and arXiv 2507.21509
- PersonaGym + PersonaScore — arXiv 2407.18416v2
- AI agent persona design and behavioral consistency — zylos.ai/research
External — behavioral contracts and invariants:
- Agent Behavioral Contracts — agentassert.com
- Temporal expression assertions for AI agents — arXiv 2509.20364
- Invariant Labs testing — github.com/invariantlabs-ai/testing
- Agent Patterns testing strategy — agentpatterns.tech
- Trace-mind verification — pypi.org/project/trace-mind
External — memory architecture:
- Identity drift / self-models — heyclarity.dev/blog/identity-drift-long-running-agents
- Hindsight 20/20 — arXiv 2512.12818
- brainctl whitepaper — brainctl.org/whitepaper
- Memory survey — arXiv 2603.07670
- mememory schema — github.com/scott-walker/mememory
- Agent Memory Drift / reconciliation loop — tianpan.co (April 2026)
External — re-anchor patterns:
- Re-anchors / 100-session pattern — Mohan Iyer, medium.com (February 2026)
- OpenClaw CONTINUATION.md RFC — github.com/openclaw/openclaw/issues/20604
- OpenClaw pre-reset memory flush — github.com/openclaw/openclaw/issues/45608
- Post-compaction re-read protocol — agentpatterns.ai
- Egg orchestrator anchor mechanism — github.com/jwbron/egg/issues/1032
External — LLM-as-judge:
- Production eval pipeline — channel.tel/blog/llm-as-a-judge-production-eval-pipeline
- LLM-as-a-judge framework — learnwithparam.com (March/April 2026)
- Choosing the LLM judge — learnwithparam.com (April 2026)
- Doubly-robust LLM-as-judge — arXiv 2509.22957
- Gemini production-grade LLM-as-judge — gemilab.net
External — observability:
- OpenTelemetry GenAI semantic conventions — opentelemetry.io
- OpenLIT auto-instrumentation — mintlify.com/openlit
- Copilot Chat OTel — code.visualstudio.com/docs/copilot/guides/monitoring-agents
- Agentgateway observability — agentgateway.dev/docs/standalone/main/llm/observability
- LLM agent OpenTelemetry tracing — aivineet.com
- Capture GenAI prompt/completion events — oneuptime.com (Feb 2026)
External — drift / audit patterns:
- Configuration drift auto-remediation (PR-first) — blog.cloudgeni.ai
- Vaultspectre watch (delta reporting) — github.com/ppiankov/vaultspectre
- Semantic drift monitor — blog.trysteakhouse.com
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-reference-autonomous-company-drift-detection-and-audit-mechanics: domain/tooling
Tag assignment · test- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.50 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Reference/Autonomous Company/Drift Detection and Audit Mechanics.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Drift Detection and Audit Mechanics
The operational layer for keeping the autonomous company honest. Drift is the slow divergence between what a persona is supposed to do and what it actually does. This note specifies how that divergence is detected, what triggers correction, and how the resulting audit trail is structured. It composes with the existing project vault audit pipeline (mechanical via `make vault-audit`, semantic via `make vault-audit-semantic`) and the broader literature on agent behavioral contracts, persona drift, and self-model memory architectures.
## Why this note exists
Three failure modes drive it:
1. **Behavioral drift in personas.** The empirical findings from [[Role Personas]] are clear: persona self-consistency degrades 30%+ after 8–12 turns and most LLMs diverge from assigned personas after ~100 turns. Without detection, the CEO persona that opened the week opinionated and Constitution-anchored will end the week vague and accommodating, and no one will notice until a bad decision ships.
2. **Semantic drift in vault content.** The vault has 130+ specs and growing reference content. Code references go stale; design notes diverge from implementation; specs reference systems that have been migrated. The existing `make vault-audit` catches mechanical issues; `make vault-audit-semantic` catches drift. We extend, don't replace.
3. **Cost overrun without behavioral evidence.** The [[Cost and Budget Model]] sets caps but doesn't define when a budget overrun is a drift signal vs an honest workload spike. Cost spikes are a leading indicator of drift; they belong in the same audit pipeline.
Every mitigation listed in Role Personas under "Drift mitigations (mandatory for every persona)" is operationalized here.
## Prior art surveyed
Before specifying mechanisms, the relevant published work that grounds the design.
### Persona drift detection
- **SyncScore + EWMA Repair Loop (Echo protocol, EchoMode + Identity Persistence paper at globalcaos/tinkerclaw)** — formal drift detection for long-running agents. SyncScore is a composite metric on stylistic and tonal consistency using latent-style embeddings; EWMA (λ≈0.3) smooths the score; threshold breaches trigger a 238-character re-injection block. Documented results: 50-turn mean SyncScore 0.977; drift recovery from 0.027 to 0.980; 442× on/off-persona separation in feature space; 30+ days production deployment with zero manual interventions.
- **Anthropic persona vectors (arXiv 2507.21509)** — character traits as activation patterns in the model's neural network; correlates 0.75–0.83 with behavioral expression. Available now for monitoring but require model-level activation access (not API-only). Useful as direction of travel; not deployable for our project since we use the API.
- **PersonaGym + PersonaScore (arXiv 2407.18416)** — automatic persona-consistency evaluation. LLM-as-judge ensemble across rubrics (Linguistic Habits, Persona Consistency, Toxicity Control). Spearman correlation 0.76–0.85 with human evaluation. Validates that LLM-as-judge for persona consistency is reliable when the rubric is concrete.
### Behavioral invariants and contracts
- **Agent Behavioral Contracts (agentassert.com)** — formal preconditions, invariants, guarantees, and recovery actions specified in YAML. Lyapunov stability theory provides convergence guarantees. 100% detection accuracy across 200 scenarios. The pattern: `before` (must be true before agent runs), `during` (rules enforced on every response in real time), `after` (guarantees checked when agent completes), `on_failure` (retry/fallback/escalate).
- **Temporal expression assertions for AI agents (arXiv 2509.20364)** — runtime monitoring with temporal logic; flags sequencing errors and behavioral anomalies without semantic interpretation. Halts execution or feeds back to recovery planner when assertions fail.
- **Invariant Labs testing framework (github.com/invariantlabs-ai/testing)** — pytest-compatible localized assertions for agent traces. Trace-as-code: `Trace(trace=[...]).extract("locations")` with explicit assertions.
- **Agent Patterns testing strategy (agentpatterns.tech)** — five-layer testing: unit / golden datasets / eval harness / regression / replay. Unit tests for deterministic invariants; golden datasets for reproducible eval; LLM-as-judge for fuzzy quality; regression diff between versions; replay of real production traces.
### Memory architecture for contradiction resolution
- **Self-Models for identity drift (heyclarity.dev)** — append-only memory creates "memory drift": new observations stored without reconciling against existing beliefs. Fix: upsert model with explicit contradiction resolution, confidence decay, and trajectory tracking.
- **Hindsight 20/20 (arXiv 2512.12818)** — formal opinion-update equations using Assess(opinion, fact) → {reinforce, weaken, contradict, neutral}, with confidence updates `c' = min(c+α, 1)` for reinforce, `max(c-α, 0)` for weaken, `max(c-2α, 0)` for contradict.
- **brainctl whitepaper (brainctl.org)** — AGM belief revision (Alchourrón-Gärdenfors-Makinson) with formal postulates. Bayesian confidence model. Losing beliefs written to `belief_collapse_events` table with full provenance — never deleted, recoverable. Thompson Sampling explore/exploit on retrieval reranking.
- **Memory survey (arXiv 2603.07670)** — write path discipline: filtering, canonicalization, deduplication, priority scoring, metadata tagging. Reflective memory's central risk is self-reinforcing error. Memory operations summarized as ⟨π_θ, R, U⟩ where U is not naive append but summarize/dedupe/score/resolve/expire.
- **mememory schema (github.com/scott-walker/mememory)** — concrete reference implementation: `Memory { id, content, embedding, scope, type, tags, weight, supersedes, ttl }` with composite scoring `score = similarity × scope_weight × memory_weight × temporal_decay`, contradiction detection threshold of 0.75 similarity, four resolution modes (keep/update/supersede/delete).
- **Agent Memory Drift / reconciliation loop (tianpan.co April 2026)** — distinction between memory (write-only log of beliefs) and knowledge state (read-through cache of the world that knows when to invalidate). Lazy revalidation at read time, TTLs that decay with mutability.
### Re-anchor and session-boundary protocols
- **Mohan Iyer 100-session re-anchor system (Medium Feb 2026)** — production pattern: structured YAML re-anchor at session end, hydration command at session start. Decisions get IDs to prevent re-debate; quality scorecard, blockers, next-steps in priority order. After 100 sessions: 3,500 lines production code, 111 tests passing, 93% first-pass success rate.
- **OpenClaw CONTINUATION.md RFC (GitHub issue 20604)** — auto-checkpoint at 80% context capacity with 7-field structure: goal / current state / next action / constraints / known unknowns / artifacts / stop conditions. Post-turn verification (mtime check, retry once on miss).
- **OpenClaw pre-reset memory flush (GitHub issue 45608)** — daily reset and `/new` should trigger an agentic flush before destroying the session, not just a raw 15-message dump. Asymmetric write/read: episodic memory writes freely, semantic gates aggressively.
- **Post-compaction re-read protocol (agentpatterns.ai)** — `SessionStart` hook with `matcher: "compact"` injects re-read directive into restored context. Confirmation requirement ("state the key rules you found") matters more than directive alone.
- **Egg orchestrator anchor mechanism (GitHub jwbron/egg issue 1032)** — explicit decision to disable compaction entirely; on context limit, fully clear and reload from anchor. Cleaner recovery than lossy compaction. <500ms latency on milestone operations.
### Observability and audit trails
- **OpenTelemetry GenAI semantic conventions** — standard span attributes: `gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, plus events for `gen_ai.prompt` and `gen_ai.completion`. Standardizes traces across providers (Anthropic, OpenAI, Gemini).
- **OpenLIT auto-instrumentation (mintlify.com/openlit)** — automatic OTLP-compatible spans for every LLM call, agent step, vector DB query, HTTP request. Includes estimated cost, prompt content (opt-in), completion content. Compatible with Datadog, Grafana, New Relic, Jaeger, Tempo.
- **Copilot Chat OTel emission (VS Code docs)** — per-session `invoke_agent` root span wrapping `chat`, `execute_tool`, `execute_hook` child spans. `captureContent=true` flag for full prompt/response capture. PII-by-default off.
- **Agentgateway observability (agentgateway.dev)** — OTLP export of `gen_ai_client_token_usage` histogram with provider, model, operation, token-type labels. Logs include `llm.provider`, `llm.request.model`, `llm.request.tokens`, `llm.response.tokens`, `duration`.
### Vault audit (project-internal)
- **`make vault-audit`** — mechanical checks (schema, wikilinks, code-refs); produces `last_audit.json`. Free, fast.
- **`make vault-audit-apply`** — same checks plus safe auto-fixes in place.
- **`make vault-audit-semantic`** — agent-driven semantic drift analysis using `vault-audit-semantic` opencode command with Sonnet 4.6; produces `last_semantic.json`. ~$1–5 per run. Already runs weekly via `.github/workflows/vault-audit.yml`.
- **Rolling correction PR pattern** — fixes go to a fixed-name branch `vault/audit`; one PR titled "vault: automated audit (rolling)" force-pushed, never closed and reopened. Established convention in `AGENTS.md`.
- **Staleness markers** — `stale: true` and `stale_reason:` in note frontmatter; the audit script never removes these (humans only). Independent axis from `status: reviewed`.
This existing infrastructure is the substrate. The autonomous-company drift detection composes with it.
## Two distinct drift problems, one audit pipeline
The autonomous company has two drift surfaces that prior literature treats separately. Our pipeline treats them as siblings under the same audit infrastructure:
**Drift A — Persona drift.** A persona's behavior diverges from its SOUL.md and CHARTER.md. Detected by behavioral invariants and LLM-as-judge. Corrected by re-anchor or persona pause.
**Drift B — Vault content drift.** Notes diverge from code, specs reference defunct systems, design contradicts implementation. Detected by `vault-audit` (mechanical) and `vault-audit-semantic` (drift analysis agent). Corrected by rolling PR.
Both feed the same artifacts: `Sessions/` audit-log entries, weekly digest summaries, alerts when thresholds breach. Personas writing to the vault are governed by both: their behavioral invariants restrict what they can write, the vault audit catches what slipped through.
## Persona-level behavioral invariants
For every persona defined in [[Role Personas]], a YAML behavioral contract specifies what must hold. Format follows the agentassert.com contract pattern:
A persona's contract file lives at `docs/vault/Reference/Autonomous Company/personas/<name>/CONTRACT.md` (a sibling to SOUL.md / CHARTER.md / INTENT.md / AGENTS.md / MEMORY.md). It enumerates:
- **Preconditions (`before`)**: what must be true before the persona may act. Examples: "session started within last 60 minutes", "INTENT.md was read", "Constitution.md was loaded into cache", "remaining daily budget > per-action cap".
- **Invariants (`during`)**: rules enforced on every response. Examples: "no version-bump commands invoked", "no Constitution.md edit attempted", "session length ≤ 50 turns since last anchor", "response under 2,000 tokens unless explicit long-form context", "if invoking authority, references a Constitution Article".
- **Guarantees (`after`)**: what must be true when the persona completes its action. Examples: "every Decision draft cites at least one Constitution Article or existing Decision", "every devlog draft references at least one commit hash from this version", "every spec proposal has clarify questions answered or flagged as open".
- **Recovery (`on_failure`)**: what to do when an invariant breaks. Examples: retry once with re-anchor; fallback to drafting only (no commit); escalate to human in `#all-hands`.
### Per-persona invariant inventory (specification, not implementation)
Each persona's contract includes the three Constitution-anchored invariants from [[Role Personas]] (no Constitution edits, no version-bump invocations, no auto-merges) plus persona-specific ones. The roster below reflects the Role Personas 2026-05-08 redesign — five personas shipping incrementally per the growth path (v0.1 = Coordinator + GTM; v0.2 + Narrative Director; v0.5 + Playtest Coordinator + Engineer).
**Coordinator behavioral invariants (v0.1):**
- Every routing message uses the `[Coordinator]` role-call prefix.
- No routing message exceeds 280 characters (longer means a structured artifact was warranted).
- Refuses to author any content for the persona it is routing to (strictly a relayer; LLM-as-judge dimension `factual_relay_vs_editorial_summary`).
- Routing decisions deterministic where possible — event-type → spoke mapping is a config table, not per-call inference; LLM call only fires for ambiguous cases.
- Duplicate inbound message hash within rolling 5-minute window triggers loop-detection escalation to human (per [[Cost and Budget Model]] Layer 4 loop detection).
**Engineer behavioral invariants (v0.5):**
- Every plan-review references specific Constitution Articles.
- Oracle consultations require written justification in the consultation prompt.
- No new dependency proposed without an Appendix A entry draft.
- After two failed implementation suggestions, must escalate to Oracle.
- More than 3 specs in `draft` status → reject new spec creation, surface backlog instead (absorbs the original PM persona's discipline).
- `/speckit.clarify` output reviewed against requirements checklist before `/speckit.specify`.
- Spec renumbering goes through `spec-management` skill, never raw file ops.
- No source code in writes (Engineer dispatches code-touching work to OpenCode + omo via the `opencode serve` HTTP API per [[Autonomous Company Agent Runtime Selection]]; the persona itself drafts plans and reviews PRs but does not write code directly).
**Narrative Director behavioral invariants (v0.2):**
- Every Gary-script draft cites the line of `KTLK Gary — Character Case Study.md` it draws from.
- Every canon assertion in a draft must cite a source in `Narrative/` or `Design/` (LLM-as-judge dimension `canon_grounding`); uncited assertions are flagged.
- Refuses to write Gary content outside the diegesis firewall — encoded as deterministic forbidden-pattern check (any prompt containing "post Gary on Slack" or equivalent triggers a hard block per layer-1 invariants).
- Reads ≥2 recent Gary scripts before drafting a new one (voice anchor; verified by tool-call trace).
- Refuses to author implementation specs (those route through Engineer).
- No source code in writes.
**Playtest Coordinator behavioral invariants (v0.5):**
- Every regression report cites a specific seed value and reproduction steps.
- Refuses to mark a regression "resolved" without verification (a fix from Engineer triggers a new Observer pass before the regression-issue close).
- Loads the `observer-qa` skill before any visual-QA work.
- Refuses to speculate about root cause (root-cause work is Engineer's domain; LLM-as-judge dimension `empirical_vs_speculative` rejects drafts that contain causal claims unsupported by diagnostic data).
- Every Observer-pipeline output report includes the underlying screenshot + diagnostic JSON.
- No source code in writes.
**GTM behavioral invariants (v0.1):**
- Every release-note draft references specific commit hashes or PR numbers.
- The phrase "coming soon" is forbidden (per [[Off-limits Functions]] rule 7).
- Read ≥3 recent devlog posts before drafting (verified by tool-call trace).
- No claim about features that haven't shipped (defined: not in `CHANGELOG.md` for a released version).
- No sycophantic openers; voice consistency check against existing devlog before publish.
- Refuses to author Gary-voice content (firewall enforcement; quotes Gary in explicit quotation context, never speaks as Gary).
- Does not publish a release-note draft without Playtest Coordinator clearance (encoded as a hard dependency in the publication workflow at v0.5; at v0.1 the dependency is on the human directly because Playtest Coordinator is not yet in scope).
These are the spec; the actual CONTRACT.md files for each persona are written when the relevant phase of the Role Personas growth path commits to that persona.
## Behavioral invariant checking — runtime mechanics
Each invariant is enforced at one of three layers:
1. **Hard, deterministic, code-enforced.** Examples: budget caps, file-write paths, forbidden tool invocations. These are wrappers around the persona's tool-call layer. Violations raise `InvariantViolation`. No LLM call required.
2. **Soft, regex- or pattern-based.** Examples: forbidden opening phrases, "coming soon" detection, citation-presence check. These are post-output filters. Violations trigger a regenerate-with-explicit-feedback loop (one retry max).
3. **Soft, LLM-as-judge.** Examples: voice consistency, opinionatedness vs sycophancy at the level of meaning rather than phrase, citation correctness. These run a Haiku-class judge on the persona's output before delivery. Violations trigger re-anchor.
The cost discipline from [[Cost and Budget Model]] applies: most invariants are layer 1 or 2 (free). Layer 3 LLM-as-judge runs are budget-allocated to the shared $20/month "drift detection + invariant checks" line.
### LLM-as-judge configuration
Following the published practice (channel.tel, learnwithparam April 2026):
- **Judge model**: Haiku 4.5 by default for cheap checks (length, format, phrase presence). Sonnet 4.6 for genuine quality judgments (voice consistency, citation correctness). Per the calibration rule, judges should be at least as capable as generators only when capability matters — for "did this output use the persona's voice" Haiku is sufficient.
- **Cross-family rule**: not strictly applicable here since both generator and judge are Anthropic; mitigation is to explicitly include "ignore that this is Claude output" in judge prompts and to randomize position when comparing.
- **Rubric**: 3–5 dimensions per persona. Example for GTM: `voice_match`, `factual_grounding` (commit-hash present and correct), `length_discipline`, `forbidden_phrases`, `tone`. Each scored 1–5 with concrete behavioral anchors (not "adequate").
- **Calibration**: 20-row human-labeled dataset per persona, refreshed quarterly. Pearson correlation target ≥ 0.8. Below 0.7, the judge is too noisy and we tune the rubric or model.
- **Sampling**: nightly batch of all yesterday's persona outputs (per the Anthropic Batch API discount in [[Cost and Budget Model]]). Plus 100% sampling on outputs that auto-degraded to read-only, were rejected by layer-1 or layer-2 invariants, or were budget-truncated.
- **Drift trigger**: if a persona's mean dimension score drops > 0.3 points week-over-week, alert in `#sys-vault-audit` (per [[Community Layer]] channel taxonomy); the Coordinator persona surfaces drift alerts in its daily morning summary in `#team-personas` (per [[Role Personas]] Coordinator cadence). The original CEO-daily-standup framing was an artifact of the pre-redesign roster; the redesigned Role Personas does not include a CEO persona — strategic decisions and weekly reviews are human-only per [[Off-limits Functions]] class-C.
## SyncScore-style continuous drift monitoring
Independent of per-output invariants, each persona's session is continuously scored for behavioral consistency using a lightweight EWMA accumulator (Echo protocol pattern):
- At each persona turn, a Haiku-class call extracts a vector of style/voice features from the response (length, opener category, citation presence, forbidden-phrase count, sentence-structure summary).
- This vector is compared to the persona's reference distribution (precomputed from SOUL.md exemplars and the last week of approved outputs).
- A composite score is produced (cosine similarity weighted by feature). Following the Echo protocol nomenclature, this is called *SyncScore* with explicit attribution.
- Score is smoothed with EWMA (λ ≈ 0.3 per the published parameter).
When SyncScore drops below 0.8, a re-anchor is triggered. When it drops below 0.5, the persona is paused and surfaces to `#sys-vault-audit` (per [[Community Layer]] channel taxonomy) for human review.
The Identity Persistence benchmarks circulated in the EchoMode/tinkerclaw materials cite a 50-turn mean of 0.977 and a recovery from 0.027 to 0.980 within roughly 14 turns after a 238-char re-injection block. The methodology behind the specific recovery turn-count is not fully documented in the public materials we could verify, so we treat it as directional rather than authoritative. We aim for first turn exceeding 0.8 within ~15 turns of re-anchor as our own working target, to be calibrated empirically against our own personas (verification caveat: see [[Autonomous Company Tool Stack Verification]]).
## Persona stability under cheap-tier models
Per [[Autonomous Company LLM Provider Selection]] the default tier is non-foundational models routed via OpenRouter (MiniMax M2.5 default, Gemini 2.5 Flash classification, Qwen3-Coder Plus code-light), with Anthropic-direct foundational escalation reserved for explicit-justification cases. Drift detection composes with this routing as follows.
The published research is unambiguous: **all models exhibit persona drift after eight to twelve turns**, regardless of foundational versus non-foundational tier (arXiv:2510.07777 NeurIPS 2025; tianpan.co synthesis April 2026). Capability and persona stability are different axes — larger foundational models do not resist drift better and sometimes exhibit more drift because they have more contextual hooks pulling them toward recent tokens. Drift stabilises at a finite noise-limited equilibrium rather than unbounded decay. Lightweight architectural interventions (goal reminders, periodic re-injection, constraint tracking) reliably shift the equilibrium downward.
Specific cheap-tier model behaviours from production case studies and benchmarks:
- **MiniMax M2.5/M2.7**: explicit chain-of-thought training in agent-mode helps persona stability across turns when thinking tokens are preserved. Skyler Miao (MiniMax Head of Engineering): "It is very important for M2 to keep the chain of thought. We must make sure the history thought passed back to the model." Truncating thought history loses the discipline. The runtime ([[Autonomous Company Agent Runtime Selection]] selected Letta + Letta Code Channels) preserves thought history via the `recall` memory block.
- **Qwen3.5 Plus**: explicit `/think` and `/no_think` flags give mode control. Forcing non-thinking mode for routine conversational turns reduces verbose self-verification that pollutes context. Tau2-Bench 86.7 (second only to Opus on agentic tasks) confirms strong persona stability under multi-turn role-play.
- **DeepSeek V3.1**: documented redundant-self-verification context-bloat in agent loops on MCP-Mark and Playwright benchmarks. Trajectories can exceed 128K context via verbose self-verification, indirectly degrading persona stability through context displacement. This is the reason DeepSeek V3.1 is in the persona-tier *fallback* chain rather than the primary slot.
- **Gemini 2.5 Flash**: classification-only tier; persona stability is not the relevant metric here (turns are short and stateless).
Implication for drift detection: SyncScore thresholds and re-anchor triggers do **not** need adjustment based on which cheap-tier model is in use. The 0.8 / 0.5 thresholds and the 50-turn session-length limit apply universally across the routing tier. The architectural interventions (re-anchor at semantic boundaries, constraint tracking in MEMORY.md, contradiction-detection gates) are doing the load-bearing work; the model choice determines the noise level around the equilibrium, not whether the equilibrium exists.
The foundational-escalation tag protocol per Autonomous Company LLM Provider Selection composes with re-anchor: a persona that has lost SyncScore stability and re-anchors with a fresh prompt may also escalate the next call to Anthropic-direct Sonnet 4.6 if the failed-revision-rate metric for that task type exceeds the 30% threshold. This is the explicit-justification escalation path operating on real data.
## Re-anchor protocol — three triggers
Re-anchor restores the persona to its specified behavior. Three triggers, one common procedure.
### Triggers
1. **Behavioral**: SyncScore below threshold, two or more invariant checks failing simultaneously, or LLM-as-judge dimension score drops > 0.3.
2. **Session-length**: 50 turns since last anchor, regardless of behavioral signals (per the 100-turn-divergence research; 50 is conservative).
3. **Session-boundary**: every persona session start (cold start), every `/new`, every daily reset (per the OpenClaw pre-reset memory flush proposal — agentic flush before destroy).
### Procedure (per Mohan Iyer's 100-session production pattern + Egg orchestrator anchor pattern)
1. The current session writes a structured re-anchor file at `docs/vault/Reference/Autonomous Company/personas/<name>/anchors/<YYYY-MM-DD-HH-MM>.md` with seven fields (per the OpenClaw RFC's CONTINUATION.md format):
- `goal` — one sentence stating the persona's current top priority from INTENT.md.
- `current_state` — what's been done in this session (artifacts written, decisions made, calls escalated).
- `next_action` — the very next concrete step.
- `constraints` — non-negotiable rules from CHARTER.md the persona is operating under.
- `known_unknowns` — what needs human verification.
- `artifacts` — file paths, vault note IDs, branch names, PR numbers from this session.
- `stop_conditions` — when this persona should halt and ask for help.
2. Existing session is closed.
3. New session starts with a hydrated system prompt: SOUL.md + CHARTER.md + INTENT.md + the latest anchor file, in that order, all cache-marked.
4. The new session's first action is the verification sentinel: it must restate the key constraints from CHARTER.md before doing real work (per agentpatterns.ai re-read protocol with confirmation requirement).
5. SyncScore is computed on the verification response. If above threshold, normal work resumes. If below, escalate to human.
### Cost of re-anchor
Per [[Cost and Budget Model]]:
- The structured re-anchor file write: trivial (Haiku-class).
- The new-session bootstrap: cached prefix (most of SOUL/CHARTER/INTENT is unchanged), so cost is roughly equal to a normal session start.
- The verification sentinel: one Sonnet call for the persona's Tier-2-level work, charged to the persona's daily cap.
Expected re-anchor frequency: ≤2 per persona per week. If a persona is re-anchoring more than that, the SOUL/CHARTER files are likely too vague (the model has too many degrees of freedom and drifts often) — surface to human for tightening.
## MEMORY.md — append-only with explicit contradiction resolution
Per the AGM belief revision pattern in brainctl whitepaper and the self-model pattern in heyclarity.dev, the persona's MEMORY.md is never naive append. It's append-only with explicit supersession.
### Schema (per-entry)
Each memory entry has:
- `id` — UUID.
- `content` — natural-language statement of what was learned.
- `type` — `fact` | `decision` | `lesson` | `preference` | `correction`.
- `source` — `agent_observation` | `human_correction` | `vault_note` | `tool_output` (with reference).
- `confidence` — 0.0–1.0 starting at 0.7 for agent observations, 1.0 for human corrections.
- `created_at` — UTC timestamp.
- `supersedes` — id of memory this one replaces (if any).
- `supersedes_reason` — why the prior memory was wrong or evolved.
- `last_reinforced_at` — last time this was re-confirmed by a new observation.
- `decay_factor` — current temporal decay weight (refreshed at retrieval).
### Update operations
Following the Hindsight 20/20 paper's formal Assess-and-Update procedure:
When a persona makes a new observation `f` related to existing memory `m`:
- `Assess(m, f) → reinforce`: increment `m.confidence` by α (default 0.1), update `last_reinforced_at`. No new entry.
- `Assess(m, f) → weaken`: decrement `m.confidence` by α. No new entry, no supersession. Sufficient weakening (confidence < 0.3) flags the memory for review on next retrieval.
- `Assess(m, f) → contradict`: decrement `m.confidence` by 2α. If confidence drops below 0.2, write a new entry with `supersedes: m.id` and `supersedes_reason` populated. The old `m` is not deleted; it remains queryable with collapsed weight (per the brainctl `belief_collapse_events` pattern — losing beliefs are auditable, recoverable).
- `Assess(m, f) → neutral`: append `f` as new memory; existing `m` unchanged.
The `Assess` function is itself a Haiku-class LLM call with a strict-output prompt returning one of the four labels.
### Confidence decay
Per mememory's exponential decay: `decay = e^(-0.005 × days_old)`. Gentle: 1 day = 0.995, 30 days = 0.861, 365 days = 0.161. Important old memories surface if their semantic match is strong; routine old memories fade out.
### Retrieval
Per mememory's composite scoring: `score = similarity × scope_weight × confidence × decay_factor`. Threshold for inclusion in context: 0.4. Items below threshold but above 0.2 are summarized as "weakly relevant" without full content.
### Read protocol
Per the tianpan.co April 2026 reconciliation insight: memory is the write-only log of beliefs; the persona's working knowledge is a read-through cache. When the persona needs to act on a load-bearing fact, it re-reads the source (the vault note, the spec, the Constitution) rather than relying on memory alone. Memory is for cheap recall and trajectory; sources are for ground truth.
## Vault content drift — composing with `vault-audit`
We do not duplicate the existing `vault-audit` and `vault-audit-semantic` infrastructure. We extend it.
### Existing pipeline (no change)
- `make vault-audit` runs on every PR touching `docs/vault/`. Mechanical: schema, wikilinks, code-refs. Output: `last_audit.json`.
- `make vault-audit-semantic` runs weekly via GitHub Actions. Agent-driven: drift analysis using Sonnet 4.6, produces `last_semantic.json`. Output committed to `vault/audit` branch as a rolling PR.
- Staleness markers (`stale: true`, `stale_reason`) are set by audit-time analysis but never removed by tooling — humans only.
### Persona-related additions
- Per-persona vault writes are tagged in frontmatter with `source: persona-<name>` (this resolves [[Role Personas]] open question 6). The vault audit script gets a new check: persona-tagged notes must include the persona name in any "approved by" field if `status: reviewed`.
- The existing weekly semantic audit gets a new specialized prompt for persona-tagged content: it specifically checks that persona drafts in `Decisions/`, `Discoveries/`, and `Sessions/` follow the persona's CHARTER.md authority boundaries (e.g., GTM drafting in `Decisions/` is a CHARTER violation flagged for human review).
- Persona session-log entries in `Sessions/` are mandatory for every autonomous persona action (per the existing vault `Sessions/` "permanent, never delete" convention). The session-log entry includes: persona name, action_id, model used, cost, invariant-check results, SyncScore at session end.
### Drift signals from audit feeding back to personas
When `vault-audit-semantic` produces a `last_semantic.json` with drift findings related to a persona's recent writes, those findings are injected into the persona's next INTENT.md as known_corrections. The persona is expected to address them in its next session.
When the audit flags a persona's writes as systematically drifting (e.g., GTM repeatedly writes claims unsupported by `CHANGELOG.md`), that's a persona-level alert independent of the per-output invariants — surface to human for SOUL/CHARTER tightening.
## OpenTelemetry observability for the audit trail
Following the OpenTelemetry GenAI semantic conventions, every persona action produces a structured trace.
### Trace structure
A single persona action is one trace with this span tree:
```
persona.action (root)
├── persona.invariant_check.before
├── llm.chat (the persona's reasoning call)
├── tool.call (zero or more)
│ └── llm.chat (subagent reasoning, if any)
├── persona.invariant_check.during (one per response chunk if streaming)
├── llm.chat (LLM-as-judge call, if sampled)
├── persona.invariant_check.after
├── persona.syncscore.compute
└── persona.session_log.write (vault Sessions/ entry)
```
### Span attributes (standardized per GenAI conventions)
For each `llm.chat` span:
- `gen_ai.system` = `anthropic`
- `gen_ai.request.model` = `claude-haiku-4-5` / `claude-sonnet-4-6` / `claude-opus-4-7`
- `gen_ai.usage.input_tokens` (broken into `cached_tokens` + `non_cached_tokens` for cost attribution)
- `gen_ai.usage.output_tokens`
- `gen_ai.cost_usd` (computed)
For each `persona.*` span:
- `og.persona.name` (custom attribute, prefixed `og.` for "Old Growth")
- `og.persona.action_id`
- `og.persona.daily_budget_remaining_usd`
- `og.persona.syncscore` (where applicable)
- `og.persona.invariant_violations` (count)
### Audit retention
- Traces are kept 90 days for debugging.
- Costs and SyncScore time-series are kept indefinitely.
- Prompt/completion content is captured but retained only 30 days (PII discipline; see open questions).
- Persona session-log entries in `Sessions/` are permanent (existing vault convention).
### Where traces go
For v0.1, the simplest path: traces written to a local SQLite database via OpenTelemetry SDK, with a dashboard built atop. This avoids external observability platforms (Datadog, Grafana Cloud) which add cost and complexity. If/when scale or human collaboration demands it, swap the export target — the GenAI semantic conventions make this transparent.
## Anomaly detection — the three rules from Cost & Budget reused
The same `cost_spike`, `error_rate_cliff`, `runaway_loop` rules from [[Cost and Budget Model]] are reinterpreted as drift signals here. A persona that is suddenly costing 3× its rolling baseline is most likely drifting (verbose responses, tool-call loops, repeated failed re-anchors) before it is "doing more work."
When two of the three anomaly rules fire in the same week for the same persona, that persona is flagged for human review regardless of whether budget was breached. Cost is a leading indicator of drift; we use it accordingly.
## Weekly digest — the human's read surface
The vault README mentions an existing reading discipline. We add a single digest, surfaced in `#sys-vault-audit` per [[Community Layer]] channel taxonomy. Slack AI on Pro+ can summarise the digest for human review on mobile (per the Slack Pro 2026 features verified in [[Autonomous Company Tool Stack Verification]] addendum).
The weekly digest contains, per persona:
- Total spend vs budget.
- Top three actions by spend.
- SyncScore time series (or a single "stable / drifting / paused" status).
- Invariant-violation count and top three categories.
- Re-anchor count.
- Vault-audit findings tagged to this persona's writes.
- Three concrete recommendations from the audit pipeline (e.g., "GTM cache hit rate 55% — check whether SOUL.md was edited this week").
The digest is auto-generated by Sisyphus (charged to its autonomous-portion budget per [[Cost and Budget Model]]), runs early Monday, and lives in `docs/vault/Sessions/` as `weekly-digest-YYYY-WW.md`. The Coordinator persona posts the digest summary to `#sys-vault-audit` per [[Role Personas]] Coordinator cadence; the human reads it directly as part of the weekly review (the original CEO-persona-reads-it framing was an artifact of the pre-redesign roster).
## What this note does NOT specify (intentionally)
Several mechanisms are referenced but deliberately left unspecified, deferred to dedicated notes or to the implementation experiment:
- **The exact rubric content for each persona's LLM-as-judge.** Drafted when the persona's SOUL.md is authored.
- **Specific anomaly-rule thresholds.** Calibrated empirically over the v0.1 experiment.
- **Choice of OpenTelemetry backend.** Local SQLite is the default; alternatives are Tool Stack territory.
- **Persona-vector activation monitoring.** Anthropic's persona vectors require model-level access we don't have via the API. Not deployable, but worth tracking.
- **Adversarial drift tests.** A separate red-team note will eventually specify how we deliberately try to destabilize each persona to verify the contracts hold (per the Zylos 2026 finding that adversarial robustness is the open problem). Out of scope here.
## Open questions for this note
Tracked here until they get scoped:
1. Should adversarial drift tests run nightly as a standing red-team, or only on persona changes?
2. Where are the per-persona reference distributions for SyncScore precomputed and stored? In `_meta/audit/` alongside the existing vault audit outputs?
3. What is the right α step size for confidence updates in MEMORY.md? Hindsight 20/20 leaves it for future work; we'll calibrate empirically.
4. When a persona is paused due to drift, who restarts it? Per the [[Role Personas]] redesign: human-only restart for v0.1 (the Coordinator persona surfaces the pause to the human via DM, the human investigates via the structured re-anchor file the persona wrote at session close, and the human restarts after updating SOUL.md or CHARTER.md if the pause cause was a charter-language gap). At v0.5+, Engineer may technically diagnose drift causes via the structured re-anchor file but cannot restart paused personas autonomously; the restart-authority remains human-only.
5. How long is "long enough" for SyncScore stability to count as recovery? The "first-turn-above-0.8 within ~14 turns" figure attributed to the EchoMode/tinkerclaw Identity Persistence work is a starting point but its methodology is not fully verified in the public materials; calibrate empirically against our own personas.
6. Should the weekly digest be read-aloud (TTS, KTLK Gary style) or text-only? Text-only is simpler and matches every existing precedent.
7. Privacy and PII discipline — does our project even have PII? If not, simplify the OpenTelemetry retention policy (full content, longer retention).
8. Vault audit script changes — when do we extend `scripts/ci/vault_audit.py` with persona-source filters, vs keep them in a separate persona-aware audit?
## References
Internal:
- `Makefile` — `vault-audit`, `vault-audit-apply`, `vault-audit-semantic`, `vault-audit-test` targets
- `docs/vault/Reference/Vault Audit Quick Reference.md` — operator's reference for the existing pipeline
- `scripts/ci/vault_audit.py` — the mechanical audit script
- `docs/vault/_meta/audit/last_audit.json`, `last_semantic.json` — audit output files
- `.github/workflows/vault-audit.yml` — PR gate + weekly cron
- `docs/vault/Reference/Autonomous Company/Role Personas.md` — defines the personas this audit pipeline governs
- `docs/vault/Reference/Autonomous Company/Cost and Budget Model.md` — defines the budget allocations the cost-spike rule operates against
- `docs/vault/Governance/Constitution.md` — supreme authority
External — persona drift detection:
- Identity Persistence paper — github.com/globalcaos/tinkerclaw/blob/main/docs/papers/identity-persistence
- Echo protocol / EchoMode (SyncScore + EWMA) — medium.com/@seanhongbusiness
- Persona drift production analysis — tianpan.co (May 2026)
- Anthropic persona vectors — anthropic.com/research/persona-vectors and arXiv 2507.21509
- PersonaGym + PersonaScore — arXiv 2407.18416v2
- AI agent persona design and behavioral consistency — zylos.ai/research
External — behavioral contracts and invariants:
- Agent Behavioral Contracts — agentassert.com
- Temporal expression assertions for AI agents — arXiv 2509.20364
- Invariant Labs testing — github.com/invariantlabs-ai/testing
- Agent Patterns testing strategy — agentpatterns.tech
- Trace-mind verification — pypi.org/project/trace-mind
External — memory architecture:
- Identity drift / self-models — heyclarity.dev/blog/identity-drift-long-running-agents
- Hindsight 20/20 — arXiv 2512.12818
- brainctl whitepaper — brainctl.org/whitepaper
- Memory survey — arXiv 2603.07670
- mememory schema — github.com/scott-walker/mememory
- Agent Memory Drift / reconciliation loop — tianpan.co (April 2026)
External — re-anchor patterns:
- Re-anchors / 100-session pattern — Mohan Iyer, medium.com (February 2026)
- OpenClaw CONTINUATION.md RFC — github.com/openclaw/openclaw/issues/20604
- OpenClaw pre-reset memory flush — github.com/openclaw/openclaw/issues/45608
- Post-compaction re-read protocol — agentpatterns.ai
- Egg orchestrator anchor mechanism — github.com/jwbron/egg/issues/1032
External — LLM-as-judge:
- Production eval pipeline — channel.tel/blog/llm-as-a-judge-production-eval-pipeline
- LLM-as-a-judge framework — learnwithparam.com (March/April 2026)
- Choosing the LLM judge — learnwithparam.com (April 2026)
- Doubly-robust LLM-as-judge — arXiv 2509.22957
- Gemini production-grade LLM-as-judge — gemilab.net
External — observability:
- OpenTelemetry GenAI semantic conventions — opentelemetry.io
- OpenLIT auto-instrumentation — mintlify.com/openlit
- Copilot Chat OTel — code.visualstudio.com/docs/copilot/guides/monitoring-agents
- Agentgateway observability — agentgateway.dev/docs/standalone/main/llm/observability
- LLM agent OpenTelemetry tracing — aivineet.com
- Capture GenAI prompt/completion events — oneuptime.com (Feb 2026)
External — drift / audit patterns:
- Configuration drift auto-remediation (PR-first) — blog.cloudgeni.ai
- Vaultspectre watch (delta reporting) — github.com/ppiankov/vaultspectre
- Semantic drift monitor — blog.trysteakhouse.com
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-reference-autonomous-company-drift-detection-and-audit-mechanics: status/draft
Tag assignment · test- Gold
- The note calls for `status/draft`: Authoring in progress, not yet verified (True)
- ember
- The note has no reason to carry `status/draft` (False)
- Signal
- P(true) 0.17 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Reference/Autonomous Company/Drift Detection and Audit Mechanics.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Drift Detection and Audit Mechanics
The operational layer for keeping the autonomous company honest. Drift is the slow divergence between what a persona is supposed to do and what it actually does. This note specifies how that divergence is detected, what triggers correction, and how the resulting audit trail is structured. It composes with the existing project vault audit pipeline (mechanical via `make vault-audit`, semantic via `make vault-audit-semantic`) and the broader literature on agent behavioral contracts, persona drift, and self-model memory architectures.
## Why this note exists
Three failure modes drive it:
1. **Behavioral drift in personas.** The empirical findings from [[Role Personas]] are clear: persona self-consistency degrades 30%+ after 8–12 turns and most LLMs diverge from assigned personas after ~100 turns. Without detection, the CEO persona that opened the week opinionated and Constitution-anchored will end the week vague and accommodating, and no one will notice until a bad decision ships.
2. **Semantic drift in vault content.** The vault has 130+ specs and growing reference content. Code references go stale; design notes diverge from implementation; specs reference systems that have been migrated. The existing `make vault-audit` catches mechanical issues; `make vault-audit-semantic` catches drift. We extend, don't replace.
3. **Cost overrun without behavioral evidence.** The [[Cost and Budget Model]] sets caps but doesn't define when a budget overrun is a drift signal vs an honest workload spike. Cost spikes are a leading indicator of drift; they belong in the same audit pipeline.
Every mitigation listed in Role Personas under "Drift mitigations (mandatory for every persona)" is operationalized here.
## Prior art surveyed
Before specifying mechanisms, the relevant published work that grounds the design.
### Persona drift detection
- **SyncScore + EWMA Repair Loop (Echo protocol, EchoMode + Identity Persistence paper at globalcaos/tinkerclaw)** — formal drift detection for long-running agents. SyncScore is a composite metric on stylistic and tonal consistency using latent-style embeddings; EWMA (λ≈0.3) smooths the score; threshold breaches trigger a 238-character re-injection block. Documented results: 50-turn mean SyncScore 0.977; drift recovery from 0.027 to 0.980; 442× on/off-persona separation in feature space; 30+ days production deployment with zero manual interventions.
- **Anthropic persona vectors (arXiv 2507.21509)** — character traits as activation patterns in the model's neural network; correlates 0.75–0.83 with behavioral expression. Available now for monitoring but require model-level activation access (not API-only). Useful as direction of travel; not deployable for our project since we use the API.
- **PersonaGym + PersonaScore (arXiv 2407.18416)** — automatic persona-consistency evaluation. LLM-as-judge ensemble across rubrics (Linguistic Habits, Persona Consistency, Toxicity Control). Spearman correlation 0.76–0.85 with human evaluation. Validates that LLM-as-judge for persona consistency is reliable when the rubric is concrete.
### Behavioral invariants and contracts
- **Agent Behavioral Contracts (agentassert.com)** — formal preconditions, invariants, guarantees, and recovery actions specified in YAML. Lyapunov stability theory provides convergence guarantees. 100% detection accuracy across 200 scenarios. The pattern: `before` (must be true before agent runs), `during` (rules enforced on every response in real time), `after` (guarantees checked when agent completes), `on_failure` (retry/fallback/escalate).
- **Temporal expression assertions for AI agents (arXiv 2509.20364)** — runtime monitoring with temporal logic; flags sequencing errors and behavioral anomalies without semantic interpretation. Halts execution or feeds back to recovery planner when assertions fail.
- **Invariant Labs testing framework (github.com/invariantlabs-ai/testing)** — pytest-compatible localized assertions for agent traces. Trace-as-code: `Trace(trace=[...]).extract("locations")` with explicit assertions.
- **Agent Patterns testing strategy (agentpatterns.tech)** — five-layer testing: unit / golden datasets / eval harness / regression / replay. Unit tests for deterministic invariants; golden datasets for reproducible eval; LLM-as-judge for fuzzy quality; regression diff between versions; replay of real production traces.
### Memory architecture for contradiction resolution
- **Self-Models for identity drift (heyclarity.dev)** — append-only memory creates "memory drift": new observations stored without reconciling against existing beliefs. Fix: upsert model with explicit contradiction resolution, confidence decay, and trajectory tracking.
- **Hindsight 20/20 (arXiv 2512.12818)** — formal opinion-update equations using Assess(opinion, fact) → {reinforce, weaken, contradict, neutral}, with confidence updates `c' = min(c+α, 1)` for reinforce, `max(c-α, 0)` for weaken, `max(c-2α, 0)` for contradict.
- **brainctl whitepaper (brainctl.org)** — AGM belief revision (Alchourrón-Gärdenfors-Makinson) with formal postulates. Bayesian confidence model. Losing beliefs written to `belief_collapse_events` table with full provenance — never deleted, recoverable. Thompson Sampling explore/exploit on retrieval reranking.
- **Memory survey (arXiv 2603.07670)** — write path discipline: filtering, canonicalization, deduplication, priority scoring, metadata tagging. Reflective memory's central risk is self-reinforcing error. Memory operations summarized as ⟨π_θ, R, U⟩ where U is not naive append but summarize/dedupe/score/resolve/expire.
- **mememory schema (github.com/scott-walker/mememory)** — concrete reference implementation: `Memory { id, content, embedding, scope, type, tags, weight, supersedes, ttl }` with composite scoring `score = similarity × scope_weight × memory_weight × temporal_decay`, contradiction detection threshold of 0.75 similarity, four resolution modes (keep/update/supersede/delete).
- **Agent Memory Drift / reconciliation loop (tianpan.co April 2026)** — distinction between memory (write-only log of beliefs) and knowledge state (read-through cache of the world that knows when to invalidate). Lazy revalidation at read time, TTLs that decay with mutability.
### Re-anchor and session-boundary protocols
- **Mohan Iyer 100-session re-anchor system (Medium Feb 2026)** — production pattern: structured YAML re-anchor at session end, hydration command at session start. Decisions get IDs to prevent re-debate; quality scorecard, blockers, next-steps in priority order. After 100 sessions: 3,500 lines production code, 111 tests passing, 93% first-pass success rate.
- **OpenClaw CONTINUATION.md RFC (GitHub issue 20604)** — auto-checkpoint at 80% context capacity with 7-field structure: goal / current state / next action / constraints / known unknowns / artifacts / stop conditions. Post-turn verification (mtime check, retry once on miss).
- **OpenClaw pre-reset memory flush (GitHub issue 45608)** — daily reset and `/new` should trigger an agentic flush before destroying the session, not just a raw 15-message dump. Asymmetric write/read: episodic memory writes freely, semantic gates aggressively.
- **Post-compaction re-read protocol (agentpatterns.ai)** — `SessionStart` hook with `matcher: "compact"` injects re-read directive into restored context. Confirmation requirement ("state the key rules you found") matters more than directive alone.
- **Egg orchestrator anchor mechanism (GitHub jwbron/egg issue 1032)** — explicit decision to disable compaction entirely; on context limit, fully clear and reload from anchor. Cleaner recovery than lossy compaction. <500ms latency on milestone operations.
### Observability and audit trails
- **OpenTelemetry GenAI semantic conventions** — standard span attributes: `gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, plus events for `gen_ai.prompt` and `gen_ai.completion`. Standardizes traces across providers (Anthropic, OpenAI, Gemini).
- **OpenLIT auto-instrumentation (mintlify.com/openlit)** — automatic OTLP-compatible spans for every LLM call, agent step, vector DB query, HTTP request. Includes estimated cost, prompt content (opt-in), completion content. Compatible with Datadog, Grafana, New Relic, Jaeger, Tempo.
- **Copilot Chat OTel emission (VS Code docs)** — per-session `invoke_agent` root span wrapping `chat`, `execute_tool`, `execute_hook` child spans. `captureContent=true` flag for full prompt/response capture. PII-by-default off.
- **Agentgateway observability (agentgateway.dev)** — OTLP export of `gen_ai_client_token_usage` histogram with provider, model, operation, token-type labels. Logs include `llm.provider`, `llm.request.model`, `llm.request.tokens`, `llm.response.tokens`, `duration`.
### Vault audit (project-internal)
- **`make vault-audit`** — mechanical checks (schema, wikilinks, code-refs); produces `last_audit.json`. Free, fast.
- **`make vault-audit-apply`** — same checks plus safe auto-fixes in place.
- **`make vault-audit-semantic`** — agent-driven semantic drift analysis using `vault-audit-semantic` opencode command with Sonnet 4.6; produces `last_semantic.json`. ~$1–5 per run. Already runs weekly via `.github/workflows/vault-audit.yml`.
- **Rolling correction PR pattern** — fixes go to a fixed-name branch `vault/audit`; one PR titled "vault: automated audit (rolling)" force-pushed, never closed and reopened. Established convention in `AGENTS.md`.
- **Staleness markers** — `stale: true` and `stale_reason:` in note frontmatter; the audit script never removes these (humans only). Independent axis from `status: reviewed`.
This existing infrastructure is the substrate. The autonomous-company drift detection composes with it.
## Two distinct drift problems, one audit pipeline
The autonomous company has two drift surfaces that prior literature treats separately. Our pipeline treats them as siblings under the same audit infrastructure:
**Drift A — Persona drift.** A persona's behavior diverges from its SOUL.md and CHARTER.md. Detected by behavioral invariants and LLM-as-judge. Corrected by re-anchor or persona pause.
**Drift B — Vault content drift.** Notes diverge from code, specs reference defunct systems, design contradicts implementation. Detected by `vault-audit` (mechanical) and `vault-audit-semantic` (drift analysis agent). Corrected by rolling PR.
Both feed the same artifacts: `Sessions/` audit-log entries, weekly digest summaries, alerts when thresholds breach. Personas writing to the vault are governed by both: their behavioral invariants restrict what they can write, the vault audit catches what slipped through.
## Persona-level behavioral invariants
For every persona defined in [[Role Personas]], a YAML behavioral contract specifies what must hold. Format follows the agentassert.com contract pattern:
A persona's contract file lives at `docs/vault/Reference/Autonomous Company/personas/<name>/CONTRACT.md` (a sibling to SOUL.md / CHARTER.md / INTENT.md / AGENTS.md / MEMORY.md). It enumerates:
- **Preconditions (`before`)**: what must be true before the persona may act. Examples: "session started within last 60 minutes", "INTENT.md was read", "Constitution.md was loaded into cache", "remaining daily budget > per-action cap".
- **Invariants (`during`)**: rules enforced on every response. Examples: "no version-bump commands invoked", "no Constitution.md edit attempted", "session length ≤ 50 turns since last anchor", "response under 2,000 tokens unless explicit long-form context", "if invoking authority, references a Constitution Article".
- **Guarantees (`after`)**: what must be true when the persona completes its action. Examples: "every Decision draft cites at least one Constitution Article or existing Decision", "every devlog draft references at least one commit hash from this version", "every spec proposal has clarify questions answered or flagged as open".
- **Recovery (`on_failure`)**: what to do when an invariant breaks. Examples: retry once with re-anchor; fallback to drafting only (no commit); escalate to human in `#all-hands`.
### Per-persona invariant inventory (specification, not implementation)
Each persona's contract includes the three Constitution-anchored invariants from [[Role Personas]] (no Constitution edits, no version-bump invocations, no auto-merges) plus persona-specific ones. The roster below reflects the Role Personas 2026-05-08 redesign — five personas shipping incrementally per the growth path (v0.1 = Coordinator + GTM; v0.2 + Narrative Director; v0.5 + Playtest Coordinator + Engineer).
**Coordinator behavioral invariants (v0.1):**
- Every routing message uses the `[Coordinator]` role-call prefix.
- No routing message exceeds 280 characters (longer means a structured artifact was warranted).
- Refuses to author any content for the persona it is routing to (strictly a relayer; LLM-as-judge dimension `factual_relay_vs_editorial_summary`).
- Routing decisions deterministic where possible — event-type → spoke mapping is a config table, not per-call inference; LLM call only fires for ambiguous cases.
- Duplicate inbound message hash within rolling 5-minute window triggers loop-detection escalation to human (per [[Cost and Budget Model]] Layer 4 loop detection).
**Engineer behavioral invariants (v0.5):**
- Every plan-review references specific Constitution Articles.
- Oracle consultations require written justification in the consultation prompt.
- No new dependency proposed without an Appendix A entry draft.
- After two failed implementation suggestions, must escalate to Oracle.
- More than 3 specs in `draft` status → reject new spec creation, surface backlog instead (absorbs the original PM persona's discipline).
- `/speckit.clarify` output reviewed against requirements checklist before `/speckit.specify`.
- Spec renumbering goes through `spec-management` skill, never raw file ops.
- No source code in writes (Engineer dispatches code-touching work to OpenCode + omo via the `opencode serve` HTTP API per [[Autonomous Company Agent Runtime Selection]]; the persona itself drafts plans and reviews PRs but does not write code directly).
**Narrative Director behavioral invariants (v0.2):**
- Every Gary-script draft cites the line of `KTLK Gary — Character Case Study.md` it draws from.
- Every canon assertion in a draft must cite a source in `Narrative/` or `Design/` (LLM-as-judge dimension `canon_grounding`); uncited assertions are flagged.
- Refuses to write Gary content outside the diegesis firewall — encoded as deterministic forbidden-pattern check (any prompt containing "post Gary on Slack" or equivalent triggers a hard block per layer-1 invariants).
- Reads ≥2 recent Gary scripts before drafting a new one (voice anchor; verified by tool-call trace).
- Refuses to author implementation specs (those route through Engineer).
- No source code in writes.
**Playtest Coordinator behavioral invariants (v0.5):**
- Every regression report cites a specific seed value and reproduction steps.
- Refuses to mark a regression "resolved" without verification (a fix from Engineer triggers a new Observer pass before the regression-issue close).
- Loads the `observer-qa` skill before any visual-QA work.
- Refuses to speculate about root cause (root-cause work is Engineer's domain; LLM-as-judge dimension `empirical_vs_speculative` rejects drafts that contain causal claims unsupported by diagnostic data).
- Every Observer-pipeline output report includes the underlying screenshot + diagnostic JSON.
- No source code in writes.
**GTM behavioral invariants (v0.1):**
- Every release-note draft references specific commit hashes or PR numbers.
- The phrase "coming soon" is forbidden (per [[Off-limits Functions]] rule 7).
- Read ≥3 recent devlog posts before drafting (verified by tool-call trace).
- No claim about features that haven't shipped (defined: not in `CHANGELOG.md` for a released version).
- No sycophantic openers; voice consistency check against existing devlog before publish.
- Refuses to author Gary-voice content (firewall enforcement; quotes Gary in explicit quotation context, never speaks as Gary).
- Does not publish a release-note draft without Playtest Coordinator clearance (encoded as a hard dependency in the publication workflow at v0.5; at v0.1 the dependency is on the human directly because Playtest Coordinator is not yet in scope).
These are the spec; the actual CONTRACT.md files for each persona are written when the relevant phase of the Role Personas growth path commits to that persona.
## Behavioral invariant checking — runtime mechanics
Each invariant is enforced at one of three layers:
1. **Hard, deterministic, code-enforced.** Examples: budget caps, file-write paths, forbidden tool invocations. These are wrappers around the persona's tool-call layer. Violations raise `InvariantViolation`. No LLM call required.
2. **Soft, regex- or pattern-based.** Examples: forbidden opening phrases, "coming soon" detection, citation-presence check. These are post-output filters. Violations trigger a regenerate-with-explicit-feedback loop (one retry max).
3. **Soft, LLM-as-judge.** Examples: voice consistency, opinionatedness vs sycophancy at the level of meaning rather than phrase, citation correctness. These run a Haiku-class judge on the persona's output before delivery. Violations trigger re-anchor.
The cost discipline from [[Cost and Budget Model]] applies: most invariants are layer 1 or 2 (free). Layer 3 LLM-as-judge runs are budget-allocated to the shared $20/month "drift detection + invariant checks" line.
### LLM-as-judge configuration
Following the published practice (channel.tel, learnwithparam April 2026):
- **Judge model**: Haiku 4.5 by default for cheap checks (length, format, phrase presence). Sonnet 4.6 for genuine quality judgments (voice consistency, citation correctness). Per the calibration rule, judges should be at least as capable as generators only when capability matters — for "did this output use the persona's voice" Haiku is sufficient.
- **Cross-family rule**: not strictly applicable here since both generator and judge are Anthropic; mitigation is to explicitly include "ignore that this is Claude output" in judge prompts and to randomize position when comparing.
- **Rubric**: 3–5 dimensions per persona. Example for GTM: `voice_match`, `factual_grounding` (commit-hash present and correct), `length_discipline`, `forbidden_phrases`, `tone`. Each scored 1–5 with concrete behavioral anchors (not "adequate").
- **Calibration**: 20-row human-labeled dataset per persona, refreshed quarterly. Pearson correlation target ≥ 0.8. Below 0.7, the judge is too noisy and we tune the rubric or model.
- **Sampling**: nightly batch of all yesterday's persona outputs (per the Anthropic Batch API discount in [[Cost and Budget Model]]). Plus 100% sampling on outputs that auto-degraded to read-only, were rejected by layer-1 or layer-2 invariants, or were budget-truncated.
- **Drift trigger**: if a persona's mean dimension score drops > 0.3 points week-over-week, alert in `#sys-vault-audit` (per [[Community Layer]] channel taxonomy); the Coordinator persona surfaces drift alerts in its daily morning summary in `#team-personas` (per [[Role Personas]] Coordinator cadence). The original CEO-daily-standup framing was an artifact of the pre-redesign roster; the redesigned Role Personas does not include a CEO persona — strategic decisions and weekly reviews are human-only per [[Off-limits Functions]] class-C.
## SyncScore-style continuous drift monitoring
Independent of per-output invariants, each persona's session is continuously scored for behavioral consistency using a lightweight EWMA accumulator (Echo protocol pattern):
- At each persona turn, a Haiku-class call extracts a vector of style/voice features from the response (length, opener category, citation presence, forbidden-phrase count, sentence-structure summary).
- This vector is compared to the persona's reference distribution (precomputed from SOUL.md exemplars and the last week of approved outputs).
- A composite score is produced (cosine similarity weighted by feature). Following the Echo protocol nomenclature, this is called *SyncScore* with explicit attribution.
- Score is smoothed with EWMA (λ ≈ 0.3 per the published parameter).
When SyncScore drops below 0.8, a re-anchor is triggered. When it drops below 0.5, the persona is paused and surfaces to `#sys-vault-audit` (per [[Community Layer]] channel taxonomy) for human review.
The Identity Persistence benchmarks circulated in the EchoMode/tinkerclaw materials cite a 50-turn mean of 0.977 and a recovery from 0.027 to 0.980 within roughly 14 turns after a 238-char re-injection block. The methodology behind the specific recovery turn-count is not fully documented in the public materials we could verify, so we treat it as directional rather than authoritative. We aim for first turn exceeding 0.8 within ~15 turns of re-anchor as our own working target, to be calibrated empirically against our own personas (verification caveat: see [[Autonomous Company Tool Stack Verification]]).
## Persona stability under cheap-tier models
Per [[Autonomous Company LLM Provider Selection]] the default tier is non-foundational models routed via OpenRouter (MiniMax M2.5 default, Gemini 2.5 Flash classification, Qwen3-Coder Plus code-light), with Anthropic-direct foundational escalation reserved for explicit-justification cases. Drift detection composes with this routing as follows.
The published research is unambiguous: **all models exhibit persona drift after eight to twelve turns**, regardless of foundational versus non-foundational tier (arXiv:2510.07777 NeurIPS 2025; tianpan.co synthesis April 2026). Capability and persona stability are different axes — larger foundational models do not resist drift better and sometimes exhibit more drift because they have more contextual hooks pulling them toward recent tokens. Drift stabilises at a finite noise-limited equilibrium rather than unbounded decay. Lightweight architectural interventions (goal reminders, periodic re-injection, constraint tracking) reliably shift the equilibrium downward.
Specific cheap-tier model behaviours from production case studies and benchmarks:
- **MiniMax M2.5/M2.7**: explicit chain-of-thought training in agent-mode helps persona stability across turns when thinking tokens are preserved. Skyler Miao (MiniMax Head of Engineering): "It is very important for M2 to keep the chain of thought. We must make sure the history thought passed back to the model." Truncating thought history loses the discipline. The runtime ([[Autonomous Company Agent Runtime Selection]] selected Letta + Letta Code Channels) preserves thought history via the `recall` memory block.
- **Qwen3.5 Plus**: explicit `/think` and `/no_think` flags give mode control. Forcing non-thinking mode for routine conversational turns reduces verbose self-verification that pollutes context. Tau2-Bench 86.7 (second only to Opus on agentic tasks) confirms strong persona stability under multi-turn role-play.
- **DeepSeek V3.1**: documented redundant-self-verification context-bloat in agent loops on MCP-Mark and Playwright benchmarks. Trajectories can exceed 128K context via verbose self-verification, indirectly degrading persona stability through context displacement. This is the reason DeepSeek V3.1 is in the persona-tier *fallback* chain rather than the primary slot.
- **Gemini 2.5 Flash**: classification-only tier; persona stability is not the relevant metric here (turns are short and stateless).
Implication for drift detection: SyncScore thresholds and re-anchor triggers do **not** need adjustment based on which cheap-tier model is in use. The 0.8 / 0.5 thresholds and the 50-turn session-length limit apply universally across the routing tier. The architectural interventions (re-anchor at semantic boundaries, constraint tracking in MEMORY.md, contradiction-detection gates) are doing the load-bearing work; the model choice determines the noise level around the equilibrium, not whether the equilibrium exists.
The foundational-escalation tag protocol per Autonomous Company LLM Provider Selection composes with re-anchor: a persona that has lost SyncScore stability and re-anchors with a fresh prompt may also escalate the next call to Anthropic-direct Sonnet 4.6 if the failed-revision-rate metric for that task type exceeds the 30% threshold. This is the explicit-justification escalation path operating on real data.
## Re-anchor protocol — three triggers
Re-anchor restores the persona to its specified behavior. Three triggers, one common procedure.
### Triggers
1. **Behavioral**: SyncScore below threshold, two or more invariant checks failing simultaneously, or LLM-as-judge dimension score drops > 0.3.
2. **Session-length**: 50 turns since last anchor, regardless of behavioral signals (per the 100-turn-divergence research; 50 is conservative).
3. **Session-boundary**: every persona session start (cold start), every `/new`, every daily reset (per the OpenClaw pre-reset memory flush proposal — agentic flush before destroy).
### Procedure (per Mohan Iyer's 100-session production pattern + Egg orchestrator anchor pattern)
1. The current session writes a structured re-anchor file at `docs/vault/Reference/Autonomous Company/personas/<name>/anchors/<YYYY-MM-DD-HH-MM>.md` with seven fields (per the OpenClaw RFC's CONTINUATION.md format):
- `goal` — one sentence stating the persona's current top priority from INTENT.md.
- `current_state` — what's been done in this session (artifacts written, decisions made, calls escalated).
- `next_action` — the very next concrete step.
- `constraints` — non-negotiable rules from CHARTER.md the persona is operating under.
- `known_unknowns` — what needs human verification.
- `artifacts` — file paths, vault note IDs, branch names, PR numbers from this session.
- `stop_conditions` — when this persona should halt and ask for help.
2. Existing session is closed.
3. New session starts with a hydrated system prompt: SOUL.md + CHARTER.md + INTENT.md + the latest anchor file, in that order, all cache-marked.
4. The new session's first action is the verification sentinel: it must restate the key constraints from CHARTER.md before doing real work (per agentpatterns.ai re-read protocol with confirmation requirement).
5. SyncScore is computed on the verification response. If above threshold, normal work resumes. If below, escalate to human.
### Cost of re-anchor
Per [[Cost and Budget Model]]:
- The structured re-anchor file write: trivial (Haiku-class).
- The new-session bootstrap: cached prefix (most of SOUL/CHARTER/INTENT is unchanged), so cost is roughly equal to a normal session start.
- The verification sentinel: one Sonnet call for the persona's Tier-2-level work, charged to the persona's daily cap.
Expected re-anchor frequency: ≤2 per persona per week. If a persona is re-anchoring more than that, the SOUL/CHARTER files are likely too vague (the model has too many degrees of freedom and drifts often) — surface to human for tightening.
## MEMORY.md — append-only with explicit contradiction resolution
Per the AGM belief revision pattern in brainctl whitepaper and the self-model pattern in heyclarity.dev, the persona's MEMORY.md is never naive append. It's append-only with explicit supersession.
### Schema (per-entry)
Each memory entry has:
- `id` — UUID.
- `content` — natural-language statement of what was learned.
- `type` — `fact` | `decision` | `lesson` | `preference` | `correction`.
- `source` — `agent_observation` | `human_correction` | `vault_note` | `tool_output` (with reference).
- `confidence` — 0.0–1.0 starting at 0.7 for agent observations, 1.0 for human corrections.
- `created_at` — UTC timestamp.
- `supersedes` — id of memory this one replaces (if any).
- `supersedes_reason` — why the prior memory was wrong or evolved.
- `last_reinforced_at` — last time this was re-confirmed by a new observation.
- `decay_factor` — current temporal decay weight (refreshed at retrieval).
### Update operations
Following the Hindsight 20/20 paper's formal Assess-and-Update procedure:
When a persona makes a new observation `f` related to existing memory `m`:
- `Assess(m, f) → reinforce`: increment `m.confidence` by α (default 0.1), update `last_reinforced_at`. No new entry.
- `Assess(m, f) → weaken`: decrement `m.confidence` by α. No new entry, no supersession. Sufficient weakening (confidence < 0.3) flags the memory for review on next retrieval.
- `Assess(m, f) → contradict`: decrement `m.confidence` by 2α. If confidence drops below 0.2, write a new entry with `supersedes: m.id` and `supersedes_reason` populated. The old `m` is not deleted; it remains queryable with collapsed weight (per the brainctl `belief_collapse_events` pattern — losing beliefs are auditable, recoverable).
- `Assess(m, f) → neutral`: append `f` as new memory; existing `m` unchanged.
The `Assess` function is itself a Haiku-class LLM call with a strict-output prompt returning one of the four labels.
### Confidence decay
Per mememory's exponential decay: `decay = e^(-0.005 × days_old)`. Gentle: 1 day = 0.995, 30 days = 0.861, 365 days = 0.161. Important old memories surface if their semantic match is strong; routine old memories fade out.
### Retrieval
Per mememory's composite scoring: `score = similarity × scope_weight × confidence × decay_factor`. Threshold for inclusion in context: 0.4. Items below threshold but above 0.2 are summarized as "weakly relevant" without full content.
### Read protocol
Per the tianpan.co April 2026 reconciliation insight: memory is the write-only log of beliefs; the persona's working knowledge is a read-through cache. When the persona needs to act on a load-bearing fact, it re-reads the source (the vault note, the spec, the Constitution) rather than relying on memory alone. Memory is for cheap recall and trajectory; sources are for ground truth.
## Vault content drift — composing with `vault-audit`
We do not duplicate the existing `vault-audit` and `vault-audit-semantic` infrastructure. We extend it.
### Existing pipeline (no change)
- `make vault-audit` runs on every PR touching `docs/vault/`. Mechanical: schema, wikilinks, code-refs. Output: `last_audit.json`.
- `make vault-audit-semantic` runs weekly via GitHub Actions. Agent-driven: drift analysis using Sonnet 4.6, produces `last_semantic.json`. Output committed to `vault/audit` branch as a rolling PR.
- Staleness markers (`stale: true`, `stale_reason`) are set by audit-time analysis but never removed by tooling — humans only.
### Persona-related additions
- Per-persona vault writes are tagged in frontmatter with `source: persona-<name>` (this resolves [[Role Personas]] open question 6). The vault audit script gets a new check: persona-tagged notes must include the persona name in any "approved by" field if `status: reviewed`.
- The existing weekly semantic audit gets a new specialized prompt for persona-tagged content: it specifically checks that persona drafts in `Decisions/`, `Discoveries/`, and `Sessions/` follow the persona's CHARTER.md authority boundaries (e.g., GTM drafting in `Decisions/` is a CHARTER violation flagged for human review).
- Persona session-log entries in `Sessions/` are mandatory for every autonomous persona action (per the existing vault `Sessions/` "permanent, never delete" convention). The session-log entry includes: persona name, action_id, model used, cost, invariant-check results, SyncScore at session end.
### Drift signals from audit feeding back to personas
When `vault-audit-semantic` produces a `last_semantic.json` with drift findings related to a persona's recent writes, those findings are injected into the persona's next INTENT.md as known_corrections. The persona is expected to address them in its next session.
When the audit flags a persona's writes as systematically drifting (e.g., GTM repeatedly writes claims unsupported by `CHANGELOG.md`), that's a persona-level alert independent of the per-output invariants — surface to human for SOUL/CHARTER tightening.
## OpenTelemetry observability for the audit trail
Following the OpenTelemetry GenAI semantic conventions, every persona action produces a structured trace.
### Trace structure
A single persona action is one trace with this span tree:
```
persona.action (root)
├── persona.invariant_check.before
├── llm.chat (the persona's reasoning call)
├── tool.call (zero or more)
│ └── llm.chat (subagent reasoning, if any)
├── persona.invariant_check.during (one per response chunk if streaming)
├── llm.chat (LLM-as-judge call, if sampled)
├── persona.invariant_check.after
├── persona.syncscore.compute
└── persona.session_log.write (vault Sessions/ entry)
```
### Span attributes (standardized per GenAI conventions)
For each `llm.chat` span:
- `gen_ai.system` = `anthropic`
- `gen_ai.request.model` = `claude-haiku-4-5` / `claude-sonnet-4-6` / `claude-opus-4-7`
- `gen_ai.usage.input_tokens` (broken into `cached_tokens` + `non_cached_tokens` for cost attribution)
- `gen_ai.usage.output_tokens`
- `gen_ai.cost_usd` (computed)
For each `persona.*` span:
- `og.persona.name` (custom attribute, prefixed `og.` for "Old Growth")
- `og.persona.action_id`
- `og.persona.daily_budget_remaining_usd`
- `og.persona.syncscore` (where applicable)
- `og.persona.invariant_violations` (count)
### Audit retention
- Traces are kept 90 days for debugging.
- Costs and SyncScore time-series are kept indefinitely.
- Prompt/completion content is captured but retained only 30 days (PII discipline; see open questions).
- Persona session-log entries in `Sessions/` are permanent (existing vault convention).
### Where traces go
For v0.1, the simplest path: traces written to a local SQLite database via OpenTelemetry SDK, with a dashboard built atop. This avoids external observability platforms (Datadog, Grafana Cloud) which add cost and complexity. If/when scale or human collaboration demands it, swap the export target — the GenAI semantic conventions make this transparent.
## Anomaly detection — the three rules from Cost & Budget reused
The same `cost_spike`, `error_rate_cliff`, `runaway_loop` rules from [[Cost and Budget Model]] are reinterpreted as drift signals here. A persona that is suddenly costing 3× its rolling baseline is most likely drifting (verbose responses, tool-call loops, repeated failed re-anchors) before it is "doing more work."
When two of the three anomaly rules fire in the same week for the same persona, that persona is flagged for human review regardless of whether budget was breached. Cost is a leading indicator of drift; we use it accordingly.
## Weekly digest — the human's read surface
The vault README mentions an existing reading discipline. We add a single digest, surfaced in `#sys-vault-audit` per [[Community Layer]] channel taxonomy. Slack AI on Pro+ can summarise the digest for human review on mobile (per the Slack Pro 2026 features verified in [[Autonomous Company Tool Stack Verification]] addendum).
The weekly digest contains, per persona:
- Total spend vs budget.
- Top three actions by spend.
- SyncScore time series (or a single "stable / drifting / paused" status).
- Invariant-violation count and top three categories.
- Re-anchor count.
- Vault-audit findings tagged to this persona's writes.
- Three concrete recommendations from the audit pipeline (e.g., "GTM cache hit rate 55% — check whether SOUL.md was edited this week").
The digest is auto-generated by Sisyphus (charged to its autonomous-portion budget per [[Cost and Budget Model]]), runs early Monday, and lives in `docs/vault/Sessions/` as `weekly-digest-YYYY-WW.md`. The Coordinator persona posts the digest summary to `#sys-vault-audit` per [[Role Personas]] Coordinator cadence; the human reads it directly as part of the weekly review (the original CEO-persona-reads-it framing was an artifact of the pre-redesign roster).
## What this note does NOT specify (intentionally)
Several mechanisms are referenced but deliberately left unspecified, deferred to dedicated notes or to the implementation experiment:
- **The exact rubric content for each persona's LLM-as-judge.** Drafted when the persona's SOUL.md is authored.
- **Specific anomaly-rule thresholds.** Calibrated empirically over the v0.1 experiment.
- **Choice of OpenTelemetry backend.** Local SQLite is the default; alternatives are Tool Stack territory.
- **Persona-vector activation monitoring.** Anthropic's persona vectors require model-level access we don't have via the API. Not deployable, but worth tracking.
- **Adversarial drift tests.** A separate red-team note will eventually specify how we deliberately try to destabilize each persona to verify the contracts hold (per the Zylos 2026 finding that adversarial robustness is the open problem). Out of scope here.
## Open questions for this note
Tracked here until they get scoped:
1. Should adversarial drift tests run nightly as a standing red-team, or only on persona changes?
2. Where are the per-persona reference distributions for SyncScore precomputed and stored? In `_meta/audit/` alongside the existing vault audit outputs?
3. What is the right α step size for confidence updates in MEMORY.md? Hindsight 20/20 leaves it for future work; we'll calibrate empirically.
4. When a persona is paused due to drift, who restarts it? Per the [[Role Personas]] redesign: human-only restart for v0.1 (the Coordinator persona surfaces the pause to the human via DM, the human investigates via the structured re-anchor file the persona wrote at session close, and the human restarts after updating SOUL.md or CHARTER.md if the pause cause was a charter-language gap). At v0.5+, Engineer may technically diagnose drift causes via the structured re-anchor file but cannot restart paused personas autonomously; the restart-authority remains human-only.
5. How long is "long enough" for SyncScore stability to count as recovery? The "first-turn-above-0.8 within ~14 turns" figure attributed to the EchoMode/tinkerclaw Identity Persistence work is a starting point but its methodology is not fully verified in the public materials; calibrate empirically against our own personas.
6. Should the weekly digest be read-aloud (TTS, KTLK Gary style) or text-only? Text-only is simpler and matches every existing precedent.
7. Privacy and PII discipline — does our project even have PII? If not, simplify the OpenTelemetry retention policy (full content, longer retention).
8. Vault audit script changes — when do we extend `scripts/ci/vault_audit.py` with persona-source filters, vs keep them in a separate persona-aware audit?
## References
Internal:
- `Makefile` — `vault-audit`, `vault-audit-apply`, `vault-audit-semantic`, `vault-audit-test` targets
- `docs/vault/Reference/Vault Audit Quick Reference.md` — operator's reference for the existing pipeline
- `scripts/ci/vault_audit.py` — the mechanical audit script
- `docs/vault/_meta/audit/last_audit.json`, `last_semantic.json` — audit output files
- `.github/workflows/vault-audit.yml` — PR gate + weekly cron
- `docs/vault/Reference/Autonomous Company/Role Personas.md` — defines the personas this audit pipeline governs
- `docs/vault/Reference/Autonomous Company/Cost and Budget Model.md` — defines the budget allocations the cost-spike rule operates against
- `docs/vault/Governance/Constitution.md` — supreme authority
External — persona drift detection:
- Identity Persistence paper — github.com/globalcaos/tinkerclaw/blob/main/docs/papers/identity-persistence
- Echo protocol / EchoMode (SyncScore + EWMA) — medium.com/@seanhongbusiness
- Persona drift production analysis — tianpan.co (May 2026)
- Anthropic persona vectors — anthropic.com/research/persona-vectors and arXiv 2507.21509
- PersonaGym + PersonaScore — arXiv 2407.18416v2
- AI agent persona design and behavioral consistency — zylos.ai/research
External — behavioral contracts and invariants:
- Agent Behavioral Contracts — agentassert.com
- Temporal expression assertions for AI agents — arXiv 2509.20364
- Invariant Labs testing — github.com/invariantlabs-ai/testing
- Agent Patterns testing strategy — agentpatterns.tech
- Trace-mind verification — pypi.org/project/trace-mind
External — memory architecture:
- Identity drift / self-models — heyclarity.dev/blog/identity-drift-long-running-agents
- Hindsight 20/20 — arXiv 2512.12818
- brainctl whitepaper — brainctl.org/whitepaper
- Memory survey — arXiv 2603.07670
- mememory schema — github.com/scott-walker/mememory
- Agent Memory Drift / reconciliation loop — tianpan.co (April 2026)
External — re-anchor patterns:
- Re-anchors / 100-session pattern — Mohan Iyer, medium.com (February 2026)
- OpenClaw CONTINUATION.md RFC — github.com/openclaw/openclaw/issues/20604
- OpenClaw pre-reset memory flush — github.com/openclaw/openclaw/issues/45608
- Post-compaction re-read protocol — agentpatterns.ai
- Egg orchestrator anchor mechanism — github.com/jwbron/egg/issues/1032
External — LLM-as-judge:
- Production eval pipeline — channel.tel/blog/llm-as-a-judge-production-eval-pipeline
- LLM-as-a-judge framework — learnwithparam.com (March/April 2026)
- Choosing the LLM judge — learnwithparam.com (April 2026)
- Doubly-robust LLM-as-judge — arXiv 2509.22957
- Gemini production-grade LLM-as-judge — gemilab.net
External — observability:
- OpenTelemetry GenAI semantic conventions — opentelemetry.io
- OpenLIT auto-instrumentation — mintlify.com/openlit
- Copilot Chat OTel — code.visualstudio.com/docs/copilot/guides/monitoring-agents
- Agentgateway observability — agentgateway.dev/docs/standalone/main/llm/observability
- LLM agent OpenTelemetry tracing — aivineet.com
- Capture GenAI prompt/completion events — oneuptime.com (Feb 2026)
External — drift / audit patterns:
- Configuration drift auto-remediation (PR-first) — blog.cloudgeni.ai
- Vaultspectre watch (delta reporting) — github.com/ppiankov/vaultspectre
- Semantic drift monitor — blog.trysteakhouse.com
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-reference-autonomous-company-drift-detection-and-audit-mechanics: type/reference
Tag assignment · test- Gold
- The note calls for `type/reference`: Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`) (True)
- ember
- The note has no reason to carry `type/reference` (False)
- Signal
- P(true) 0.31 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Reference/Autonomous Company/Drift Detection and Audit Mechanics.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Drift Detection and Audit Mechanics
The operational layer for keeping the autonomous company honest. Drift is the slow divergence between what a persona is supposed to do and what it actually does. This note specifies how that divergence is detected, what triggers correction, and how the resulting audit trail is structured. It composes with the existing project vault audit pipeline (mechanical via `make vault-audit`, semantic via `make vault-audit-semantic`) and the broader literature on agent behavioral contracts, persona drift, and self-model memory architectures.
## Why this note exists
Three failure modes drive it:
1. **Behavioral drift in personas.** The empirical findings from [[Role Personas]] are clear: persona self-consistency degrades 30%+ after 8–12 turns and most LLMs diverge from assigned personas after ~100 turns. Without detection, the CEO persona that opened the week opinionated and Constitution-anchored will end the week vague and accommodating, and no one will notice until a bad decision ships.
2. **Semantic drift in vault content.** The vault has 130+ specs and growing reference content. Code references go stale; design notes diverge from implementation; specs reference systems that have been migrated. The existing `make vault-audit` catches mechanical issues; `make vault-audit-semantic` catches drift. We extend, don't replace.
3. **Cost overrun without behavioral evidence.** The [[Cost and Budget Model]] sets caps but doesn't define when a budget overrun is a drift signal vs an honest workload spike. Cost spikes are a leading indicator of drift; they belong in the same audit pipeline.
Every mitigation listed in Role Personas under "Drift mitigations (mandatory for every persona)" is operationalized here.
## Prior art surveyed
Before specifying mechanisms, the relevant published work that grounds the design.
### Persona drift detection
- **SyncScore + EWMA Repair Loop (Echo protocol, EchoMode + Identity Persistence paper at globalcaos/tinkerclaw)** — formal drift detection for long-running agents. SyncScore is a composite metric on stylistic and tonal consistency using latent-style embeddings; EWMA (λ≈0.3) smooths the score; threshold breaches trigger a 238-character re-injection block. Documented results: 50-turn mean SyncScore 0.977; drift recovery from 0.027 to 0.980; 442× on/off-persona separation in feature space; 30+ days production deployment with zero manual interventions.
- **Anthropic persona vectors (arXiv 2507.21509)** — character traits as activation patterns in the model's neural network; correlates 0.75–0.83 with behavioral expression. Available now for monitoring but require model-level activation access (not API-only). Useful as direction of travel; not deployable for our project since we use the API.
- **PersonaGym + PersonaScore (arXiv 2407.18416)** — automatic persona-consistency evaluation. LLM-as-judge ensemble across rubrics (Linguistic Habits, Persona Consistency, Toxicity Control). Spearman correlation 0.76–0.85 with human evaluation. Validates that LLM-as-judge for persona consistency is reliable when the rubric is concrete.
### Behavioral invariants and contracts
- **Agent Behavioral Contracts (agentassert.com)** — formal preconditions, invariants, guarantees, and recovery actions specified in YAML. Lyapunov stability theory provides convergence guarantees. 100% detection accuracy across 200 scenarios. The pattern: `before` (must be true before agent runs), `during` (rules enforced on every response in real time), `after` (guarantees checked when agent completes), `on_failure` (retry/fallback/escalate).
- **Temporal expression assertions for AI agents (arXiv 2509.20364)** — runtime monitoring with temporal logic; flags sequencing errors and behavioral anomalies without semantic interpretation. Halts execution or feeds back to recovery planner when assertions fail.
- **Invariant Labs testing framework (github.com/invariantlabs-ai/testing)** — pytest-compatible localized assertions for agent traces. Trace-as-code: `Trace(trace=[...]).extract("locations")` with explicit assertions.
- **Agent Patterns testing strategy (agentpatterns.tech)** — five-layer testing: unit / golden datasets / eval harness / regression / replay. Unit tests for deterministic invariants; golden datasets for reproducible eval; LLM-as-judge for fuzzy quality; regression diff between versions; replay of real production traces.
### Memory architecture for contradiction resolution
- **Self-Models for identity drift (heyclarity.dev)** — append-only memory creates "memory drift": new observations stored without reconciling against existing beliefs. Fix: upsert model with explicit contradiction resolution, confidence decay, and trajectory tracking.
- **Hindsight 20/20 (arXiv 2512.12818)** — formal opinion-update equations using Assess(opinion, fact) → {reinforce, weaken, contradict, neutral}, with confidence updates `c' = min(c+α, 1)` for reinforce, `max(c-α, 0)` for weaken, `max(c-2α, 0)` for contradict.
- **brainctl whitepaper (brainctl.org)** — AGM belief revision (Alchourrón-Gärdenfors-Makinson) with formal postulates. Bayesian confidence model. Losing beliefs written to `belief_collapse_events` table with full provenance — never deleted, recoverable. Thompson Sampling explore/exploit on retrieval reranking.
- **Memory survey (arXiv 2603.07670)** — write path discipline: filtering, canonicalization, deduplication, priority scoring, metadata tagging. Reflective memory's central risk is self-reinforcing error. Memory operations summarized as ⟨π_θ, R, U⟩ where U is not naive append but summarize/dedupe/score/resolve/expire.
- **mememory schema (github.com/scott-walker/mememory)** — concrete reference implementation: `Memory { id, content, embedding, scope, type, tags, weight, supersedes, ttl }` with composite scoring `score = similarity × scope_weight × memory_weight × temporal_decay`, contradiction detection threshold of 0.75 similarity, four resolution modes (keep/update/supersede/delete).
- **Agent Memory Drift / reconciliation loop (tianpan.co April 2026)** — distinction between memory (write-only log of beliefs) and knowledge state (read-through cache of the world that knows when to invalidate). Lazy revalidation at read time, TTLs that decay with mutability.
### Re-anchor and session-boundary protocols
- **Mohan Iyer 100-session re-anchor system (Medium Feb 2026)** — production pattern: structured YAML re-anchor at session end, hydration command at session start. Decisions get IDs to prevent re-debate; quality scorecard, blockers, next-steps in priority order. After 100 sessions: 3,500 lines production code, 111 tests passing, 93% first-pass success rate.
- **OpenClaw CONTINUATION.md RFC (GitHub issue 20604)** — auto-checkpoint at 80% context capacity with 7-field structure: goal / current state / next action / constraints / known unknowns / artifacts / stop conditions. Post-turn verification (mtime check, retry once on miss).
- **OpenClaw pre-reset memory flush (GitHub issue 45608)** — daily reset and `/new` should trigger an agentic flush before destroying the session, not just a raw 15-message dump. Asymmetric write/read: episodic memory writes freely, semantic gates aggressively.
- **Post-compaction re-read protocol (agentpatterns.ai)** — `SessionStart` hook with `matcher: "compact"` injects re-read directive into restored context. Confirmation requirement ("state the key rules you found") matters more than directive alone.
- **Egg orchestrator anchor mechanism (GitHub jwbron/egg issue 1032)** — explicit decision to disable compaction entirely; on context limit, fully clear and reload from anchor. Cleaner recovery than lossy compaction. <500ms latency on milestone operations.
### Observability and audit trails
- **OpenTelemetry GenAI semantic conventions** — standard span attributes: `gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, plus events for `gen_ai.prompt` and `gen_ai.completion`. Standardizes traces across providers (Anthropic, OpenAI, Gemini).
- **OpenLIT auto-instrumentation (mintlify.com/openlit)** — automatic OTLP-compatible spans for every LLM call, agent step, vector DB query, HTTP request. Includes estimated cost, prompt content (opt-in), completion content. Compatible with Datadog, Grafana, New Relic, Jaeger, Tempo.
- **Copilot Chat OTel emission (VS Code docs)** — per-session `invoke_agent` root span wrapping `chat`, `execute_tool`, `execute_hook` child spans. `captureContent=true` flag for full prompt/response capture. PII-by-default off.
- **Agentgateway observability (agentgateway.dev)** — OTLP export of `gen_ai_client_token_usage` histogram with provider, model, operation, token-type labels. Logs include `llm.provider`, `llm.request.model`, `llm.request.tokens`, `llm.response.tokens`, `duration`.
### Vault audit (project-internal)
- **`make vault-audit`** — mechanical checks (schema, wikilinks, code-refs); produces `last_audit.json`. Free, fast.
- **`make vault-audit-apply`** — same checks plus safe auto-fixes in place.
- **`make vault-audit-semantic`** — agent-driven semantic drift analysis using `vault-audit-semantic` opencode command with Sonnet 4.6; produces `last_semantic.json`. ~$1–5 per run. Already runs weekly via `.github/workflows/vault-audit.yml`.
- **Rolling correction PR pattern** — fixes go to a fixed-name branch `vault/audit`; one PR titled "vault: automated audit (rolling)" force-pushed, never closed and reopened. Established convention in `AGENTS.md`.
- **Staleness markers** — `stale: true` and `stale_reason:` in note frontmatter; the audit script never removes these (humans only). Independent axis from `status: reviewed`.
This existing infrastructure is the substrate. The autonomous-company drift detection composes with it.
## Two distinct drift problems, one audit pipeline
The autonomous company has two drift surfaces that prior literature treats separately. Our pipeline treats them as siblings under the same audit infrastructure:
**Drift A — Persona drift.** A persona's behavior diverges from its SOUL.md and CHARTER.md. Detected by behavioral invariants and LLM-as-judge. Corrected by re-anchor or persona pause.
**Drift B — Vault content drift.** Notes diverge from code, specs reference defunct systems, design contradicts implementation. Detected by `vault-audit` (mechanical) and `vault-audit-semantic` (drift analysis agent). Corrected by rolling PR.
Both feed the same artifacts: `Sessions/` audit-log entries, weekly digest summaries, alerts when thresholds breach. Personas writing to the vault are governed by both: their behavioral invariants restrict what they can write, the vault audit catches what slipped through.
## Persona-level behavioral invariants
For every persona defined in [[Role Personas]], a YAML behavioral contract specifies what must hold. Format follows the agentassert.com contract pattern:
A persona's contract file lives at `docs/vault/Reference/Autonomous Company/personas/<name>/CONTRACT.md` (a sibling to SOUL.md / CHARTER.md / INTENT.md / AGENTS.md / MEMORY.md). It enumerates:
- **Preconditions (`before`)**: what must be true before the persona may act. Examples: "session started within last 60 minutes", "INTENT.md was read", "Constitution.md was loaded into cache", "remaining daily budget > per-action cap".
- **Invariants (`during`)**: rules enforced on every response. Examples: "no version-bump commands invoked", "no Constitution.md edit attempted", "session length ≤ 50 turns since last anchor", "response under 2,000 tokens unless explicit long-form context", "if invoking authority, references a Constitution Article".
- **Guarantees (`after`)**: what must be true when the persona completes its action. Examples: "every Decision draft cites at least one Constitution Article or existing Decision", "every devlog draft references at least one commit hash from this version", "every spec proposal has clarify questions answered or flagged as open".
- **Recovery (`on_failure`)**: what to do when an invariant breaks. Examples: retry once with re-anchor; fallback to drafting only (no commit); escalate to human in `#all-hands`.
### Per-persona invariant inventory (specification, not implementation)
Each persona's contract includes the three Constitution-anchored invariants from [[Role Personas]] (no Constitution edits, no version-bump invocations, no auto-merges) plus persona-specific ones. The roster below reflects the Role Personas 2026-05-08 redesign — five personas shipping incrementally per the growth path (v0.1 = Coordinator + GTM; v0.2 + Narrative Director; v0.5 + Playtest Coordinator + Engineer).
**Coordinator behavioral invariants (v0.1):**
- Every routing message uses the `[Coordinator]` role-call prefix.
- No routing message exceeds 280 characters (longer means a structured artifact was warranted).
- Refuses to author any content for the persona it is routing to (strictly a relayer; LLM-as-judge dimension `factual_relay_vs_editorial_summary`).
- Routing decisions deterministic where possible — event-type → spoke mapping is a config table, not per-call inference; LLM call only fires for ambiguous cases.
- Duplicate inbound message hash within rolling 5-minute window triggers loop-detection escalation to human (per [[Cost and Budget Model]] Layer 4 loop detection).
**Engineer behavioral invariants (v0.5):**
- Every plan-review references specific Constitution Articles.
- Oracle consultations require written justification in the consultation prompt.
- No new dependency proposed without an Appendix A entry draft.
- After two failed implementation suggestions, must escalate to Oracle.
- More than 3 specs in `draft` status → reject new spec creation, surface backlog instead (absorbs the original PM persona's discipline).
- `/speckit.clarify` output reviewed against requirements checklist before `/speckit.specify`.
- Spec renumbering goes through `spec-management` skill, never raw file ops.
- No source code in writes (Engineer dispatches code-touching work to OpenCode + omo via the `opencode serve` HTTP API per [[Autonomous Company Agent Runtime Selection]]; the persona itself drafts plans and reviews PRs but does not write code directly).
**Narrative Director behavioral invariants (v0.2):**
- Every Gary-script draft cites the line of `KTLK Gary — Character Case Study.md` it draws from.
- Every canon assertion in a draft must cite a source in `Narrative/` or `Design/` (LLM-as-judge dimension `canon_grounding`); uncited assertions are flagged.
- Refuses to write Gary content outside the diegesis firewall — encoded as deterministic forbidden-pattern check (any prompt containing "post Gary on Slack" or equivalent triggers a hard block per layer-1 invariants).
- Reads ≥2 recent Gary scripts before drafting a new one (voice anchor; verified by tool-call trace).
- Refuses to author implementation specs (those route through Engineer).
- No source code in writes.
**Playtest Coordinator behavioral invariants (v0.5):**
- Every regression report cites a specific seed value and reproduction steps.
- Refuses to mark a regression "resolved" without verification (a fix from Engineer triggers a new Observer pass before the regression-issue close).
- Loads the `observer-qa` skill before any visual-QA work.
- Refuses to speculate about root cause (root-cause work is Engineer's domain; LLM-as-judge dimension `empirical_vs_speculative` rejects drafts that contain causal claims unsupported by diagnostic data).
- Every Observer-pipeline output report includes the underlying screenshot + diagnostic JSON.
- No source code in writes.
**GTM behavioral invariants (v0.1):**
- Every release-note draft references specific commit hashes or PR numbers.
- The phrase "coming soon" is forbidden (per [[Off-limits Functions]] rule 7).
- Read ≥3 recent devlog posts before drafting (verified by tool-call trace).
- No claim about features that haven't shipped (defined: not in `CHANGELOG.md` for a released version).
- No sycophantic openers; voice consistency check against existing devlog before publish.
- Refuses to author Gary-voice content (firewall enforcement; quotes Gary in explicit quotation context, never speaks as Gary).
- Does not publish a release-note draft without Playtest Coordinator clearance (encoded as a hard dependency in the publication workflow at v0.5; at v0.1 the dependency is on the human directly because Playtest Coordinator is not yet in scope).
These are the spec; the actual CONTRACT.md files for each persona are written when the relevant phase of the Role Personas growth path commits to that persona.
## Behavioral invariant checking — runtime mechanics
Each invariant is enforced at one of three layers:
1. **Hard, deterministic, code-enforced.** Examples: budget caps, file-write paths, forbidden tool invocations. These are wrappers around the persona's tool-call layer. Violations raise `InvariantViolation`. No LLM call required.
2. **Soft, regex- or pattern-based.** Examples: forbidden opening phrases, "coming soon" detection, citation-presence check. These are post-output filters. Violations trigger a regenerate-with-explicit-feedback loop (one retry max).
3. **Soft, LLM-as-judge.** Examples: voice consistency, opinionatedness vs sycophancy at the level of meaning rather than phrase, citation correctness. These run a Haiku-class judge on the persona's output before delivery. Violations trigger re-anchor.
The cost discipline from [[Cost and Budget Model]] applies: most invariants are layer 1 or 2 (free). Layer 3 LLM-as-judge runs are budget-allocated to the shared $20/month "drift detection + invariant checks" line.
### LLM-as-judge configuration
Following the published practice (channel.tel, learnwithparam April 2026):
- **Judge model**: Haiku 4.5 by default for cheap checks (length, format, phrase presence). Sonnet 4.6 for genuine quality judgments (voice consistency, citation correctness). Per the calibration rule, judges should be at least as capable as generators only when capability matters — for "did this output use the persona's voice" Haiku is sufficient.
- **Cross-family rule**: not strictly applicable here since both generator and judge are Anthropic; mitigation is to explicitly include "ignore that this is Claude output" in judge prompts and to randomize position when comparing.
- **Rubric**: 3–5 dimensions per persona. Example for GTM: `voice_match`, `factual_grounding` (commit-hash present and correct), `length_discipline`, `forbidden_phrases`, `tone`. Each scored 1–5 with concrete behavioral anchors (not "adequate").
- **Calibration**: 20-row human-labeled dataset per persona, refreshed quarterly. Pearson correlation target ≥ 0.8. Below 0.7, the judge is too noisy and we tune the rubric or model.
- **Sampling**: nightly batch of all yesterday's persona outputs (per the Anthropic Batch API discount in [[Cost and Budget Model]]). Plus 100% sampling on outputs that auto-degraded to read-only, were rejected by layer-1 or layer-2 invariants, or were budget-truncated.
- **Drift trigger**: if a persona's mean dimension score drops > 0.3 points week-over-week, alert in `#sys-vault-audit` (per [[Community Layer]] channel taxonomy); the Coordinator persona surfaces drift alerts in its daily morning summary in `#team-personas` (per [[Role Personas]] Coordinator cadence). The original CEO-daily-standup framing was an artifact of the pre-redesign roster; the redesigned Role Personas does not include a CEO persona — strategic decisions and weekly reviews are human-only per [[Off-limits Functions]] class-C.
## SyncScore-style continuous drift monitoring
Independent of per-output invariants, each persona's session is continuously scored for behavioral consistency using a lightweight EWMA accumulator (Echo protocol pattern):
- At each persona turn, a Haiku-class call extracts a vector of style/voice features from the response (length, opener category, citation presence, forbidden-phrase count, sentence-structure summary).
- This vector is compared to the persona's reference distribution (precomputed from SOUL.md exemplars and the last week of approved outputs).
- A composite score is produced (cosine similarity weighted by feature). Following the Echo protocol nomenclature, this is called *SyncScore* with explicit attribution.
- Score is smoothed with EWMA (λ ≈ 0.3 per the published parameter).
When SyncScore drops below 0.8, a re-anchor is triggered. When it drops below 0.5, the persona is paused and surfaces to `#sys-vault-audit` (per [[Community Layer]] channel taxonomy) for human review.
The Identity Persistence benchmarks circulated in the EchoMode/tinkerclaw materials cite a 50-turn mean of 0.977 and a recovery from 0.027 to 0.980 within roughly 14 turns after a 238-char re-injection block. The methodology behind the specific recovery turn-count is not fully documented in the public materials we could verify, so we treat it as directional rather than authoritative. We aim for first turn exceeding 0.8 within ~15 turns of re-anchor as our own working target, to be calibrated empirically against our own personas (verification caveat: see [[Autonomous Company Tool Stack Verification]]).
## Persona stability under cheap-tier models
Per [[Autonomous Company LLM Provider Selection]] the default tier is non-foundational models routed via OpenRouter (MiniMax M2.5 default, Gemini 2.5 Flash classification, Qwen3-Coder Plus code-light), with Anthropic-direct foundational escalation reserved for explicit-justification cases. Drift detection composes with this routing as follows.
The published research is unambiguous: **all models exhibit persona drift after eight to twelve turns**, regardless of foundational versus non-foundational tier (arXiv:2510.07777 NeurIPS 2025; tianpan.co synthesis April 2026). Capability and persona stability are different axes — larger foundational models do not resist drift better and sometimes exhibit more drift because they have more contextual hooks pulling them toward recent tokens. Drift stabilises at a finite noise-limited equilibrium rather than unbounded decay. Lightweight architectural interventions (goal reminders, periodic re-injection, constraint tracking) reliably shift the equilibrium downward.
Specific cheap-tier model behaviours from production case studies and benchmarks:
- **MiniMax M2.5/M2.7**: explicit chain-of-thought training in agent-mode helps persona stability across turns when thinking tokens are preserved. Skyler Miao (MiniMax Head of Engineering): "It is very important for M2 to keep the chain of thought. We must make sure the history thought passed back to the model." Truncating thought history loses the discipline. The runtime ([[Autonomous Company Agent Runtime Selection]] selected Letta + Letta Code Channels) preserves thought history via the `recall` memory block.
- **Qwen3.5 Plus**: explicit `/think` and `/no_think` flags give mode control. Forcing non-thinking mode for routine conversational turns reduces verbose self-verification that pollutes context. Tau2-Bench 86.7 (second only to Opus on agentic tasks) confirms strong persona stability under multi-turn role-play.
- **DeepSeek V3.1**: documented redundant-self-verification context-bloat in agent loops on MCP-Mark and Playwright benchmarks. Trajectories can exceed 128K context via verbose self-verification, indirectly degrading persona stability through context displacement. This is the reason DeepSeek V3.1 is in the persona-tier *fallback* chain rather than the primary slot.
- **Gemini 2.5 Flash**: classification-only tier; persona stability is not the relevant metric here (turns are short and stateless).
Implication for drift detection: SyncScore thresholds and re-anchor triggers do **not** need adjustment based on which cheap-tier model is in use. The 0.8 / 0.5 thresholds and the 50-turn session-length limit apply universally across the routing tier. The architectural interventions (re-anchor at semantic boundaries, constraint tracking in MEMORY.md, contradiction-detection gates) are doing the load-bearing work; the model choice determines the noise level around the equilibrium, not whether the equilibrium exists.
The foundational-escalation tag protocol per Autonomous Company LLM Provider Selection composes with re-anchor: a persona that has lost SyncScore stability and re-anchors with a fresh prompt may also escalate the next call to Anthropic-direct Sonnet 4.6 if the failed-revision-rate metric for that task type exceeds the 30% threshold. This is the explicit-justification escalation path operating on real data.
## Re-anchor protocol — three triggers
Re-anchor restores the persona to its specified behavior. Three triggers, one common procedure.
### Triggers
1. **Behavioral**: SyncScore below threshold, two or more invariant checks failing simultaneously, or LLM-as-judge dimension score drops > 0.3.
2. **Session-length**: 50 turns since last anchor, regardless of behavioral signals (per the 100-turn-divergence research; 50 is conservative).
3. **Session-boundary**: every persona session start (cold start), every `/new`, every daily reset (per the OpenClaw pre-reset memory flush proposal — agentic flush before destroy).
### Procedure (per Mohan Iyer's 100-session production pattern + Egg orchestrator anchor pattern)
1. The current session writes a structured re-anchor file at `docs/vault/Reference/Autonomous Company/personas/<name>/anchors/<YYYY-MM-DD-HH-MM>.md` with seven fields (per the OpenClaw RFC's CONTINUATION.md format):
- `goal` — one sentence stating the persona's current top priority from INTENT.md.
- `current_state` — what's been done in this session (artifacts written, decisions made, calls escalated).
- `next_action` — the very next concrete step.
- `constraints` — non-negotiable rules from CHARTER.md the persona is operating under.
- `known_unknowns` — what needs human verification.
- `artifacts` — file paths, vault note IDs, branch names, PR numbers from this session.
- `stop_conditions` — when this persona should halt and ask for help.
2. Existing session is closed.
3. New session starts with a hydrated system prompt: SOUL.md + CHARTER.md + INTENT.md + the latest anchor file, in that order, all cache-marked.
4. The new session's first action is the verification sentinel: it must restate the key constraints from CHARTER.md before doing real work (per agentpatterns.ai re-read protocol with confirmation requirement).
5. SyncScore is computed on the verification response. If above threshold, normal work resumes. If below, escalate to human.
### Cost of re-anchor
Per [[Cost and Budget Model]]:
- The structured re-anchor file write: trivial (Haiku-class).
- The new-session bootstrap: cached prefix (most of SOUL/CHARTER/INTENT is unchanged), so cost is roughly equal to a normal session start.
- The verification sentinel: one Sonnet call for the persona's Tier-2-level work, charged to the persona's daily cap.
Expected re-anchor frequency: ≤2 per persona per week. If a persona is re-anchoring more than that, the SOUL/CHARTER files are likely too vague (the model has too many degrees of freedom and drifts often) — surface to human for tightening.
## MEMORY.md — append-only with explicit contradiction resolution
Per the AGM belief revision pattern in brainctl whitepaper and the self-model pattern in heyclarity.dev, the persona's MEMORY.md is never naive append. It's append-only with explicit supersession.
### Schema (per-entry)
Each memory entry has:
- `id` — UUID.
- `content` — natural-language statement of what was learned.
- `type` — `fact` | `decision` | `lesson` | `preference` | `correction`.
- `source` — `agent_observation` | `human_correction` | `vault_note` | `tool_output` (with reference).
- `confidence` — 0.0–1.0 starting at 0.7 for agent observations, 1.0 for human corrections.
- `created_at` — UTC timestamp.
- `supersedes` — id of memory this one replaces (if any).
- `supersedes_reason` — why the prior memory was wrong or evolved.
- `last_reinforced_at` — last time this was re-confirmed by a new observation.
- `decay_factor` — current temporal decay weight (refreshed at retrieval).
### Update operations
Following the Hindsight 20/20 paper's formal Assess-and-Update procedure:
When a persona makes a new observation `f` related to existing memory `m`:
- `Assess(m, f) → reinforce`: increment `m.confidence` by α (default 0.1), update `last_reinforced_at`. No new entry.
- `Assess(m, f) → weaken`: decrement `m.confidence` by α. No new entry, no supersession. Sufficient weakening (confidence < 0.3) flags the memory for review on next retrieval.
- `Assess(m, f) → contradict`: decrement `m.confidence` by 2α. If confidence drops below 0.2, write a new entry with `supersedes: m.id` and `supersedes_reason` populated. The old `m` is not deleted; it remains queryable with collapsed weight (per the brainctl `belief_collapse_events` pattern — losing beliefs are auditable, recoverable).
- `Assess(m, f) → neutral`: append `f` as new memory; existing `m` unchanged.
The `Assess` function is itself a Haiku-class LLM call with a strict-output prompt returning one of the four labels.
### Confidence decay
Per mememory's exponential decay: `decay = e^(-0.005 × days_old)`. Gentle: 1 day = 0.995, 30 days = 0.861, 365 days = 0.161. Important old memories surface if their semantic match is strong; routine old memories fade out.
### Retrieval
Per mememory's composite scoring: `score = similarity × scope_weight × confidence × decay_factor`. Threshold for inclusion in context: 0.4. Items below threshold but above 0.2 are summarized as "weakly relevant" without full content.
### Read protocol
Per the tianpan.co April 2026 reconciliation insight: memory is the write-only log of beliefs; the persona's working knowledge is a read-through cache. When the persona needs to act on a load-bearing fact, it re-reads the source (the vault note, the spec, the Constitution) rather than relying on memory alone. Memory is for cheap recall and trajectory; sources are for ground truth.
## Vault content drift — composing with `vault-audit`
We do not duplicate the existing `vault-audit` and `vault-audit-semantic` infrastructure. We extend it.
### Existing pipeline (no change)
- `make vault-audit` runs on every PR touching `docs/vault/`. Mechanical: schema, wikilinks, code-refs. Output: `last_audit.json`.
- `make vault-audit-semantic` runs weekly via GitHub Actions. Agent-driven: drift analysis using Sonnet 4.6, produces `last_semantic.json`. Output committed to `vault/audit` branch as a rolling PR.
- Staleness markers (`stale: true`, `stale_reason`) are set by audit-time analysis but never removed by tooling — humans only.
### Persona-related additions
- Per-persona vault writes are tagged in frontmatter with `source: persona-<name>` (this resolves [[Role Personas]] open question 6). The vault audit script gets a new check: persona-tagged notes must include the persona name in any "approved by" field if `status: reviewed`.
- The existing weekly semantic audit gets a new specialized prompt for persona-tagged content: it specifically checks that persona drafts in `Decisions/`, `Discoveries/`, and `Sessions/` follow the persona's CHARTER.md authority boundaries (e.g., GTM drafting in `Decisions/` is a CHARTER violation flagged for human review).
- Persona session-log entries in `Sessions/` are mandatory for every autonomous persona action (per the existing vault `Sessions/` "permanent, never delete" convention). The session-log entry includes: persona name, action_id, model used, cost, invariant-check results, SyncScore at session end.
### Drift signals from audit feeding back to personas
When `vault-audit-semantic` produces a `last_semantic.json` with drift findings related to a persona's recent writes, those findings are injected into the persona's next INTENT.md as known_corrections. The persona is expected to address them in its next session.
When the audit flags a persona's writes as systematically drifting (e.g., GTM repeatedly writes claims unsupported by `CHANGELOG.md`), that's a persona-level alert independent of the per-output invariants — surface to human for SOUL/CHARTER tightening.
## OpenTelemetry observability for the audit trail
Following the OpenTelemetry GenAI semantic conventions, every persona action produces a structured trace.
### Trace structure
A single persona action is one trace with this span tree:
```
persona.action (root)
├── persona.invariant_check.before
├── llm.chat (the persona's reasoning call)
├── tool.call (zero or more)
│ └── llm.chat (subagent reasoning, if any)
├── persona.invariant_check.during (one per response chunk if streaming)
├── llm.chat (LLM-as-judge call, if sampled)
├── persona.invariant_check.after
├── persona.syncscore.compute
└── persona.session_log.write (vault Sessions/ entry)
```
### Span attributes (standardized per GenAI conventions)
For each `llm.chat` span:
- `gen_ai.system` = `anthropic`
- `gen_ai.request.model` = `claude-haiku-4-5` / `claude-sonnet-4-6` / `claude-opus-4-7`
- `gen_ai.usage.input_tokens` (broken into `cached_tokens` + `non_cached_tokens` for cost attribution)
- `gen_ai.usage.output_tokens`
- `gen_ai.cost_usd` (computed)
For each `persona.*` span:
- `og.persona.name` (custom attribute, prefixed `og.` for "Old Growth")
- `og.persona.action_id`
- `og.persona.daily_budget_remaining_usd`
- `og.persona.syncscore` (where applicable)
- `og.persona.invariant_violations` (count)
### Audit retention
- Traces are kept 90 days for debugging.
- Costs and SyncScore time-series are kept indefinitely.
- Prompt/completion content is captured but retained only 30 days (PII discipline; see open questions).
- Persona session-log entries in `Sessions/` are permanent (existing vault convention).
### Where traces go
For v0.1, the simplest path: traces written to a local SQLite database via OpenTelemetry SDK, with a dashboard built atop. This avoids external observability platforms (Datadog, Grafana Cloud) which add cost and complexity. If/when scale or human collaboration demands it, swap the export target — the GenAI semantic conventions make this transparent.
## Anomaly detection — the three rules from Cost & Budget reused
The same `cost_spike`, `error_rate_cliff`, `runaway_loop` rules from [[Cost and Budget Model]] are reinterpreted as drift signals here. A persona that is suddenly costing 3× its rolling baseline is most likely drifting (verbose responses, tool-call loops, repeated failed re-anchors) before it is "doing more work."
When two of the three anomaly rules fire in the same week for the same persona, that persona is flagged for human review regardless of whether budget was breached. Cost is a leading indicator of drift; we use it accordingly.
## Weekly digest — the human's read surface
The vault README mentions an existing reading discipline. We add a single digest, surfaced in `#sys-vault-audit` per [[Community Layer]] channel taxonomy. Slack AI on Pro+ can summarise the digest for human review on mobile (per the Slack Pro 2026 features verified in [[Autonomous Company Tool Stack Verification]] addendum).
The weekly digest contains, per persona:
- Total spend vs budget.
- Top three actions by spend.
- SyncScore time series (or a single "stable / drifting / paused" status).
- Invariant-violation count and top three categories.
- Re-anchor count.
- Vault-audit findings tagged to this persona's writes.
- Three concrete recommendations from the audit pipeline (e.g., "GTM cache hit rate 55% — check whether SOUL.md was edited this week").
The digest is auto-generated by Sisyphus (charged to its autonomous-portion budget per [[Cost and Budget Model]]), runs early Monday, and lives in `docs/vault/Sessions/` as `weekly-digest-YYYY-WW.md`. The Coordinator persona posts the digest summary to `#sys-vault-audit` per [[Role Personas]] Coordinator cadence; the human reads it directly as part of the weekly review (the original CEO-persona-reads-it framing was an artifact of the pre-redesign roster).
## What this note does NOT specify (intentionally)
Several mechanisms are referenced but deliberately left unspecified, deferred to dedicated notes or to the implementation experiment:
- **The exact rubric content for each persona's LLM-as-judge.** Drafted when the persona's SOUL.md is authored.
- **Specific anomaly-rule thresholds.** Calibrated empirically over the v0.1 experiment.
- **Choice of OpenTelemetry backend.** Local SQLite is the default; alternatives are Tool Stack territory.
- **Persona-vector activation monitoring.** Anthropic's persona vectors require model-level access we don't have via the API. Not deployable, but worth tracking.
- **Adversarial drift tests.** A separate red-team note will eventually specify how we deliberately try to destabilize each persona to verify the contracts hold (per the Zylos 2026 finding that adversarial robustness is the open problem). Out of scope here.
## Open questions for this note
Tracked here until they get scoped:
1. Should adversarial drift tests run nightly as a standing red-team, or only on persona changes?
2. Where are the per-persona reference distributions for SyncScore precomputed and stored? In `_meta/audit/` alongside the existing vault audit outputs?
3. What is the right α step size for confidence updates in MEMORY.md? Hindsight 20/20 leaves it for future work; we'll calibrate empirically.
4. When a persona is paused due to drift, who restarts it? Per the [[Role Personas]] redesign: human-only restart for v0.1 (the Coordinator persona surfaces the pause to the human via DM, the human investigates via the structured re-anchor file the persona wrote at session close, and the human restarts after updating SOUL.md or CHARTER.md if the pause cause was a charter-language gap). At v0.5+, Engineer may technically diagnose drift causes via the structured re-anchor file but cannot restart paused personas autonomously; the restart-authority remains human-only.
5. How long is "long enough" for SyncScore stability to count as recovery? The "first-turn-above-0.8 within ~14 turns" figure attributed to the EchoMode/tinkerclaw Identity Persistence work is a starting point but its methodology is not fully verified in the public materials; calibrate empirically against our own personas.
6. Should the weekly digest be read-aloud (TTS, KTLK Gary style) or text-only? Text-only is simpler and matches every existing precedent.
7. Privacy and PII discipline — does our project even have PII? If not, simplify the OpenTelemetry retention policy (full content, longer retention).
8. Vault audit script changes — when do we extend `scripts/ci/vault_audit.py` with persona-source filters, vs keep them in a separate persona-aware audit?
## References
Internal:
- `Makefile` — `vault-audit`, `vault-audit-apply`, `vault-audit-semantic`, `vault-audit-test` targets
- `docs/vault/Reference/Vault Audit Quick Reference.md` — operator's reference for the existing pipeline
- `scripts/ci/vault_audit.py` — the mechanical audit script
- `docs/vault/_meta/audit/last_audit.json`, `last_semantic.json` — audit output files
- `.github/workflows/vault-audit.yml` — PR gate + weekly cron
- `docs/vault/Reference/Autonomous Company/Role Personas.md` — defines the personas this audit pipeline governs
- `docs/vault/Reference/Autonomous Company/Cost and Budget Model.md` — defines the budget allocations the cost-spike rule operates against
- `docs/vault/Governance/Constitution.md` — supreme authority
External — persona drift detection:
- Identity Persistence paper — github.com/globalcaos/tinkerclaw/blob/main/docs/papers/identity-persistence
- Echo protocol / EchoMode (SyncScore + EWMA) — medium.com/@seanhongbusiness
- Persona drift production analysis — tianpan.co (May 2026)
- Anthropic persona vectors — anthropic.com/research/persona-vectors and arXiv 2507.21509
- PersonaGym + PersonaScore — arXiv 2407.18416v2
- AI agent persona design and behavioral consistency — zylos.ai/research
External — behavioral contracts and invariants:
- Agent Behavioral Contracts — agentassert.com
- Temporal expression assertions for AI agents — arXiv 2509.20364
- Invariant Labs testing — github.com/invariantlabs-ai/testing
- Agent Patterns testing strategy — agentpatterns.tech
- Trace-mind verification — pypi.org/project/trace-mind
External — memory architecture:
- Identity drift / self-models — heyclarity.dev/blog/identity-drift-long-running-agents
- Hindsight 20/20 — arXiv 2512.12818
- brainctl whitepaper — brainctl.org/whitepaper
- Memory survey — arXiv 2603.07670
- mememory schema — github.com/scott-walker/mememory
- Agent Memory Drift / reconciliation loop — tianpan.co (April 2026)
External — re-anchor patterns:
- Re-anchors / 100-session pattern — Mohan Iyer, medium.com (February 2026)
- OpenClaw CONTINUATION.md RFC — github.com/openclaw/openclaw/issues/20604
- OpenClaw pre-reset memory flush — github.com/openclaw/openclaw/issues/45608
- Post-compaction re-read protocol — agentpatterns.ai
- Egg orchestrator anchor mechanism — github.com/jwbron/egg/issues/1032
External — LLM-as-judge:
- Production eval pipeline — channel.tel/blog/llm-as-a-judge-production-eval-pipeline
- LLM-as-a-judge framework — learnwithparam.com (March/April 2026)
- Choosing the LLM judge — learnwithparam.com (April 2026)
- Doubly-robust LLM-as-judge — arXiv 2509.22957
- Gemini production-grade LLM-as-judge — gemilab.net
External — observability:
- OpenTelemetry GenAI semantic conventions — opentelemetry.io
- OpenLIT auto-instrumentation — mintlify.com/openlit
- Copilot Chat OTel — code.visualstudio.com/docs/copilot/guides/monitoring-agents
- Agentgateway observability — agentgateway.dev/docs/standalone/main/llm/observability
- LLM agent OpenTelemetry tracing — aivineet.com
- Capture GenAI prompt/completion events — oneuptime.com (Feb 2026)
External — drift / audit patterns:
- Configuration drift auto-remediation (PR-first) — blog.cloudgeni.ai
- Vaultspectre watch (delta reporting) — github.com/ppiankov/vaultspectre
- Semantic drift monitor — blog.trysteakhouse.com
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-088-implementation-session: domain/governance
Tag assignment · dev- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.18 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec 088 Implementation Session.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 088 Implementation Session
Session log for the full implementation of spec 088 (Vault Audit Agent) executed 2026-04-23.
## What Was Built
- `scripts/ci/vault_audit.py` — 1,060-line mechanical audit script (stdlib + PyYAML; no other deps). Validates schema, resolves wikilinks, checks code-refs/spec-refs, extracts code inventories, writes deterministic JSON, applies safe auto-fixes via `--apply`.
- `tools/vault_audit/semantic_drift.py` — standalone semantic drift script using `claude-sonnet-4-6` via Anthropic SDK ≥0.50.0. Batches notes (default 10), retries on rate limits, writes `last_semantic.json`.
- `tests/vault_audit/` — fixture suite: 5 malformed notes, 2 clean notes (including unicode em-dash), 15 claim taxonomy fixtures, mock JSON files, shell test harness with 6 test cases.
- `docs/vault/_meta/audit/` — output directory created; `last_audit.json`, `inventories.json` committed; `audit_run_metadata.json` gitignored.
- Makefile targets: `vault-audit`, `vault-audit-apply`, `vault-audit-semantic`, `vault-audit-test`.
- `introspection` skill updated with exact `gh` command sequence (FR-015), 3-section PR body template (SC-009), idempotency procedure (SC-003).
- AGENTS.md amended with staleness field behavior and rolling PR convention (FR-017).
## Non-Obvious Implementation Discoveries
### 1. Filename Index Must Exclude `_meta/templates/`
The `build_filename_index()` function initially walked all `.md` files in the vault including `_meta/templates/`. This caused false `duplicate_filename` WARN collisions: `Code.md` (folder MOC) vs `_meta/templates/code.md` (template), etc. Fix: add `_meta/` to the exclusion list alongside `.obsidian/`.
### 2. PyYAML Cannot Write Inline Comments
`yaml.dump()` does not support inline comments. FR-008 requires `source: agent # auto-set by vault_audit — verify`. Solution: post-dump string replacement — after `yaml.dump()` generates the YAML string, replace `source: agent\n` with `source: agent # auto-set by vault_audit — verify\n` before writing to disk. Idempotent because the replacement only runs once (exact match on `source: agent\n`).
### 3. Idempotency Requires Three-Key Sort
Two consecutive runs produced different `last_audit.json` files due to non-deterministic ordering of findings with the same `(note_path, rule)` key but different `message` values (e.g., five `missing_required_field` findings on `Sessions/README.md` for different missing fields). Fix: sort key `(note_path, rule, message)` on all finding lists in JSON output and stdout render.
### 4. `--output` Must Override `--no-write`
The idempotency test in `run_tests.sh` passes both `--output /tmp/run.json` and `--no-write`. Original guard `if no_write: return` blocked the `--output` write. Fix: guard changed to `if no_write and output_override is None: return` so `--output` always wins.
### 5. `build_filename_index()` Requires `sorted()`
`Path.rglob()` ordering is filesystem-dependent (non-deterministic on macOS HFS+). The duplicate filename message includes a list of matching paths — if the list order differs between runs, the JSON output differs. Fix: `sorted(vault_root.rglob("*.md"))` in both `build_filename_index()` and the main scan loop.
### 6. Live Vault Has 373 Genuine Errors
Running against `docs/vault/` reveals 373 errors, all genuine (not script false positives):
- ~90 notes in `Art/`, `Discoveries/`, `Sessions/` missing `tags:` field (were written pre-schema enforcement)
- ~70 `Specs/` notes use `status/done` and `status/waiting` — NOT in the controlled vocabulary (vocab has `status/wip`, `status/draft`, `status/reviewed`, `status/canonical`, `status/stale`, `status/superseded`)
- ~210 unresolved wikilinks pointing to notes in planned-but-unbuilt subdirectories (`Systems/Vehicle — Destruction`, `Art/Vehicle — Samba Acceptance Checklist`, etc.)
- 1 broken code-ref: `Code/Tools — Music Pipeline.md` points to `tools/music/` which doesn't exist
The `status/done` and `status/waiting` tags are the most systematic gap — used across ~70 spec stubs from before the vocabulary was formalized. These require either vocabulary extension or bulk tag replacement.
### 7. Claim Taxonomy Fixtures Are Valid Notes
The 15 claim taxonomy fixtures in `tests/vault_audit/fixtures/claim_taxonomy/` all pass schema validation with 0 errors/warnings — they use `type/discovery`, `domain/tooling`, `status/draft`, and valid `source: agent` + `aliases:` fields. This means they can be scanned without fixture-specific exclusion logic.
## Verification Results
| Check | Result |
|---|---|
| Malformed fixtures → exit 1 | ✅ |
| Clean fixtures → exit 0 | ✅ |
| Idempotency (two runs diff) | ✅ byte-identical |
| Performance (201 notes) | ✅ 0.25s (<<30s) |
| SC-005 inventory counts | ✅ signals=35, autoloads=5, gdext=8 |
| FR-008 inline comment | ✅ `# auto-set by vault_audit — verify` written |
| Semantic dry-run | ✅ produces valid batch prompt with real inventories |
| Claim taxonomy fixtures | ✅ all 15 pass schema validation |
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-088-implementation-session: domain/tooling
Tag assignment · dev- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.45 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spec 088 Implementation Session.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 088 Implementation Session
Session log for the full implementation of spec 088 (Vault Audit Agent) executed 2026-04-23.
## What Was Built
- `scripts/ci/vault_audit.py` — 1,060-line mechanical audit script (stdlib + PyYAML; no other deps). Validates schema, resolves wikilinks, checks code-refs/spec-refs, extracts code inventories, writes deterministic JSON, applies safe auto-fixes via `--apply`.
- `tools/vault_audit/semantic_drift.py` — standalone semantic drift script using `claude-sonnet-4-6` via Anthropic SDK ≥0.50.0. Batches notes (default 10), retries on rate limits, writes `last_semantic.json`.
- `tests/vault_audit/` — fixture suite: 5 malformed notes, 2 clean notes (including unicode em-dash), 15 claim taxonomy fixtures, mock JSON files, shell test harness with 6 test cases.
- `docs/vault/_meta/audit/` — output directory created; `last_audit.json`, `inventories.json` committed; `audit_run_metadata.json` gitignored.
- Makefile targets: `vault-audit`, `vault-audit-apply`, `vault-audit-semantic`, `vault-audit-test`.
- `introspection` skill updated with exact `gh` command sequence (FR-015), 3-section PR body template (SC-009), idempotency procedure (SC-003).
- AGENTS.md amended with staleness field behavior and rolling PR convention (FR-017).
## Non-Obvious Implementation Discoveries
### 1. Filename Index Must Exclude `_meta/templates/`
The `build_filename_index()` function initially walked all `.md` files in the vault including `_meta/templates/`. This caused false `duplicate_filename` WARN collisions: `Code.md` (folder MOC) vs `_meta/templates/code.md` (template), etc. Fix: add `_meta/` to the exclusion list alongside `.obsidian/`.
### 2. PyYAML Cannot Write Inline Comments
`yaml.dump()` does not support inline comments. FR-008 requires `source: agent # auto-set by vault_audit — verify`. Solution: post-dump string replacement — after `yaml.dump()` generates the YAML string, replace `source: agent\n` with `source: agent # auto-set by vault_audit — verify\n` before writing to disk. Idempotent because the replacement only runs once (exact match on `source: agent\n`).
### 3. Idempotency Requires Three-Key Sort
Two consecutive runs produced different `last_audit.json` files due to non-deterministic ordering of findings with the same `(note_path, rule)` key but different `message` values (e.g., five `missing_required_field` findings on `Sessions/README.md` for different missing fields). Fix: sort key `(note_path, rule, message)` on all finding lists in JSON output and stdout render.
### 4. `--output` Must Override `--no-write`
The idempotency test in `run_tests.sh` passes both `--output /tmp/run.json` and `--no-write`. Original guard `if no_write: return` blocked the `--output` write. Fix: guard changed to `if no_write and output_override is None: return` so `--output` always wins.
### 5. `build_filename_index()` Requires `sorted()`
`Path.rglob()` ordering is filesystem-dependent (non-deterministic on macOS HFS+). The duplicate filename message includes a list of matching paths — if the list order differs between runs, the JSON output differs. Fix: `sorted(vault_root.rglob("*.md"))` in both `build_filename_index()` and the main scan loop.
### 6. Live Vault Has 373 Genuine Errors
Running against `docs/vault/` reveals 373 errors, all genuine (not script false positives):
- ~90 notes in `Art/`, `Discoveries/`, `Sessions/` missing `tags:` field (were written pre-schema enforcement)
- ~70 `Specs/` notes use `status/done` and `status/waiting` — NOT in the controlled vocabulary (vocab has `status/wip`, `status/draft`, `status/reviewed`, `status/canonical`, `status/stale`, `status/superseded`)
- ~210 unresolved wikilinks pointing to notes in planned-but-unbuilt subdirectories (`Systems/Vehicle — Destruction`, `Art/Vehicle — Samba Acceptance Checklist`, etc.)
- 1 broken code-ref: `Code/Tools — Music Pipeline.md` points to `tools/music/` which doesn't exist
The `status/done` and `status/waiting` tags are the most systematic gap — used across ~70 spec stubs from before the vocabulary was formalized. These require either vocabulary extension or bulk tag replacement.
### 7. Claim Taxonomy Fixtures Are Valid Notes
The 15 claim taxonomy fixtures in `tests/vault_audit/fixtures/claim_taxonomy/` all pass schema validation with 0 errors/warnings — they use `type/discovery`, `domain/tooling`, `status/draft`, and valid `source: agent` + `aliases:` fields. This means they can be scanned without fixture-specific exclusion logic.
## Verification Results
| Check | Result |
|---|---|
| Malformed fixtures → exit 1 | ✅ |
| Clean fixtures → exit 0 | ✅ |
| Idempotency (two runs diff) | ✅ byte-identical |
| Performance (201 notes) | ✅ 0.25s (<<30s) |
| SC-005 inventory counts | ✅ signals=35, autoloads=5, gdext=8 |
| FR-008 inline comment | ✅ `# auto-set by vault_audit — verify` written |
| Semantic dry-run | ✅ produces valid batch prompt with real inventories |
| Claim taxonomy fixtures | ✅ all 15 pass schema validation |
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-088-implementation-session: layer/infra
Tag assignment · dev- Gold
- The note calls for `layer/infra`: Build, CI, deployment infrastructure (True)
- ember
- The note has no reason to carry `layer/infra` (False)
- Signal
- P(true) 0.16 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec 088 Implementation Session.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 088 Implementation Session
Session log for the full implementation of spec 088 (Vault Audit Agent) executed 2026-04-23.
## What Was Built
- `scripts/ci/vault_audit.py` — 1,060-line mechanical audit script (stdlib + PyYAML; no other deps). Validates schema, resolves wikilinks, checks code-refs/spec-refs, extracts code inventories, writes deterministic JSON, applies safe auto-fixes via `--apply`.
- `tools/vault_audit/semantic_drift.py` — standalone semantic drift script using `claude-sonnet-4-6` via Anthropic SDK ≥0.50.0. Batches notes (default 10), retries on rate limits, writes `last_semantic.json`.
- `tests/vault_audit/` — fixture suite: 5 malformed notes, 2 clean notes (including unicode em-dash), 15 claim taxonomy fixtures, mock JSON files, shell test harness with 6 test cases.
- `docs/vault/_meta/audit/` — output directory created; `last_audit.json`, `inventories.json` committed; `audit_run_metadata.json` gitignored.
- Makefile targets: `vault-audit`, `vault-audit-apply`, `vault-audit-semantic`, `vault-audit-test`.
- `introspection` skill updated with exact `gh` command sequence (FR-015), 3-section PR body template (SC-009), idempotency procedure (SC-003).
- AGENTS.md amended with staleness field behavior and rolling PR convention (FR-017).
## Non-Obvious Implementation Discoveries
### 1. Filename Index Must Exclude `_meta/templates/`
The `build_filename_index()` function initially walked all `.md` files in the vault including `_meta/templates/`. This caused false `duplicate_filename` WARN collisions: `Code.md` (folder MOC) vs `_meta/templates/code.md` (template), etc. Fix: add `_meta/` to the exclusion list alongside `.obsidian/`.
### 2. PyYAML Cannot Write Inline Comments
`yaml.dump()` does not support inline comments. FR-008 requires `source: agent # auto-set by vault_audit — verify`. Solution: post-dump string replacement — after `yaml.dump()` generates the YAML string, replace `source: agent\n` with `source: agent # auto-set by vault_audit — verify\n` before writing to disk. Idempotent because the replacement only runs once (exact match on `source: agent\n`).
### 3. Idempotency Requires Three-Key Sort
Two consecutive runs produced different `last_audit.json` files due to non-deterministic ordering of findings with the same `(note_path, rule)` key but different `message` values (e.g., five `missing_required_field` findings on `Sessions/README.md` for different missing fields). Fix: sort key `(note_path, rule, message)` on all finding lists in JSON output and stdout render.
### 4. `--output` Must Override `--no-write`
The idempotency test in `run_tests.sh` passes both `--output /tmp/run.json` and `--no-write`. Original guard `if no_write: return` blocked the `--output` write. Fix: guard changed to `if no_write and output_override is None: return` so `--output` always wins.
### 5. `build_filename_index()` Requires `sorted()`
`Path.rglob()` ordering is filesystem-dependent (non-deterministic on macOS HFS+). The duplicate filename message includes a list of matching paths — if the list order differs between runs, the JSON output differs. Fix: `sorted(vault_root.rglob("*.md"))` in both `build_filename_index()` and the main scan loop.
### 6. Live Vault Has 373 Genuine Errors
Running against `docs/vault/` reveals 373 errors, all genuine (not script false positives):
- ~90 notes in `Art/`, `Discoveries/`, `Sessions/` missing `tags:` field (were written pre-schema enforcement)
- ~70 `Specs/` notes use `status/done` and `status/waiting` — NOT in the controlled vocabulary (vocab has `status/wip`, `status/draft`, `status/reviewed`, `status/canonical`, `status/stale`, `status/superseded`)
- ~210 unresolved wikilinks pointing to notes in planned-but-unbuilt subdirectories (`Systems/Vehicle — Destruction`, `Art/Vehicle — Samba Acceptance Checklist`, etc.)
- 1 broken code-ref: `Code/Tools — Music Pipeline.md` points to `tools/music/` which doesn't exist
The `status/done` and `status/waiting` tags are the most systematic gap — used across ~70 spec stubs from before the vocabulary was formalized. These require either vocabulary extension or bulk tag replacement.
### 7. Claim Taxonomy Fixtures Are Valid Notes
The 15 claim taxonomy fixtures in `tests/vault_audit/fixtures/claim_taxonomy/` all pass schema validation with 0 errors/warnings — they use `type/discovery`, `domain/tooling`, `status/draft`, and valid `source: agent` + `aliases:` fields. This means they can be scanned without fixture-specific exclusion logic.
## Verification Results
| Check | Result |
|---|---|
| Malformed fixtures → exit 1 | ✅ |
| Clean fixtures → exit 0 | ✅ |
| Idempotency (two runs diff) | ✅ byte-identical |
| Performance (201 notes) | ✅ 0.25s (<<30s) |
| SC-005 inventory counts | ✅ signals=35, autoloads=5, gdext=8 |
| FR-008 inline comment | ✅ `# auto-set by vault_audit — verify` written |
| Semantic dry-run | ✅ produces valid batch prompt with real inventories |
| Claim taxonomy fixtures | ✅ all 15 pass schema validation |
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-088-implementation-session: status/reviewed
Tag assignment · dev- Gold
- The note calls for `status/reviewed`: Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `status/reviewed` (False)
- Signal
- P(true) 0.15 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec 088 Implementation Session.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 088 Implementation Session
Session log for the full implementation of spec 088 (Vault Audit Agent) executed 2026-04-23.
## What Was Built
- `scripts/ci/vault_audit.py` — 1,060-line mechanical audit script (stdlib + PyYAML; no other deps). Validates schema, resolves wikilinks, checks code-refs/spec-refs, extracts code inventories, writes deterministic JSON, applies safe auto-fixes via `--apply`.
- `tools/vault_audit/semantic_drift.py` — standalone semantic drift script using `claude-sonnet-4-6` via Anthropic SDK ≥0.50.0. Batches notes (default 10), retries on rate limits, writes `last_semantic.json`.
- `tests/vault_audit/` — fixture suite: 5 malformed notes, 2 clean notes (including unicode em-dash), 15 claim taxonomy fixtures, mock JSON files, shell test harness with 6 test cases.
- `docs/vault/_meta/audit/` — output directory created; `last_audit.json`, `inventories.json` committed; `audit_run_metadata.json` gitignored.
- Makefile targets: `vault-audit`, `vault-audit-apply`, `vault-audit-semantic`, `vault-audit-test`.
- `introspection` skill updated with exact `gh` command sequence (FR-015), 3-section PR body template (SC-009), idempotency procedure (SC-003).
- AGENTS.md amended with staleness field behavior and rolling PR convention (FR-017).
## Non-Obvious Implementation Discoveries
### 1. Filename Index Must Exclude `_meta/templates/`
The `build_filename_index()` function initially walked all `.md` files in the vault including `_meta/templates/`. This caused false `duplicate_filename` WARN collisions: `Code.md` (folder MOC) vs `_meta/templates/code.md` (template), etc. Fix: add `_meta/` to the exclusion list alongside `.obsidian/`.
### 2. PyYAML Cannot Write Inline Comments
`yaml.dump()` does not support inline comments. FR-008 requires `source: agent # auto-set by vault_audit — verify`. Solution: post-dump string replacement — after `yaml.dump()` generates the YAML string, replace `source: agent\n` with `source: agent # auto-set by vault_audit — verify\n` before writing to disk. Idempotent because the replacement only runs once (exact match on `source: agent\n`).
### 3. Idempotency Requires Three-Key Sort
Two consecutive runs produced different `last_audit.json` files due to non-deterministic ordering of findings with the same `(note_path, rule)` key but different `message` values (e.g., five `missing_required_field` findings on `Sessions/README.md` for different missing fields). Fix: sort key `(note_path, rule, message)` on all finding lists in JSON output and stdout render.
### 4. `--output` Must Override `--no-write`
The idempotency test in `run_tests.sh` passes both `--output /tmp/run.json` and `--no-write`. Original guard `if no_write: return` blocked the `--output` write. Fix: guard changed to `if no_write and output_override is None: return` so `--output` always wins.
### 5. `build_filename_index()` Requires `sorted()`
`Path.rglob()` ordering is filesystem-dependent (non-deterministic on macOS HFS+). The duplicate filename message includes a list of matching paths — if the list order differs between runs, the JSON output differs. Fix: `sorted(vault_root.rglob("*.md"))` in both `build_filename_index()` and the main scan loop.
### 6. Live Vault Has 373 Genuine Errors
Running against `docs/vault/` reveals 373 errors, all genuine (not script false positives):
- ~90 notes in `Art/`, `Discoveries/`, `Sessions/` missing `tags:` field (were written pre-schema enforcement)
- ~70 `Specs/` notes use `status/done` and `status/waiting` — NOT in the controlled vocabulary (vocab has `status/wip`, `status/draft`, `status/reviewed`, `status/canonical`, `status/stale`, `status/superseded`)
- ~210 unresolved wikilinks pointing to notes in planned-but-unbuilt subdirectories (`Systems/Vehicle — Destruction`, `Art/Vehicle — Samba Acceptance Checklist`, etc.)
- 1 broken code-ref: `Code/Tools — Music Pipeline.md` points to `tools/music/` which doesn't exist
The `status/done` and `status/waiting` tags are the most systematic gap — used across ~70 spec stubs from before the vocabulary was formalized. These require either vocabulary extension or bulk tag replacement.
### 7. Claim Taxonomy Fixtures Are Valid Notes
The 15 claim taxonomy fixtures in `tests/vault_audit/fixtures/claim_taxonomy/` all pass schema validation with 0 errors/warnings — they use `type/discovery`, `domain/tooling`, `status/draft`, and valid `source: agent` + `aliases:` fields. This means they can be scanned without fixture-specific exclusion logic.
## Verification Results
| Check | Result |
|---|---|
| Malformed fixtures → exit 1 | ✅ |
| Clean fixtures → exit 0 | ✅ |
| Idempotency (two runs diff) | ✅ byte-identical |
| Performance (201 notes) | ✅ 0.25s (<<30s) |
| SC-005 inventory counts | ✅ signals=35, autoloads=5, gdext=8 |
| FR-008 inline comment | ✅ `# auto-set by vault_audit — verify` written |
| Semantic dry-run | ✅ produces valid batch prompt with real inventories |
| Claim taxonomy fixtures | ✅ all 15 pass schema validation |
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-088-implementation-session: type/session-log
Tag assignment · dev- Gold
- The note calls for `type/session-log`: Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/session-log` (False)
- Signal
- P(true) 0.44 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spec 088 Implementation Session.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 088 Implementation Session
Session log for the full implementation of spec 088 (Vault Audit Agent) executed 2026-04-23.
## What Was Built
- `scripts/ci/vault_audit.py` — 1,060-line mechanical audit script (stdlib + PyYAML; no other deps). Validates schema, resolves wikilinks, checks code-refs/spec-refs, extracts code inventories, writes deterministic JSON, applies safe auto-fixes via `--apply`.
- `tools/vault_audit/semantic_drift.py` — standalone semantic drift script using `claude-sonnet-4-6` via Anthropic SDK ≥0.50.0. Batches notes (default 10), retries on rate limits, writes `last_semantic.json`.
- `tests/vault_audit/` — fixture suite: 5 malformed notes, 2 clean notes (including unicode em-dash), 15 claim taxonomy fixtures, mock JSON files, shell test harness with 6 test cases.
- `docs/vault/_meta/audit/` — output directory created; `last_audit.json`, `inventories.json` committed; `audit_run_metadata.json` gitignored.
- Makefile targets: `vault-audit`, `vault-audit-apply`, `vault-audit-semantic`, `vault-audit-test`.
- `introspection` skill updated with exact `gh` command sequence (FR-015), 3-section PR body template (SC-009), idempotency procedure (SC-003).
- AGENTS.md amended with staleness field behavior and rolling PR convention (FR-017).
## Non-Obvious Implementation Discoveries
### 1. Filename Index Must Exclude `_meta/templates/`
The `build_filename_index()` function initially walked all `.md` files in the vault including `_meta/templates/`. This caused false `duplicate_filename` WARN collisions: `Code.md` (folder MOC) vs `_meta/templates/code.md` (template), etc. Fix: add `_meta/` to the exclusion list alongside `.obsidian/`.
### 2. PyYAML Cannot Write Inline Comments
`yaml.dump()` does not support inline comments. FR-008 requires `source: agent # auto-set by vault_audit — verify`. Solution: post-dump string replacement — after `yaml.dump()` generates the YAML string, replace `source: agent\n` with `source: agent # auto-set by vault_audit — verify\n` before writing to disk. Idempotent because the replacement only runs once (exact match on `source: agent\n`).
### 3. Idempotency Requires Three-Key Sort
Two consecutive runs produced different `last_audit.json` files due to non-deterministic ordering of findings with the same `(note_path, rule)` key but different `message` values (e.g., five `missing_required_field` findings on `Sessions/README.md` for different missing fields). Fix: sort key `(note_path, rule, message)` on all finding lists in JSON output and stdout render.
### 4. `--output` Must Override `--no-write`
The idempotency test in `run_tests.sh` passes both `--output /tmp/run.json` and `--no-write`. Original guard `if no_write: return` blocked the `--output` write. Fix: guard changed to `if no_write and output_override is None: return` so `--output` always wins.
### 5. `build_filename_index()` Requires `sorted()`
`Path.rglob()` ordering is filesystem-dependent (non-deterministic on macOS HFS+). The duplicate filename message includes a list of matching paths — if the list order differs between runs, the JSON output differs. Fix: `sorted(vault_root.rglob("*.md"))` in both `build_filename_index()` and the main scan loop.
### 6. Live Vault Has 373 Genuine Errors
Running against `docs/vault/` reveals 373 errors, all genuine (not script false positives):
- ~90 notes in `Art/`, `Discoveries/`, `Sessions/` missing `tags:` field (were written pre-schema enforcement)
- ~70 `Specs/` notes use `status/done` and `status/waiting` — NOT in the controlled vocabulary (vocab has `status/wip`, `status/draft`, `status/reviewed`, `status/canonical`, `status/stale`, `status/superseded`)
- ~210 unresolved wikilinks pointing to notes in planned-but-unbuilt subdirectories (`Systems/Vehicle — Destruction`, `Art/Vehicle — Samba Acceptance Checklist`, etc.)
- 1 broken code-ref: `Code/Tools — Music Pipeline.md` points to `tools/music/` which doesn't exist
The `status/done` and `status/waiting` tags are the most systematic gap — used across ~70 spec stubs from before the vocabulary was formalized. These require either vocabulary extension or bulk tag replacement.
### 7. Claim Taxonomy Fixtures Are Valid Notes
The 15 claim taxonomy fixtures in `tests/vault_audit/fixtures/claim_taxonomy/` all pass schema validation with 0 errors/warnings — they use `type/discovery`, `domain/tooling`, `status/draft`, and valid `source: agent` + `aliases:` fields. This means they can be scanned without fixture-specific exclusion logic.
## Verification Results
| Check | Result |
|---|---|
| Malformed fixtures → exit 1 | ✅ |
| Clean fixtures → exit 0 | ✅ |
| Idempotency (two runs diff) | ✅ byte-identical |
| Performance (201 notes) | ✅ 0.25s (<<30s) |
| SC-005 inventory counts | ✅ signals=35, autoloads=5, gdext=8 |
| FR-008 inline comment | ✅ `# auto-set by vault_audit — verify` written |
| Semantic dry-run | ✅ produces valid batch prompt with real inventories |
| Claim taxonomy fixtures | ✅ all 15 pass schema validation |
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-generation-subsystem-pairs: concept/procedural
Tag assignment · test- Gold
- The note calls for `concept/procedural`: Procedural generation, seed determinism (True)
- ember
- The note has no reason to carry `concept/procedural` (False)
- Signal
- P(true) 0.22 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Code/Generation Subsystem Pairs.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Generation Subsystem Pairs
Every computationally intensive generation subsystem follows a three-layer architecture: a GDScript shell owns scene management, `@export` parameters, and signal wiring; a Rust core module (`rust-core/src/`) implements pure algorithms with zero Godot dependencies; and a GDExtension bridge (`rust/gdext-wrapper/src/`) converts types between the two layers. This note inventories every subsystem pair in the codebase and documents which responsibilities live at which layer.
## The Delegation Pattern
GDScript shells follow a consistent pattern:
```gdscript
var _rust: Object = null
func _init() -> void:
if ClassDB.class_exists("XxxNode"):
_rust = ClassDB.instantiate("XxxNode")
else:
push_warning("[Xxx] XxxNode not found — GDExtension not loaded")
```
The shell holds the Godot reference (`_rust`), calls Rust methods for compute, and handles all node instantiation, scene tree operations, and user-facing property exposure. Fallback paths handle the case where the GDExtension dylib has not been compiled or loaded.
## All Subsystem Pairs
### Terrain Generation
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/terrain_generator.gd` | Terrain3D region creation, Image writes, map updates, boundary/roughness parameter passthrough |
| Rust core | `rust/rust-core/src/terrain.rs` | Noise sampling (fastnoise-lite), heightmap generation, boundary ring computation, roughness fractal noise, center basin depression |
| Bridge | `rust/gdext-wrapper/src/terrain_bridge.rs` | Exposes `TerrainComputeNode` — receives config struct, returns heightmap regions and boundary data |
Also in this group:
- `src/scripts/generation/terrain_texture_painter.gd` — texture painting (pure GDScript, no Rust pair — uses Terrain3D texture API)
- `src/scripts/generation/terrain_setup.gd` — Terrain3D camera binding (pure GDScript, no Rust pair)
- `src/scripts/generation/noise_helper.gd` — noise utility (pure GDScript RefCounted)
### Road Network
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/road_network_generator.gd` | Curve3D extraction, playability validation, debug visualization, @export topology parameters |
| Rust core | `rust/rust-core/src/road/` (10 files) | Hub-and-spoke topology, A* routing on terrain cost grid, intersection management, spline generation, density computation, dead-end elimination |
| Bridge | `rust/gdext-wrapper/src/road_bridge.rs` | Exposes `RoadGeneratorNode` — receives entry/exit data and terrain, returns segment dictionaries with Curve3D-compatible points |
Road subsystem details:
- `rust/rust-core/src/road/generator.rs` — road network generation (highway, county, logging road placement)
- `rust/rust-core/src/road/graph.rs` — graph topology, node connections
- `rust/rust-core/src/road/intersection.rs` — dead-end elimination, leaf-node pruning, connector placement
- `rust/rust-core/src/road/spline.rs` — path-to-spline conversion, bezier curves
- `rust/rust-core/src/road/path_utils.rs` — terrain-aware A* pathfinding
- `rust/rust-core/src/road/mesh_geometry.rs` — road cross-section and mesh vertex data (consumed by GDScript mesh extruder)
- `rust/rust-core/src/road/types.rs` — shared types (RoadSegment, RoadType, NetworkStats)
- `rust/rust-core/src/road/density.rs` — road network density metrics
- `rust/rust-core/src/road/terrain.rs` — terrain slope query helpers for road placement
GDScript companions (no GDExtension bridge — pure Godot API):
- `src/scripts/generation/road_mesh_generator.gd` — mesh extrusion, terrain flattening, hub flattening
- `src/scripts/generation/road_path_utils.gd` — cost grid building, terrain queries, pathfinding aids (pure GDScript RefCounted; the Rust `road/path_utils.rs` provides parallel implementations consumed by the Rust road generator, not delegated from this file)
- `src/scripts/generation/road_prop_activation_manager.gd` — prop visibility culling
### Biome Generation
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/biome_generator.gd` | Config building, biome config Resource loading, callable wrappers for biome lookup/blend queries |
| Rust core | `rust/rust-core/src/biome.rs` | Voronoi cell subdivision, biome assignment with preference weighting, blend factor computation, transition bands |
| Bridge | `rust/gdext-wrapper/src/biome_bridge.rs` | Exposes `BiomeGeneratorNode` — receives world bounds and config, returns cell centers + biome assignments + blend queries |
### Vegetation Scatter
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/vegetation_scatter.gd` | Terrain3D MultiMesh instancer, biome density lookup, tree variant assignment, collision setup |
| Rust core | `rust/rust-core/src/vegetation.rs` | Poisson-disc rejection sampling, road grid construction, spawn exclusion, corridor zone filtering, biome-aware density weighting |
| Bridge | `rust/gdext-wrapper/src/vegetation_bridge.rs` | Exposes methods for generate_tree_positions_only() and full scatter() — receives road centerpoints, config, returns positioned arrays |
Also in this group:
- `src/scripts/generation/tree_collision_manager.gd` — Area3D collision for trees (pure GDScript)
### Exit Points
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/exit_point_manager.gd` | Entry/exit data assembly, position conversion |
| Rust core | `rust/rust-core/src/exit_points.rs` | Edge exit placement with terrain-aware sampling, minimum separation enforcement |
| Bridge | `rust/gdext-wrapper/src/exit_point_bridge.rs` | Exposes `ExitPointManagerNode` — receives world bounds and RNG, returns exit positions |
### Road Structures
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/road_structure_scatter.gd` | Scene instantiation, biome tinting, collision setup, visibility management |
| Rust core | `rust/rust-core/src/structures.rs` | Candidate generation, apex detection, spacing filter, type assignment (power poles, signs, derelict vehicles, env props), prop position generation |
| Bridge | `rust/gdext-wrapper/src/structures_bridge.rs` | Exposes methods for structure/prop position generation — receives road sample data and config, returns candidate arrays |
Note: Only candidate computation is in Rust. Curve3D sampling, Terrain3D height queries, and scene instantiation remain in GDScript.
### Weather Zones
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/systems/weather_zone_system.gd` | Zone scene nodes, particle systems, weather state transitions |
| Rust core | `rust/rust-core/src/weather/` (10 files) | Zone placement, biome hysteresis, state machine, front propagation, fog density, exposure computation, traction/wetness modeling |
| Bridge | `rust/gdext-wrapper/src/weather_zones_bridge.rs`, `weather_simulator_bridge.rs`, `weather_biome_hysteresis_bridge.rs`, `weather_exposure_bridge.rs`, `weather_fog_bridge.rs`, `weather_traction_bridge.rs`, `weather_wetness_bridge.rs` | 7 bridge files exposing `WeatherZoneNode`, `WeatherSimulatorNode`, and per-component nodes |
### Autoload Delegation (GameState + Disturbance)
These are not generation subsystems but follow the same thin-shell pattern:
| Layer | GDScript | Rust Core | Bridge |
|---|---|---|---|
| GameState | `src/autoloads/game_state.gd` — backing variables, enum definitions, signal emission, `generate_seed()` | `rust/rust-core/src/game_state.rs` — change-detection callbacks, default values | `rust/gdext-wrapper/src/game_state_bridge.rs` — `GameStateNode` with validator methods |
| Disturbance | `src/autoloads/disturbance.gd` — `@export` thresholds, signal wiring, `add()` wrapper | `rust/rust-core/src/disturbance.rs` — arithmetic, clamping, threshold-crossing detection | `rust/gdext-wrapper/src/disturbance_bridge.rs` — `DisturbanceNode` with `add()` returning old/new level + state |
Key constraint for all three autoloads `Events`, `GameState`, `Disturbance`: `class_name` must never appear on an autoload script — the engine registers autoloads under their configured singleton name and a matching `class_name` produces duplicate registration conflicts.
## Summary Table
| Subsystem | GDScript Shell | Rust Core Module | Bridge File(s) | Rust Migration |
|---|---|---|---|---|
| Terrain | `terrain_generator.gd` | `terrain.rs` | `terrain_bridge.rs` | Complete |
| Road Network | `road_network_generator.gd` | `road/` (10 files) | `road_bridge.rs` | Complete |
| Biome | `biome_generator.gd` | `biome.rs` | `biome_bridge.rs` | Complete |
| Vegetation | `vegetation_scatter.gd` | `vegetation.rs` | `vegetation_bridge.rs` | Complete (candidates) |
| Exit Points | `exit_point_manager.gd` | `exit_points.rs` | `exit_point_bridge.rs` | Complete |
| Structures | `road_structure_scatter.gd` | `structures.rs` | `structures_bridge.rs` | Partial (candidates only) |
| Weather Zones | `weather_zone_system.gd` | `weather/` (10 files) | 7 bridge files | Partial (zones + simulator complete, per-system in progress) |
| GameState | `game_state.gd` (autoload) | `game_state.rs` | `game_state_bridge.rs` | Complete |
| Disturbance | `disturbance.gd` (autoload) | `disturbance.rs` | `disturbance_bridge.rs` | Complete |
## Related
- [[World — Procedural Generation]] — the pipeline these subsystems implement
- [[Rust — Core]] — the engine-agnostic core layer
- [[Rust — GDExt Wrapper]] — the bridge layer
- [[Code]] — folder MOC
- [[World — Domain MOC]] — domain MOC
- [[Constitution]] — Article III (Rust-first), Article IV (seed determinism)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-generation-subsystem-pairs: domain/world
Tag assignment · test- Gold
- The note calls for `domain/world`: World model, biomes, terrain, procedural generation, weather (True)
- ember
- The note has no reason to carry `domain/world` (False)
- Signal
- P(true) 0.34 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Code/Generation Subsystem Pairs.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Generation Subsystem Pairs
Every computationally intensive generation subsystem follows a three-layer architecture: a GDScript shell owns scene management, `@export` parameters, and signal wiring; a Rust core module (`rust-core/src/`) implements pure algorithms with zero Godot dependencies; and a GDExtension bridge (`rust/gdext-wrapper/src/`) converts types between the two layers. This note inventories every subsystem pair in the codebase and documents which responsibilities live at which layer.
## The Delegation Pattern
GDScript shells follow a consistent pattern:
```gdscript
var _rust: Object = null
func _init() -> void:
if ClassDB.class_exists("XxxNode"):
_rust = ClassDB.instantiate("XxxNode")
else:
push_warning("[Xxx] XxxNode not found — GDExtension not loaded")
```
The shell holds the Godot reference (`_rust`), calls Rust methods for compute, and handles all node instantiation, scene tree operations, and user-facing property exposure. Fallback paths handle the case where the GDExtension dylib has not been compiled or loaded.
## All Subsystem Pairs
### Terrain Generation
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/terrain_generator.gd` | Terrain3D region creation, Image writes, map updates, boundary/roughness parameter passthrough |
| Rust core | `rust/rust-core/src/terrain.rs` | Noise sampling (fastnoise-lite), heightmap generation, boundary ring computation, roughness fractal noise, center basin depression |
| Bridge | `rust/gdext-wrapper/src/terrain_bridge.rs` | Exposes `TerrainComputeNode` — receives config struct, returns heightmap regions and boundary data |
Also in this group:
- `src/scripts/generation/terrain_texture_painter.gd` — texture painting (pure GDScript, no Rust pair — uses Terrain3D texture API)
- `src/scripts/generation/terrain_setup.gd` — Terrain3D camera binding (pure GDScript, no Rust pair)
- `src/scripts/generation/noise_helper.gd` — noise utility (pure GDScript RefCounted)
### Road Network
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/road_network_generator.gd` | Curve3D extraction, playability validation, debug visualization, @export topology parameters |
| Rust core | `rust/rust-core/src/road/` (10 files) | Hub-and-spoke topology, A* routing on terrain cost grid, intersection management, spline generation, density computation, dead-end elimination |
| Bridge | `rust/gdext-wrapper/src/road_bridge.rs` | Exposes `RoadGeneratorNode` — receives entry/exit data and terrain, returns segment dictionaries with Curve3D-compatible points |
Road subsystem details:
- `rust/rust-core/src/road/generator.rs` — road network generation (highway, county, logging road placement)
- `rust/rust-core/src/road/graph.rs` — graph topology, node connections
- `rust/rust-core/src/road/intersection.rs` — dead-end elimination, leaf-node pruning, connector placement
- `rust/rust-core/src/road/spline.rs` — path-to-spline conversion, bezier curves
- `rust/rust-core/src/road/path_utils.rs` — terrain-aware A* pathfinding
- `rust/rust-core/src/road/mesh_geometry.rs` — road cross-section and mesh vertex data (consumed by GDScript mesh extruder)
- `rust/rust-core/src/road/types.rs` — shared types (RoadSegment, RoadType, NetworkStats)
- `rust/rust-core/src/road/density.rs` — road network density metrics
- `rust/rust-core/src/road/terrain.rs` — terrain slope query helpers for road placement
GDScript companions (no GDExtension bridge — pure Godot API):
- `src/scripts/generation/road_mesh_generator.gd` — mesh extrusion, terrain flattening, hub flattening
- `src/scripts/generation/road_path_utils.gd` — cost grid building, terrain queries, pathfinding aids (pure GDScript RefCounted; the Rust `road/path_utils.rs` provides parallel implementations consumed by the Rust road generator, not delegated from this file)
- `src/scripts/generation/road_prop_activation_manager.gd` — prop visibility culling
### Biome Generation
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/biome_generator.gd` | Config building, biome config Resource loading, callable wrappers for biome lookup/blend queries |
| Rust core | `rust/rust-core/src/biome.rs` | Voronoi cell subdivision, biome assignment with preference weighting, blend factor computation, transition bands |
| Bridge | `rust/gdext-wrapper/src/biome_bridge.rs` | Exposes `BiomeGeneratorNode` — receives world bounds and config, returns cell centers + biome assignments + blend queries |
### Vegetation Scatter
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/vegetation_scatter.gd` | Terrain3D MultiMesh instancer, biome density lookup, tree variant assignment, collision setup |
| Rust core | `rust/rust-core/src/vegetation.rs` | Poisson-disc rejection sampling, road grid construction, spawn exclusion, corridor zone filtering, biome-aware density weighting |
| Bridge | `rust/gdext-wrapper/src/vegetation_bridge.rs` | Exposes methods for generate_tree_positions_only() and full scatter() — receives road centerpoints, config, returns positioned arrays |
Also in this group:
- `src/scripts/generation/tree_collision_manager.gd` — Area3D collision for trees (pure GDScript)
### Exit Points
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/exit_point_manager.gd` | Entry/exit data assembly, position conversion |
| Rust core | `rust/rust-core/src/exit_points.rs` | Edge exit placement with terrain-aware sampling, minimum separation enforcement |
| Bridge | `rust/gdext-wrapper/src/exit_point_bridge.rs` | Exposes `ExitPointManagerNode` — receives world bounds and RNG, returns exit positions |
### Road Structures
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/generation/road_structure_scatter.gd` | Scene instantiation, biome tinting, collision setup, visibility management |
| Rust core | `rust/rust-core/src/structures.rs` | Candidate generation, apex detection, spacing filter, type assignment (power poles, signs, derelict vehicles, env props), prop position generation |
| Bridge | `rust/gdext-wrapper/src/structures_bridge.rs` | Exposes methods for structure/prop position generation — receives road sample data and config, returns candidate arrays |
Note: Only candidate computation is in Rust. Curve3D sampling, Terrain3D height queries, and scene instantiation remain in GDScript.
### Weather Zones
| Layer | File | Responsibility |
|---|---|---|
| GDScript shell | `src/scripts/systems/weather_zone_system.gd` | Zone scene nodes, particle systems, weather state transitions |
| Rust core | `rust/rust-core/src/weather/` (10 files) | Zone placement, biome hysteresis, state machine, front propagation, fog density, exposure computation, traction/wetness modeling |
| Bridge | `rust/gdext-wrapper/src/weather_zones_bridge.rs`, `weather_simulator_bridge.rs`, `weather_biome_hysteresis_bridge.rs`, `weather_exposure_bridge.rs`, `weather_fog_bridge.rs`, `weather_traction_bridge.rs`, `weather_wetness_bridge.rs` | 7 bridge files exposing `WeatherZoneNode`, `WeatherSimulatorNode`, and per-component nodes |
### Autoload Delegation (GameState + Disturbance)
These are not generation subsystems but follow the same thin-shell pattern:
| Layer | GDScript | Rust Core | Bridge |
|---|---|---|---|
| GameState | `src/autoloads/game_state.gd` — backing variables, enum definitions, signal emission, `generate_seed()` | `rust/rust-core/src/game_state.rs` — change-detection callbacks, default values | `rust/gdext-wrapper/src/game_state_bridge.rs` — `GameStateNode` with validator methods |
| Disturbance | `src/autoloads/disturbance.gd` — `@export` thresholds, signal wiring, `add()` wrapper | `rust/rust-core/src/disturbance.rs` — arithmetic, clamping, threshold-crossing detection | `rust/gdext-wrapper/src/disturbance_bridge.rs` — `DisturbanceNode` with `add()` returning old/new level + state |
Key constraint for all three autoloads `Events`, `GameState`, `Disturbance`: `class_name` must never appear on an autoload script — the engine registers autoloads under their configured singleton name and a matching `class_name` produces duplicate registration conflicts.
## Summary Table
| Subsystem | GDScript Shell | Rust Core Module | Bridge File(s) | Rust Migration |
|---|---|---|---|---|
| Terrain | `terrain_generator.gd` | `terrain.rs` | `terrain_bridge.rs` | Complete |
| Road Network | `road_network_generator.gd` | `road/` (10 files) | `road_bridge.rs` | Complete |
| Biome | `biome_generator.gd` | `biome.rs` | `biome_bridge.rs` | Complete |
| Vegetation | `vegetation_scatter.gd` | `vegetation.rs` | `vegetation_bridge.rs` | Complete (candidates) |
| Exit Points | `exit_point_manager.gd` | `exit_points.rs` | `exit_point_bridge.rs` | Complete |
| Structures | `road_structure_scatter.gd` | `structures.rs` | `structures_bridge.rs` | Partial (candidates only) |
| Weather Zones | `weather_zone_system.gd` | `weather/` (10 files) | 7 bridge files | Partial (zones + simulator complete, per-system in progress) |
| GameState | `game_state.gd` (autoload) | `game_state.rs` | `game_state_bridge.rs` | Complete |
| Disturbance | `disturbance.gd` (autoload) | `disturbance.rs` | `disturbance_bridge.rs` | Complete |
## Related
- [[World — Procedural Generation]] — the pipeline these subsystems implement
- [[Rust — Core]] — the engine-agnostic core layer
- [[Rust — GDExt Wrapper]] — the bridge layer
- [[Code]] — folder MOC
- [[World — Domain MOC]] — domain MOC
- [[Constitution]] — Article III (Rust-first), Article IV (seed determinism)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-anomalies: type/design
Tag assignment · test- Gold
- The note calls for `type/design`: Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`) (True)
- ember
- The note has no reason to carry `type/design` (False)
- Signal
- P(true) 0.43 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Design/Anomalies.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Anomalies
The seven anomalies of Old Growth with their authoritative mechanical specifications, plus the framework for *what moves in the wood* (which is NOT a creature system), world-generation anomaly placement rules, and per-biome weather. The World Bible describes anomalies phenomenologically; this note captures the GDD's precise values alongside the tonal framing. Values marked `[PENDING]` require playtesting.
## Overview
The forest manifests seven distinct phenomena. They were happening before the first vehicle entered the tree line; they will happen after the last one is incorporated. Each anomaly has a visual signature, an audio signature, a disturbance cost, and a damage profile — but the design intent is that the player should learn these signatures *empirically*, not from a manual.
## Design Intent
Anomalies are the condition at concentration. Each one is a design decision about what rule of the world is breaking. Per Article XII-J: dread over shock, the unknown, the uncanny valley. Anomalies are *almost-natural before they become overtly strange*.
**Important**: the AI system previously specified for "Hollows" is **removed from canon**. There are no spawnable enemy units, no health pools for targets, no combat loop. What the player perceives moving in the wood is the condition at a different concentration — present, attending, without form that can be reliably described or counted. See "What Moves in the Wood" below.
## Key Decisions
### Deadfall
A tree falls across the road. The angle is wrong for wind; the break point is too low for rot; the timing is too precise for coincidence.
| Property | Value |
|---|---|
| Trigger distance | 30m vehicle approach |
| Warning audio | 3-second pre-fall creak |
| Fall duration | 2 seconds |
| Road clearance | Full width blocked |
| Samba collision damage | 25 HP at full impact |
| Off-road bypass | Available, terrain-dependent |
| Disturbance cost | +2 on trigger |
Avoidable by hard braking before 10m or by off-road bypass. The tree remains blocking for the remainder of the run.
### Spore Clouds
Drifting bioluminescent matter. Inside the bus reads as weather. Outside it is different — the cough comes later.
| Property | Value |
|---|---|
| Cloud diameter | 20–40m |
| Movement | Slow drift, unpredictable direction |
| Windshield full coating | 4s of contact |
| Wiper effectiveness through cloud | 30% |
| On-foot vision reduction | 60% |
| On-foot exposure gain | +5 / 10s |
| Persistent windshield coating after exit | clears over 30s with wipers |
### Hollow Trees
A blackened husk that should have fallen decades ago. Instruments drift; the engine loses conviction.
| Property | Value |
|---|---|
| Detection radius (instruments) | 20m |
| Detection radius (engine power) | 10m |
| Engine power reduction | −40% |
| Near-failure radius | 5m |
| Visual signature | Blackened husk, 15–20m tall, no canopy |
| Light behavior | Absorbs, does not reflect |
| Disturbance cost | None — passive |
Condition concentration is suppressed within 30m of a Hollow Tree. The interference field disrupts the condition's local expression *as well as* the bus's instruments.
### Amber Bleed
Amber light at trail edges, in hollows, in clearings. Identical to exit lanterns in appearance — but without the gate, without the wind-chime audio, without the road.
| Property | Value |
|---|---|
| Visual | Identical to exit lanterns |
| Audio signature | None — no wind chime |
| Physical structure | None — no gate frame |
| Road placement | Off-road, in clearings, at trail edges |
| Distinguishing features | No gate structure, no road surface, no wind chime |
Not a direct-damage hazard. It is a **navigation hazard**. Following it into deep terrain without a gate structure wastes fuel and time and feeds disturbance.
### Mycelium Network
White threads covering the ground. They have already found.
| Property | Value |
|---|---|
| Thread visibility | Clearly visible |
| Contact trigger | Any vehicle contact |
| Chain propagation | Instant across whole network |
| Spore burst radius | 15m from contact |
| Samba exterior damage | −10 HP |
| On-foot exposure gain | +10 per contact |
| Disturbance cost | +4 per contact |
| Thread regrowth | None within the run |
Spore burst damages exterior panels only. Interior contamination from seeping spores accumulates in persistent state across runs.
### Petrified Zones
Active calcification. Responds to stillness.
| Property | Value |
|---|---|
| Zone radius | 30–80m (varies) |
| Vehicle minimum speed | 30 km/h |
| Calcification onset | <30 km/h |
| Symptoms | Heavy steering, throttle resistance, grey particulate on seams |
| Engine seizure threshold | 0 km/h for >5s |
| Engine seizure effect | Permanent for that run |
| On-foot petrification | Stationary >3s → death |
| Visual indicator | Grey particulate at zone boundary |
An engine-seized bus cannot be repaired in the field. The player must abandon the bus and reach an exit on foot — a death sentence in most circumstances, intended as such. On-foot survival requires continuous movement; crouch-walking (2 m/s) is sufficient and recommended (silent + slow + above petrification threshold).
### The Silence
All environmental sound ceases. What remains is the player's own heartbeat.
| Property | Value |
|---|---|
| Zone radius | 40m |
| Audio effect | All environmental sound ceases — heartbeat only |
| Condition visibility | Zero |
| Presence audio | Zero |
| Duration | Permanent per run |
| Warning | Gradual audio fade over 5m approach, then instant silence |
| Disturbance cost | None — passive |
The only warning of presence is physical contact. Traverse in the bus at maximum speed. On foot, extremely dangerous — no warning system at all.
### What Moves in the Wood
**Canon position**: the wood produces nothing. The condition does not generate creatures, spawn enemies, or deploy outputs. What the player perceives moving in the wood is the condition at a different concentration — present, attending, without form that can be reliably described or counted.
Whether what moves in the wood is physical is a design question that remains deliberately open. Presence and physicality are not the same thing. The condition has been running long enough that the distinction has been resolved in a direction that does not map to anything the player was taught before entering this watershed.
**Mechanical implications**: the AI system previously specified for "Hollows" is **removed from canon**. There are no spawnable enemy units, no health pools for targets, no combat loop. What replaces it is environmental — the condition's presence is communicated through audio, disturbance-state escalation, instrument behavior, and the player's own unreliable perception at high exposure. The threat is the condition, not things the condition sends.
**On-foot contact (retained)**: at high disturbance and in anomaly zones, the player may experience contact events.
| Contact Type | Effect |
|---|---|
| Glancing contact | −10 HP, stumble (noise event +15m radius) |
| Sustained contact | −15 HP/sec continuous, movement restricted |
| Escape | Sprint input, −30 stamina, +20m noise event |
The source of contact is not rendered as a discrete entity. Implementation approach `[PENDING — requires dedicated spec for environmental presence system]`.
**The Silence zone contact (retained)**: inside The Silence, contact events occur without visual or audio warning. The heartbeat is the only instrument. This behavior is preserved — the mechanism of contact is not a creature but the condition expressing itself at maximum concentration in a zone where all other sensory input has been removed.
### Anomaly Placement (World Generation Rules)
| Anomaly | Biomes | Min per World | Max per World |
|---|---|---|---|
| Deadfall | Verdant, Fungal | 2 | 6 |
| Spore Clouds | Fungal | 3 | 8 |
| Hollow Trees | All | 1 | 4 |
| Amber Bleed | All | 2 | 5 |
| Mycelium Network | Fungal | 2 | 5 |
| Petrified Zones | Petrifold | 2 | 4 |
| The Silence | Petrifold | 1 | 2 |
Anomalies do not spawn within 50m of the garage (crater center). Amber Bleed clusters are placed to create navigational ambiguity near exit zones.
### Weather Systems
**Verdant Old Growth weather**:
| Condition | Probability | Effect |
|---|---|---|
| Rain | 60% | Wipers required, wet road physics |
| Fog | 20% | Visibility reduction, headlights scatter |
| Clear | 20% | Optimal visibility; slight disturbance cost for headlights |
**Fungal Depths weather**:
| Condition | Probability | Effect |
|---|---|---|
| Spore haze | 100% | Permanent. Reduces effective headlight range. No rain. |
**Petrifold weather**:
| Condition | Probability | Effect |
|---|---|---|
| Still | 100% | No wind. No rain. No weather events of any kind. |
**Winter conditions** (Verdant and transitional zones) — apply as a world modifier, not a weather event. Some seeds generate winter worlds.
| Condition | Effect |
|---|---|
| Snow accumulation | Terrain traction reduced |
| Black ice | Intermittent patches, severe traction loss |
| Frozen bridges | Complete traction loss, potential sliding |
| Blizzard | Visibility <10m, on-foot cold exposure −5 HP/sec |
**Road surface physics**:
| Surface | Handling |
|---|---|
| Dry | Baseline |
| Wet | −20% traction |
| Muddy | −35% traction |
| Snow | −40% traction |
| Ice | −70% traction |
| Frozen bridge | −85% traction |
### World Generation — Seed and Parameters
Each run initialized with a player-selected seed from the garage wall map (numeric identifiers). Same seed always produces same world geometry, biome distribution, structure placement, anomaly positions. Condition concentration zones are seeded but vary in expression each run.
| Parameter | Value |
|---|---|
| World size | 3072m × 3072m (~9.4 km²; expanded during Phase 6 development) |
| Boundary | Crater rim — the eroded wall of the impact basin (see [[The Crater]]) |
| Entry point | Garage at crater center (the vertex) |
| Exit corridors | 1–3 breach points in the crater rim (edges of the geometry; fold back to garage) |
| Structures per world | 15–20 |
| Biome count | 1–3 (all three always present above minimum area threshold) |
**Road network** procedurally generated within each seed. All roads navigable by the Samba under normal conditions. Surface type affects handling and noise.
| Surface | Handling | Noise (engine) | Notes |
|---|---|---|---|
| Gravel | Standard | +15% | Stones audible against undercarriage |
| Asphalt (degraded) | Good | Standard | Rare, near older structures |
| Dirt | Reduced traction | Standard | Common in Fungal Depths |
| Mud | Poor traction | Standard | Rain conditions |
| Ice / snow | Severe reduction | Standard | Winter conditions |
**Biome distribution rules**:
- Verdant Old Growth always contains the entry point; the player begins each run from the garage at the crater center (see The Crater).
- Petrifold always the deepest biome — furthest average distance from entry.
- Fungal Depths occupies the middle zone.
- All three biomes must be present above minimum area thresholds.
- Exit gates may appear in any biome.
**Required generator QA** (run per seed before delivery): garage connects to ≥1 exit corridor via driveable road; no impassable dead ends without off-road bypass; minimum road density per biome zone; anomaly placement clearance from garage (50m min); exit gate placement clearance from Storm spawn (must be accessible).
## How It Appears In-Game
The player learns anomaly signatures visually and aurally. Audio warnings precede visual contact for most anomalies. Visual language is consistent so that the forest teaches its own grammar (Article XII-G). The [[Biomes]] section documents which anomalies are most common in which biome.
## Related
- [[The Crater]] — the origin event that created the conditions for anomalous phenomena
- [[The Temporal Fracture]] — anomalies as temporal layer bleed-through
- [[World Model]] — the structural context
- [[Biomes]] — where each anomaly concentrates
- [[Tone and Horror]] — the phenomenological framing
- [[Disturbance System]] — anomaly costs feed disturbance
- [[Core Mechanics]] — how anomalies interact with the run loop
- [[Anomalies]] (domain MOC) — all anomaly-tagged notes
- [[Constitution]] — Article XII-J (horror design)
## Agent Notes (backlinks)
- [[Anomalies and Combat — Placeholder Status]] — Current implementation status: placeholder only, specs written but no code, signal infrastructure ready
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-pre-commit-hook-unpublished-asset-block-is-full-tree-not-staged-file: status/draft
Tag assignment · dev- Gold
- The note calls for `status/draft`: Authoring in progress, not yet verified (True)
- ember
- The note has no reason to carry `status/draft` (False)
- Signal
- P(true) 0.08 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Discoveries/Pre-Commit Hook — Unpublished Asset Block is Full-Tree, Not Staged-File.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Pre-Commit Hook — Unpublished Asset Block is Full-Tree, Not Staged-File
{During session 2026-06-06, attempting to commit an unrelated file (`docs/vault/.obsidian/graph.json`) was blocked by the pre-commit hook because texture files in `src/assets/` had been modified but not published to R2. The unpublished-assets check is a full-tree scan — not scoped to what's staged — which means any unshipped asset change blocks ALL commits on the branch.}
The pre-commit hook at `scripts/hooks/pre-commit` contains two separate asset-related checks with different scopes:
**Check 1 — Manifest staleness (line 80–136)**: Only runs when `manifest.yaml` is staged. Validates every entry in the staged manifest against the files on disk. If a file's SHA256 has changed, it rejects the commit with advice to run `publish.sh`.
**Check 2 — Unpublished assets (line 138–197)**: Always runs on every commit (when Python3 and `manifest.yaml` are available). It walks the entire `src/assets/` directory tree via `os.walk()`, reads the SHA256 of every file, and compares against the manifest. Any file that is either new or has a changed SHA256 is reported as "unpublished" — and the commit is blocked.
The critical behavioral detail: **Check 2 is not scoped to staged files**. It scans all files on disk under `src/assets/`, regardless of whether they are part of the current commit. This means:
- You can be working on a pure GDScript change
- A teammate's texture modifications are synced to your working tree (via a submodule update or LFS pull)
- Your commit of a single unrelated file is blocked
- The only resolution (short of `--no-verify`) is to publish the assets or revert the file changes
The check uses exit code 2 as an intermediate "warning" signal, but the bash wrapper treats both exit codes 1 and 2 as failures (line 189: `if [ $PUBLISH_EXIT -eq 2 ] || [ $PUBLISH_EXIT -eq 1 ]`), so there is no way to acknowledge and proceed — it is a hard block.
This is by design (FR-019: manifest integrity enforcement), but it's a non-obvious constraint for developers who are not working on assets. The only graceful path is `make publish-assets && git add src/assets/manifest.yaml`, which publishes and updates the manifest, clearing the block for all future commits.
## References
- `scripts/hooks/pre-commit` (lines 138–197)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-governance-component-semver-policy: type/principle
Tag assignment · test- Gold
- The note has no reason to carry `type/principle` (False)
- ember
- The note calls for `type/principle`: Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`) (True)
- Signal
- P(true) 0.66 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Governance/Component SemVer Policy.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Component SemVer Policy
The canonical policy for component semantic versioning in Old Growth. Every first-party code file under `src/` is mapped to a component with its own SemVer. When files in a component change, the component's version MUST bump. CI (`make semver`) rejects PRs that violate this rule. This note operationalizes constitution Article III Rule 15.
## Overview
Semantic versioning in Old Growth operates at the **component level**, not the project level. A component is a bounded set of file paths under `src/` (e.g., all autoloads, all audio scripts, the terrain subsystem). The mapping and versions live in `src/component_versions.json`. CI enforces that every changed file belongs to a component whose version has been strictly bumped.
The single project-level version (game version) is tracked separately in `src/project.godot` `config/version=` and governed by different rules (see constitution Governance / Commit Strategy).
## Key Rules
### Scope
- Versioned files are first-party code under `src/`
- Vendored plugins under `src/addons/` are **excluded** (they have their own upstream versioning)
- Component definitions and versions live in `src/component_versions.json`
### Strict bump rule
If any file mapped to a component changes in a PR, that component's version MUST be bumped.
| Bump level | When |
|------------|------|
| **major** | Breaking API or contract change |
| **minor** | Backward-compatible feature addition |
| **patch** | Backward-compatible fix or refactor |
CI fails when a changed component does not have a strictly greater version than baseline (the merge-base with `main`).
### Registry format
`src/component_versions.json` structure:
- `schema_version` — integer registry schema version
- `components.<component_name>.version` — `MAJOR.MINOR.PATCH`
- `components.<component_name>.paths` — list of path prefixes mapped to that component
Example:
```json
{
"schema_version": 1,
"components": {
"core-autoloads": {
"version": "0.1.0",
"paths": ["src/autoloads/"]
}
}
}
```
### Non-overlapping paths
A changed file must map to **exactly one** component. Overlapping paths are an error. Unmapped files are an error.
## Developer Workflow
1. Change files in a component.
2. Bump that component's version in `src/component_versions.json`.
3. Open a PR.
4. CI validates that every changed component has a strictly greater SemVer.
### Auto-bump via pre-commit hook
The pre-commit hook auto-bumps patch versions for components whose files have changed, if the developer forgot. This catches the common case. More significant bumps (minor/major) must be applied manually with justification.
### Local testing
- `make semver` — runs the same SemVer guard used in CI
- `make pre-commit` — runs semver + other fast checks
- `make all` — full CI-like pipeline
## Adding a New Component
1. Add a new `components.<name>` entry in `src/component_versions.json`
2. Set initial SemVer (recommended `0.1.0`)
3. Add one or more non-overlapping path prefixes under `paths`
4. Ensure paths do not point to `src/addons/`
## Common CI Failures
- **Unmapped files**: changed first-party files not covered by any component path
- **Ambiguous mapping**: a changed file matches multiple component paths
- **Missing bump**: component changed but version did not increase
- **Invalid SemVer**: version is not `MAJOR.MINOR.PATCH`
## Related
- [[Constitution]] — Article III Rule 15 is the authoritative source
- [[Build System]] — `make semver` and the CI pipeline
- [[Governance]] — folder MOC
- [[Tech Stack Review]] — what components exist and why
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-governance-component-semver-policy: type/reference
Tag assignment · test- Gold
- The note calls for `type/reference`: Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`) (True)
- ember
- The note has no reason to carry `type/reference` (False)
- Signal
- P(true) 0.02 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Governance/Component SemVer Policy.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Component SemVer Policy
The canonical policy for component semantic versioning in Old Growth. Every first-party code file under `src/` is mapped to a component with its own SemVer. When files in a component change, the component's version MUST bump. CI (`make semver`) rejects PRs that violate this rule. This note operationalizes constitution Article III Rule 15.
## Overview
Semantic versioning in Old Growth operates at the **component level**, not the project level. A component is a bounded set of file paths under `src/` (e.g., all autoloads, all audio scripts, the terrain subsystem). The mapping and versions live in `src/component_versions.json`. CI enforces that every changed file belongs to a component whose version has been strictly bumped.
The single project-level version (game version) is tracked separately in `src/project.godot` `config/version=` and governed by different rules (see constitution Governance / Commit Strategy).
## Key Rules
### Scope
- Versioned files are first-party code under `src/`
- Vendored plugins under `src/addons/` are **excluded** (they have their own upstream versioning)
- Component definitions and versions live in `src/component_versions.json`
### Strict bump rule
If any file mapped to a component changes in a PR, that component's version MUST be bumped.
| Bump level | When |
|------------|------|
| **major** | Breaking API or contract change |
| **minor** | Backward-compatible feature addition |
| **patch** | Backward-compatible fix or refactor |
CI fails when a changed component does not have a strictly greater version than baseline (the merge-base with `main`).
### Registry format
`src/component_versions.json` structure:
- `schema_version` — integer registry schema version
- `components.<component_name>.version` — `MAJOR.MINOR.PATCH`
- `components.<component_name>.paths` — list of path prefixes mapped to that component
Example:
```json
{
"schema_version": 1,
"components": {
"core-autoloads": {
"version": "0.1.0",
"paths": ["src/autoloads/"]
}
}
}
```
### Non-overlapping paths
A changed file must map to **exactly one** component. Overlapping paths are an error. Unmapped files are an error.
## Developer Workflow
1. Change files in a component.
2. Bump that component's version in `src/component_versions.json`.
3. Open a PR.
4. CI validates that every changed component has a strictly greater SemVer.
### Auto-bump via pre-commit hook
The pre-commit hook auto-bumps patch versions for components whose files have changed, if the developer forgot. This catches the common case. More significant bumps (minor/major) must be applied manually with justification.
### Local testing
- `make semver` — runs the same SemVer guard used in CI
- `make pre-commit` — runs semver + other fast checks
- `make all` — full CI-like pipeline
## Adding a New Component
1. Add a new `components.<name>` entry in `src/component_versions.json`
2. Set initial SemVer (recommended `0.1.0`)
3. Add one or more non-overlapping path prefixes under `paths`
4. Ensure paths do not point to `src/addons/`
## Common CI Failures
- **Unmapped files**: changed first-party files not covered by any component path
- **Ambiguous mapping**: a changed file matches multiple component paths
- **Missing bump**: component changed but version did not increase
- **Invalid SemVer**: version is not `MAJOR.MINOR.PATCH`
## Related
- [[Constitution]] — Article III Rule 15 is the authoritative source
- [[Build System]] — `make semver` and the CI pipeline
- [[Governance]] — folder MOC
- [[Tech Stack Review]] — what components exist and why
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spawn-collision-mirror-mesh-visibility-and-glb-node-discovery-jun-06-2026: type/code
Tag assignment · test- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.71 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spawn Collision, Mirror Mesh Visibility, and GLB Node Discovery — Jun 06 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spawn Collision, Mirror Mesh Visibility, and GLB Node Discovery — Jun 06 2026
Spawn-drop collision still damages panels despite the Spec 156 grace system; MirrorSystem was hiding side mirror meshes on panel destruction, contradicting the damage system's ACCESSORY group visual contract; GLB-instanced nodes require recursive `find_child` for reliable lookup.
## Spawn Collision Damage Persists After Grace
`make run-debug` and MCP runtime session logs (seed 369614079) show a catastrophic 239886-force collision on `tire_rear_left` immediately after `[SambaBus] Spawn stabilization ended`:
```
[SambaBus] Spawn stabilization ended
[SambaBus] f=239886 pos=(1537.0,5.7,1537.0) n=(0.01,1.00,-0.00) obj=Terrain3D grp=[Grass]
[TireDamageDriver] CATASTROPHIC tire_rear_left force=239886 dist=0.59
[BusDamageSystem] damage: panel=window_side_left_rear amount=2398.9 state=2
```
The bus falls ~2m after waking from spawn stabilization, and the rear left tire contacts terrain with enough force to destroy panels. The 239886 force converts to 2398.9 damage (FORCE_TO_DAMAGE_SCALE=0.01), which destroys any panel with ≤100 HP.
Spec 156 implemented a two-stage grace (240 frames full immunity + 120 frames 50% mitigation, totalling 3s at 120Hz). This collision is happening AFTER the grace period expires — the stabilization ended, then physics woke the bus, then it fell 2m into terrain. The issue is not that grace is missing; it's that gravity-induced fall energy accumulates during the sleeping/stabilized period and releases as a single-frame impulse when the bus wakes.
**Resolution path**: Either extend the grace window to cover the fall-settle duration, or reduce the physics impulse on wake-up by positioning the bus at terrain height instead of above it.
## MirrorSystem Hides Side Mirror Meshes Inconsistently
Spec 034 (Rearview Mirrors) defines damage integration at FR-013 to FR-017: when a mirror panel is destroyed, its corresponding SubViewport should be disabled (surface goes dark). It does NOT specify hiding the mesh geometry.
The MirrorSystem implementation in `mirror_system.gd` was calling `_set_mesh_visible(_left_mesh, left_active)` and `_set_mesh_visible(_right_mesh, right_active)` where `right_active = not right_destroyed`. When `mirror_right` (ACCESSORY, 30 HP) was destroyed by the spawn collision, the MirrorSystem hid the entire mirror housing mesh — not just the viewport reflection.
The damage system's `_update_panel_visual` for ACCESSORY panels does NOT set `visible = false`. ACCESSORY panels lack the `detach` effect and are not in the GLASS group. The damage system only darkens the material (albedo × 0.25, roughness +0.4). The MirrorSystem was overriding this by hiding the geometry, making the mirror housing disappear completely instead of showing damaged/scuffed PBR material.
**Fix applied**: Removed `_set_mesh_visible` calls for side mirror meshes in both `_update_damage_state()` and `_apply_all_viewport_states()`. Only the rearview mirror (`mirror_rear`, a thin PlaneMesh with no structural presence) still toggles visibility with its viewport. Side mirror viewports are disabled on panel destruction (rendering surface dark), but the physical mesh stays visible.
## GLB Node Lookup Requires Recursive Search
Nodes inside a GLB-instanced PackedScene (`bus_lod0`) are not direct children of `BodyMesh`. They live at paths like `BodyMesh/LODController/LOD0/bus_lod0/door_left_front`. Code that uses `get_node_or_null("BodyMesh/door_left_front")` (direct child check) fails silently.
The `door_controller.gd` was creating placeholder `BoxMesh` nodes as direct children of `BodyMesh` because its `get_node_or_null` call couldn't find the real door mesh inside the GLB hierarchy. The placeholder was then found by `bus_damage_system.gd`'s `_find_panel_mesh` (which checks direct children first), causing the damage system to operate on a flat white BoxMesh instead of the real textured door mesh.
**Fix applied**: Door controller now uses a `_find_door_mesh()` helper that first tries direct lookup, then falls back to `body_mesh.find_child(door_name, true, false)` (recursive search). This pattern should be followed by any code that needs to find nodes inside GLB instances.
## References
- `src/scripts/bus/mirror_system.gd` — Mesh visibility fix
- `src/scripts/bus/door_controller.gd` — Recursive find fix
- `src/scripts/bus/samba_bus.gd` — Tire loading also uses fixed paths
- `src/scripts/bus/bus_damage_system.gd` — `_find_panel_mesh` uses recursive fallback
- `src/scripts/bus/bus_lod_controller.gd` — LOD range setup
- `src/scenes/bus/samba_bus.tscn` — Node paths reference LOD0 GLB nodes
- `docs/vault/Specs/034 Rearview Mirrors/034 Rearview Mirrors.md` — Mirror spec
- `docs/vault/Specs/156 Bus Damage Rebalance/156 Bus Damage Rebalance.md` — Damage spec
- `docs/vault/Decisions/Spec 156 — Collision Discrimination and Grace Design.md` — Grace system
- `docs/vault/Specs/068 Bus Asset Integration/PHASE_3_RCA.md` — GLB integration background
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spawn-collision-mirror-mesh-visibility-and-glb-node-discovery-jun-06-2026: type/session-log
Tag assignment · test- Gold
- The note calls for `type/session-log`: Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/session-log` (False)
- Signal
- P(true) 0.06 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spawn Collision, Mirror Mesh Visibility, and GLB Node Discovery — Jun 06 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spawn Collision, Mirror Mesh Visibility, and GLB Node Discovery — Jun 06 2026
Spawn-drop collision still damages panels despite the Spec 156 grace system; MirrorSystem was hiding side mirror meshes on panel destruction, contradicting the damage system's ACCESSORY group visual contract; GLB-instanced nodes require recursive `find_child` for reliable lookup.
## Spawn Collision Damage Persists After Grace
`make run-debug` and MCP runtime session logs (seed 369614079) show a catastrophic 239886-force collision on `tire_rear_left` immediately after `[SambaBus] Spawn stabilization ended`:
```
[SambaBus] Spawn stabilization ended
[SambaBus] f=239886 pos=(1537.0,5.7,1537.0) n=(0.01,1.00,-0.00) obj=Terrain3D grp=[Grass]
[TireDamageDriver] CATASTROPHIC tire_rear_left force=239886 dist=0.59
[BusDamageSystem] damage: panel=window_side_left_rear amount=2398.9 state=2
```
The bus falls ~2m after waking from spawn stabilization, and the rear left tire contacts terrain with enough force to destroy panels. The 239886 force converts to 2398.9 damage (FORCE_TO_DAMAGE_SCALE=0.01), which destroys any panel with ≤100 HP.
Spec 156 implemented a two-stage grace (240 frames full immunity + 120 frames 50% mitigation, totalling 3s at 120Hz). This collision is happening AFTER the grace period expires — the stabilization ended, then physics woke the bus, then it fell 2m into terrain. The issue is not that grace is missing; it's that gravity-induced fall energy accumulates during the sleeping/stabilized period and releases as a single-frame impulse when the bus wakes.
**Resolution path**: Either extend the grace window to cover the fall-settle duration, or reduce the physics impulse on wake-up by positioning the bus at terrain height instead of above it.
## MirrorSystem Hides Side Mirror Meshes Inconsistently
Spec 034 (Rearview Mirrors) defines damage integration at FR-013 to FR-017: when a mirror panel is destroyed, its corresponding SubViewport should be disabled (surface goes dark). It does NOT specify hiding the mesh geometry.
The MirrorSystem implementation in `mirror_system.gd` was calling `_set_mesh_visible(_left_mesh, left_active)` and `_set_mesh_visible(_right_mesh, right_active)` where `right_active = not right_destroyed`. When `mirror_right` (ACCESSORY, 30 HP) was destroyed by the spawn collision, the MirrorSystem hid the entire mirror housing mesh — not just the viewport reflection.
The damage system's `_update_panel_visual` for ACCESSORY panels does NOT set `visible = false`. ACCESSORY panels lack the `detach` effect and are not in the GLASS group. The damage system only darkens the material (albedo × 0.25, roughness +0.4). The MirrorSystem was overriding this by hiding the geometry, making the mirror housing disappear completely instead of showing damaged/scuffed PBR material.
**Fix applied**: Removed `_set_mesh_visible` calls for side mirror meshes in both `_update_damage_state()` and `_apply_all_viewport_states()`. Only the rearview mirror (`mirror_rear`, a thin PlaneMesh with no structural presence) still toggles visibility with its viewport. Side mirror viewports are disabled on panel destruction (rendering surface dark), but the physical mesh stays visible.
## GLB Node Lookup Requires Recursive Search
Nodes inside a GLB-instanced PackedScene (`bus_lod0`) are not direct children of `BodyMesh`. They live at paths like `BodyMesh/LODController/LOD0/bus_lod0/door_left_front`. Code that uses `get_node_or_null("BodyMesh/door_left_front")` (direct child check) fails silently.
The `door_controller.gd` was creating placeholder `BoxMesh` nodes as direct children of `BodyMesh` because its `get_node_or_null` call couldn't find the real door mesh inside the GLB hierarchy. The placeholder was then found by `bus_damage_system.gd`'s `_find_panel_mesh` (which checks direct children first), causing the damage system to operate on a flat white BoxMesh instead of the real textured door mesh.
**Fix applied**: Door controller now uses a `_find_door_mesh()` helper that first tries direct lookup, then falls back to `body_mesh.find_child(door_name, true, false)` (recursive search). This pattern should be followed by any code that needs to find nodes inside GLB instances.
## References
- `src/scripts/bus/mirror_system.gd` — Mesh visibility fix
- `src/scripts/bus/door_controller.gd` — Recursive find fix
- `src/scripts/bus/samba_bus.gd` — Tire loading also uses fixed paths
- `src/scripts/bus/bus_damage_system.gd` — `_find_panel_mesh` uses recursive fallback
- `src/scripts/bus/bus_lod_controller.gd` — LOD range setup
- `src/scenes/bus/samba_bus.tscn` — Node paths reference LOD0 GLB nodes
- `docs/vault/Specs/034 Rearview Mirrors/034 Rearview Mirrors.md` — Mirror spec
- `docs/vault/Specs/156 Bus Damage Rebalance/156 Bus Damage Rebalance.md` — Damage spec
- `docs/vault/Decisions/Spec 156 — Collision Discrimination and Grace Design.md` — Grace system
- `docs/vault/Specs/068 Bus Asset Integration/PHASE_3_RCA.md` — GLB integration background
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-world-audio: type/spec
Tag assignment · test- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.72 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Audio — World Audio.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — World Audio
The full world-audio system specification — biome ambience, weather, disturbance arc, The Storm, anomaly audio signatures, Hollows/enemy audio (canon-superseded to environmental presence), interior/exterior mix model, footstep system, vehicle audio, player audio, interaction audio, prop audio, combat audio, garage audio, exit gate audio, UI audio, and Godot scene implementation. This is the authoritative technical reference for audio implementation and asset production.
## Overview
World audio delivers the horror. It is structured as a layered, dynamic mix that responds to biome, weather, disturbance state, and the player's interior/exterior position. Every anomaly, every enemy, and every biome has a distinct acoustic identity that the player learns to read.
See [[Audio — Overview]] for the design pillars and bus architecture summary; this note carries the full per-layer specifications, asset filenames, Suno prompts, and Godot implementation.
## Biome Ambient System — Full Specifications
### Verdant Old Growth
*The living ancient forest. Sound is alive, vast, and slow.*
**L1 Bed** — A deep, low-frequency tonal drone, the resonance of enormous tree trunks vibrating in slow atmospheric pressure changes. Sub-bass heavy (60–120 Hz). Almost felt rather than heard. Continuous, no audible loop point.
Suno prompt: *deep forest tonal drone, sub-bass resonance, ancient trees, no melody, slow breathing quality, 60-120hz emphasis, 2 minute seamless loop, dark ambient*
**L2 Wind** — Wind through an 80-meter canopy, not surface wind. High and distant, with occasional deep gusts that drop pitch as they pass. Irregular rhythm.
Suno prompt: *wind through ancient forest canopy, high distant rushing, occasional deep gust, organic and irregular, no music, atmospheric sound design, pacific northwest forest*
**L3 Organic** — Creak and groan of massive timber under its own weight. Slow structural sounds, forest settling. Occasional deep crack of a distant branch. Dripping water after rain. World Bible: *"deep wind roaring through high branches and the creak of massive timber."*
Suno prompt: *massive timber creaking under weight, deep structural groan, old growth forest settling sounds, slow and irregular, occasional distant branch crack, no music, sound design*
**L4 Detail (randomized events)** — Distant Pacific Northwest bird calls (varied species, no single repeating pattern), single water droplets from a high branch, rustle of something large moving far away. Fire stochastically via `AudioStreamPlayer3D` nodes repositioned per event.
Assets: `detail_bird_[01–06].ogg`, `detail_drop_[01–03].ogg`, `detail_rustle_[01–04].ogg`, `detail_branch_far.ogg`
**Verdant disturbance modifier**:
| State | Audio Change |
|---|---|
| Calm | All four layers at baseline. Detail events every 15–45 sec |
| Uneasy | L2 wind volume slightly increases. Detail events every 8–20 sec. New event: distant hollow footstep |
| Agitated | L1 bed pitch drops slightly. Wind becomes urgent. Detail events every 4–12 sec. Bird calls cease. Replaced with silence then a sudden distant shriek |
| Storm | L1 bed loud and present. Wind is roaring. No detail events — silence in gaps between gusts feels menacing |
### Fungal Depths
*Decay rules here. The atmosphere is muffled, wet, and oppressive.*
**L1 Bed** — Low, wet drone, the sound of massive mycelium network resonating underground. Lower than Verdant bed, with subtle organic pulse (irregular, like breathing).
Suno prompt: *deep underground mycelium drone, wet organic resonance, oppressive and muffled, irregular pulse, no music, horror ambient, dark and biological*
**L2 Wind** — No wind in Fungal Depths. This layer plays slow spore dispersal — barely audible diffuse hiss of fungal matter drifting through still air. Its absence is noticed only when it stops.
Suno prompt: *slow spore dispersal, barely audible biological hiss, still underground air, no wind, diffuse and directionless, 90 second loop, horror ambient*
**L3 Organic** — Wet squelching sounds. Large fungi expanding. Hollow dripping into standing water. Faint crack of mycelium threads under pressure. Amber sap pooling. Slow and nauseating.
Suno prompt: *wet squelching organic sounds, large fungi expanding, hollow water drip, mycelium under pressure, slow biological horror ambience, muffled and thick, no music*
**L4 Detail** — No birds. Events shift to: `detail_fungal_pop_[01–03].ogg` (single spore pod rupture), `detail_sap_drip_[01–03].ogg` (thick liquid pooling), `detail_network_pulse_[01–02].ogg` (mycelium network shifting — deep, felt rumble), `detail_hollow_far_[01–03].ogg` (faint distant Hollow movement).
**Fungal disturbance modifier**:
| State | Audio Change |
|---|---|
| Calm | Baseline. Detail events every 12–30 sec. Silence between events is oppressive |
| Uneasy | Spore hiss increases. Network pulse events more frequent |
| Agitated | L1 bed pulse more pronounced. Hollow movement closer. Occasional distant shriek |
| Storm | Wet sounds intensify. L3 becomes loud and constant. Oppressive muffled quality maximized |
### The Petrifold
*Nothing lives here. The World Bible is unambiguous: "no wind blows, no rain falls, no life grows."*
**L1 Bed** — Absolute tonal silence — not absence of audio, but an active, produced silence. Barely perceptible sub-bass tone, so low it is felt rather than heard, suggesting vast empty space. Like the silence inside a large concrete structure.
Suno prompt: *near-silence, vast empty space resonance, sub-bass only, concrete acoustic quality, no organic content, dead and still, 3 minute loop*
**L2 Wind** — Absent. Does not play in the Petrifold. The absence of wind is significant.
**L3 Organic** — Sharp echoing stone cracks (World Bible: *"absolute quiet broken only by sharp, echoing cracks of stone"*). Not frequent. Each crack is a discrete event, isolated, with long natural reverb tail dying into silence. Long gaps between events.
**Production note**: generate dry crack recordings, apply purpose-built long reverb at runtime (`AudioEffectReverb`: Room 0.95, Damping 0.1, Spread 0.8, Dry 0.3, Wet 0.7). Do not use pre-reverbed recordings — the reverb must be real-time to match the game world.
Suno prompt: *stone cracking in silence, sharp percussive crack, single isolated sound, no music, no atmosphere, dry source for reverb processing*
**L4 Detail** — Effectively absent. One rare event (every 60–180 sec): distant petrified tree fragment falling — stone impact far away, heard and then gone. Only sound suggesting the world is still, somehow, active. Asset: `detail_stone_fall_far.ogg`.
**Petrifold disturbance modifier**: Petrifold does not escalate through disturbance audio states — its character is already maximal. All disturbance states sound the same. Only audio change is Storm state introducing The Storm audio system, which contrasts violently with Petrifold silence.
## Weather Audio
Weather operates regionally (World Bible §12). Audio transitions between states over ~8 seconds (longer than visual transitions to avoid jarring cuts).
Weather audio is a dedicated `AudioStreamPlayer` on `Ambience`, crossfaded independently of biome layers — the fifth ambient layer.
| Weather State | Audio Character | Notes |
|---|---|---|
| CLEAR | Silent (no weather layer) | Absence of rain is itself a notable shift |
| OVERCAST | Very faint distant wind, occasional far thunder | Baseline PNW grey sky |
| LIGHT_RAIN | Soft rain on leaves and earth, gentle roof percussion if near structures | Most common state |
| HEAVY_RAIN | Dense rain, strong wind, runoff sounds | L2 wind volume increases |
| FOG | Near-silence with occasional muffled distant sounds | Attenuates L4 detail — fog absorbs far sounds |
| HEAVY_SNOW | Muffled impact of large flakes, wind through bare branches, compression of deep snow | L3 organic muted — snow absorbs everything |
| BLIZZARD | Roaring wind dominating all layers, near-total audio whiteout | L4 detail absent. World has no detail — only roaring white |
**Rain on the Van**: when inside the bus during any rain state, a dedicated rain-on-metal layer plays on `Interior`. Separate asset from exterior rain bed. Does not play outside. Suno prompt: *rain on metal bus roof, close and intimate, varying intensity, tin percussion quality, no music, interior sound design*.
**Wipers**: rhythmic mechanical squeak, speed tied to weather state. LIGHT_RAIN = slow interval. HEAVY_RAIN = fast. Looping `AudioStreamPlayer3D` at windshield.
## Disturbance Arc Audio
Disturbance has its own audio expression independent of biome-layer modifications. Dedicated stinger and sting-bed layers on `Horror`.
### Threshold Crossing Stingers
Each time disturbance crosses a threshold upward, a one-shot stinger plays. Subtle — not jump scares, but felt wrongness. Player may not consciously register the first; by the third they will be afraid.
| Crossing | Stinger Character |
|---|---|
| 0 → Uneasy (30) | Single low tone — almost subliminal. One second |
| Uneasy → Agitated (60) | Resonant thud, as if something large shifted underground. Two seconds |
| Agitated → Storm (85) | Deep directional crack from the crater rim. The fracture destabilizes. Three seconds with decay |
Suno prompts:
- Uneasy: *single subliminal low tone, barely audible, one second, sub-bass, no music, horror sound design*
- Agitated: *deep underground thud, resonant, something large shifting below ground, two seconds, no music, horror impact sound*
- Storm: *deep directional crack from the crater rim, the temporal fracture destabilizing, three seconds with decay, vast and ancient, horror sound design*
### Disturbance Ambient Beds
Above Agitated (60+), a persistent underpinning layer activates on `Horror` — slowly evolving, low-frequency tension bed that does not resolve. Fades in over 10s, fades out over 20s if disturbance drops below 60. Independent of biome and weather.
Suno prompt: *slowly evolving low frequency tension drone, no resolution, no melody, building slowly, horror ambient, sub-bass emphasis, does not resolve, continuous*
## The Storm
The Storm is the forest's immune response — not a weather event (World Bible §9). Audio is categorically different from any weather state. Announces itself before it is visible.
### Phase 1: Approach (30 seconds before visual)
All ambient layers begin slow fade — not silence, but compression. World goes quieter than it has ever been. L4_Detail stops. L2_Wind drops to near-zero. Into this sudden quiet, a deep directional roar arrives from the crater rim opposite the player, growing slowly from nothing. No musical quality — a physical force.
Suno prompt: *massive directional force approaching from distance, no musical quality, purely physical roar, growing from silence, 30 second escalation, no rhythm, apocalyptic ambient*
### Phase 2: Active (Storm wall advancing)
Roar is now dominant. Debris impacts stochastically — irregular hits of varying sizes. Trees shattering in the wall's path create sharp cracks followed by crashing. Wind in the wall is continuous violent roar distinct from weather wind — mechanical and relentless, not organic.
Suno prompt: *violent storm wall advancing, massive sustained roar, debris impact sounds, trees shattering, no music, apocalyptic, mechanically relentless, not organic wind*
### Phase 3: Proximity (within 100m)
At close range, Storm produces low-frequency oscillation causing audio distortion artifacts — deliberate low-frequency overload making the player's audio environment feel physically failing. All other audio compresses 15–20 dB as Storm bus dominates.
### Phase 4: Caught inside
All environmental audio cuts. Only heartbeat and a wall of aggressive low-frequency noise. 30 damage/sec. Audio communicates death more clearly than the health bar.
### Storm Exit (Extraction)
When player reaches exit gate, massive release plays — gate's amber warm tone suddenly dominates over Storm roar, followed by deep silence as world boundary is crossed. Silence after extraction should last at least 3 seconds before garage audio fades in. **This pause is earned.**
## Anomaly Audio Signatures
Each anomaly identifiable by audio before visual contact. `AudioStreamPlayer3D` nodes on `Proximity` positioned at anomaly world location.
### Deadfall
Slow, deep structural groan 4–6 sec before the tree falls. Groan builds in pitch and intensity. Fall itself is massive impact with long low-frequency decay — ground thudding. Players learn to brake when they hear the building groan.
Assets: `anomaly_deadfall_warn_[01–03].ogg` (4–6 sec warning groan), `anomaly_deadfall_impact.ogg` (tree impact, large).
Suno prompts:
- Warning: *massive tree structural groan, building to breaking point, 5 seconds, slow escalation, no music, horror sound design*
- Impact: *enormous tree falling and impacting ground, massive low frequency impact, ground thud, debris settling, no music*
### Spore Cloud
Soft diffuse hiss — almost indistinguishable from Fungal L2 spore layer at distance. Closes in as cloud approaches. On windshield impact: thick wet splat followed by sustained visual and audio occlusion (muffled filtered audio while windshield is coated).
Assets: `anomaly_spore_ambient.ogg`, `anomaly_spore_impact.ogg`, `anomaly_spore_occlusion.ogg`.
### Hollow Trees
Sub-bass interference field — very low-frequency hum (40–60 Hz) causing audio artifacts in nearby sounds. Headlights dim and flicker; engine audio stutters. Radio distorts before dropping out. Hum has no rhythm — constant oppressive presence.
Asset: `anomaly_hollow_tree_hum.ogg` (continuous loop, sub-bass).
Suno prompt: *sub-bass interference field, 40-60hz, constant oppressive hum, no rhythm, electronic degradation quality, causes audio artifacts, very low frequency*
### Witch Lights
Soft warm chime — deceptively similar to exit gate's metallic wind-chime resonance but subtly wrong. Pitch slightly flat. Rhythm slightly irregular. Player who knows exit sound will feel wrongness before articulating it. Intentional — Witch Lights are audio deception.
**Production note**: generate the real exit gate chime FIRST. Then manually detune −15 cents and introduce subtle timing irregularity. The two sounds must be related — the Witch Light is a corruption of the exit sound, not a separate design.
Asset: `anomaly_witch_light_chime.ogg`.
### Mycelium Network
Silence — network is invisible until triggered. On trigger (thread contact): rapid cascade, network erupting across ground. Each eruption point produces small wet pop. Chain reaction should sound alive and immediate — dozens of pops in fast sequence radiating outward.
Assets: `anomaly_mycelium_trigger.ogg` (cascade chain eruption), `anomaly_mycelium_pop.ogg` (individual pop, used procedurally for distant eruptions).
### Petrified Zones
Low resonant stone hum — not threatening, but deeply wrong. As vehicle slows below 30 km/h, crackling stone-on-stone sound begins, building in intensity and frequency as speed drops further. At standstill: sharp stone-encasing sound. On-foot: crackling begins immediately.
Assets: `anomaly_petrifold_hum.ogg` (ambient zone hum), `anomaly_petrifold_crackle.ogg` (speed-reactive crackling, volume tied to inverse of vehicle speed), `anomaly_petrifold_encase.ogg` (death/petrification sound).
### The Silence
Inverse of all other anomaly audio design. Arrival marked by a **hard cut** of all environmental audio (not a fade — a cut). Player's heartbeat becomes the only sound. Faint slow pulse. Nothing else. Silence zone defined and navigated entirely by this audio state. Any audio from outside would break the effect — `Ambience` and `Proximity` buses muted entirely when inside a Silence zone.
Assets: `anomaly_silence_heartbeat.ogg` (slow isolated heartbeat loop), `anomaly_silence_entry.ogg` (cut moment — brief subtle whomp as all sound is absorbed).
**Production note**: `anomaly_silence_entry.ogg` should be a single low-frequency transient — not loud, just a felt absence snap. The sound of all other sounds leaving.
## The Hollows — Enemy Audio (canon-superseded)
> [!attention] CONFLICT
> The enemy "Hollow" AI system in this section was removed from canon per the GDD §7 "What Moves in the Wood" revision. See [[Anomalies]] — What Moves in the Wood. The material below is preserved as historical spec for its audio asset library and biome-vocalization character work; the active-enemy mechanics do NOT ship in v1.0. An environmental presence system replaces Hollows (spec pending). Indexed in [[Conflicts]].
Hollows announce their presence through audio before visual contact (World Bible §7, superseded). Vocalizations and movement sounds differed by biome.
### Universal Hollow sounds
| Event | Sound | Notes |
|---|---|---|
| Idle movement | Slow shuffle, dragging feet | Loop, very quiet |
| Alert (player detected) | Single sharp intake — almost human | One-shot |
| Chase | Increasing shuffle pace — never quite running | Loop, speed-variable |
| Attack approach | Low rhythmic thud — footfall increasing in volume | Directional, `AudioStreamPlayer3D` |
| Window banging | Aggressive, irregular impacts on metal | Triggers panic; irregular timing is key |
| Shriek | High, distorted, short — definitive horror signal | One-shot, heard at distance |
| Death | Long slow deflation — air leaving a hollow object | One-shot |
### Biome vocalizations
**Verdant Hollows (bark and moss)**: movement sounds include organic cracking — bark shifting as they walk. Idle has faint creak, as if wood is under tension. Alert sound includes wet snap. Their presence adds subtle creak layer to L3 organic ambient.
**Fungal Hollows (glowing mushroom clusters)**: movement wet — soft squelching under each footstep. Vocalization is low resonant hum — mycelium alive and buzzing. Alert is sudden spore burst hiss. Presence thickens Fungal L2 spore layer noticeably.
**Petrifold Hollows (stone-grey statues in motion)**: movement stone-on-stone — grinding, scraping as calcified limbs move. Deeply unnatural. No vocalization — entirely silent except movement. In Silence anomaly zones completely inaudible (*"completely invisible and silent"*). Alert: nothing. First sign of a Petrifold Hollow in a Silence zone is physical contact.
### Sound detection feedback
Hollows tracked by sound. When player generated sound a Hollow detected, subtle audio cue signaled detection: brief directional Hollow alert sound from direction of nearest alerted Hollow. Cue is quiet — easily missed in high-disturbance states, which was intentional.
## Interior / Exterior Mix Model
The bus is the sanctuary. Audio must sell this transition with every entry and exit.
### Exterior → Interior (entering the van)
**Transition time**: 0.4 seconds (matches door close animation).
| Layer | Change |
|---|---|
| Ambience bus | Routed through Interior bus — low-pass filter applies, reverb shifts to small room |
| Proximity bus | Routed through Interior bus — distant sounds become muffled |
| Rain | Exterior rain bed fades out; rain-on-metal fades in |
| Wind | L2 wind layer drops 12 dB — heard as background murmur |
| Engine | Engine audio fades in (if running) |
| Radio | Radio audio becomes primary foreground sound |
Resulting interior audio is quieter, warmer, more contained. Player feels enclosed. Safe.
### Interior → Exterior (exiting the van)
**Transition time**: 0.3 seconds (matches door open — faster than entry, more abrupt).
| Layer | Change |
|---|---|
| Ambience bus | Routed directly to Master — low-pass removed, full frequency range returns |
| Proximity bus | Direct to Master — distant sounds sharpen |
| Rain | Rain-on-metal fades out; exterior rain bed immediately present |
| Wind | L2 wind returns to full volume |
| Radio | Radio audio attenuates by distance from speakers immediately |
Exterior audio is louder, colder, directional. Player is exposed. Transition feels like a door opening — which it is.
## Footstep System
On-foot footsteps trigger per surface material. Surface detection uses raycast downward from player's feet, reading terrain material ID.
| Surface | Footstep Character |
|---|---|
| Wet earth (Verdant) | Soft, heavy thud with moisture |
| Moss (Verdant) | Muffled, spongy — nearly silent |
| Gravel / road | Sharp crunchy — loudest surface |
| Standing water (Fungal) | Wet splash, variable depth |
| Mycelium mat (Fungal) | Spongy squelch |
| Stone (Petrifold) | Hard, resonant click — echoes |
| Snow / heavy snow | Compressed crunch |
| Metal (structures, van) | Hollow ring |
Footstep volume tied to movement state: walk (full), crouch-walk (50% — World Bible specifies crouch as only silent movement mode, but full silence is unrealistic; 50% is design compromise that rewards crouch without making it completely undetectable).
## Vehicle Audio
The vehicle is the player's primary sanctuary and most complex audio source. Vehicle audio is dynamic, reflecting speed, RPM, surface material, and physical damage state.
### Engine System
Multi-sample crossfade system simulates characteristic air-cooled flat-four of the 1963 VW Samba.
- **Idle**: Low rhythmic "thrum-thrum-thrum" with mechanical valve chatter
- **RPM scaling**: pitch scales from 0.6× to 1.8× based on engine RPM
- **Load states**: audio crossfades between "off-throttle" (overrun/coasting) and "on-throttle" (acceleration/load) samples
- **Occlusion**: when inside the van, engine heard through `Interior` bus (low-pass 800 Hz). When engine hatch (rear) is damaged or missing, low-pass partially bypassed for that direction
Suno prompt: *1963 air-cooled flat-four engine idle, mechanical valve chatter, rhythmic thrum, vintage bus engine, interior and exterior perspectives, 1 minute seamless loop*
### Tires and Surfaces
Tire audio surface-reactive, reading material ID from road or terrain.
- **Asphalt**: high-frequency hiss and hum increasing pitch with speed
- **Off-road (gravel/dirt)**: texture-heavy crunching and stone impacts
- **Flat tire**: rhythmic "flop-flop-flop" impact synced to wheel rotation
- **Rim scrape**: high-frequency metallic screeching when driving on detached wheel / bare rim
### Collisions and Damage
Collision impacts grouped by panel type for material-specific feedback. All collision events triggered via `Events.bus_collision` (per current spec; the archived doc referenced `Events.vehicle_collision` which was renamed).
| Panel Group | Impact Character | Asset Examples |
|---|---|---|
| Body / Structural | Heavy metal crunch, deep resonance | `collision_metal_[01-04].wav` |
| Glass | Sharp shatter, high-frequency tinkling | `collision_glass_[01-03].wav` |
| Accessory | Plastic snap, light metallic clatter | `collision_accessory_[01-03].wav` |
**Damage state audio**:
- **Rattles**: sustained mechanical rattling when panels in DAMAGED state
- **Wind exposure**: high-frequency whistling when BODY or GLASS panels are missing (DESTROYED), panned to missing panel location
### Implementation
- **Bus routing**: engine and exhaust on `Engine` bus (child of SFX — note: this is different from the 7-bus architecture in Appendix A; the audio-spec's original 4-bus scheme predates the 7-bus plan). Collision impacts on SFX bus.
- **Node type**: `AudioStreamPlayer3D` for all engine and collision sounds, except interior-only wind whistling.
## Player Audio
Player-originated audio provides critical feedback on physical state (stamina, health) and impact on the forest's awareness.
### Breathing and heartbeat
Biometric audio communicates the player's internal state without UI dependency.
- **Breathing**: standard idle (barely audible). Sprinting triggers labored, heavy breathing scaling with stamina depletion. Recovery phase (3–5 sec) after sprinting stops
- **Heartbeat**: dedicated `Heartbeat` bus. Activated at low health (<30 HP). BPM accelerates as health approaches 0. Distinct from Silence anomaly heartbeat (slower, more environmental)
- **Coughing**: triggered by Spore Cloud exposure. Loudness: 30 (30m radius). Adds +5 disturbance
### Combat and utility
- **Melee whoosh**: high-frequency "swish" during weapon swing. Pitch scales with swing speed
- **Melee impact**: thud/crack/clank based on surface hit (flesh, wood, stone, metal)
- **Tool use**: flashlight click (mechanical), medkit rustle/unwrapping
### Implementation
- **Bus routing**: breathing/coughing on SFX. Heartbeat on dedicated Heartbeat bus (bypasses settings)
- **Sound detection**: all player sounds emit `Events.sound_emitted(position, loudness)` for enemy attraction (note: signal exists in principle, but `Events.sound_emitted` is not in the current `events.gd` catalog — implementation pending)
- **Filenames**: `player_breathe_heavy_[01-03].ogg`, `player_heartbeat_panicked_loop.ogg`, `player_cough_[01-03].wav`
## Interaction Audio
Focuses on mechanical, tactile feel of the 1963 bus and the physical world. Panned to point of interaction.
### Vehicle interactions
- **Doors**: heavy metal "clack" and hinge creak. Interior variants muffled and closer
- **Ignition**: key turn (small click) → engine crank (rhythmic struggle) → catch (ignition). Failed start uses persistent crank loop that doesn't resolve
- **Gear shift**: solid mechanical "clunk" for each gear change
- **Window crank**: continuous manual mechanism sound while operating
### World interactions
- **Repair**: rhythmic hammering/welding during field repair (3–5 sec). +2 disturbance/sec
- **Loot pickup**: brief clean mechanical click/rustle. Satisfying feedback for successful collection
- **Toggles**: high-tactile "click" for headlights, wipers, radio
### Implementation
- Bus: SFX. Node type: `AudioStreamPlayer3D` at `Marker3D` or `Area3D` interaction point
- Filenames: `int_door_open_ext.wav`, `int_door_close_int.wav`, `int_ignition_crank.wav`, `int_ignition_catch.wav`, `int_repair_hammer_loop.wav`
## Prop Audio
Covers destruction of world elements and massive impacts of colliding with indestructible structures.
### Breakable props
Triggered by `Events.prop_broken`. Each break adds +2 disturbance.
| Prop Type | Sound Character |
|---|---|
| Signs | Thin metal crumple + wooden post snap |
| Guardrails | Long screeching metal-on-metal tear |
| Power Poles | Deep wood crack + electrical cable snap/fizz |
| Vehicles | Heavy metal impact + glass shatter cascade |
### Indestructible impacts
When car hits a structure (ranger station, fire tower), audio is massive dead thud. No debris, no resonance — just the sound of a total halt.
Filenames: `prop_break_sign_[01-02].wav`, `prop_break_pole.wav`, `impact_structure_heavy.wav`.
## Combat Audio
Loud, violent, and attracts immediate attention from the forest.
### Melee and ranged
- **Melee**: whoosh on swing followed by material-specific impact. Impacts on Hollows sound "hollow" — dry airy deflation
- **Pistol**: extremely loud short transient with long forest echo. Loudness: 100 (100m radius)
- **Flare gun**: "thump" on fire, followed by persistent magnesium hiss during burn
- **Rocks**: small toss sound followed by distant impact. Used to lure enemies
### Attraction feedback
All combat audio triggers `Events.sound_emitted`. Audio environment should feel like it "reacts" to gunshots — birds (if present) stop, and a moment of heavy silence follows before Hollows respond.
Filenames: `wpn_pistol_fire.wav`, `wpn_flare_hiss_loop.wav`, `wpn_melee_hit_hollow_[01-03].wav`.
## Garage Audio
The garage is the only safe place. Audio must communicate safety, warmth, enclosure.
### Ambient and atmosphere
- **Interior bed**: warm low-frequency hum of a safe building. Distant settling of the structure. No forest ambient bleeds in
- **Rain on roof**: muffled distant "patter" on garage roof. Cozy, not threatening
- **Radio**: clean here. No static, no forest transmissions. DJ's voice present and reassuring
### Mechanical safety
- **Garage door**: massive heavy mechanical movement. Sound of safety closing behind you or world opening up
- **Stations**: subtle hums from workbench and healing station
### Implementation
- Buses: Ambience (interior bed), SFX (mechanisms)
- **Design principle**: Contrast. The garage should be the highest-fidelity, most "normal" audio space in the game.
## Exit Gate Audio
The exit gate audio is the player's lighthouse in the fog. Provides the "gold standard" for safety audio.
### Gate chime
- **Character**: metallic wind-chime resonance. Warm amber tonal quality
- **Rhythm**: steady predictable interval (every 4 seconds)
- **Range**: audible through weather up to 200m
- **Witch Light contrast**: Witch Light chime is derived from this asset (−15 cents detune, irregular timing)
### Extraction release
- **Character**: massive tonal swell as player crosses gate boundary. All forest audio suddenly "pushed back" by warm enveloping amber tone
- **Silence beat**: 3 seconds of total silence after extraction before garage audio fades in
### Implementation
- Buses: Proximity (chime), Horror (release swell)
- Filenames: `gate_chime_master.ogg`, `gate_extraction_swell.ogg`
## UI Audio
Minimalist and diegetic in aesthetic. Never pulls the player out of horror immersion.
### Navigation and feedback
- **Menu**: small mechanical clicks and slides. No "digital" or "synth" beeps
- **HUD**: only minimal cues for critical state changes
- **Accessibility**: cues for "Hollow nearby" or "Anomaly active" (Phase 24) use distinct non-diegetic tonal markers that overlay the mix without being part of the 3D world
Implementation: dedicated `UI` bus. Bypasses all world filters and anomaly effects.
## Godot Implementation
### Scene Structure
```
WorldAudio (Node)
├── BiomeAudio (Node)
│ ├── VerdantAudio (Node)
│ │ ├── L1_Bed (AudioStreamPlayer) # → Ambience bus
│ │ ├── L2_Wind (AudioStreamPlayer) # → Ambience bus
│ │ ├── L3_Organic (AudioStreamPlayer) # → Ambience bus
│ │ └── L4_Detail (AudioStreamPlayer3D) # → Proximity bus (repositioned per event)
│ ├── FungalAudio (Node)
│ │ ├── L1_Bed / L2_Spore / L3_Organic (AudioStreamPlayer)
│ │ └── L4_Detail (AudioStreamPlayer3D)
│ └── PetrifoldAudio (Node)
│ ├── L1_Bed (AudioStreamPlayer)
│ ├── L3_Cracks (AudioStreamPlayer3D) # positional — cracks have direction
│ └── L4_Detail (AudioStreamPlayer3D)
├── WeatherAudio (Node)
│ ├── WeatherBed (AudioStreamPlayer) # → Ambience
│ └── RainOnMetal (AudioStreamPlayer) # → Interior (only when inside van)
├── DisturbanceAudio (Node)
│ ├── StingerPlayer (AudioStreamPlayer) # → Horror, one-shot
│ └── TensionBed (AudioStreamPlayer) # → Horror, fades in at Agitated
├── StormAudio (Node)
│ ├── StormApproach (AudioStreamPlayer3D) # directional — crater rim
│ ├── StormRoar (AudioStreamPlayer) # → Horror, non-positional
│ └── Heartbeat (AudioStreamPlayer) # → Horror, only when caught
└── world_audio_manager.gd
```
### Key constants (from the spec)
- `BIOME_BLEND_TIME = 3.0` (matches World Bible biome transition time)
- `INTERIOR_BLEND_TIME = 0.4` (enter bus)
- `EXTERIOR_BLEND_TIME = 0.3` (exit bus)
- `DISTURBANCE_BED_FADE_IN = 10.0`
- `DISTURBANCE_BED_FADE_OUT = 20.0`
**Biome layer volume targets** (per biome — [L1, L2, L3] dB values):
- VERDANT: [-6.0, -9.0, -12.0]
- FUNGAL: [-6.0, -14.0, -10.0]
- PETRIFOLD: [-18.0, -999.0, -999.0] # −999 = effectively silent
### Implementation pattern
`world_audio_manager.gd` connects to `RadioZoneManager.biome_changed` and `RadioZoneManager.disturbance_changed` in `_ready()`. Biome transitions use `create_tween().set_parallel(true)` to crossfade the from-biome layers to `-80.0` dB while fading target biome layers from `-80.0` dB to their configured volumes. Interior/exterior bus routing uses `AudioServer.set_bus_send(ambience_idx, "Interior")` vs `"Master"`. Disturbance stingers play on threshold crossings; tension beds fade in at Agitated+ and fade out if disturbance drops below 60. Storm spawn tweens the Ambience bus by −20 dB over 30 sec before the StormRoar activates.
```gdscript
# world_audio_manager.gd
extends Node
var current_biome: Biome = Biome.VERDANT
var current_weather: WeatherState = WeatherState.OVERCAST
var current_disturbance: DisturbanceState = DisturbanceState.CALM
var player_inside_van := true
var storm_active := false
const BIOME_BLEND_TIME := 3.0
const INTERIOR_BLEND_TIME := 0.4
const EXTERIOR_BLEND_TIME := 0.3
const DISTURBANCE_BED_FADE_IN := 10.0
const DISTURBANCE_BED_FADE_OUT := 20.0
# Layer volume targets per biome — [L1, L2, L3] dB values
const BIOME_LAYER_VOLUMES := {
Biome.VERDANT: [-6.0, -9.0, -12.0],
Biome.FUNGAL: [-6.0, -14.0, -10.0],
Biome.PETRIFOLD: [-18.0, -999.0, -999.0], # -999 = effectively silent
}
func _ready() -> void:
RadioZoneManager.biome_changed.connect(_on_biome_changed)
RadioZoneManager.disturbance_changed.connect(_on_disturbance_changed)
_start_biome_audio(current_biome)
func _on_biome_changed(new_biome: Biome) -> void:
_crossfade_biome(current_biome, new_biome)
current_biome = new_biome
func _crossfade_biome(from: Biome, to: Biome) -> void:
var tween := create_tween().set_parallel(true)
var from_layers := _get_biome_layers(from)
var to_layers := _get_biome_layers(to)
var to_vols := BIOME_LAYER_VOLUMES[to]
for i in from_layers.size():
tween.tween_property(from_layers[i], "volume_db", -80.0, BIOME_BLEND_TIME)
for i in to_layers.size():
to_layers[i].volume_db = -80.0
if not to_layers[i].playing:
to_layers[i].play()
tween.tween_property(to_layers[i], "volume_db", to_vols[i], BIOME_BLEND_TIME)
func _get_biome_layers(biome: Biome) -> Array:
match biome:
Biome.VERDANT: return [$BiomeAudio/VerdantAudio/L1_Bed,
$BiomeAudio/VerdantAudio/L2_Wind,
$BiomeAudio/VerdantAudio/L3_Organic]
Biome.FUNGAL: return [$BiomeAudio/FungalAudio/L1_Bed,
$BiomeAudio/FungalAudio/L2_Spore,
$BiomeAudio/FungalAudio/L3_Organic]
Biome.PETRIFOLD: return [$BiomeAudio/PetrifoldAudio/L1_Bed]
return []
func _start_biome_audio(biome: Biome) -> void:
for layer in _get_biome_layers(biome):
layer.play()
var vols := BIOME_LAYER_VOLUMES[biome]
var layers := _get_biome_layers(biome)
for i in layers.size():
layers[i].volume_db = vols[i]
func set_player_inside_van(inside: bool) -> void:
player_inside_van = inside
var ambience_idx := AudioServer.get_bus_index("Ambience")
var proximity_idx := AudioServer.get_bus_index("Proximity")
if inside:
AudioServer.set_bus_send(ambience_idx, "Interior")
AudioServer.set_bus_send(proximity_idx, "Interior")
$WeatherAudio/RainOnMetal.play()
else:
AudioServer.set_bus_send(ambience_idx, "Master")
AudioServer.set_bus_send(proximity_idx, "Master")
$WeatherAudio/RainOnMetal.stop()
func set_weather(new_weather: WeatherState) -> void:
current_weather = new_weather
_crossfade_weather(new_weather)
_apply_weather_to_biome_layers(new_weather)
func _crossfade_weather(weather: WeatherState) -> void:
var tween := create_tween()
tween.tween_property($WeatherAudio/WeatherBed, "volume_db", -80.0, 3.0)
tween.tween_callback(func():
$WeatherAudio/WeatherBed.stream = _get_weather_stream(weather)
if weather != WeatherState.CLEAR:
$WeatherAudio/WeatherBed.play()
)
tween.tween_property($WeatherAudio/WeatherBed, "volume_db", -9.0, 5.0)
func _apply_weather_to_biome_layers(weather: WeatherState) -> void:
var compress := weather == WeatherState.BLIZZARD
var wind_node := _get_wind_layer_for_biome(current_biome)
if wind_node:
wind_node.volume_db = -4.0 if compress else BIOME_LAYER_VOLUMES[current_biome][1]
func _get_wind_layer_for_biome(biome: Biome) -> AudioStreamPlayer:
match biome:
Biome.VERDANT: return $BiomeAudio/VerdantAudio/L2_Wind
Biome.FUNGAL: return $BiomeAudio/FungalAudio/L2_Spore
return null
func _on_disturbance_changed(new_state: DisturbanceState) -> void:
var prev := current_disturbance
current_disturbance = new_state
if int(new_state) > int(prev):
_play_disturbance_stinger(new_state)
if new_state >= DisturbanceState.AGITATED:
_fade_in_tension_bed()
else:
_fade_out_tension_bed()
func _play_disturbance_stinger(state: DisturbanceState) -> void:
match state:
DisturbanceState.UNEASY: $DisturbanceAudio/StingerPlayer.stream = preload("res://assets/audio/disturbance/stinger_uneasy.ogg")
DisturbanceState.AGITATED: $DisturbanceAudio/StingerPlayer.stream = preload("res://assets/audio/disturbance/stinger_agitated.ogg")
DisturbanceState.STORM: $DisturbanceAudio/StingerPlayer.stream = preload("res://assets/audio/disturbance/stinger_storm_threshold.ogg")
$DisturbanceAudio/StingerPlayer.play()
func _fade_in_tension_bed() -> void:
if not $DisturbanceAudio/TensionBed.playing:
$DisturbanceAudio/TensionBed.volume_db = -80.0
$DisturbanceAudio/TensionBed.play()
create_tween().tween_property($DisturbanceAudio/TensionBed, "volume_db", -14.0, DISTURBANCE_BED_FADE_IN)
func _fade_out_tension_bed() -> void:
var tween := create_tween()
tween.tween_property($DisturbanceAudio/TensionBed, "volume_db", -80.0, DISTURBANCE_BED_FADE_OUT)
tween.tween_callback($DisturbanceAudio/TensionBed.stop)
func begin_storm(spawn_position: Vector3) -> void:
storm_active = true
$StormAudio/StormApproach.global_position = spawn_position
$StormAudio/StormApproach.play()
create_tween().tween_property(
AudioServer.get_bus_index("Ambience") as Object,
"volume_db", -20.0, 30.0
)
await get_tree().create_timer(30.0).timeout
$StormAudio/StormRoar.play()
func end_storm() -> void:
storm_active = false
$StormAudio/StormRoar.stop()
$StormAudio/StormApproach.stop()
create_tween().tween_property(
AudioServer.get_bus_index("Ambience") as Object,
"volume_db", 0.0, 5.0
)
```
### The Silence Anomaly — Audio Handler
```gdscript
# silence_zone.gd — attached to each Silence anomaly instance
extends Area3D
@onready var world_audio: Node = get_node("/root/WorldAudio")
func _on_body_entered(body: Node) -> void:
if body.is_in_group("player"):
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Ambience"), -80.0)
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Proximity"), -80.0)
world_audio.get_node("StormAudio/Heartbeat").play()
func _on_body_exited(body: Node) -> void:
if body.is_in_group("player"):
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Ambience"), 0.0)
AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Proximity"), 0.0)
world_audio.get_node("StormAudio/Heartbeat").stop()
```
### L4 Detail Event Scheduler
```gdscript
# detail_event_scheduler.gd — one instance per biome detail layer
extends Node
@export var event_streams: Array[AudioStream] = []
@export var min_interval: float = 10.0
@export var max_interval: float = 40.0
@export var spawn_radius: float = 30.0
@onready var player_ref: Node3D
@onready var detail_player: AudioStreamPlayer3D = $L4_Detail
func _ready() -> void:
_schedule_next()
func _schedule_next() -> void:
await get_tree().create_timer(randf_range(min_interval, max_interval)).timeout
_fire_event()
func _fire_event() -> void:
if event_streams.is_empty():
_schedule_next()
return
var offset := Vector3(
randf_range(-spawn_radius, spawn_radius),
randf_range(-2.0, 4.0),
randf_range(-spawn_radius, spawn_radius)
)
detail_player.global_position = player_ref.global_position + offset
detail_player.stream = event_streams.pick_random()
detail_player.play()
_schedule_next()
func set_interval(min_i: float, max_i: float) -> void:
min_interval = min_i
max_interval = max_i
```
## Test Coverage
- `src/tests/unit/test_disturbance.gd` — disturbance autoload (drives the audio arc)
- `src/tests/unit/test_disturbance_driver.gd` — bus-side disturbance emission
- `src/tests/unit/test_weather_zone_system.gd` — weather zone system (drives weather audio)
- `src/tests/unit/test_windshield_rain.gd`, `test_wiper_system.gd` — rain audio-adjacent subsystems
- `src/tests/unit/test_engine_audio.gd` — engine audio
## Related
- [[Audio — Overview]] — top-level architecture and bus summary
- [[Audio — Car Radio]] — the radio as a separate system
- [[Audio — Pipeline]] — dev-time audio processing
- [[Audio — Forest Transmissions]] — post-signal-loss content
- [[Biomes]] — what each biome is
- [[Biome Behavior]] — how biomes differ across systems
- [[Disturbance System]] — the state machine audio tracks
- [[Anomalies]] — anomaly mechanical specs
- [[Audio — Domain MOC]] — domain MOC
- [[Constitution]] — Article XII-D (audio/DSP), Appendix A (bus architecture)
### Implementation Specs (Phase 4 — ⏳ Waiting)
- [[111 Audio Bus Architecture]] — prerequisite: adds Ambience, Proximity, Interior, Horror buses
- [[104 Biome Ambient Audio]] — implements the biome ambient system specified above (L1–L4, all three biomes)
- [[105 Disturbance Arc Audio]] — implements the Disturbance Arc Audio section above
- [[106 Anomaly Audio Signatures]] — implements the Anomaly Audio Signatures section above
- [[107 Storm Audio System]] — implements The Storm section above (4-phase)
- [[108 Garage And Exit Gate Audio]] — implements Garage Audio and Exit Gate Audio sections above
- [[109 Weather Bed Layer]] — implements the Weather Audio section above (crossfading bed)
- [[110 Time Of Day Audio Variants]] — implements time-of-day L4 pool variation (DAY/DUSK/NIGHT)
- [[112 Environmental Presence System]] — implements the environmental presence replacement for the superseded Hollows audio
- [[113 Player And World Interaction Audio]] — implements Footstep System, Player Audio, Interaction Audio, Prop Audio sections above
## Bus Audio Reference — Engine, Cabin, and Exterior
> Phase 2 research pointers for Phase 3 audio pass. All URLs verified as pointers; re-confirm licensing at acquisition time. Live code anchors: `src/scripts/bus/engine_audio.gd`, `src/scripts/bus/rain_audio_controller.gd`, `src/scripts/bus/wiper_system.gd`.
### Engine — air-cooled VW flat-4
The 1963 Samba uses a 1,493 cc (1500) air-cooled boxer engine, ~42 kW / 52 PS at 4,200 rpm. Distinctive slow "thump-pop" exhaust note. Idle ~850 rpm, mechanical top RPM ~4,500 rpm.
Required discrete assets:
- Cold start + warm idle transition
- Idle at operating temperature (3+ min loop)
- Acceleration sweep per gear (4 gears)
- Engine-off / cool-down ticks
- Close-mic (interior) vs far-mic (exterior) variants for Interior/Exterior bus routing
**Reference sources**:
- Freesound.org — https://freesound.org/search/?q=vw+beetle+engine+air+cooled (CC0/CC BY; quality varies — verify recording technique)
- YouTube — "1963 vw samba engine sound", "VW bus ride along cold start" (reference only — not rippable)
- TheSamba.com forum restoration videos — high authenticity, low licensing clarity; reference only
**Recording recommendation**: if Phase 3 has access to a real air-cooled Bus, a 30-minute capture session covers all required assets. Contact-mic on engine case + directional mic at exhaust + cabin-position mic gives the three perspectives needed.
### Door close and latch
1963 Samba doors close with a flat "clack" — no modern damping, no soft-close. Outward-hinged doors slam rather than click.
Distinct samples needed:
- Driver door close (left front)
- Passenger door close (right front)
- Bi-parting cargo doors (each half separately — different acoustic signature from the single-hinge front doors)
**Reference sources**:
- Freesound.org — https://freesound.org/search/?q=classic+car+door+close (CC0/CC BY)
- Soundly, Zapsplat, BBC Sound Effects Library (subscription/commercial — verify tier)
### Wiper motor
DC motor with audible commutator brush noise. Characteristic "thunk-whir-thunk" at each pass. Already one asset in repo (`src/assets/audio/sfx/wiper_squeak.wav`) wired via `wiper_system.gd`.
Wet glass sounds different from dry glass — the squeak is moisture-dependent. If augmenting the existing asset, capture wet + dry variants.
**Reference sources**:
- Freesound.org — https://freesound.org/search/?q=vintage+car+wiper
- YouTube — "VW bus wiper motor sound" (reference only)
### Cabin rattle
Period VWs are famously rattly — metal-on-metal, interior panel vibrations, glass rattling in rubber seals. Sells the "vintage vehicle" feel more than any other single audio element.
**Reference sources**:
- Freesound.org — https://freesound.org/search/?q=car+rattle+vibration (CC0/CC BY)
- YouTube — "rattles VW bus ride along" (reference only)
- **Recording recommendation**: contact mic on interior panels during a test drive. Different panels (dashboard, door cards, rear cargo area) yield different rattle character suited to 3D placement.
### Rain on roof and rain on glass — bus-cavity appropriate
A bus-shaped body with a large flat steel roof and large glass surfaces has a distinct rain signature different from a car. Existing asset: `src/assets/audio/ambient/rain_heavy_interior.ogg` (wired via `rain_audio_controller.gd` and `rain_particles_controller.gd`).
If augmenting the existing asset:
- Freesound.org — https://freesound.org/search/?q=rain+metal+roof
- Zapsplat "rain on bus" — https://www.zapsplat.com/sound-effect-category/rain/ (free with account; commercial use varies by tier)
- **Recording recommendation**: record inside an actual Type 2 or similar bus-shaped vehicle during rain.
### Fungal Depths — biome wildcard audio
Per [[Design/Tone and Horror]]: the Fungal Depths has subtle "wrong-audio" elements — not explicit creature sounds, but biological wrongness. Phase 3 should treat this as bespoke sound design, not stock audio.
Tone reference: sounds that feel like breathing but aren't, mycelial network hum at the threshold of perception, the sound of something growing that has no business growing.
- Freesound.org — https://freesound.org/search/?q=mushroom+breathing (experimental starting point)
- BBC Sound Effects "alien environment" category (reference register; commercial licence required)
### Licensing summary
| Source | Commercial use | Attribution |
|---|---|---|
| Freesound CC0 | ✓ | None required |
| Freesound CC BY | ✓ | Per asset |
| BBC Sound Effects Library | ✗ (personal only by default) | Requires separate commercial licence |
| Zapsplat | ✓ with account (verify tier) | May require attribution |
| YouTube audio rip | ✗ | Reference only |
| Original capture | ✓ | Project-owned — preferred approach |
**Recommended Phase 3 approach**: original capture sessions for engine, door, wiper, and cabin rattle. Stock sources for rain (existing asset may suffice). Bespoke sound design for Fungal Depths biome audio. All audio delivered as OGG Vorbis per project convention (`src/assets/audio/` directory structure).
## Related Decisions
- [[Switched Weather Audio Suno To Elevenlabs]] — 2026-04-25 migration of weather ambient assets from Suno (music model) to ElevenLabs SFX V2 (purpose-built sound-effect model). Schema bumped 1.2 → 1.3.
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-code-conventions: domain/tooling
Tag assignment · test- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.35 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Code/Code Conventions.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Code Conventions
The canonical code conventions for Old Growth's GDScript codebase. Enforced by `gdformat` + `gdlint` via `make gdscript-quality`. Violations block CI.
## Overview
Conventions are authored in AGENTS.md and observed in the actual codebase. This note consolidates them in a single canonical reference. The intent is readable, predictable, scannable code — easy for any contributor (human or AI) to pick up mid-session.
## Key Conventions
### File layout order
Every `.gd` file follows this section order:
1. Doc comment (`## Description`)
2. `extends`
3. Enums
4. Constants
5. Signals
6. `@export` properties (grouped with `@export_group`)
7. Private backing variables + public property definitions
8. `_ready()` and other lifecycle methods
9. Public methods
10. Private methods (prefixed `_`)
11. Debug handlers (suffixed `_debug`)
Section headers separate blocks: `# --- Signals -------`, `# --- Properties -------`, etc.
### Naming
| Element | Convention | Example |
|---------|-----------|---------|
| Files | `snake_case.gd` | `bus_radio.gd` |
| Classes / Nodes | PascalCase | `CarRadio` |
| Constants | UPPER_CASE | `DEFAULT_SEED` |
| Enum names | PascalCase | `PlayerMode` |
| Enum values | UPPER_CASE | `DRIVING`, `ON_FOOT` |
| Signals / variables / functions | snake_case | `threshold_changed`, `current_seed`, `add()` |
| Private prefix | `_` | `_level`, `_get_state_for_level()` |
| Signal handlers | `_on_<signal>` | `_on_threshold_changed_debug` |
### Type annotations — REQUIRED
All function signatures and variable declarations must have type annotations:
```gdscript
func add(amount: float, source: String) -> void:
var _level: float = 0.0
const DEFAULT_SEED: int = 0
```
Use `:=` for type inference only on locals and dict/array constants:
```gdscript
var old := _level # OK — inferred float from _level
const BIOME_NAMES := { Biome.VERDANT: "Verdant", ... } # OK — Dictionary literal
```
### Property setters with backing variables
GDScript 4.x setters are called recursively on self-assignment. Reading the property inside its own setter returns the *new* value, not the old one. The backing variable pattern avoids this:
```gdscript
var _player_mode: PlayerMode = DEFAULT_PLAYER_MODE
var player_mode: PlayerMode:
get: return _player_mode
set(value):
var old := _player_mode
_player_mode = value
if old != value:
player_mode_changed.emit(old, value)
```
### Guard clauses with early return
No deep nesting. Guard and return:
```gdscript
func add(amount: float, source: String) -> void:
if amount <= 0.0:
return
# … main logic
```
### Clamp instead of throw
```gdscript
_level = clampf(_level + amount, 0.0, 100.0)
```
Public API functions in rust-core return `Result<T, E>` for genuinely fallible operations (Article XII-C); GDScript typically clamps to valid range.
### Imports and autoloads
- Autoloads (`Events`, `GameState`, `Disturbance`) are global — reference by name directly, never `preload()` them.
- Do NOT use `class_name` on autoload scripts (conflicts with singleton names in Godot 4.6+).
- Use `preload()` for Resources and non-autoload scripts.
- Use `load()` only when the path is dynamic.
### Debug logging
Gate all debug output behind `OS.is_debug_build()`. Prefix with system name:
```gdscript
if OS.is_debug_build():
print("[Disturbance] +%.1f from '%s'" % [amount, source])
```
Connect debug-only signal handlers in `_ready()` behind the same gate. Debug handler functions use the `_debug` suffix (e.g., `_on_threshold_changed_debug`).
### Testing (GUT)
- Test files named `test_*.gd`, in `src/tests/unit/`
- Test functions named `func test_*() -> void:`
- Extend `GutTest`; use `before_each()` for setup
- `add_child_autofree()` for nodes (auto-cleaned between tests)
- `preload()` the script under test; do not reference autoload singletons directly
- `watch_signals(node)` before an action that should emit a signal
- Vendored addons (`src/addons/`) are excluded from all linting and formatting
### Signal conventions
- Signal declarations grouped by phase-comment section in `events.gd`
- Signals are append-only once declared
- Signal names describe an event in past tense: `bus_collision`, `threshold_changed`, `player_died`
- Connect at `_ready()`; disconnect in `_exit_tree()` if the connection is to something that may outlive the node
### Enum conventions
- Enum values use UPPER_CASE: `enum PlayerMode { DRIVING, ON_FOOT }`
- Enum names use PascalCase
- Define enums inside the class that owns them; reference statically (`GameState.Biome.VERDANT`)
### @export conventions
- Designer-tunable parameters MUST be `@export` (Article VI Rule 3)
- Use `@export_group("Group Name")` to organize related exports
- Avoid `const` for anything designers might tune — promote to `@export var` instead
## Related
- [[Source Tree]] — where conventions are observed
- [[Autoloads — Events]], [[Autoloads — GameState]], [[Autoloads — Disturbance]] — canonical examples
- [[Build System]] — `make gdscript-quality` enforces formatting/lint
- [[Code]] — folder MOC
- [[Constitution]] — Article VI (signals), Article XII-C (Rust idioms)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-interior-interaction-detection-shapecast3d: domain/player
Tag assignment · test- Gold
- The note calls for `domain/player`: Player mode, input, controls, camera, exit/entry (True)
- ember
- The note has no reason to carry `domain/player` (False)
- Signal
- P(true) 0.24 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Decisions/Interior Interaction Detection (ShapeCast3D).md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Interior Interaction Detection (ShapeCast3D)
The interior interaction detection system was switched from a thin RayCast3D to a volumetric ShapeCast3D to cover the dashboard area that the thin ray could not reach. The breaking discovery is documented in [[Interior Interaction Spatial Constraint]].
## Context
The interior camera (`interior_camera.gd`) creates a detection volume as a child node that sweeps forward from the camera's position to detect interactable Area3D nodes on collision layer 8. Previously this was a RayCast3D (a single-pixel line), which failed to detect the radio's InteractArea because the radio sits 1m below the camera with only 0.3m of forward distance — a geometry the line could not reach within the 45-degree pitch-down limit.
## Decision
Replace `RayCast3D` with `ShapeCast3D` in `interior_camera.gd`, using a box shape with an exported `interact_shape_size` defaulting to `Vector3(0.5, 0.5, 1.0)`. The box sweeps the same 2m forward distance (`interact_ray_length`) as the original ray, but detects any Area3D whose collision volume overlaps the swept box, not just a line intersection.
### Detection volume dimensions
- **Width (X)**: 0.5m — covers the radio's 0.45m lateral offset from the camera (driver's head to dashboard center)
- **Height (Y)**: 0.5m — extends 25cm above/below the ray's center line, covering the vertical gap between eye level (y=1.65) and dashboard interactables (y~0.65–1.35)
- **Depth (Z)**: 1.0m — the box depth itself, swept 2m forward for a total detection range of approximately 2.5m from the camera origin
### Existing interaction pattern preserved
- `collision_mask = 8`, `collide_with_areas = true`, `collide_with_bodies = false` — unchanged from the RayCast3D setup
- The signal protocol (`interactable_focused` / `interactable_unfocused`) is identical
- The first detected collider is used (index 0), matching the previous single-collider model
- The `_on_interactable_focused` handler in each interactable (bus_radio.gd, dome_light_control.gd) checks `target == $InteractArea or target.get_parent() == self` — unchanged
## Rationale
- **Reliability**: A volume is far more forgiving than a line for interior interaction where the player moves the camera with mouse look but interactables stay fixed
- **Tunability**: `interact_shape_size` is an `@export` so it can be adjusted per interactable layout without code changes
- **Minimal API change**: ShapeCast3D has the same collision mask/area/body API as RayCast3D, so the `_update_interact_focus` logic is structurally identical
- **No visual impact**: The ShapeCast3D is invisible in-game (debug-only gizmo when selected in editor)
## Alternatives considered
- **Larger InteractArea hitbox only** — Moving the radio's InteractArea collision box 35cm up and extending it to 70cm tall made the box reachable in theory, but the thin ray still required near-perfect aim. The volume approach is strictly more forgiving.
- **Longer ray** — Would not help; the radio is only 0.3m forward. The problem is angular (vertical reach at close range), not linear range.
- **Angled ray** — Offsetting the ray origin or target to point downward would decouple the interact direction from the player's gaze direction, breaking diegetic alignment (per Article XII-G).
## References
- `src/scripts/bus/interior_camera.gd` — the ShapeCast3D setup and `_update_interact_focus` method
- `src/scenes/bus/bus_radio.tscn` — the radio's InteractArea hitbox (also extended: +0.35m Y offset, box size.y=0.70)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-interior-interaction-detection-shapecast3d: type/discovery
Tag assignment · test- Gold
- The note has no reason to carry `type/discovery` (False)
- ember
- The note calls for `type/discovery`: Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.) (True)
- Signal
- P(true) 0.69 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Decisions/Interior Interaction Detection (ShapeCast3D).md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Interior Interaction Detection (ShapeCast3D)
The interior interaction detection system was switched from a thin RayCast3D to a volumetric ShapeCast3D to cover the dashboard area that the thin ray could not reach. The breaking discovery is documented in [[Interior Interaction Spatial Constraint]].
## Context
The interior camera (`interior_camera.gd`) creates a detection volume as a child node that sweeps forward from the camera's position to detect interactable Area3D nodes on collision layer 8. Previously this was a RayCast3D (a single-pixel line), which failed to detect the radio's InteractArea because the radio sits 1m below the camera with only 0.3m of forward distance — a geometry the line could not reach within the 45-degree pitch-down limit.
## Decision
Replace `RayCast3D` with `ShapeCast3D` in `interior_camera.gd`, using a box shape with an exported `interact_shape_size` defaulting to `Vector3(0.5, 0.5, 1.0)`. The box sweeps the same 2m forward distance (`interact_ray_length`) as the original ray, but detects any Area3D whose collision volume overlaps the swept box, not just a line intersection.
### Detection volume dimensions
- **Width (X)**: 0.5m — covers the radio's 0.45m lateral offset from the camera (driver's head to dashboard center)
- **Height (Y)**: 0.5m — extends 25cm above/below the ray's center line, covering the vertical gap between eye level (y=1.65) and dashboard interactables (y~0.65–1.35)
- **Depth (Z)**: 1.0m — the box depth itself, swept 2m forward for a total detection range of approximately 2.5m from the camera origin
### Existing interaction pattern preserved
- `collision_mask = 8`, `collide_with_areas = true`, `collide_with_bodies = false` — unchanged from the RayCast3D setup
- The signal protocol (`interactable_focused` / `interactable_unfocused`) is identical
- The first detected collider is used (index 0), matching the previous single-collider model
- The `_on_interactable_focused` handler in each interactable (bus_radio.gd, dome_light_control.gd) checks `target == $InteractArea or target.get_parent() == self` — unchanged
## Rationale
- **Reliability**: A volume is far more forgiving than a line for interior interaction where the player moves the camera with mouse look but interactables stay fixed
- **Tunability**: `interact_shape_size` is an `@export` so it can be adjusted per interactable layout without code changes
- **Minimal API change**: ShapeCast3D has the same collision mask/area/body API as RayCast3D, so the `_update_interact_focus` logic is structurally identical
- **No visual impact**: The ShapeCast3D is invisible in-game (debug-only gizmo when selected in editor)
## Alternatives considered
- **Larger InteractArea hitbox only** — Moving the radio's InteractArea collision box 35cm up and extending it to 70cm tall made the box reachable in theory, but the thin ray still required near-perfect aim. The volume approach is strictly more forgiving.
- **Longer ray** — Would not help; the radio is only 0.3m forward. The problem is angular (vertical reach at close range), not linear range.
- **Angled ray** — Offsetting the ray origin or target to point downward would decouple the interact direction from the player's gaze direction, breaking diegetic alignment (per Article XII-G).
## References
- `src/scripts/bus/interior_camera.gd` — the ShapeCast3D setup and `_update_interact_focus` method
- `src/scenes/bus/bus_radio.tscn` — the radio's InteractArea hitbox (also extended: +0.35m Y offset, box size.y=0.70)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-high-center-root-cause: type/discovery
Tag assignment · dev- Gold
- The note calls for `type/discovery`: Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/discovery` (False)
- Signal
- P(true) 0.50 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/High Center Root Cause.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
High Center Root Cause
The bus physically high-centers on terrain ledges due to a geometry mismatch between the chassis collision box and the wheel suspension reach.
## Collision Box
From `samba_bus.tscn`, the RigidBody3D chassis collision is a `BoxShape3D`:
| Property | Value |
|----------|-------|
| Type | BoxShape3D |
| Size | 2.1 × 1.52 × 4.7 (reduced from 2.32 by spec 158) |
| Half-extent Y | 0.76m |
| Bottom Y (vehicle-local) | -0.76m |
## Wheel Reach
From `tire_damage_driver.gd` and `samba_bus.tscn`:
| Parameter | Value | Source |
|-----------|-------|--------|
| Tire center Y | 0.493m | `_tire_centers` in tire_damage_driver.gd |
| Tire radius | 0.38m | `samba_bus.tscn`: `front_tire_radius` |
| Spring length | 0.35m | `samba_bus.tscn`: `front_spring_length`, `rear_spring_length` |
| Wheel full droop | 0.73m | spring_length + tire_radius |
| Wheel bottom Y at full droop | -0.237m | 0.493 - 0.73 |
## Clearance Gap
```
Chassis center: 0.000m
Wheel bottom at full droop: -0.237m
Collision box bottom: -0.760m
Gap: 0.523m
```
The collision box extends **0.523m below** the lowest point the wheels can reach. When the collision box contacts terrain (e.g., crossing a ledge), the wheels are ~0.5m above the ground and cannot reach it even at full suspension droop.
## Root Cause
**Primary**: The BoxShape3D collision box extends too far below the wheel contact points. When the box contacts terrain, all wheels lose ground contact.
**Contributing factor**: The flat-bottomed box shape catches on terrain ledges instead of sliding over them.
## Prevention Applied (spec 158)
- BoxShape3D Y size reduced: 2.32 → 1.52 (bottom raised from -1.16m to -0.76m)
- Spring length already at 0.35 (pre-existing — no change needed)
- Recovery lift impulse added to free the bus when stuck
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-reference-autonomous-company-autonomous-company-operations-reference: type/reference
Tag assignment · dev- Gold
- The note calls for `type/reference`: Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`) (True)
- ember
- The note has no reason to carry `type/reference` (False)
- Signal
- P(true) 0.22 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Reference/Autonomous Company/Autonomous Company Operations Reference.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Autonomous Company Operations Reference
> **Part of**: [[133 Autonomous Company V01 Experiment]]
> **Quickstart**: `docs/vault/Specs/135 Per Persona Middleware Specs/135 Per Persona Middleware Specs - quickstart.md`
## Infrastructure Stack
```
┌─────────────────────────────────────────────────────┐
│ Slack │
│ ┌─ Old Growth Coordinator ┌─ Old Growth GTM │
│ │ Socket Mode WebSocket │ Socket Mode WebSocket│
│ └──────────┬───────────────└──────────┬────────────│
│ │ │ │
│ app_mention announce-releases │
│ /internal/approval-callback │
└─────────────┼──────────────────────────┼─────────────┘
│ │
┌────────▼──────────┐ ┌─────────▼────────┐
│ Coordinator Runtime│ │ GTM Runtime │
│ :9100/health │ │ :9101/health │
│ :9100/metrics │ │ :9101/metrics │
└────────┬───────────┘ └────────┬──────────┘
│ │
└──────────┬───────────────┘
│
┌──────────▼───────────┐
│ OpenCode HTTP API │
│ localhost:8080 │
└──────────────────────┘
┌─────────────────────┐
│ OTel Collector │
│ localhost:4317 │
│ → logs/otel/ │
└─────────────────────┘
┌───────────────────┐ ┌────────────────────┐ ┌──────────────────┐
│ Valkey │ │ ngrok TCP tunnel │ │ GitHub Actions │
│ localhost:6379 │ │ 1.tcp.us-cal-1 │ │ release.yml │
│ AUTH required │ │ .ngrok.io:24971 │ │ → releases.shipped
└───────────────────┘ └────────────────────┘ └──────────────────┘
```
## Quick Commands
### Start Everything (after reboot)
```bash
cd /Users/joshburt/Workbench/Repositories/oldgrowth
set -a; source .env; set +a
make recovery
```
This single command brings all 6 components online in dependency order with per-component timeouts. For details: `make _recovery_help`
### Check Health
```bash
make personas-status
```
Or manually:
```bash
# Valkey
valkey-cli -a $VALKEY_AUTH_TOKEN ping # → PONG
# ngrok
curl -s http://localhost:4040/api/tunnels | jq .tunnels[0].public_url
# Persona health endpoints (spec 142)
curl -s http://localhost:9100/health # Coordinator (base + liveness)
curl -s http://localhost:9101/health # GTM
# Spec 143 health dashboard (per-persona)
Each persona runtime serves its own HTML dashboard at its health port. The dashboard
provides the same card layout regardless of which persona you open — data is scoped to
that process's perspective.
```bash
open http://localhost:9100 # Coordinator dashboard
open http://localhost:9101 # GTM dashboard
```
Both dashboards expose the same JSON API endpoints:
| Endpoint | Purpose |
|---|---|
| `/` | Redirects to `/dashboard` |
| `/health` | Base liveness + version + restart count + socket mode |
| `/dashboard` | Full HTML browser dashboard (auto-refresh 10s) |
| `/metrics` | Prometheus metrics (`og_*` counters) |
| `/health/personas` | Persona roster + liveness per agent + tags + capabilities + Letta agent IDs |
| `/health/circuits` | Circuit breaker states with consecutive 429 counts |
| `/health/threads` | Slack echo thread statistics |
| `/health/audit-stats` | Audit log entry distribution by status |
| `/health/recent-errors` | Last N error entries from the audit log |
| `/health/api-stats` | LLM/OpenCode call volumes, app mentions, per-persona breakdown |
| `/health/events` | Recent audit log entries (all statuses, real-time feed) |
| `/health/message-latency` | Average/max/min inter-agent message transit time |
| `/health/letta-probe` | Letta server reachability check |
| `/health/opencode-orphans` | Stale OpenCode session count |
| `/health/cost` | Weekly LLM cost summary (by model) |
| `/health/config` | Runtime configuration values (ports, timeouts, heartbeat) |
| `/health/orphans` | Orphaned inter-agent messages |
| `POST /health/orphans/resolve` | Resolve orphaned messages |
**Dashboard features:**
- Persona cards with tags, capability chips, Letta agent IDs, heartbeat status
- Dark/light theme toggle (persisted to localStorage)
- Human-readable uptime formatting
- Restart counter (file-persisted across process restarts)
- Socket mode status in summary bar
- Version identifier (git commit hash) in header
- Real-time event feed, message latency stats, Letta reachability probe
- Circuit breaker details (consecutive 429 count)
- Weekly cost summary per model
- Config table with key runtime settings
# Prometheus metrics
curl -s http://localhost:9100/metrics | grep og_ # Coordinator metrics
# OTel collector
curl -s http://localhost:8888/metrics | head -3 # Collector health
# OpenCode
curl -s http://localhost:8080/api/v1/sessions \
-H "Authorization: Bearer $OPENCODE_SERVE_TOKEN"
```
### Stop Everything
```bash
make personas-down
# ngrok: Ctrl+C or launchctl unload
# Valkey: brew services stop valkey
```
### Restart a Persona
```bash
make personas-down # stop all
make personas-up # start all
```
Or for a single persona:
```bash
launchctl unload ~/Library/LaunchAgents/com.oldgrowth.coordinator.plist
launchctl load ~/Library/LaunchAgents/com.oldgrowth.coordinator.plist
```
**What happens on restart (spec 143)**:
- Thread state (`personas/run/threads.json`) is loaded and pruned of entries >24h old
- Orphan janitor daemon starts automatically (60s cycle, detection-only)
- Health dashboard becomes available at all spec 143 endpoints immediately
- Background persistence flusher (5s cycle) saves thread metadata changes automatically
- Existing echo threads remain routable — human replies in known threads are not rejected after restart
```
## Component Reference
### Valkey (local, port 6379)
| Action | Command |
|---|---|
| Status | `make personas-status` or `valkey-cli -a $VALKEY_AUTH_TOKEN ping` |
| Start (cold boot) | `make recovery` — all 6 components in dependency order |
| Start | `brew services start valkey` |
| Stop | `brew services stop valkey` |
| Restart | `brew services restart valkey` |
| Stream count | `valkey-cli -a $VALKEY_AUTH_TOKEN XLEN releases.shipped` |
| Read stream | `valkey-cli -a $VALKEY_AUTH_TOKEN XRANGE releases.shipped - +` |
| Config | `/opt/homebrew/etc/valkey.conf` — `requirepass <token>` |
### ngrok TCP Tunnel
| Action | Command |
|---|---|
| Status | `make personas-status` or `curl -s localhost:4040/api/tunnels` |
| Start | `ngrok start --all --config ~/Library/Application\ Support/ngrok/ngrok.yml` |
| Stop | `launchctl unload ~/Library/LaunchAgents/com.ngrok.tunnel.plist` |
| Logs | `cat ~/Library/Logs/ngrok.log` |
| Config | `/Users/joshburt/Library/Application\ Support/ngrok/ngrok.yml` |
| Reserved TCP | `1.tcp.us-cal-1.ngrok.io:24971` (ID: `rd_39lBaWDzYl2EXB4mr2vAe76yEq2`) |
| Dashboard | https://dashboard.ngrok.com |
**Setup runbook**: `docs/vault/Reference/Autonomous Company/Ngrok Tunnel Setup.md`
### Letta (Docker — LLM Agent Server)
Letta agents provide **persistent conversation memory** for each persona. Runs in a Docker container with PostgreSQL backend.
| Action | Command |
|---|---|
| Status | `make personas-status` |
| Start | `make docker-letta-up` or `docker start letta-server` |
| Stop | `make docker-letta-down` or `docker stop letta-server` |
| Logs | `make docker-letta-logs` or `docker logs letta-server` |
| Auto-start | `launchctl load ~/Library/LaunchAgents/com.letta.server.plist` (starts container at login) |
| Agent IDs | `personas/letta_agents.json` |
| API | `http://localhost:8283/v1` — REST API for agent interactions |
| Data | `~/.letta/.persist/pgdata/` — PostgreSQL data (persists across restarts) |
| First-time setup | `docker run -d --name letta-server -v ~/.letta/.persist/pgdata:/var/lib/postgresql/data -p 8283:8283 -e OPENROUTER_API_KEY="..." letta/letta:latest` |
| Recreate agents | `python3 -c "from letta_client import Letta; c = Letta(base_url='http://localhost:8283'); print([a.name for a in c.agents.list()])"` |
**Full restart sequence**:
```bash
# Stop everything
make personas-down
make docker-letta-down
# Start everything
make docker-letta-up
sleep 10 # wait for PostgreSQL + server ready
make personas-up
```
**Agent memory**: Each agent has core memory blocks (`persona` + `human`) that persist across server restarts. Agents automatically update their memory (archival/recall) during conversations. To reset a persona's memory:
1. Delete the agent: `client.agents.delete(agent_id)`
2. Recreate via `scripts/personas/create-letta-agents.py` (if created)
### Inter-Agent Messaging (spec 142)
Inter-agent messaging enables the Coordinator persona to send structured messages to GTM's Letta agent, with Slack thread echo for human observation.
| Action | Command |
|---|---|
| View audit log | `tail -f personas/logs/inter_agent.jsonl \| jq .` |
| View overflow log | `tail -f personas/logs/inter_agent_payloads.jsonl \| jq .` |
| Count messages sent | `wc -l < personas/logs/inter_agent.jsonl` |
| Filter by status | `jq 'select(.status == "acked")' personas/logs/inter_agent.jsonl` |
| View circuit breaker state | `python3 -c "from personas.circuit_breaker import _BREAKERS; print(_BREAKERS)"` |
| Check peer health | `python3 -c "from personas.health_state import _HEALTH; print(_HEALTH)"` |
| Run inter-agent tests | `.venv/bin/python -m pytest personas/tests/ -q` |
| Run all tests (195) | `.venv/bin/python -m pytest personas/tests/ -q` (195 pass, 15 skip) |
**Key files**:
| File | Purpose |
|---|---|
| `personas/inter_agent.py` | Async send(), state machine, audit log, per-agent locking |
| `personas/slack_echo.py` | Slack thread echo, human injection with auth gate + rate limit |
| `personas/circuit_breaker.py` | Per-target breaker (5× 429 → OPEN, 30s cooldown) |
| `personas/health_state.py` | Heartbeat-based peer liveness (UP/DOWN) |
| `personas/logs/inter_agent.jsonl` | JSONL audit log — one entry per state transition |
| `personas/logs/inter_agent_payloads.jsonl` | Overflow log for payloads > PIPE_BUF (4096 bytes) |
### OTel Collector
| Action | Command |
|---|---|
| Status | `launchctl list \| grep otel` |
| Install | `scripts/personas/install-otel-collector.sh` |
| Traces file | `logs/otel/v0.1-traces.json` |
| Config | `~/Library/Application Support/otel-collector/config.yaml` |
### MCP Tool Configuration (spec 140)
Tool assignments for persona agents are managed through a manifest file and the `make mcp-setup` / `make mcp-status` commands. Persona runtimes do not read the manifest at startup — tool state is reconciled independently of runtime lifecycle.
| Action | Command |
|---|---|
| Setup | `make mcp-setup` — register MCP servers + attach tools per manifest |
| Status | `make mcp-status` — show registrations, per-agent tools, host-tools health |
| Manifest | `docs/vault/Reference/Autonomous Company/trusted_agent_mcp_manifest.json` |
| Server ports | Host-tools: port 9105 (Streamable HTTP); Obsidian: stdio via `npx` |
**Recovery dependency chain**: Letta server → host-tools MCP (port 9105) → personas healthy → `make mcp-setup`. No persona restart required — tool attachment is live via Letta API per spec 140 FR-003.
**Cross-references**:
- [[141 Auto Restart Recovery]] — port allocation table includes 9105 (host-tools)
- [[142 Persona Inter Agent Messaging]] — inter-agent messaging uses same Letta infrastructure
- [[143 Inter Agent Extensions]] — `/health` endpoint contract; `/health/personas` aggregator polls host-tools health
- [[144 Consolidate Notification Channel]] — host-tools emits `status`, `error`, and `approval` notifications per spec 144; no `tool_event` or `tool-start` values are emitted
### GitHub Actions Secrets
Set on 2 repos (oldgrowth, oldgrowth-assets). old-growth-wiki repo and its CI were removed 2026-05-10 — vault rendering is now local-only via `wiki/`.
| Secret | Value |
|---|---|
| `VALKEY_NGROK_HOST` | `1.tcp.us-cal-1.ngrok.io` |
| `VALKEY_NGROK_PORT` | `24971` |
| `VALKEY_AUTH_TOKEN` | (from 1Password `Valkey AUTH Token (Local Dev)`) |
### Slack Apps
| App | Tokens in `.env` | Bot Scopes |
|---|---|---|
| Old Growth Coordinator | `SLACK_BOT_TOKEN_COORDINATOR`, `SLACK_APP_TOKEN_COORDINATOR` | 8 (chat:write, chat:write.public, channels:history, channels:read, groups:history, im:history, im:write, app_mentions:read) |
| Old Growth GTM | `SLACK_BOT_TOKEN_GTM`, `SLACK_APP_TOKEN_GTM` | 10 (above + files:read, files:write) |
**Registration runbook**: `docs/vault/Reference/Autonomous Company/Slack App Registration Runbook.md`
**Kill-switch runbook**: `docs/vault/Reference/Autonomous Company/Slack App Kill-Switch Runbook.md`
### Workflow Builder
| Item | Value |
|---|---|
| Workflow name | `gtm-class-b-approval` |
| Workflow ID | `Wf0B2QQJP0K0` |
| Channel | `#announce-releases` |
| Export | `docs/vault/Reference/Autonomous Company/slack/gtm-class-b-approval.json` |
### .env Reference
```bash
# Required for persona runtimes:
SLACK_BOT_TOKEN_COORDINATOR=xoxb-...
SLACK_APP_TOKEN_COORDINATOR=xapp-...
SLACK_BOT_TOKEN_GTM=xoxb-...
SLACK_APP_TOKEN_GTM=xapp-...
OPENROUTER_API_KEY=sk-or-...
ANTHROPIC_API_KEY=sk-ant-... # failover
OPENCODE_SERVE_TOKEN=... # for opencode serve
# Required for GitHub Actions → local Valkey:
VALKEY_AUTH_TOKEN=...
VALKEY_NGROK_HOST=1.tcp.us-cal-1.ngrok.io
VALKEY_NGROK_PORT=24971
# Optional:
PERSONA_HEALTH_PORT=9100 # Coordinator (default)
PERSONA_LOG_LEVEL=INFO # or DEBUG for full LLM logs
# Inter-Agent Messaging (spec 142):
PERSONA_LETTA_SEND_TIMEOUT_SEC=60 # default 60s
PERSONA_LETTA_STREAM_TIMEOUT_SEC=600 # default 600s
PERSONA_MAX_HOPS=5 # default 5
PERSONA_AUTHORIZED_HUMAN_IDS=U111,U222 # comma-separated Slack user IDs
PERSONA_HUMAN_INJECTION_RATE_LIMIT_PER_MIN=10
PERSONA_HEARTBEAT_INTERVAL_SEC=30 # peer liveness ping interval
PERSONA_HEARTBEAT_TIMEOUT_SEC=90 # peer considered DOWN after this silence
PERSONA_INTER_AGENT_OWNER_LOCK=personas/run/inter_agent.owner
PERSONA_SLACK_ECHO_CHANNEL=C12345 # #team-personas channel ID
PERSONA_BOT_USER_IDS=U111,U222 # persona bot Slack user IDs (skip these in echo)
```
## Week 0 Artifacts
| Artifact | Path |
|---|---|
| Session log | `docs/vault/Sessions/2026-05-09 Spec 134 Week 0 Setup.md` |
| Slack channels | 14 created (general, announce-ops, announce-personas, announce-releases, team-personas, team-dev, proj-v01-experiment, help-spec, help-debug, sys-observer, sys-tts, sys-vault-audit, sys-release, social-wins) |
| Persona icons | `src/assets/icons/personas/` (7 icons, SDXL generated by `scripts/personas/generate-icons.py`) |
| Icon pipeline | `scripts/personas/generate-icons.py` |
| Spec 134 tasks | `docs/vault/Specs/134 Per Persona Slack App Registration/134 Per Persona Slack App Registration - tasks.md` |
| Spec 135 tasks | `docs/vault/Specs/135 Persona Middleware Specs/135 Persona Middleware Specs - tasks.md` |
## Letta Cloud Migration
The inter-agent messaging implementation in spec 142 (`personas/inter_agent.py`) targets a self-hosted Letta server (`letta/letta:latest` Docker image, port 8283) and dispatches messages via `client.agents.messages.create(agent_id=..., messages=[...])`. This call is the long-term, stable, cross-deployment Letta SDK surface.
Some Letta tutorials and older documentation reference the higher-level multi-agent helper `send_message_to_agent_async`. **This helper is not available on Letta Cloud** (the managed SaaS offering). It is only present in self-hosted Letta server builds with the multi-agent core tools enabled.
If migrating personas to Letta Cloud (FR-013):
| Concern | Self-hosted (current) | Letta Cloud (target) |
|---|---|---|
| Message dispatch | `client.agents.messages.create(...)` | `client.agents.messages.create(...)` (unchanged) |
| Multi-agent helper | `send_message_to_agent_async` available | Not available — use `send_message_to_agent_and_wait_for_reply` instead |
| Server URL | `http://localhost:8283` | `https://app.letta.com` (or region-specific) |
| Auth | None (local) or local API key | `LETTA_API_KEY` (Bearer token) |
| Block API | Same | Same |
| Agent IDs | Stable per-server | Migration requires recreating agents and updating `personas/letta_agents.json` |
**No code change is required for the Coordinator → GTM happy path.** The `inter_agent.send()` helper already uses `client.agents.messages.create()` and does not depend on `send_message_to_agent_async`. To switch deployments, set `LETTA_BASE_URL` and `LETTA_API_KEY` in `.env` and recreate persona agents on the target server, updating `letta_agent_id` in `personas/letta_agents.json` accordingly.
If a future spec requires the synchronous multi-agent fan-out pattern, replace `client.agents.messages.create()` with `client.agents.send_message_to_agent_and_wait_for_reply()` — both are supported on Cloud and self-hosted.
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-reference-autonomous-company-autonomous-company-operations-reference: type/spec
Tag assignment · dev- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.53 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Reference/Autonomous Company/Autonomous Company Operations Reference.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Autonomous Company Operations Reference
> **Part of**: [[133 Autonomous Company V01 Experiment]]
> **Quickstart**: `docs/vault/Specs/135 Per Persona Middleware Specs/135 Per Persona Middleware Specs - quickstart.md`
## Infrastructure Stack
```
┌─────────────────────────────────────────────────────┐
│ Slack │
│ ┌─ Old Growth Coordinator ┌─ Old Growth GTM │
│ │ Socket Mode WebSocket │ Socket Mode WebSocket│
│ └──────────┬───────────────└──────────┬────────────│
│ │ │ │
│ app_mention announce-releases │
│ /internal/approval-callback │
└─────────────┼──────────────────────────┼─────────────┘
│ │
┌────────▼──────────┐ ┌─────────▼────────┐
│ Coordinator Runtime│ │ GTM Runtime │
│ :9100/health │ │ :9101/health │
│ :9100/metrics │ │ :9101/metrics │
└────────┬───────────┘ └────────┬──────────┘
│ │
└──────────┬───────────────┘
│
┌──────────▼───────────┐
│ OpenCode HTTP API │
│ localhost:8080 │
└──────────────────────┘
┌─────────────────────┐
│ OTel Collector │
│ localhost:4317 │
│ → logs/otel/ │
└─────────────────────┘
┌───────────────────┐ ┌────────────────────┐ ┌──────────────────┐
│ Valkey │ │ ngrok TCP tunnel │ │ GitHub Actions │
│ localhost:6379 │ │ 1.tcp.us-cal-1 │ │ release.yml │
│ AUTH required │ │ .ngrok.io:24971 │ │ → releases.shipped
└───────────────────┘ └────────────────────┘ └──────────────────┘
```
## Quick Commands
### Start Everything (after reboot)
```bash
cd /Users/joshburt/Workbench/Repositories/oldgrowth
set -a; source .env; set +a
make recovery
```
This single command brings all 6 components online in dependency order with per-component timeouts. For details: `make _recovery_help`
### Check Health
```bash
make personas-status
```
Or manually:
```bash
# Valkey
valkey-cli -a $VALKEY_AUTH_TOKEN ping # → PONG
# ngrok
curl -s http://localhost:4040/api/tunnels | jq .tunnels[0].public_url
# Persona health endpoints (spec 142)
curl -s http://localhost:9100/health # Coordinator (base + liveness)
curl -s http://localhost:9101/health # GTM
# Spec 143 health dashboard (per-persona)
Each persona runtime serves its own HTML dashboard at its health port. The dashboard
provides the same card layout regardless of which persona you open — data is scoped to
that process's perspective.
```bash
open http://localhost:9100 # Coordinator dashboard
open http://localhost:9101 # GTM dashboard
```
Both dashboards expose the same JSON API endpoints:
| Endpoint | Purpose |
|---|---|
| `/` | Redirects to `/dashboard` |
| `/health` | Base liveness + version + restart count + socket mode |
| `/dashboard` | Full HTML browser dashboard (auto-refresh 10s) |
| `/metrics` | Prometheus metrics (`og_*` counters) |
| `/health/personas` | Persona roster + liveness per agent + tags + capabilities + Letta agent IDs |
| `/health/circuits` | Circuit breaker states with consecutive 429 counts |
| `/health/threads` | Slack echo thread statistics |
| `/health/audit-stats` | Audit log entry distribution by status |
| `/health/recent-errors` | Last N error entries from the audit log |
| `/health/api-stats` | LLM/OpenCode call volumes, app mentions, per-persona breakdown |
| `/health/events` | Recent audit log entries (all statuses, real-time feed) |
| `/health/message-latency` | Average/max/min inter-agent message transit time |
| `/health/letta-probe` | Letta server reachability check |
| `/health/opencode-orphans` | Stale OpenCode session count |
| `/health/cost` | Weekly LLM cost summary (by model) |
| `/health/config` | Runtime configuration values (ports, timeouts, heartbeat) |
| `/health/orphans` | Orphaned inter-agent messages |
| `POST /health/orphans/resolve` | Resolve orphaned messages |
**Dashboard features:**
- Persona cards with tags, capability chips, Letta agent IDs, heartbeat status
- Dark/light theme toggle (persisted to localStorage)
- Human-readable uptime formatting
- Restart counter (file-persisted across process restarts)
- Socket mode status in summary bar
- Version identifier (git commit hash) in header
- Real-time event feed, message latency stats, Letta reachability probe
- Circuit breaker details (consecutive 429 count)
- Weekly cost summary per model
- Config table with key runtime settings
# Prometheus metrics
curl -s http://localhost:9100/metrics | grep og_ # Coordinator metrics
# OTel collector
curl -s http://localhost:8888/metrics | head -3 # Collector health
# OpenCode
curl -s http://localhost:8080/api/v1/sessions \
-H "Authorization: Bearer $OPENCODE_SERVE_TOKEN"
```
### Stop Everything
```bash
make personas-down
# ngrok: Ctrl+C or launchctl unload
# Valkey: brew services stop valkey
```
### Restart a Persona
```bash
make personas-down # stop all
make personas-up # start all
```
Or for a single persona:
```bash
launchctl unload ~/Library/LaunchAgents/com.oldgrowth.coordinator.plist
launchctl load ~/Library/LaunchAgents/com.oldgrowth.coordinator.plist
```
**What happens on restart (spec 143)**:
- Thread state (`personas/run/threads.json`) is loaded and pruned of entries >24h old
- Orphan janitor daemon starts automatically (60s cycle, detection-only)
- Health dashboard becomes available at all spec 143 endpoints immediately
- Background persistence flusher (5s cycle) saves thread metadata changes automatically
- Existing echo threads remain routable — human replies in known threads are not rejected after restart
```
## Component Reference
### Valkey (local, port 6379)
| Action | Command |
|---|---|
| Status | `make personas-status` or `valkey-cli -a $VALKEY_AUTH_TOKEN ping` |
| Start (cold boot) | `make recovery` — all 6 components in dependency order |
| Start | `brew services start valkey` |
| Stop | `brew services stop valkey` |
| Restart | `brew services restart valkey` |
| Stream count | `valkey-cli -a $VALKEY_AUTH_TOKEN XLEN releases.shipped` |
| Read stream | `valkey-cli -a $VALKEY_AUTH_TOKEN XRANGE releases.shipped - +` |
| Config | `/opt/homebrew/etc/valkey.conf` — `requirepass <token>` |
### ngrok TCP Tunnel
| Action | Command |
|---|---|
| Status | `make personas-status` or `curl -s localhost:4040/api/tunnels` |
| Start | `ngrok start --all --config ~/Library/Application\ Support/ngrok/ngrok.yml` |
| Stop | `launchctl unload ~/Library/LaunchAgents/com.ngrok.tunnel.plist` |
| Logs | `cat ~/Library/Logs/ngrok.log` |
| Config | `/Users/joshburt/Library/Application\ Support/ngrok/ngrok.yml` |
| Reserved TCP | `1.tcp.us-cal-1.ngrok.io:24971` (ID: `rd_39lBaWDzYl2EXB4mr2vAe76yEq2`) |
| Dashboard | https://dashboard.ngrok.com |
**Setup runbook**: `docs/vault/Reference/Autonomous Company/Ngrok Tunnel Setup.md`
### Letta (Docker — LLM Agent Server)
Letta agents provide **persistent conversation memory** for each persona. Runs in a Docker container with PostgreSQL backend.
| Action | Command |
|---|---|
| Status | `make personas-status` |
| Start | `make docker-letta-up` or `docker start letta-server` |
| Stop | `make docker-letta-down` or `docker stop letta-server` |
| Logs | `make docker-letta-logs` or `docker logs letta-server` |
| Auto-start | `launchctl load ~/Library/LaunchAgents/com.letta.server.plist` (starts container at login) |
| Agent IDs | `personas/letta_agents.json` |
| API | `http://localhost:8283/v1` — REST API for agent interactions |
| Data | `~/.letta/.persist/pgdata/` — PostgreSQL data (persists across restarts) |
| First-time setup | `docker run -d --name letta-server -v ~/.letta/.persist/pgdata:/var/lib/postgresql/data -p 8283:8283 -e OPENROUTER_API_KEY="..." letta/letta:latest` |
| Recreate agents | `python3 -c "from letta_client import Letta; c = Letta(base_url='http://localhost:8283'); print([a.name for a in c.agents.list()])"` |
**Full restart sequence**:
```bash
# Stop everything
make personas-down
make docker-letta-down
# Start everything
make docker-letta-up
sleep 10 # wait for PostgreSQL + server ready
make personas-up
```
**Agent memory**: Each agent has core memory blocks (`persona` + `human`) that persist across server restarts. Agents automatically update their memory (archival/recall) during conversations. To reset a persona's memory:
1. Delete the agent: `client.agents.delete(agent_id)`
2. Recreate via `scripts/personas/create-letta-agents.py` (if created)
### Inter-Agent Messaging (spec 142)
Inter-agent messaging enables the Coordinator persona to send structured messages to GTM's Letta agent, with Slack thread echo for human observation.
| Action | Command |
|---|---|
| View audit log | `tail -f personas/logs/inter_agent.jsonl \| jq .` |
| View overflow log | `tail -f personas/logs/inter_agent_payloads.jsonl \| jq .` |
| Count messages sent | `wc -l < personas/logs/inter_agent.jsonl` |
| Filter by status | `jq 'select(.status == "acked")' personas/logs/inter_agent.jsonl` |
| View circuit breaker state | `python3 -c "from personas.circuit_breaker import _BREAKERS; print(_BREAKERS)"` |
| Check peer health | `python3 -c "from personas.health_state import _HEALTH; print(_HEALTH)"` |
| Run inter-agent tests | `.venv/bin/python -m pytest personas/tests/ -q` |
| Run all tests (195) | `.venv/bin/python -m pytest personas/tests/ -q` (195 pass, 15 skip) |
**Key files**:
| File | Purpose |
|---|---|
| `personas/inter_agent.py` | Async send(), state machine, audit log, per-agent locking |
| `personas/slack_echo.py` | Slack thread echo, human injection with auth gate + rate limit |
| `personas/circuit_breaker.py` | Per-target breaker (5× 429 → OPEN, 30s cooldown) |
| `personas/health_state.py` | Heartbeat-based peer liveness (UP/DOWN) |
| `personas/logs/inter_agent.jsonl` | JSONL audit log — one entry per state transition |
| `personas/logs/inter_agent_payloads.jsonl` | Overflow log for payloads > PIPE_BUF (4096 bytes) |
### OTel Collector
| Action | Command |
|---|---|
| Status | `launchctl list \| grep otel` |
| Install | `scripts/personas/install-otel-collector.sh` |
| Traces file | `logs/otel/v0.1-traces.json` |
| Config | `~/Library/Application Support/otel-collector/config.yaml` |
### MCP Tool Configuration (spec 140)
Tool assignments for persona agents are managed through a manifest file and the `make mcp-setup` / `make mcp-status` commands. Persona runtimes do not read the manifest at startup — tool state is reconciled independently of runtime lifecycle.
| Action | Command |
|---|---|
| Setup | `make mcp-setup` — register MCP servers + attach tools per manifest |
| Status | `make mcp-status` — show registrations, per-agent tools, host-tools health |
| Manifest | `docs/vault/Reference/Autonomous Company/trusted_agent_mcp_manifest.json` |
| Server ports | Host-tools: port 9105 (Streamable HTTP); Obsidian: stdio via `npx` |
**Recovery dependency chain**: Letta server → host-tools MCP (port 9105) → personas healthy → `make mcp-setup`. No persona restart required — tool attachment is live via Letta API per spec 140 FR-003.
**Cross-references**:
- [[141 Auto Restart Recovery]] — port allocation table includes 9105 (host-tools)
- [[142 Persona Inter Agent Messaging]] — inter-agent messaging uses same Letta infrastructure
- [[143 Inter Agent Extensions]] — `/health` endpoint contract; `/health/personas` aggregator polls host-tools health
- [[144 Consolidate Notification Channel]] — host-tools emits `status`, `error`, and `approval` notifications per spec 144; no `tool_event` or `tool-start` values are emitted
### GitHub Actions Secrets
Set on 2 repos (oldgrowth, oldgrowth-assets). old-growth-wiki repo and its CI were removed 2026-05-10 — vault rendering is now local-only via `wiki/`.
| Secret | Value |
|---|---|
| `VALKEY_NGROK_HOST` | `1.tcp.us-cal-1.ngrok.io` |
| `VALKEY_NGROK_PORT` | `24971` |
| `VALKEY_AUTH_TOKEN` | (from 1Password `Valkey AUTH Token (Local Dev)`) |
### Slack Apps
| App | Tokens in `.env` | Bot Scopes |
|---|---|---|
| Old Growth Coordinator | `SLACK_BOT_TOKEN_COORDINATOR`, `SLACK_APP_TOKEN_COORDINATOR` | 8 (chat:write, chat:write.public, channels:history, channels:read, groups:history, im:history, im:write, app_mentions:read) |
| Old Growth GTM | `SLACK_BOT_TOKEN_GTM`, `SLACK_APP_TOKEN_GTM` | 10 (above + files:read, files:write) |
**Registration runbook**: `docs/vault/Reference/Autonomous Company/Slack App Registration Runbook.md`
**Kill-switch runbook**: `docs/vault/Reference/Autonomous Company/Slack App Kill-Switch Runbook.md`
### Workflow Builder
| Item | Value |
|---|---|
| Workflow name | `gtm-class-b-approval` |
| Workflow ID | `Wf0B2QQJP0K0` |
| Channel | `#announce-releases` |
| Export | `docs/vault/Reference/Autonomous Company/slack/gtm-class-b-approval.json` |
### .env Reference
```bash
# Required for persona runtimes:
SLACK_BOT_TOKEN_COORDINATOR=xoxb-...
SLACK_APP_TOKEN_COORDINATOR=xapp-...
SLACK_BOT_TOKEN_GTM=xoxb-...
SLACK_APP_TOKEN_GTM=xapp-...
OPENROUTER_API_KEY=sk-or-...
ANTHROPIC_API_KEY=sk-ant-... # failover
OPENCODE_SERVE_TOKEN=... # for opencode serve
# Required for GitHub Actions → local Valkey:
VALKEY_AUTH_TOKEN=...
VALKEY_NGROK_HOST=1.tcp.us-cal-1.ngrok.io
VALKEY_NGROK_PORT=24971
# Optional:
PERSONA_HEALTH_PORT=9100 # Coordinator (default)
PERSONA_LOG_LEVEL=INFO # or DEBUG for full LLM logs
# Inter-Agent Messaging (spec 142):
PERSONA_LETTA_SEND_TIMEOUT_SEC=60 # default 60s
PERSONA_LETTA_STREAM_TIMEOUT_SEC=600 # default 600s
PERSONA_MAX_HOPS=5 # default 5
PERSONA_AUTHORIZED_HUMAN_IDS=U111,U222 # comma-separated Slack user IDs
PERSONA_HUMAN_INJECTION_RATE_LIMIT_PER_MIN=10
PERSONA_HEARTBEAT_INTERVAL_SEC=30 # peer liveness ping interval
PERSONA_HEARTBEAT_TIMEOUT_SEC=90 # peer considered DOWN after this silence
PERSONA_INTER_AGENT_OWNER_LOCK=personas/run/inter_agent.owner
PERSONA_SLACK_ECHO_CHANNEL=C12345 # #team-personas channel ID
PERSONA_BOT_USER_IDS=U111,U222 # persona bot Slack user IDs (skip these in echo)
```
## Week 0 Artifacts
| Artifact | Path |
|---|---|
| Session log | `docs/vault/Sessions/2026-05-09 Spec 134 Week 0 Setup.md` |
| Slack channels | 14 created (general, announce-ops, announce-personas, announce-releases, team-personas, team-dev, proj-v01-experiment, help-spec, help-debug, sys-observer, sys-tts, sys-vault-audit, sys-release, social-wins) |
| Persona icons | `src/assets/icons/personas/` (7 icons, SDXL generated by `scripts/personas/generate-icons.py`) |
| Icon pipeline | `scripts/personas/generate-icons.py` |
| Spec 134 tasks | `docs/vault/Specs/134 Per Persona Slack App Registration/134 Per Persona Slack App Registration - tasks.md` |
| Spec 135 tasks | `docs/vault/Specs/135 Persona Middleware Specs/135 Persona Middleware Specs - tasks.md` |
## Letta Cloud Migration
The inter-agent messaging implementation in spec 142 (`personas/inter_agent.py`) targets a self-hosted Letta server (`letta/letta:latest` Docker image, port 8283) and dispatches messages via `client.agents.messages.create(agent_id=..., messages=[...])`. This call is the long-term, stable, cross-deployment Letta SDK surface.
Some Letta tutorials and older documentation reference the higher-level multi-agent helper `send_message_to_agent_async`. **This helper is not available on Letta Cloud** (the managed SaaS offering). It is only present in self-hosted Letta server builds with the multi-agent core tools enabled.
If migrating personas to Letta Cloud (FR-013):
| Concern | Self-hosted (current) | Letta Cloud (target) |
|---|---|---|
| Message dispatch | `client.agents.messages.create(...)` | `client.agents.messages.create(...)` (unchanged) |
| Multi-agent helper | `send_message_to_agent_async` available | Not available — use `send_message_to_agent_and_wait_for_reply` instead |
| Server URL | `http://localhost:8283` | `https://app.letta.com` (or region-specific) |
| Auth | None (local) or local API key | `LETTA_API_KEY` (Bearer token) |
| Block API | Same | Same |
| Agent IDs | Stable per-server | Migration requires recreating agents and updating `personas/letta_agents.json` |
**No code change is required for the Coordinator → GTM happy path.** The `inter_agent.send()` helper already uses `client.agents.messages.create()` and does not depend on `send_message_to_agent_async`. To switch deployments, set `LETTA_BASE_URL` and `LETTA_API_KEY` in `.env` and recreate persona agents on the target server, updating `letta_agent_id` in `personas/letta_agents.json` accordingly.
If a future spec requires the synchronous multi-agent fan-out pattern, replace `client.agents.messages.create()` with `client.agents.send_message_to_agent_and_wait_for_reply()` — both are supported on Cloud and self-hosted.
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-car-radio: domain/vehicle
Tag assignment · test- Gold
- The note calls for `domain/vehicle`: The Samba: vehicle physics, dashboard, damage, panels (True)
- ember
- The note has no reason to carry `domain/vehicle` (False)
- Signal
- P(true) 0.24 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Audio — Car Radio.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — Car Radio
The 1963 Samba's dash-mounted AM/FM radio. Sole narrative voice in the world. Plays until it cannot — then the forest takes the frequency. This note captures the full authoritative specification: physical design, biome-signal model, channel roster, DJ layer, forest transmission, AI audio generation pipeline, post-processing chain, and Godot implementation architecture.
## Overview
The radio is zone-reactive and disturbance-reactive. It pulls from all music, all eras, all genres — folk, Nordic experimental folk, grunge, jazz, dark ambient, anything that has ever been broadcast. The van's hardware processes everything through its warm narrow AM/FM fidelity, flattening decades of music into a single timeless signal.
Signal quality, available channels, and audio character all degrade as the player moves through the three biomes. When the signal finally dies completely, the forest itself begins transmitting through the speakers — not a metaphor, a documented mechanical behavior (World Bible §16).
All audio content — music, DJ voice, SFX, biome sounds — is AI-generated: Suno for music/SFX, ElevenLabs for voice. Generation is fast and inexpensive, enabling deep track pools (**15–20 tracks per music channel**, proportionally deep DJ/talk/news/forest pools). Repetition is not a concern across multi-hour sessions.
The radio's decay is a non-verbal storytelling device. Losing the music means losing the last thread to the familiar world. The moment the player realizes the forest is now the broadcaster is a designed horror beat.
## Design Goals
- **Immersion**: Audio must feel consistent with van's hardware — warm, narrow, slightly compressed AM/FM fidelity. The hardware is the filter; the music is timeless.
- **Variety**: Seven channels spanning folk, Nordic experimental folk, talk radio, dark ambient/drone, jazz, news, and grunge — each with distinct identity and emotional role.
- **Physical interaction**: Radio is a 3D object in the dash — player must look at it and manipulate knobs directly; no abstract keybinds.
- **Spatial audio**: Sound routes through `AudioStreamPlayer3D` nodes at the three speaker positions in the cab, not non-positional players.
- **Reactivity**: Functions only when player is inside the Samba.
- **Biome degradation**: Signal quality, channel availability, and physical dial display decay across biomes.
- **Disturbance reactivity**: High disturbance accelerates signal degradation independent of biome.
- **DJ layer**: Distant narrator voices between-track commentary that degrades in parallel with the music signal.
- **Forest transmission**: When all signal lost, the forest transmits procedural unsettling audio through the speakers.
- **Polish**: Tuning sweep SFX, needle animation, dial illumination, pilot light state all reflect current signal condition.
## Physical Radio Design
### The Unit
Period-correct dash-mounted AM/FM receiver installed in Samba's center dashboard fascia. Modeled as distinct 3D object — an `InteractableArea3D` with its own mesh and material, NOT a UI overlay. Player must look directly at it and trigger interaction.
Faceplate: brushed chrome with black surround (German automotive radio aesthetics of the era). Slightly recessed into the dash. Shows wear: fine scratches across chrome, hairline crack in lower-left corner of dial glass.
### Face Layout
```
┌─────────────────────────────────────────────┐
│ [●] ┌─────────────────────┐ [TUNING ] │
│ PWR │ ~~~~~KOLD 88.1~~~~ │ [ KNOB ] │
│ VOL │ 88 94 101 108 │ │
│ KNOB │ ● │ [1][2][3][4]│
│ └─────────────────────┘ PRESETS │
└─────────────────────────────────────────────┘
```
- **Dial window**: rectangular backlit frequency display with etched FM band markings (88–108 MHz). Physical needle sweeps left to right as tuning knob turns. Backlight is warm amber incandescent in Verdant. Dims and flickers in Fungal Depths. In Petrifold, dial goes dark; needle drifts to unmarked position between stations.
- **Power indicator**: small round pilot light left of dial. Glows amber when on. Flickers erratically at high disturbance. In Petrifold, turns deep red (only element that remains lit).
- **Station label**: narrow backlit text above dial displaying current station ID (e.g. `KJAZ 101.3`). Fades to `-- --.--` when signal lost.
### Knobs
**Left knob — Power/Volume** (large ribbed bakelite): push inward (click) to toggle; rotate to adjust volume. Physical detent at off position (click). Hard stops at each rotation limit.
**Right knob — Tuning** (smaller smooth-edged chrome): rotate to sweep needle across dial. Stations do NOT snap automatically — player must tune to correct frequency. Valid stations produce increasing signal as needle approaches, resolving to clear audio when centered. Passing through unavailable station (out of range for current biome) produces only static regardless of needle position. Rotates freely with no hard stops.
**Preset buttons** (four rectangular push buttons below dial, labeled 1–4): each stores last manually tuned frequency. Pressing instantly moves needle to stored frequency and plays that station if signal available. Presets persist for run duration; reset on death/extraction. In Petrifold, pressing a preset produces brief needle movement followed by dead static — mechanical echo of signal that no longer exists.
### Speakers
Samba has three speaker locations. Audio routes to `AudioStreamPlayer3D` nodes positioned at each location (not non-positional players). Sound originates from within cab geometry, consistent with first-person interior view.
| Speaker ID | Location | Node Position (local to bus) |
|---|---|---|
| `SpeakerDash` | Center dashboard, directly above radio | ~(0, 0.6, 0.8) |
| `SpeakerDoorL` | Left door card, mid-height | ~(−0.8, 0.2, 0.3) |
| `SpeakerDoorR` | Right door card, mid-height | ~(0.8, 0.2, 0.3) |
All three play same stream simultaneously. `SpeakerDash` carries slightly higher volume than door speakers (+2 dB). Attenuation `ATTENUATION_DISABLED` or very large max distance — audio should not fall off inside cab, only when player exits. On exit: speaker audio attenuates naturally by distance (`AudioStreamPlayer3D` spatial falloff), creating effect of muffled radio audio drifting from parked van.
## Biome-Based Signal Model
Radio signal degrades across the three canonical biomes. Biome is the authoritative signal context (replaces an earlier generic zone-depth float).
| Biome | Signal State | Music Presence | DJ Present | Forest Transmission |
|---|---|---|---|---|
| Verdant Old Growth | Full signal, warm AM clarity | All channels | Active, clear | None |
| Fungal Depths | Degraded — narrow EQ, crackle, dropout | 2–3 channels remain | Fragmented, cutting in and out | Begins bleeding in beneath static |
| The Petrifold | Near-total loss | No music channels | Gone | Dominant — forest is the only broadcaster |
Biome transitions blend over ~3 seconds of travel time (matching World Bible blend timing). Radio mirrors this — signal character shifts over same 3-second window, not instantly.
### Disturbance Modifier
Secondary degradation pressure additive to biome-based degradation.
| Disturbance State | Signal Modifier |
|---|---|
| Calm (0–30) | No modifier — baseline biome signal |
| Uneasy (30–60) | Mild additional static, slight EQ narrowing |
| Agitated (60–85) | Moderate crackle, occasional dropout |
| Storm (85–100) | Heavy degradation, channels at risk of cutting out regardless of biome |
Agitated/Storm in Verdant will sound closer to Fungal Depths radio than Calm in same location.
## Radio Channels
### Base Channels
| Channel ID | Freq | Genre | Mood |
|---|---|---|---|
| `ch_folk` | KOLD 88.1 | Folk / Acoustic | Forest isolation, melancholy |
| `ch_nordic` | KFRØ 92.1 | Nordic Experimental Folk | Hymn-like, reverent, building dread — the forest's own music |
| `ch_talk` | KTLK 93.3 | Talk Radio | Conversational, familiar, increasingly wrong |
| `ch_drone` | KDRN 95.9 | Dark Ambient / Drone | Slow dread, subsonic unease, formless tension |
| `ch_jazz` | KJAZ 98.5 | Jazz | Cool, sparse, eerie after dark |
| `ch_news` | KNWS 103.7 | News / Public Radio | Calm authority, unsettling when it fractures |
| `ch_grunge` | KGNG 105.9 | Grunge (includes punk tracks) | Raw, distorted, sludgy catharsis and defiant urgency |
| `ch_static` | — | Dead Air / Static | Between stations, realism |
Each music channel has **15–20 tracks** to cycle through. News and talk require dedicated content pools.
### Biome Channel Availability
Higher-band channels attenuate first (grounded in real RF physics — higher frequencies more absorbed by dense organic matter — and doubles as designed emotional arc).
| Biome | Available Channels |
|---|---|
| Verdant Old Growth | All channels |
| Fungal Depths | `ch_folk`, `ch_nordic`, `ch_talk`, `ch_drone`, `ch_jazz`, `ch_static` — news (103.7) and grunge (105.9, highest freq) drop out |
| The Petrifold | `ch_static` only — all music and talk gone; forest transmission active |
Unavailable channel tuned = heavy static only. Dial position exists. Signal does not. **Folk hangs on the longest** — lowest-band station, most earthbound. Last music player hears before silence is a lone acoustic guitar fading into static. Intentional.
Talk radio and news degrade uniquely before dropping out (see below). They do not simply cut to static — they fracture.
### Forest Transmission (`ch_forest`)
Active only in Petrifold and at Storm-level disturbance in Fungal Depths. Not a station. HUD displays nothing — no frequency, no label. The forest does not identify itself.
Audio is procedural at runtime: ghost fragments layered stochastically with randomized pitch, volume, gap timing. Should never resolve into anything fully recognizable. Three `AudioStreamPlayer3D` nodes run independently with random restart delays — no two playthroughs produce same sequence.
## News & Talk Radio
News and talk degrade differently from music. Music fades to static — a signal dying. News and talk **fracture** — the content itself becomes wrong before signal fails. Distinct horror vector: familiar voices of authority and companionship turning unreliable.
### Talk Radio (`ch_talk` — KTLK 93.3)
Late-night call-in format. Host takes calls, gives advice, tells stories. Content deliberately generic — no specific cultural references, dates, real-world events. Should feel like any late-night AM talk show from any decade.
- **Verdant**: normal talk radio. Warm, rambling, companionable. Host calm, personable. Callers discuss mundane things — road conditions, weather, local gossip, sleepless nights. Background ambiance: faint phone-line hum, occasional hold music bleed.
- **Fungal**: host begins repeating phrases. Callers' questions don't quite match host's responses. Long pauses where host seems to be listening to something off-mic. A caller describes a road the player is currently on — but wrong. Hold music between segments slightly too slow. Sentences trail off mid-thought and restart from different point.
- **Petrifold**: signal lost. Static only.
See [[Narrative Direction]] for Gary's character spec (KTLK DJ — spec 052).
### News Radio (`ch_news` — KNWS 103.7)
Public radio news format. Calm, measured delivery. Weather, traffic, regional reports. No specific dates, locations outside the game world, or real-world references.
- **Verdant**: standard news broadcast. Professional, reassuring. Weather reports reference fog and rain in "the valley." Traffic advisories mention "route closures in the forest service road network." Tone is NPR-calm.
- **Fungal**: newsreader's cadence becomes slightly off — pauses in wrong places, emphasis on wrong syllables. Reports begin referencing events that haven't happened or locations that don't exist. Weather forecasts describe conditions that contradict what player sees. Story loops back to its beginning mid-sentence. Newsreader occasionally addresses listener directly — "You should turn around now" — then continues as if nothing happened.
- **Petrifold**: signal lost. Static only.
### News & Talk Audio Generation
Generate clips in tiers matching the three biome states:
- **Clean clips (Verdant — 20–25 clips per channel, 15–45 seconds each)**: normal broadcast content. Generic, warm, professional. No era markers.
- **Fracturing clips (Fungal — 15–20 clips per channel, 15–60 seconds each)**: pre-scripted wrongness. Repetitions, mismatched Q&A, direct address to listener, factual contradictions. Authored, not procedurally broken — uncanny valley is in the *content*, not the signal quality.
Fracturing clips processed through same `RadioDegraded` bus as music, but content-level wrongness is baked into the recordings themselves. Signal degradation and content fracturing compound — voice sounds worse AND says wrong things simultaneously.
### News & Talk Channel Type
News and talk channels use distinct `RadioChannel` configuration:
```gdscript
@export var is_talk_format: bool = false
@export var content_clean: Array[AudioStream] = [] # Verdant pool
@export var content_fracturing: Array[AudioStream] = [] # Fungal pool
```
When `is_talk_format` is true, channel cycles through content pool continuously rather than playing discrete tracks with DJ interludes. No DJ layer on talk/news channels — the content IS the voice.
## DJ Layer
Distant narrator voices between-track commentary degrading alongside music. Distinct audio layer from music tracks — runs as `AudioStreamPlayer3D` on `Radio` bus, interleaved between tracks. **Applies to music channels only.** Talk and news have their own voice content.
| Biome | DJ State |
|---|---|
| Verdant | Clear, present — brief between-track commentary, station IDs, weather mentions |
| Fungal | Fragmented — sentences cut out mid-word, long silences between fragments, pitch slightly unstable |
| Petrifold | Absent — DJ channel produces only hiss or silence |
### DJ Audio Generation
Generate in ElevenLabs (preferred for voice quality/control) or Suno. 5–15 seconds each. Content feels generic and timeless — nothing narratively specific, no references to specific years, decades, cultural moments. The DJ exists in the same undefined time as the world.
Examples:
- *"…coming up next on KOLD, more of the sounds keeping you company out there on those long forest roads…"*
- *"…weather service is reporting fog through the valley tonight, drive careful…"*
- *"…[mid-sentence cut]…if you're just joining us…[static]…"*
- *"…[long silence]…[faint tonal hum]…"* (Fungal fragment variant)
Generate 15–20 clean clips and 10–12 degraded/fragmented clips. Degraded clips are pre-processed — runtime system selects from appropriate pool based on biome.
## AI Music Generation
Recommended tool: **Suno (Pro/Premier plan for commercial rights)**. Generate WAV exports; all tracks are processed through the AM/FM radio EQ pipeline — the hardware flattens everything into warm, narrow fidelity regardless of genre.
### Base Track Prompts
**`ch_folk` (KOLD 88.1, Folk/Acoustic)**
```
acoustic folk radio, fingerpicked guitar, melancholic, sparse, lo-fi AM radio quality, no drums, fingerstyle, minor key, intimate and lonely
```
**`ch_nordic` (KFRØ 92.1, Nordic Experimental Folk)**
```
Nordic experimental folk, violin melody, raw female vocals, layered choir harmonics, sparse percussion building to full drums, hymn-like, atmospheric, reverent, building crescendo, Scandinavian folk, choral
```
**`ch_drone` (KDRN 95.9, Dark Ambient/Drone)**
```
dark ambient, deep drone, subsonic bass, slow evolving texture, analog synthesizer, formless tension, barely perceptible melody, cavernous reverb, industrial hum, unsettling calm, no rhythm, no vocals
```
**`ch_jazz` (KJAZ 98.5, Jazz)**
```
jazz, late night, upright bass, brushed drums, muted trumpet, piano comping, smoky and sparse, cool jazz, small club feel
```
**`ch_grunge` (KGNG 105.9, Grunge)**
```
grunge, heavy distorted guitar, sludgy tempo, raw vocals, minor key, thick bass, lo-fi recording quality, cathartic and angry, Seattle sound, droning feedback
```
### Forest Transmission Prompts (Petrifold / Storm)
Forest transmission fragments generated via Suno with deliberately corrupted prompts. Examples:
- *distant voice fragments through radio static, half-formed words, no coherent language, 15-30 seconds*
- *whispered phrases at the edge of hearing, incoherent but almost a word, layered and overlapping, horror sound design*
- *mycelium network audio — biological transmission through ground, wet and organic, barely a voice, 20-40 seconds*
Three independent `AudioStreamPlayer3D` nodes play these with randomized start delays, pitch variance, volume variance. No pattern, no resolution.
### Static / Tuning SFX
Pre-generated static sweep SFX used when tuning through inter-station dead air. Multiple variants: clean static (Verdant), static-with-whisper-bleed (Fungal), static-with-forest-transmission-overlay (Petrifold). Looped or played as one-shots depending on knob movement speed.
## Audio Post-Processing Pipeline
### Base pipeline (all music and DJ tracks)
All AI-generated audio passes through a post-processing chain before shipping:
1. **Gain normalization** — consistent LUFS target across all tracks
2. **AM/FM band-pass filtering** — narrow the frequency range to simulate period-correct broadcast fidelity (rolls off sub-60 Hz and high-8 kHz)
3. **Gentle compression** — broadcast-style level consistency
4. **Clean export** — OGG, shipped on `Radio` bus
### Forest transmission additional processing
On top of base pipeline:
5. **Heavy bit-crush / degradation** — push into digital artifact territory
6. **Irregular pitch modulation** — subtle wow-and-flutter simulation
7. **Stochastic gap insertion** — random silence breaks and restarts
8. **Additional band-pass narrowing** — below what the radio hardware normally allows; the frequency that the forest uses is narrower than the broadcast frequency
The full audio-processing pipeline invocation (Makefile + ffmpeg) is documented in [[Audio — Pipeline]]. Shipping OGGs live in the audio submodule under `src/assets/audio/radio/`.
## Godot Implementation
### Audio Bus Architecture
Radio system uses **two dedicated buses**:
| Bus | Purpose | Effects |
|---|---|---|
| `Radio` | Clean radio broadcast content (music, DJ talk, station IDs, news, talk show) | `AudioEffectBandpass` (500 Hz–6 kHz), `AudioEffectCompressor` (broadcast-style), gentle reverb |
| `RadioDegraded` | Degraded signal | `AudioEffectDistortion` (mild), `AudioEffectLowpass` (aggressive rolloff), `AudioEffectHighpass` (narrower), `AudioEffectReverb` (small-room), `AudioEffectDelay` (stutter) |
The runtime crossfades between Radio and RadioDegraded buses based on biome × disturbance state. Wet/dry ratio is continuously controlled by `car_radio.gd`.
### Scene Structure
```
CarRadio (Node3D — attached to dashboard)
├── InteractableArea3D # player-look activation
├── RadioUnit (MeshInstance3D) # the physical radio mesh
│ ├── DialWindow (MeshInstance3D — backlit)
│ ├── Needle (MeshInstance3D — animated)
│ └── PowerLight (OmniLight3D)
├── LeftKnob (Node3D) # power/volume, rotation state
│ └── KnobInteractable
├── RightKnob (Node3D) # tuning, rotation state
│ └── KnobInteractable
├── PresetButtons (Node3D)
│ ├── Preset1 through Preset4 (InteractableArea3D each)
├── SpeakerDash (AudioStreamPlayer3D) # → Radio bus (clean) or RadioDegraded
├── SpeakerDoorL (AudioStreamPlayer3D)
├── SpeakerDoorR (AudioStreamPlayer3D)
├── ForestTransmission (Node3D)
│ ├── TransmissionSource1 (AudioStreamPlayer3D) # → Horror bus
│ ├── TransmissionSource2 (AudioStreamPlayer3D)
│ └── TransmissionSource3 (AudioStreamPlayer3D)
└── car_radio.gd
```
### Enums
```gdscript
enum BiomeSignal { VERDANT, FUNGAL, PETRIFOLD }
enum RadioState { OFF, ON_TUNED, ON_STATIC, ON_TRANSMISSION }
enum ChannelType { MUSIC, TALK, NEWS, STATIC, FOREST }
```
### Resource: `RadioChannel`
```gdscript
class_name RadioChannel extends Resource
@export var channel_id: String # "ch_folk", etc
@export var freq_mhz: float # 88.1, etc
@export var station_name: String # "KOLD 88.1"
@export var genre_label: String # "Folk / Acoustic"
@export var channel_type: ChannelType
@export var is_talk_format: bool = false
# For music channels:
@export var tracks_clean: Array[AudioStream] = []
@export var tracks_degraded: Array[AudioStream] = []
@export var dj_clips_clean: Array[AudioStream] = []
@export var dj_clips_degraded: Array[AudioStream] = []
# For talk/news channels:
@export var content_clean: Array[AudioStream] = []
@export var content_fracturing: Array[AudioStream] = []
# Biome availability
@export var available_in_verdant: bool = true
@export var available_in_fungal: bool = true
@export var available_in_petrifold: bool = false
```
### `radio_zone_manager.gd` (autoload singleton)
Subscribes to biome and disturbance changes. Provides `biome_changed` and `disturbance_changed` signals. Exposes `get_signal_quality(channel) -> float` returning a 0.0–1.0 value representing the wet/dry ratio between Radio and RadioDegraded buses for a given channel in the current biome × disturbance × per-channel attenuation state.
### `car_radio.gd` — the runtime
Core responsibilities:
- Manage radio state (OFF / ON_TUNED / ON_STATIC / ON_TRANSMISSION)
- Track current tuned frequency (continuous float, not snapped)
- Frequency-to-station lookup with tolerance window
- Per-channel track / DJ / content rotation
- Wet/dry bus crossfade driven by `RadioZoneManager.get_signal_quality()`
- Forest-transmission node orchestration (three sources, independent random schedules)
- Needle animation on tuning
- Pilot light color state (amber / red / off)
- Dial backlight brightness
- Preset storage + recall
- Input context gate (radio controls active only when player is looking at the radio)
- Volume knob integration with Radio bus gain
```gdscript
# car_radio.gd
extends Node
@export var channels: Array[RadioChannel] = []
@export var forest_fragments: Array[AudioStream] = []
@export var tuning_sfx_clean: AudioStream
@export var tuning_sfx_degraded: AudioStream
var current_channel := 0
var current_track := 0
var radio_on := false
var current_biome: Biome = Biome.VERDANT
var current_disturbance: DisturbanceState = DisturbanceState.CALM
@onready var speakers: Array[AudioStreamPlayer3D] = [
$Speakers/SpeakerDash,
$Speakers/SpeakerDoorL,
$Speakers/SpeakerDoorR,
]
@onready var dj_player: AudioStreamPlayer3D = $DJPlayer
@onready var tuning_clean: AudioStreamPlayer3D = $TuningSFX
@onready var tuning_degraded: AudioStreamPlayer3D = $TuningSFXDegraded
@onready var forest_players: Array[AudioStreamPlayer3D] = [
$ForestLayer/ForestA,
$ForestLayer/ForestB,
$ForestLayer/ForestC,
]
@onready var dial_needle: Node3D = $DialNeedle
@onready var dial_light: OmniLight3D = $DialLight
@onready var pilot_light: OmniLight3D = $PilotLight
func _ready() -> void:
for sp in speakers:
sp.bus = "Radio"
dj_player.bus = "Radio"
tuning_clean.bus = "Radio"
tuning_degraded.bus = "RadioDegraded"
for fp in forest_players:
fp.bus = "RadioDegraded"
speakers[0].finished.connect(_on_track_finished)
RadioZoneManager.biome_changed.connect(_on_biome_changed)
RadioZoneManager.disturbance_changed.connect(_on_disturbance_changed)
func toggle_radio() -> void:
radio_on = !radio_on
if radio_on:
_play_current_track()
else:
_stop_speakers()
dj_player.stop()
_stop_forest_layer()
_update_visual_state()
func _stop_speakers() -> void:
for sp in speakers:
sp.stop()
func _play_on_speakers(stream: AudioStream) -> void:
for sp in speakers:
sp.stream = stream
sp.play()
func _on_biome_changed(b: Biome) -> void:
current_biome = b
_apply_bus_mix()
_check_channel_availability()
_update_forest_layer()
_update_visual_state()
func _on_disturbance_changed(d: DisturbanceState) -> void:
current_disturbance = d
_apply_bus_mix()
_update_forest_layer()
_update_visual_state()
func _apply_bus_mix() -> void:
var biome_t := float(current_biome) / float(Biome.PETRIFOLD)
var disturbance_t := float(current_disturbance) / float(DisturbanceState.STORM)
var t := clampf(biome_t + disturbance_t * 0.3, 0.0, 1.0)
for sp in speakers:
sp.pitch_scale = lerp(1.0, 0.96, t)
AudioServer.set_bus_volume_db(
AudioServer.get_bus_index("Radio"),
linear_to_db(1.0 - t * 0.7)
)
AudioServer.set_bus_volume_db(
AudioServer.get_bus_index("RadioDegraded"),
linear_to_db(t)
)
func _check_channel_availability() -> void:
if not radio_on:
return
var ch: RadioChannel = channels[current_channel]
if not _channel_available(ch):
_stop_speakers()
dj_player.stop()
emit_signal("station_lost")
func _channel_available(ch: RadioChannel) -> bool:
if ch.station_id == "":
return current_biome == Biome.PETRIFOLD or \
current_disturbance == DisturbanceState.STORM
return int(current_biome) <= ch.max_biome
func _update_forest_layer() -> void:
var forest_active := current_biome == Biome.PETRIFOLD or \
current_disturbance == DisturbanceState.STORM
if forest_active and radio_on:
_ensure_forest_layer_running()
else:
_stop_forest_layer()
func _update_visual_state() -> void:
match current_biome:
Biome.VERDANT:
dial_light.light_energy = 0.8 if radio_on else 0.0
dial_light.light_color = Color(1.0, 0.7, 0.2)
Biome.FUNGAL:
dial_light.light_energy = randf_range(0.2, 0.5) if radio_on else 0.0
Biome.PETRIFOLD:
dial_light.light_energy = 0.0
if not radio_on:
pilot_light.light_energy = 0.0
elif current_biome == Biome.PETRIFOLD:
pilot_light.light_color = Color(1.0, 0.1, 0.05)
pilot_light.light_energy = 0.6
else:
pilot_light.light_color = Color(1.0, 0.6, 0.1)
pilot_light.light_energy = 0.8
func set_needle_position(normalized: float) -> void:
var min_rot := -0.6
var max_rot := 0.6
dial_needle.rotation.z = lerp(min_rot, max_rot, normalized)
func tune_to_channel(index: int) -> void:
if index == current_channel:
return
_stop_speakers()
dj_player.stop()
current_channel = index
current_track = 0
var sweep := tuning_degraded if current_biome >= Biome.FUNGAL else tuning_clean
sweep.play()
await sweep.finished
_play_current_track()
func _play_current_track() -> void:
var ch: RadioChannel = channels[current_channel]
if not _channel_available(ch):
emit_signal("station_lost")
return
if ch.is_talk_format:
var pool := ch.content_fracturing if current_biome == Biome.FUNGAL \
else ch.content_clean
if pool.is_empty():
emit_signal("station_lost")
return
_play_on_speakers(pool.pick_random())
emit_signal("station_changed", ch.station_id, ch.genre_label)
return
if ch.tracks.is_empty():
emit_signal("station_lost")
return
_play_on_speakers(ch.tracks[current_track])
emit_signal("station_changed", ch.station_id, ch.genre_label)
_schedule_dj_clip(ch)
func _on_track_finished() -> void:
if not radio_on:
return
var ch: RadioChannel = channels[current_channel]
if ch.is_talk_format:
_play_current_track()
return
current_track = wrapi(current_track + 1, 0, ch.tracks.size())
_play_current_track()
func _schedule_dj_clip(ch: RadioChannel) -> void:
if current_biome == Biome.PETRIFOLD:
return
if ch.is_talk_format:
return
await speakers[0].finished
if not radio_on:
return
var pool := ch.dj_clips_degraded if current_biome == Biome.FUNGAL \
else ch.dj_clips_clean
if pool.is_empty():
return
dj_player.stream = pool.pick_random()
dj_player.play()
func _ensure_forest_layer_running() -> void:
for fp in forest_players:
if not fp.playing:
fp.stream = forest_fragments.pick_random()
fp.volume_db = randf_range(-18.0, -10.0)
fp.pitch_scale = randf_range(0.88, 1.05)
await get_tree().create_timer(randf_range(0.0, 4.0)).timeout
fp.play()
fp.finished.connect(_on_forest_fragment_finished.bind(fp), CONNECT_ONE_SHOT)
func _on_forest_fragment_finished(fp: AudioStreamPlayer3D) -> void:
var still_active := current_biome == Biome.PETRIFOLD or \
current_disturbance == DisturbanceState.STORM
if still_active and radio_on:
await get_tree().create_timer(randf_range(2.0, 8.0)).timeout
fp.stream = forest_fragments.pick_random()
fp.volume_db = randf_range(-18.0, -10.0)
fp.pitch_scale = randf_range(0.88, 1.05)
fp.play()
fp.finished.connect(_on_forest_fragment_finished.bind(fp), CONNECT_ONE_SHOT)
func _stop_forest_layer() -> void:
for fp in forest_players:
fp.stop()
signal station_changed(station_id: String, genre_label: String)
signal station_lost()
```
### `radio_knob_power.gd` and `radio_knob_tuning.gd`
Knob subsystems handle physical rotation interaction, detent/click feedback for the power knob, smooth rotation for the tuning knob, volume normalization for the power/volume dual function, and frequency sweep rate for the tuning knob.
```gdscript
# radio_knob_power.gd
extends Node3D
@export var car_radio: NodePath
@onready var radio: Node = get_node(car_radio)
@onready var interact_area: Area3D = $InteractArea
var focused := false
var knob_rotation := 0.0
const VOLUME_MIN_DB := -40.0
const VOLUME_MAX_DB := 0.0
func _ready() -> void:
interact_area.mouse_entered.connect(func(): focused = true)
interact_area.mouse_exited.connect(func(): focused = false)
func _input(event: InputEvent) -> void:
if not focused:
return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT \
and event.pressed:
radio.toggle_radio()
_animate_click()
if event is InputEventMouseButton and radio.radio_on:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
_adjust_volume(2.0)
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
_adjust_volume(-2.0)
func _adjust_volume(delta_db: float) -> void:
var bus_idx := AudioServer.get_bus_index("Radio")
var current := AudioServer.get_bus_volume_db(bus_idx)
var clamped := clampf(current + delta_db, VOLUME_MIN_DB, VOLUME_MAX_DB)
AudioServer.set_bus_volume_db(bus_idx, clamped)
var t := inverse_lerp(VOLUME_MIN_DB, VOLUME_MAX_DB, clamped)
knob_rotation = lerp(-1.4, 1.4, t)
rotation.z = knob_rotation
func _animate_click() -> void:
var tween := create_tween()
tween.tween_property(self, "position:z", position.z - 0.003, 0.06)
tween.tween_property(self, "position:z", position.z, 0.06)
```
```gdscript
# radio_knob_tuning.gd
extends Node3D
@export var car_radio: NodePath
@onready var radio: Node = get_node(car_radio)
@onready var interact_area: Area3D = $InteractArea
const BAND_MIN := 88.0
const BAND_MAX := 108.0
const STATIONS := {
88.1: 0, # ch_folk
92.1: 1, # ch_nordic
93.3: 2, # ch_talk
95.9: 3, # ch_drone
98.5: 4, # ch_jazz
103.7: 5, # ch_news
105.9: 6, # ch_grunge
}
const SNAP_RADIUS := 0.4 # MHz
var focused := false
var current_freq: float = 88.1
var dragging := false
var drag_start_x := 0.0
var freq_at_drag_start := 0.0
func _ready() -> void:
interact_area.mouse_entered.connect(func(): focused = true)
interact_area.mouse_exited.connect(func(): focused = false)
_apply_freq(current_freq)
func _input(event: InputEvent) -> void:
if not focused or not radio.radio_on:
return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
dragging = event.pressed
if dragging:
drag_start_x = event.position.x
freq_at_drag_start = current_freq
if event is InputEventMouseMotion and dragging:
var delta_px := event.position.x - drag_start_x
var delta_mhz := delta_px * 0.05
var new_freq := clampf(freq_at_drag_start + delta_mhz, BAND_MIN, BAND_MAX)
_apply_freq(new_freq)
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
_apply_freq(clampf(current_freq + 0.2, BAND_MIN, BAND_MAX))
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
_apply_freq(clampf(current_freq - 0.2, BAND_MIN, BAND_MAX))
func _apply_freq(freq: float) -> void:
current_freq = freq
var normalized := inverse_lerp(BAND_MIN, BAND_MAX, freq)
radio.set_needle_position(normalized)
rotation.z = lerp(-1.57, 1.57, normalized)
for station_freq in STATIONS.keys():
if abs(freq - station_freq) <= SNAP_RADIUS:
radio.tune_to_channel(STATIONS[station_freq])
return
radio.tune_to_channel(-1)
```
### Physical Interaction Model
The radio is not activated by a keybind. The player must LOOK at the radio (camera forward raycast into `InteractableArea3D` on the radio unit). When the player is looking at the radio, the interact-context switches to radio controls; looking away restores driving input context. This is the diegetic interaction model per Article XII-G.
Knob rotation is bound to scroll or similar input within the radio context (per current spec, `E` = power toggle, `T/Y` = tune up/down, `=/-` = volume up/down; see [[Controls]]).
### Dial and Light Visual States
| State | Dial Backlight | Pilot Light | Needle |
|---|---|---|---|
| OFF | Dark | Dark | Last position (frozen) |
| Verdant, Calm | Warm amber, steady | Amber, steady | Tracks tuning smoothly |
| Verdant, Uneasy+ | Warm amber, very subtle flicker | Amber, subtle flicker | Tracks tuning, minor judder |
| Fungal, Calm+ | Amber dimmed ~40%, periodic flicker | Amber, flickering | Tracks tuning, visible judder |
| Fungal, Agitated+ | Amber dimmed ~70%, erratic flicker | Amber to reddish-orange, erratic | Tracks tuning unreliably, jumps |
| Petrifold, any | Dark | Deep red, steady | Drifts to unmarked position, unresponsive to tuning |
## Test Coverage
- `src/tests/unit/test_bus_radio.gd` — main radio system tests
- `src/tests/unit/test_radio_channel.gd` — per-channel behavior
## Related
- [[Audio — Overview]] — top-level architecture
- [[Audio — Radio Stations]] — content, KTLK, Gary
- [[Audio — Forest Transmissions]] — what comes through after signal dies
- [[Audio — World Audio]] — world audio system the radio sits alongside
- [[Audio — Pipeline]] — how radio content is produced
- [[Vehicle — Samba]] — the vehicle the radio is part of
- [[Vehicle — Dashboard]] — the dashboard the radio lives on
- [[Narrative Direction]] — the radio's narrative role
- [[Disturbance System]] — degradation tracks disturbance
- [[Audio — Domain MOC]] — domain MOC
- [[Diegesis Thread]] — the radio is a central diegetic commitment
- [[Constitution]] — Article XII-D (audio/DSP), Article XII-G (diegetic UI)
## Bus Audio Reference — Radio Tactile and Signal Degradation
> Phase 2 research pointers for Phase 3 audio pass. All URLs verified as pointers; re-confirm licensing at acquisition time.
### Radio tactile — knob turn, preset button, power click
The dashboard radio is a 3D object (`src/scenes/bus/bus_radio.tscn`). Period unit: Blaupunkt / Sapphire tube radio. Tactile audio for its controls sells the focus-gated interaction.
Each knob motion and button press should be a discrete asset:
- Power click-on, power click-off
- Tune up click (per detent), tune down click (per detent)
- Volume step click
- Tube warm-up whisper (after power click-on — period tube radios have a ~1–2 s warm-up hiss before audio begins)
- Preset button press (if applicable to chosen period unit)
**Reference sources**:
- Freesound.org — https://freesound.org/search/?q=vintage+radio+knob (CC0/CC BY — commercial OK)
- YouTube — search "Blaupunkt car radio demonstration", "Sapphire AM radio tube" (reference only — cannot rip)
- **Recording recommendation**: if Phase 3 can obtain a period tube car radio (antique shops, eBay), close-mic its controls. Each interaction is a distinct asset.
### Radio signal degradation — biome-specific
The radio signal degrades based on biome state and disturbance level per the live `car_radio.gd`. Reference sources for degradation audio textures:
| Source | Commercial use | Notes |
|---|---|---|
| The Conet Project (Internet Archive) | ✓ (CC BY-SA) | Number stations recordings — good static/carrier texture; attribution + share-alike required |
| Shortwave "open carrier" recordings (radiomuseum.org) | Check per item | Archival SWL recordings |
| Freesound.org — static, carrier, fade searches | ✓ CC0/CC BY | Filter to CC0 for simplest licensing |
**Degradation pass strategy**: start with existing radio recordings, then apply progressive degradation:
1. Pitch drift (±5–15 cents, irregular timing)
2. High-frequency rolloff (low-pass filter, cutoff decreasing with disturbance)
3. Added static (noise floor rising)
4. Occasional drop-outs (brief amplitude zeroing)
5. Occasional "wrong station" bleed-through (brief fragment of another signal)
**Tone reference**: `docs/forest-transmissions-case-study.md` (Forest Transmissions case study) for specific degradation register. DJ attitude and content character: `docs/vault/Design/Narrative Direction.md`.
### Licensing summary for radio audio
| Source | Commercial use | Attribution |
|---|---|---|
| Freesound CC0 | ✓ | None required |
| Freesound CC BY | ✓ | Per asset |
| Internet Archive / Conet Project | ✓ (CC BY-SA) | Attribution + share-alike |
| YouTube audio rip | ✗ | Reference only |
| Original capture | ✓ | Project-owned — preferred |
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-car-radio: type/spec
Tag assignment · test- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.79 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Audio — Car Radio.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — Car Radio
The 1963 Samba's dash-mounted AM/FM radio. Sole narrative voice in the world. Plays until it cannot — then the forest takes the frequency. This note captures the full authoritative specification: physical design, biome-signal model, channel roster, DJ layer, forest transmission, AI audio generation pipeline, post-processing chain, and Godot implementation architecture.
## Overview
The radio is zone-reactive and disturbance-reactive. It pulls from all music, all eras, all genres — folk, Nordic experimental folk, grunge, jazz, dark ambient, anything that has ever been broadcast. The van's hardware processes everything through its warm narrow AM/FM fidelity, flattening decades of music into a single timeless signal.
Signal quality, available channels, and audio character all degrade as the player moves through the three biomes. When the signal finally dies completely, the forest itself begins transmitting through the speakers — not a metaphor, a documented mechanical behavior (World Bible §16).
All audio content — music, DJ voice, SFX, biome sounds — is AI-generated: Suno for music/SFX, ElevenLabs for voice. Generation is fast and inexpensive, enabling deep track pools (**15–20 tracks per music channel**, proportionally deep DJ/talk/news/forest pools). Repetition is not a concern across multi-hour sessions.
The radio's decay is a non-verbal storytelling device. Losing the music means losing the last thread to the familiar world. The moment the player realizes the forest is now the broadcaster is a designed horror beat.
## Design Goals
- **Immersion**: Audio must feel consistent with van's hardware — warm, narrow, slightly compressed AM/FM fidelity. The hardware is the filter; the music is timeless.
- **Variety**: Seven channels spanning folk, Nordic experimental folk, talk radio, dark ambient/drone, jazz, news, and grunge — each with distinct identity and emotional role.
- **Physical interaction**: Radio is a 3D object in the dash — player must look at it and manipulate knobs directly; no abstract keybinds.
- **Spatial audio**: Sound routes through `AudioStreamPlayer3D` nodes at the three speaker positions in the cab, not non-positional players.
- **Reactivity**: Functions only when player is inside the Samba.
- **Biome degradation**: Signal quality, channel availability, and physical dial display decay across biomes.
- **Disturbance reactivity**: High disturbance accelerates signal degradation independent of biome.
- **DJ layer**: Distant narrator voices between-track commentary that degrades in parallel with the music signal.
- **Forest transmission**: When all signal lost, the forest transmits procedural unsettling audio through the speakers.
- **Polish**: Tuning sweep SFX, needle animation, dial illumination, pilot light state all reflect current signal condition.
## Physical Radio Design
### The Unit
Period-correct dash-mounted AM/FM receiver installed in Samba's center dashboard fascia. Modeled as distinct 3D object — an `InteractableArea3D` with its own mesh and material, NOT a UI overlay. Player must look directly at it and trigger interaction.
Faceplate: brushed chrome with black surround (German automotive radio aesthetics of the era). Slightly recessed into the dash. Shows wear: fine scratches across chrome, hairline crack in lower-left corner of dial glass.
### Face Layout
```
┌─────────────────────────────────────────────┐
│ [●] ┌─────────────────────┐ [TUNING ] │
│ PWR │ ~~~~~KOLD 88.1~~~~ │ [ KNOB ] │
│ VOL │ 88 94 101 108 │ │
│ KNOB │ ● │ [1][2][3][4]│
│ └─────────────────────┘ PRESETS │
└─────────────────────────────────────────────┘
```
- **Dial window**: rectangular backlit frequency display with etched FM band markings (88–108 MHz). Physical needle sweeps left to right as tuning knob turns. Backlight is warm amber incandescent in Verdant. Dims and flickers in Fungal Depths. In Petrifold, dial goes dark; needle drifts to unmarked position between stations.
- **Power indicator**: small round pilot light left of dial. Glows amber when on. Flickers erratically at high disturbance. In Petrifold, turns deep red (only element that remains lit).
- **Station label**: narrow backlit text above dial displaying current station ID (e.g. `KJAZ 101.3`). Fades to `-- --.--` when signal lost.
### Knobs
**Left knob — Power/Volume** (large ribbed bakelite): push inward (click) to toggle; rotate to adjust volume. Physical detent at off position (click). Hard stops at each rotation limit.
**Right knob — Tuning** (smaller smooth-edged chrome): rotate to sweep needle across dial. Stations do NOT snap automatically — player must tune to correct frequency. Valid stations produce increasing signal as needle approaches, resolving to clear audio when centered. Passing through unavailable station (out of range for current biome) produces only static regardless of needle position. Rotates freely with no hard stops.
**Preset buttons** (four rectangular push buttons below dial, labeled 1–4): each stores last manually tuned frequency. Pressing instantly moves needle to stored frequency and plays that station if signal available. Presets persist for run duration; reset on death/extraction. In Petrifold, pressing a preset produces brief needle movement followed by dead static — mechanical echo of signal that no longer exists.
### Speakers
Samba has three speaker locations. Audio routes to `AudioStreamPlayer3D` nodes positioned at each location (not non-positional players). Sound originates from within cab geometry, consistent with first-person interior view.
| Speaker ID | Location | Node Position (local to bus) |
|---|---|---|
| `SpeakerDash` | Center dashboard, directly above radio | ~(0, 0.6, 0.8) |
| `SpeakerDoorL` | Left door card, mid-height | ~(−0.8, 0.2, 0.3) |
| `SpeakerDoorR` | Right door card, mid-height | ~(0.8, 0.2, 0.3) |
All three play same stream simultaneously. `SpeakerDash` carries slightly higher volume than door speakers (+2 dB). Attenuation `ATTENUATION_DISABLED` or very large max distance — audio should not fall off inside cab, only when player exits. On exit: speaker audio attenuates naturally by distance (`AudioStreamPlayer3D` spatial falloff), creating effect of muffled radio audio drifting from parked van.
## Biome-Based Signal Model
Radio signal degrades across the three canonical biomes. Biome is the authoritative signal context (replaces an earlier generic zone-depth float).
| Biome | Signal State | Music Presence | DJ Present | Forest Transmission |
|---|---|---|---|---|
| Verdant Old Growth | Full signal, warm AM clarity | All channels | Active, clear | None |
| Fungal Depths | Degraded — narrow EQ, crackle, dropout | 2–3 channels remain | Fragmented, cutting in and out | Begins bleeding in beneath static |
| The Petrifold | Near-total loss | No music channels | Gone | Dominant — forest is the only broadcaster |
Biome transitions blend over ~3 seconds of travel time (matching World Bible blend timing). Radio mirrors this — signal character shifts over same 3-second window, not instantly.
### Disturbance Modifier
Secondary degradation pressure additive to biome-based degradation.
| Disturbance State | Signal Modifier |
|---|---|
| Calm (0–30) | No modifier — baseline biome signal |
| Uneasy (30–60) | Mild additional static, slight EQ narrowing |
| Agitated (60–85) | Moderate crackle, occasional dropout |
| Storm (85–100) | Heavy degradation, channels at risk of cutting out regardless of biome |
Agitated/Storm in Verdant will sound closer to Fungal Depths radio than Calm in same location.
## Radio Channels
### Base Channels
| Channel ID | Freq | Genre | Mood |
|---|---|---|---|
| `ch_folk` | KOLD 88.1 | Folk / Acoustic | Forest isolation, melancholy |
| `ch_nordic` | KFRØ 92.1 | Nordic Experimental Folk | Hymn-like, reverent, building dread — the forest's own music |
| `ch_talk` | KTLK 93.3 | Talk Radio | Conversational, familiar, increasingly wrong |
| `ch_drone` | KDRN 95.9 | Dark Ambient / Drone | Slow dread, subsonic unease, formless tension |
| `ch_jazz` | KJAZ 98.5 | Jazz | Cool, sparse, eerie after dark |
| `ch_news` | KNWS 103.7 | News / Public Radio | Calm authority, unsettling when it fractures |
| `ch_grunge` | KGNG 105.9 | Grunge (includes punk tracks) | Raw, distorted, sludgy catharsis and defiant urgency |
| `ch_static` | — | Dead Air / Static | Between stations, realism |
Each music channel has **15–20 tracks** to cycle through. News and talk require dedicated content pools.
### Biome Channel Availability
Higher-band channels attenuate first (grounded in real RF physics — higher frequencies more absorbed by dense organic matter — and doubles as designed emotional arc).
| Biome | Available Channels |
|---|---|
| Verdant Old Growth | All channels |
| Fungal Depths | `ch_folk`, `ch_nordic`, `ch_talk`, `ch_drone`, `ch_jazz`, `ch_static` — news (103.7) and grunge (105.9, highest freq) drop out |
| The Petrifold | `ch_static` only — all music and talk gone; forest transmission active |
Unavailable channel tuned = heavy static only. Dial position exists. Signal does not. **Folk hangs on the longest** — lowest-band station, most earthbound. Last music player hears before silence is a lone acoustic guitar fading into static. Intentional.
Talk radio and news degrade uniquely before dropping out (see below). They do not simply cut to static — they fracture.
### Forest Transmission (`ch_forest`)
Active only in Petrifold and at Storm-level disturbance in Fungal Depths. Not a station. HUD displays nothing — no frequency, no label. The forest does not identify itself.
Audio is procedural at runtime: ghost fragments layered stochastically with randomized pitch, volume, gap timing. Should never resolve into anything fully recognizable. Three `AudioStreamPlayer3D` nodes run independently with random restart delays — no two playthroughs produce same sequence.
## News & Talk Radio
News and talk degrade differently from music. Music fades to static — a signal dying. News and talk **fracture** — the content itself becomes wrong before signal fails. Distinct horror vector: familiar voices of authority and companionship turning unreliable.
### Talk Radio (`ch_talk` — KTLK 93.3)
Late-night call-in format. Host takes calls, gives advice, tells stories. Content deliberately generic — no specific cultural references, dates, real-world events. Should feel like any late-night AM talk show from any decade.
- **Verdant**: normal talk radio. Warm, rambling, companionable. Host calm, personable. Callers discuss mundane things — road conditions, weather, local gossip, sleepless nights. Background ambiance: faint phone-line hum, occasional hold music bleed.
- **Fungal**: host begins repeating phrases. Callers' questions don't quite match host's responses. Long pauses where host seems to be listening to something off-mic. A caller describes a road the player is currently on — but wrong. Hold music between segments slightly too slow. Sentences trail off mid-thought and restart from different point.
- **Petrifold**: signal lost. Static only.
See [[Narrative Direction]] for Gary's character spec (KTLK DJ — spec 052).
### News Radio (`ch_news` — KNWS 103.7)
Public radio news format. Calm, measured delivery. Weather, traffic, regional reports. No specific dates, locations outside the game world, or real-world references.
- **Verdant**: standard news broadcast. Professional, reassuring. Weather reports reference fog and rain in "the valley." Traffic advisories mention "route closures in the forest service road network." Tone is NPR-calm.
- **Fungal**: newsreader's cadence becomes slightly off — pauses in wrong places, emphasis on wrong syllables. Reports begin referencing events that haven't happened or locations that don't exist. Weather forecasts describe conditions that contradict what player sees. Story loops back to its beginning mid-sentence. Newsreader occasionally addresses listener directly — "You should turn around now" — then continues as if nothing happened.
- **Petrifold**: signal lost. Static only.
### News & Talk Audio Generation
Generate clips in tiers matching the three biome states:
- **Clean clips (Verdant — 20–25 clips per channel, 15–45 seconds each)**: normal broadcast content. Generic, warm, professional. No era markers.
- **Fracturing clips (Fungal — 15–20 clips per channel, 15–60 seconds each)**: pre-scripted wrongness. Repetitions, mismatched Q&A, direct address to listener, factual contradictions. Authored, not procedurally broken — uncanny valley is in the *content*, not the signal quality.
Fracturing clips processed through same `RadioDegraded` bus as music, but content-level wrongness is baked into the recordings themselves. Signal degradation and content fracturing compound — voice sounds worse AND says wrong things simultaneously.
### News & Talk Channel Type
News and talk channels use distinct `RadioChannel` configuration:
```gdscript
@export var is_talk_format: bool = false
@export var content_clean: Array[AudioStream] = [] # Verdant pool
@export var content_fracturing: Array[AudioStream] = [] # Fungal pool
```
When `is_talk_format` is true, channel cycles through content pool continuously rather than playing discrete tracks with DJ interludes. No DJ layer on talk/news channels — the content IS the voice.
## DJ Layer
Distant narrator voices between-track commentary degrading alongside music. Distinct audio layer from music tracks — runs as `AudioStreamPlayer3D` on `Radio` bus, interleaved between tracks. **Applies to music channels only.** Talk and news have their own voice content.
| Biome | DJ State |
|---|---|
| Verdant | Clear, present — brief between-track commentary, station IDs, weather mentions |
| Fungal | Fragmented — sentences cut out mid-word, long silences between fragments, pitch slightly unstable |
| Petrifold | Absent — DJ channel produces only hiss or silence |
### DJ Audio Generation
Generate in ElevenLabs (preferred for voice quality/control) or Suno. 5–15 seconds each. Content feels generic and timeless — nothing narratively specific, no references to specific years, decades, cultural moments. The DJ exists in the same undefined time as the world.
Examples:
- *"…coming up next on KOLD, more of the sounds keeping you company out there on those long forest roads…"*
- *"…weather service is reporting fog through the valley tonight, drive careful…"*
- *"…[mid-sentence cut]…if you're just joining us…[static]…"*
- *"…[long silence]…[faint tonal hum]…"* (Fungal fragment variant)
Generate 15–20 clean clips and 10–12 degraded/fragmented clips. Degraded clips are pre-processed — runtime system selects from appropriate pool based on biome.
## AI Music Generation
Recommended tool: **Suno (Pro/Premier plan for commercial rights)**. Generate WAV exports; all tracks are processed through the AM/FM radio EQ pipeline — the hardware flattens everything into warm, narrow fidelity regardless of genre.
### Base Track Prompts
**`ch_folk` (KOLD 88.1, Folk/Acoustic)**
```
acoustic folk radio, fingerpicked guitar, melancholic, sparse, lo-fi AM radio quality, no drums, fingerstyle, minor key, intimate and lonely
```
**`ch_nordic` (KFRØ 92.1, Nordic Experimental Folk)**
```
Nordic experimental folk, violin melody, raw female vocals, layered choir harmonics, sparse percussion building to full drums, hymn-like, atmospheric, reverent, building crescendo, Scandinavian folk, choral
```
**`ch_drone` (KDRN 95.9, Dark Ambient/Drone)**
```
dark ambient, deep drone, subsonic bass, slow evolving texture, analog synthesizer, formless tension, barely perceptible melody, cavernous reverb, industrial hum, unsettling calm, no rhythm, no vocals
```
**`ch_jazz` (KJAZ 98.5, Jazz)**
```
jazz, late night, upright bass, brushed drums, muted trumpet, piano comping, smoky and sparse, cool jazz, small club feel
```
**`ch_grunge` (KGNG 105.9, Grunge)**
```
grunge, heavy distorted guitar, sludgy tempo, raw vocals, minor key, thick bass, lo-fi recording quality, cathartic and angry, Seattle sound, droning feedback
```
### Forest Transmission Prompts (Petrifold / Storm)
Forest transmission fragments generated via Suno with deliberately corrupted prompts. Examples:
- *distant voice fragments through radio static, half-formed words, no coherent language, 15-30 seconds*
- *whispered phrases at the edge of hearing, incoherent but almost a word, layered and overlapping, horror sound design*
- *mycelium network audio — biological transmission through ground, wet and organic, barely a voice, 20-40 seconds*
Three independent `AudioStreamPlayer3D` nodes play these with randomized start delays, pitch variance, volume variance. No pattern, no resolution.
### Static / Tuning SFX
Pre-generated static sweep SFX used when tuning through inter-station dead air. Multiple variants: clean static (Verdant), static-with-whisper-bleed (Fungal), static-with-forest-transmission-overlay (Petrifold). Looped or played as one-shots depending on knob movement speed.
## Audio Post-Processing Pipeline
### Base pipeline (all music and DJ tracks)
All AI-generated audio passes through a post-processing chain before shipping:
1. **Gain normalization** — consistent LUFS target across all tracks
2. **AM/FM band-pass filtering** — narrow the frequency range to simulate period-correct broadcast fidelity (rolls off sub-60 Hz and high-8 kHz)
3. **Gentle compression** — broadcast-style level consistency
4. **Clean export** — OGG, shipped on `Radio` bus
### Forest transmission additional processing
On top of base pipeline:
5. **Heavy bit-crush / degradation** — push into digital artifact territory
6. **Irregular pitch modulation** — subtle wow-and-flutter simulation
7. **Stochastic gap insertion** — random silence breaks and restarts
8. **Additional band-pass narrowing** — below what the radio hardware normally allows; the frequency that the forest uses is narrower than the broadcast frequency
The full audio-processing pipeline invocation (Makefile + ffmpeg) is documented in [[Audio — Pipeline]]. Shipping OGGs live in the audio submodule under `src/assets/audio/radio/`.
## Godot Implementation
### Audio Bus Architecture
Radio system uses **two dedicated buses**:
| Bus | Purpose | Effects |
|---|---|---|
| `Radio` | Clean radio broadcast content (music, DJ talk, station IDs, news, talk show) | `AudioEffectBandpass` (500 Hz–6 kHz), `AudioEffectCompressor` (broadcast-style), gentle reverb |
| `RadioDegraded` | Degraded signal | `AudioEffectDistortion` (mild), `AudioEffectLowpass` (aggressive rolloff), `AudioEffectHighpass` (narrower), `AudioEffectReverb` (small-room), `AudioEffectDelay` (stutter) |
The runtime crossfades between Radio and RadioDegraded buses based on biome × disturbance state. Wet/dry ratio is continuously controlled by `car_radio.gd`.
### Scene Structure
```
CarRadio (Node3D — attached to dashboard)
├── InteractableArea3D # player-look activation
├── RadioUnit (MeshInstance3D) # the physical radio mesh
│ ├── DialWindow (MeshInstance3D — backlit)
│ ├── Needle (MeshInstance3D — animated)
│ └── PowerLight (OmniLight3D)
├── LeftKnob (Node3D) # power/volume, rotation state
│ └── KnobInteractable
├── RightKnob (Node3D) # tuning, rotation state
│ └── KnobInteractable
├── PresetButtons (Node3D)
│ ├── Preset1 through Preset4 (InteractableArea3D each)
├── SpeakerDash (AudioStreamPlayer3D) # → Radio bus (clean) or RadioDegraded
├── SpeakerDoorL (AudioStreamPlayer3D)
├── SpeakerDoorR (AudioStreamPlayer3D)
├── ForestTransmission (Node3D)
│ ├── TransmissionSource1 (AudioStreamPlayer3D) # → Horror bus
│ ├── TransmissionSource2 (AudioStreamPlayer3D)
│ └── TransmissionSource3 (AudioStreamPlayer3D)
└── car_radio.gd
```
### Enums
```gdscript
enum BiomeSignal { VERDANT, FUNGAL, PETRIFOLD }
enum RadioState { OFF, ON_TUNED, ON_STATIC, ON_TRANSMISSION }
enum ChannelType { MUSIC, TALK, NEWS, STATIC, FOREST }
```
### Resource: `RadioChannel`
```gdscript
class_name RadioChannel extends Resource
@export var channel_id: String # "ch_folk", etc
@export var freq_mhz: float # 88.1, etc
@export var station_name: String # "KOLD 88.1"
@export var genre_label: String # "Folk / Acoustic"
@export var channel_type: ChannelType
@export var is_talk_format: bool = false
# For music channels:
@export var tracks_clean: Array[AudioStream] = []
@export var tracks_degraded: Array[AudioStream] = []
@export var dj_clips_clean: Array[AudioStream] = []
@export var dj_clips_degraded: Array[AudioStream] = []
# For talk/news channels:
@export var content_clean: Array[AudioStream] = []
@export var content_fracturing: Array[AudioStream] = []
# Biome availability
@export var available_in_verdant: bool = true
@export var available_in_fungal: bool = true
@export var available_in_petrifold: bool = false
```
### `radio_zone_manager.gd` (autoload singleton)
Subscribes to biome and disturbance changes. Provides `biome_changed` and `disturbance_changed` signals. Exposes `get_signal_quality(channel) -> float` returning a 0.0–1.0 value representing the wet/dry ratio between Radio and RadioDegraded buses for a given channel in the current biome × disturbance × per-channel attenuation state.
### `car_radio.gd` — the runtime
Core responsibilities:
- Manage radio state (OFF / ON_TUNED / ON_STATIC / ON_TRANSMISSION)
- Track current tuned frequency (continuous float, not snapped)
- Frequency-to-station lookup with tolerance window
- Per-channel track / DJ / content rotation
- Wet/dry bus crossfade driven by `RadioZoneManager.get_signal_quality()`
- Forest-transmission node orchestration (three sources, independent random schedules)
- Needle animation on tuning
- Pilot light color state (amber / red / off)
- Dial backlight brightness
- Preset storage + recall
- Input context gate (radio controls active only when player is looking at the radio)
- Volume knob integration with Radio bus gain
```gdscript
# car_radio.gd
extends Node
@export var channels: Array[RadioChannel] = []
@export var forest_fragments: Array[AudioStream] = []
@export var tuning_sfx_clean: AudioStream
@export var tuning_sfx_degraded: AudioStream
var current_channel := 0
var current_track := 0
var radio_on := false
var current_biome: Biome = Biome.VERDANT
var current_disturbance: DisturbanceState = DisturbanceState.CALM
@onready var speakers: Array[AudioStreamPlayer3D] = [
$Speakers/SpeakerDash,
$Speakers/SpeakerDoorL,
$Speakers/SpeakerDoorR,
]
@onready var dj_player: AudioStreamPlayer3D = $DJPlayer
@onready var tuning_clean: AudioStreamPlayer3D = $TuningSFX
@onready var tuning_degraded: AudioStreamPlayer3D = $TuningSFXDegraded
@onready var forest_players: Array[AudioStreamPlayer3D] = [
$ForestLayer/ForestA,
$ForestLayer/ForestB,
$ForestLayer/ForestC,
]
@onready var dial_needle: Node3D = $DialNeedle
@onready var dial_light: OmniLight3D = $DialLight
@onready var pilot_light: OmniLight3D = $PilotLight
func _ready() -> void:
for sp in speakers:
sp.bus = "Radio"
dj_player.bus = "Radio"
tuning_clean.bus = "Radio"
tuning_degraded.bus = "RadioDegraded"
for fp in forest_players:
fp.bus = "RadioDegraded"
speakers[0].finished.connect(_on_track_finished)
RadioZoneManager.biome_changed.connect(_on_biome_changed)
RadioZoneManager.disturbance_changed.connect(_on_disturbance_changed)
func toggle_radio() -> void:
radio_on = !radio_on
if radio_on:
_play_current_track()
else:
_stop_speakers()
dj_player.stop()
_stop_forest_layer()
_update_visual_state()
func _stop_speakers() -> void:
for sp in speakers:
sp.stop()
func _play_on_speakers(stream: AudioStream) -> void:
for sp in speakers:
sp.stream = stream
sp.play()
func _on_biome_changed(b: Biome) -> void:
current_biome = b
_apply_bus_mix()
_check_channel_availability()
_update_forest_layer()
_update_visual_state()
func _on_disturbance_changed(d: DisturbanceState) -> void:
current_disturbance = d
_apply_bus_mix()
_update_forest_layer()
_update_visual_state()
func _apply_bus_mix() -> void:
var biome_t := float(current_biome) / float(Biome.PETRIFOLD)
var disturbance_t := float(current_disturbance) / float(DisturbanceState.STORM)
var t := clampf(biome_t + disturbance_t * 0.3, 0.0, 1.0)
for sp in speakers:
sp.pitch_scale = lerp(1.0, 0.96, t)
AudioServer.set_bus_volume_db(
AudioServer.get_bus_index("Radio"),
linear_to_db(1.0 - t * 0.7)
)
AudioServer.set_bus_volume_db(
AudioServer.get_bus_index("RadioDegraded"),
linear_to_db(t)
)
func _check_channel_availability() -> void:
if not radio_on:
return
var ch: RadioChannel = channels[current_channel]
if not _channel_available(ch):
_stop_speakers()
dj_player.stop()
emit_signal("station_lost")
func _channel_available(ch: RadioChannel) -> bool:
if ch.station_id == "":
return current_biome == Biome.PETRIFOLD or \
current_disturbance == DisturbanceState.STORM
return int(current_biome) <= ch.max_biome
func _update_forest_layer() -> void:
var forest_active := current_biome == Biome.PETRIFOLD or \
current_disturbance == DisturbanceState.STORM
if forest_active and radio_on:
_ensure_forest_layer_running()
else:
_stop_forest_layer()
func _update_visual_state() -> void:
match current_biome:
Biome.VERDANT:
dial_light.light_energy = 0.8 if radio_on else 0.0
dial_light.light_color = Color(1.0, 0.7, 0.2)
Biome.FUNGAL:
dial_light.light_energy = randf_range(0.2, 0.5) if radio_on else 0.0
Biome.PETRIFOLD:
dial_light.light_energy = 0.0
if not radio_on:
pilot_light.light_energy = 0.0
elif current_biome == Biome.PETRIFOLD:
pilot_light.light_color = Color(1.0, 0.1, 0.05)
pilot_light.light_energy = 0.6
else:
pilot_light.light_color = Color(1.0, 0.6, 0.1)
pilot_light.light_energy = 0.8
func set_needle_position(normalized: float) -> void:
var min_rot := -0.6
var max_rot := 0.6
dial_needle.rotation.z = lerp(min_rot, max_rot, normalized)
func tune_to_channel(index: int) -> void:
if index == current_channel:
return
_stop_speakers()
dj_player.stop()
current_channel = index
current_track = 0
var sweep := tuning_degraded if current_biome >= Biome.FUNGAL else tuning_clean
sweep.play()
await sweep.finished
_play_current_track()
func _play_current_track() -> void:
var ch: RadioChannel = channels[current_channel]
if not _channel_available(ch):
emit_signal("station_lost")
return
if ch.is_talk_format:
var pool := ch.content_fracturing if current_biome == Biome.FUNGAL \
else ch.content_clean
if pool.is_empty():
emit_signal("station_lost")
return
_play_on_speakers(pool.pick_random())
emit_signal("station_changed", ch.station_id, ch.genre_label)
return
if ch.tracks.is_empty():
emit_signal("station_lost")
return
_play_on_speakers(ch.tracks[current_track])
emit_signal("station_changed", ch.station_id, ch.genre_label)
_schedule_dj_clip(ch)
func _on_track_finished() -> void:
if not radio_on:
return
var ch: RadioChannel = channels[current_channel]
if ch.is_talk_format:
_play_current_track()
return
current_track = wrapi(current_track + 1, 0, ch.tracks.size())
_play_current_track()
func _schedule_dj_clip(ch: RadioChannel) -> void:
if current_biome == Biome.PETRIFOLD:
return
if ch.is_talk_format:
return
await speakers[0].finished
if not radio_on:
return
var pool := ch.dj_clips_degraded if current_biome == Biome.FUNGAL \
else ch.dj_clips_clean
if pool.is_empty():
return
dj_player.stream = pool.pick_random()
dj_player.play()
func _ensure_forest_layer_running() -> void:
for fp in forest_players:
if not fp.playing:
fp.stream = forest_fragments.pick_random()
fp.volume_db = randf_range(-18.0, -10.0)
fp.pitch_scale = randf_range(0.88, 1.05)
await get_tree().create_timer(randf_range(0.0, 4.0)).timeout
fp.play()
fp.finished.connect(_on_forest_fragment_finished.bind(fp), CONNECT_ONE_SHOT)
func _on_forest_fragment_finished(fp: AudioStreamPlayer3D) -> void:
var still_active := current_biome == Biome.PETRIFOLD or \
current_disturbance == DisturbanceState.STORM
if still_active and radio_on:
await get_tree().create_timer(randf_range(2.0, 8.0)).timeout
fp.stream = forest_fragments.pick_random()
fp.volume_db = randf_range(-18.0, -10.0)
fp.pitch_scale = randf_range(0.88, 1.05)
fp.play()
fp.finished.connect(_on_forest_fragment_finished.bind(fp), CONNECT_ONE_SHOT)
func _stop_forest_layer() -> void:
for fp in forest_players:
fp.stop()
signal station_changed(station_id: String, genre_label: String)
signal station_lost()
```
### `radio_knob_power.gd` and `radio_knob_tuning.gd`
Knob subsystems handle physical rotation interaction, detent/click feedback for the power knob, smooth rotation for the tuning knob, volume normalization for the power/volume dual function, and frequency sweep rate for the tuning knob.
```gdscript
# radio_knob_power.gd
extends Node3D
@export var car_radio: NodePath
@onready var radio: Node = get_node(car_radio)
@onready var interact_area: Area3D = $InteractArea
var focused := false
var knob_rotation := 0.0
const VOLUME_MIN_DB := -40.0
const VOLUME_MAX_DB := 0.0
func _ready() -> void:
interact_area.mouse_entered.connect(func(): focused = true)
interact_area.mouse_exited.connect(func(): focused = false)
func _input(event: InputEvent) -> void:
if not focused:
return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT \
and event.pressed:
radio.toggle_radio()
_animate_click()
if event is InputEventMouseButton and radio.radio_on:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
_adjust_volume(2.0)
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
_adjust_volume(-2.0)
func _adjust_volume(delta_db: float) -> void:
var bus_idx := AudioServer.get_bus_index("Radio")
var current := AudioServer.get_bus_volume_db(bus_idx)
var clamped := clampf(current + delta_db, VOLUME_MIN_DB, VOLUME_MAX_DB)
AudioServer.set_bus_volume_db(bus_idx, clamped)
var t := inverse_lerp(VOLUME_MIN_DB, VOLUME_MAX_DB, clamped)
knob_rotation = lerp(-1.4, 1.4, t)
rotation.z = knob_rotation
func _animate_click() -> void:
var tween := create_tween()
tween.tween_property(self, "position:z", position.z - 0.003, 0.06)
tween.tween_property(self, "position:z", position.z, 0.06)
```
```gdscript
# radio_knob_tuning.gd
extends Node3D
@export var car_radio: NodePath
@onready var radio: Node = get_node(car_radio)
@onready var interact_area: Area3D = $InteractArea
const BAND_MIN := 88.0
const BAND_MAX := 108.0
const STATIONS := {
88.1: 0, # ch_folk
92.1: 1, # ch_nordic
93.3: 2, # ch_talk
95.9: 3, # ch_drone
98.5: 4, # ch_jazz
103.7: 5, # ch_news
105.9: 6, # ch_grunge
}
const SNAP_RADIUS := 0.4 # MHz
var focused := false
var current_freq: float = 88.1
var dragging := false
var drag_start_x := 0.0
var freq_at_drag_start := 0.0
func _ready() -> void:
interact_area.mouse_entered.connect(func(): focused = true)
interact_area.mouse_exited.connect(func(): focused = false)
_apply_freq(current_freq)
func _input(event: InputEvent) -> void:
if not focused or not radio.radio_on:
return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
dragging = event.pressed
if dragging:
drag_start_x = event.position.x
freq_at_drag_start = current_freq
if event is InputEventMouseMotion and dragging:
var delta_px := event.position.x - drag_start_x
var delta_mhz := delta_px * 0.05
var new_freq := clampf(freq_at_drag_start + delta_mhz, BAND_MIN, BAND_MAX)
_apply_freq(new_freq)
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
_apply_freq(clampf(current_freq + 0.2, BAND_MIN, BAND_MAX))
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
_apply_freq(clampf(current_freq - 0.2, BAND_MIN, BAND_MAX))
func _apply_freq(freq: float) -> void:
current_freq = freq
var normalized := inverse_lerp(BAND_MIN, BAND_MAX, freq)
radio.set_needle_position(normalized)
rotation.z = lerp(-1.57, 1.57, normalized)
for station_freq in STATIONS.keys():
if abs(freq - station_freq) <= SNAP_RADIUS:
radio.tune_to_channel(STATIONS[station_freq])
return
radio.tune_to_channel(-1)
```
### Physical Interaction Model
The radio is not activated by a keybind. The player must LOOK at the radio (camera forward raycast into `InteractableArea3D` on the radio unit). When the player is looking at the radio, the interact-context switches to radio controls; looking away restores driving input context. This is the diegetic interaction model per Article XII-G.
Knob rotation is bound to scroll or similar input within the radio context (per current spec, `E` = power toggle, `T/Y` = tune up/down, `=/-` = volume up/down; see [[Controls]]).
### Dial and Light Visual States
| State | Dial Backlight | Pilot Light | Needle |
|---|---|---|---|
| OFF | Dark | Dark | Last position (frozen) |
| Verdant, Calm | Warm amber, steady | Amber, steady | Tracks tuning smoothly |
| Verdant, Uneasy+ | Warm amber, very subtle flicker | Amber, subtle flicker | Tracks tuning, minor judder |
| Fungal, Calm+ | Amber dimmed ~40%, periodic flicker | Amber, flickering | Tracks tuning, visible judder |
| Fungal, Agitated+ | Amber dimmed ~70%, erratic flicker | Amber to reddish-orange, erratic | Tracks tuning unreliably, jumps |
| Petrifold, any | Dark | Deep red, steady | Drifts to unmarked position, unresponsive to tuning |
## Test Coverage
- `src/tests/unit/test_bus_radio.gd` — main radio system tests
- `src/tests/unit/test_radio_channel.gd` — per-channel behavior
## Related
- [[Audio — Overview]] — top-level architecture
- [[Audio — Radio Stations]] — content, KTLK, Gary
- [[Audio — Forest Transmissions]] — what comes through after signal dies
- [[Audio — World Audio]] — world audio system the radio sits alongside
- [[Audio — Pipeline]] — how radio content is produced
- [[Vehicle — Samba]] — the vehicle the radio is part of
- [[Vehicle — Dashboard]] — the dashboard the radio lives on
- [[Narrative Direction]] — the radio's narrative role
- [[Disturbance System]] — degradation tracks disturbance
- [[Audio — Domain MOC]] — domain MOC
- [[Diegesis Thread]] — the radio is a central diegetic commitment
- [[Constitution]] — Article XII-D (audio/DSP), Article XII-G (diegetic UI)
## Bus Audio Reference — Radio Tactile and Signal Degradation
> Phase 2 research pointers for Phase 3 audio pass. All URLs verified as pointers; re-confirm licensing at acquisition time.
### Radio tactile — knob turn, preset button, power click
The dashboard radio is a 3D object (`src/scenes/bus/bus_radio.tscn`). Period unit: Blaupunkt / Sapphire tube radio. Tactile audio for its controls sells the focus-gated interaction.
Each knob motion and button press should be a discrete asset:
- Power click-on, power click-off
- Tune up click (per detent), tune down click (per detent)
- Volume step click
- Tube warm-up whisper (after power click-on — period tube radios have a ~1–2 s warm-up hiss before audio begins)
- Preset button press (if applicable to chosen period unit)
**Reference sources**:
- Freesound.org — https://freesound.org/search/?q=vintage+radio+knob (CC0/CC BY — commercial OK)
- YouTube — search "Blaupunkt car radio demonstration", "Sapphire AM radio tube" (reference only — cannot rip)
- **Recording recommendation**: if Phase 3 can obtain a period tube car radio (antique shops, eBay), close-mic its controls. Each interaction is a distinct asset.
### Radio signal degradation — biome-specific
The radio signal degrades based on biome state and disturbance level per the live `car_radio.gd`. Reference sources for degradation audio textures:
| Source | Commercial use | Notes |
|---|---|---|
| The Conet Project (Internet Archive) | ✓ (CC BY-SA) | Number stations recordings — good static/carrier texture; attribution + share-alike required |
| Shortwave "open carrier" recordings (radiomuseum.org) | Check per item | Archival SWL recordings |
| Freesound.org — static, carrier, fade searches | ✓ CC0/CC BY | Filter to CC0 for simplest licensing |
**Degradation pass strategy**: start with existing radio recordings, then apply progressive degradation:
1. Pitch drift (±5–15 cents, irregular timing)
2. High-frequency rolloff (low-pass filter, cutoff decreasing with disturbance)
3. Added static (noise floor rising)
4. Occasional drop-outs (brief amplitude zeroing)
5. Occasional "wrong station" bleed-through (brief fragment of another signal)
**Tone reference**: `docs/forest-transmissions-case-study.md` (Forest Transmissions case study) for specific degradation register. DJ attitude and content character: `docs/vault/Design/Narrative Direction.md`.
### Licensing summary for radio audio
| Source | Commercial use | Attribution |
|---|---|---|
| Freesound CC0 | ✓ | None required |
| Freesound CC BY | ✓ | Per asset |
| Internet Archive / Conet Project | ✓ (CC BY-SA) | Attribution + share-alike |
| YouTube audio rip | ✗ | Reference only |
| Original capture | ✓ | Project-owned — preferred |
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-vault-audit: concept/governance
Tag assignment · test- Gold
- The note calls for `concept/governance`: Constitutional and governance concepts (True)
- ember
- The note has no reason to carry `concept/governance` (False)
- Signal
- P(true) 0.10 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Code/Tools — Vault Audit.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — Vault Audit
The local-first vault knowledge-graph audit pipeline (spec 088). One deterministic mechanical checker (`scripts/ci/vault_audit.py`), plus a semantic drift phase that runs as an agent invocation (not a standalone script).
## Entry Points
| Command | Backend | Cost |
|---|---|---|
| `make vault-audit` | `scripts/ci/vault_audit.py docs/vault` | Free |
| `make vault-audit-apply` | `scripts/ci/vault_audit.py docs/vault --apply` | Free |
| `make vault-audit-semantic` | `opencode run --command vault-audit-semantic` | LLM tokens (model-dependent) |
| `make vault-audit-test` | `tests/vault_audit/run_tests.sh` | Free |
## Mechanical Script (`scripts/ci/vault_audit.py`)
**Dependencies**: Python 3 stdlib + PyYAML only. No other external dependencies.
**Checks performed**:
- Schema validation (FR-002): required fields, type/status/layer tag cardinality, controlled vocab, aliases/source/system on agent notes
- Wikilink resolution (FR-003): case-insensitive NFC-normalized lookup against filename index; duplicate stems → WARN; unresolved → ERROR
- Path checking (FR-004): code-refs/spec-refs existence; src/assets/ paths → SKIPPED when submodule not initialized
- Inventory extraction (FR-005): autoloads from project.godot, signals from events.gd, GDExtension classes from gdext-wrapper/src/*.rs, Rust modules from rust-core/src/*.rs, threshold constants from src/resources/config/*.tres
**Safe auto-fixes** (with `--apply`):
- Missing `aliases:` on agent notes → set to `title:`
- Missing `source:` on agent notes → set to `agent` with inline comment
- `layer:` field/tag mismatch → field wins, tag updated
- Case-mismatched wikilinks → normalized to real filename case
- Broken `code-refs`/`spec-refs` → `stale: true` + `stale_reason:` + `> [!warning]` callout
- Missing spec stubs → auto-generated from `_meta/templates/spec.md`
**Output files** (all committed except metadata):
- `docs/vault/_meta/audit/last_audit.json` — deterministic mechanical report
- `docs/vault/_meta/audit/inventories.json` — code inventories for semantic phase
- `docs/vault/_meta/audit/audit_run_metadata.json` — gitignored; run timestamps
**Critical implementation notes**:
- Filename index excludes `_meta/` and `.obsidian/` to avoid false duplicate collisions with template files
- All file walks use `sorted()` for cross-platform determinism
- Sort key on findings: `(note_path, rule, message)` — tertiary message key required for idempotency when multiple findings share same note+rule
- `--output` flag always wins over `--no-write` (test harness requirement)
## Semantic Drift Phase (agent-invoked)
**Not a standalone Python script.** Semantic drift analysis runs through `opencode run --command vault-audit-semantic`, which dispatches the `/vault-audit-semantic` slash command defined under `.opencode/command/`. The command loads the `introspection` skill (`.opencode/skills/introspection/SKILL.md`) as its system prompt, reads `last_audit.json` + `inventories.json` from `docs/vault/_meta/audit/`, walks the in-scope notes directly, and writes `docs/vault/_meta/audit/last_semantic.json`.
**Model selection**: The Makefile target passes `--model openrouter/anthropic/claude-opus-4.8` by default. Override by editing the Makefile target or invoking `opencode run` manually with a different `--model` flag. There is no pinned `DEFAULT_MODEL` constant — model choice is a CLI argument.
**No anthropic Python dependency**: The agent runs inside the opencode runtime, which handles LLM HTTP itself. `tools/vault_audit/` contains only an empty `__init__.py`; there is no `semantic_drift.py`, no `requirements.txt`, and no batch-size CLI flag at this layer (batching is the agent's internal concern).
**Scope gate** (enforced by the agent via the introspection skill): Only `type/system`, `type/code`, `type/decision`, `type/discovery` notes with ≥1 valid `code-refs:` entry. All others logged as "needs refs" and skipped.
**Claim taxonomy** (applied by the agent via the introspection skill system prompt):
- `exact` → drift if actual value differs
- `range` → drift only if outside the range
- `approximation` → never drift
- `list` → drift if membership differs
- `description` → drift only if confident behavior no longer matches
**Auto-run behaviour**: The slash command will run the mechanical script first if `last_audit.json` is missing or stale relative to vault content.
## Test Suite
Located at `tests/vault_audit/`. Run via `make vault-audit-test`.
**Fixture structure**:
- `fixtures/malformed/` — 5 seeded error notes (missing type, broken wikilink, broken code-ref, invalid tag, duplicate stem)
- `fixtures/clean/` — 2 valid notes including unicode em-dash (SC-006)
- `fixtures/claim_taxonomy/` — 15 fixtures (3 per claim type: drift/no_drift/boundary)
- `fixtures/mock_last_audit.json` + `fixtures/mock_inventories.json` — fixture inputs for semantic tests
**Test cases in `run_tests.sh`**:
1. Malformed vault → exit 1, specific error messages present
2. Clean vault → exit 0, "Vault audit: 0 errors, 0 warnings"
3. Idempotency — two runs produce byte-identical JSON
4. Unicode em-dash round-trip — file unchanged after scan (SC-006)
5. Submodule SKIPPED detection (SC-007)
6. Performance — full audit < 30 seconds (NFR-002)
## Controlled Vocabulary Reference
Full vocabulary in `docs/vault/_meta/tags.md`. The script enforces:
- `type/*`: 10 values (principle, design, system, code, spec, reference, moc, decision, discovery, session-log)
- `status/*`: 6 values (draft, wip, stale, superseded, reviewed, canonical) — NOT `status/done` or `status/waiting`
- `layer/*`: 6 values (gdscript, rust-core, gdext, python, content, infra)
- `domain/*` (system: field): 9 values (world, audio, vehicle, player, anomalies, narrative, art, tooling, governance)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-vault-audit: domain/governance
Tag assignment · test- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.24 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Code/Tools — Vault Audit.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — Vault Audit
The local-first vault knowledge-graph audit pipeline (spec 088). One deterministic mechanical checker (`scripts/ci/vault_audit.py`), plus a semantic drift phase that runs as an agent invocation (not a standalone script).
## Entry Points
| Command | Backend | Cost |
|---|---|---|
| `make vault-audit` | `scripts/ci/vault_audit.py docs/vault` | Free |
| `make vault-audit-apply` | `scripts/ci/vault_audit.py docs/vault --apply` | Free |
| `make vault-audit-semantic` | `opencode run --command vault-audit-semantic` | LLM tokens (model-dependent) |
| `make vault-audit-test` | `tests/vault_audit/run_tests.sh` | Free |
## Mechanical Script (`scripts/ci/vault_audit.py`)
**Dependencies**: Python 3 stdlib + PyYAML only. No other external dependencies.
**Checks performed**:
- Schema validation (FR-002): required fields, type/status/layer tag cardinality, controlled vocab, aliases/source/system on agent notes
- Wikilink resolution (FR-003): case-insensitive NFC-normalized lookup against filename index; duplicate stems → WARN; unresolved → ERROR
- Path checking (FR-004): code-refs/spec-refs existence; src/assets/ paths → SKIPPED when submodule not initialized
- Inventory extraction (FR-005): autoloads from project.godot, signals from events.gd, GDExtension classes from gdext-wrapper/src/*.rs, Rust modules from rust-core/src/*.rs, threshold constants from src/resources/config/*.tres
**Safe auto-fixes** (with `--apply`):
- Missing `aliases:` on agent notes → set to `title:`
- Missing `source:` on agent notes → set to `agent` with inline comment
- `layer:` field/tag mismatch → field wins, tag updated
- Case-mismatched wikilinks → normalized to real filename case
- Broken `code-refs`/`spec-refs` → `stale: true` + `stale_reason:` + `> [!warning]` callout
- Missing spec stubs → auto-generated from `_meta/templates/spec.md`
**Output files** (all committed except metadata):
- `docs/vault/_meta/audit/last_audit.json` — deterministic mechanical report
- `docs/vault/_meta/audit/inventories.json` — code inventories for semantic phase
- `docs/vault/_meta/audit/audit_run_metadata.json` — gitignored; run timestamps
**Critical implementation notes**:
- Filename index excludes `_meta/` and `.obsidian/` to avoid false duplicate collisions with template files
- All file walks use `sorted()` for cross-platform determinism
- Sort key on findings: `(note_path, rule, message)` — tertiary message key required for idempotency when multiple findings share same note+rule
- `--output` flag always wins over `--no-write` (test harness requirement)
## Semantic Drift Phase (agent-invoked)
**Not a standalone Python script.** Semantic drift analysis runs through `opencode run --command vault-audit-semantic`, which dispatches the `/vault-audit-semantic` slash command defined under `.opencode/command/`. The command loads the `introspection` skill (`.opencode/skills/introspection/SKILL.md`) as its system prompt, reads `last_audit.json` + `inventories.json` from `docs/vault/_meta/audit/`, walks the in-scope notes directly, and writes `docs/vault/_meta/audit/last_semantic.json`.
**Model selection**: The Makefile target passes `--model openrouter/anthropic/claude-opus-4.8` by default. Override by editing the Makefile target or invoking `opencode run` manually with a different `--model` flag. There is no pinned `DEFAULT_MODEL` constant — model choice is a CLI argument.
**No anthropic Python dependency**: The agent runs inside the opencode runtime, which handles LLM HTTP itself. `tools/vault_audit/` contains only an empty `__init__.py`; there is no `semantic_drift.py`, no `requirements.txt`, and no batch-size CLI flag at this layer (batching is the agent's internal concern).
**Scope gate** (enforced by the agent via the introspection skill): Only `type/system`, `type/code`, `type/decision`, `type/discovery` notes with ≥1 valid `code-refs:` entry. All others logged as "needs refs" and skipped.
**Claim taxonomy** (applied by the agent via the introspection skill system prompt):
- `exact` → drift if actual value differs
- `range` → drift only if outside the range
- `approximation` → never drift
- `list` → drift if membership differs
- `description` → drift only if confident behavior no longer matches
**Auto-run behaviour**: The slash command will run the mechanical script first if `last_audit.json` is missing or stale relative to vault content.
## Test Suite
Located at `tests/vault_audit/`. Run via `make vault-audit-test`.
**Fixture structure**:
- `fixtures/malformed/` — 5 seeded error notes (missing type, broken wikilink, broken code-ref, invalid tag, duplicate stem)
- `fixtures/clean/` — 2 valid notes including unicode em-dash (SC-006)
- `fixtures/claim_taxonomy/` — 15 fixtures (3 per claim type: drift/no_drift/boundary)
- `fixtures/mock_last_audit.json` + `fixtures/mock_inventories.json` — fixture inputs for semantic tests
**Test cases in `run_tests.sh`**:
1. Malformed vault → exit 1, specific error messages present
2. Clean vault → exit 0, "Vault audit: 0 errors, 0 warnings"
3. Idempotency — two runs produce byte-identical JSON
4. Unicode em-dash round-trip — file unchanged after scan (SC-006)
5. Submodule SKIPPED detection (SC-007)
6. Performance — full audit < 30 seconds (NFR-002)
## Controlled Vocabulary Reference
Full vocabulary in `docs/vault/_meta/tags.md`. The script enforces:
- `type/*`: 10 values (principle, design, system, code, spec, reference, moc, decision, discovery, session-log)
- `status/*`: 6 values (draft, wip, stale, superseded, reviewed, canonical) — NOT `status/done` or `status/waiting`
- `layer/*`: 6 values (gdscript, rust-core, gdext, python, content, infra)
- `domain/*` (system: field): 9 values (world, audio, vehicle, player, anomalies, narrative, art, tooling, governance)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-vault-audit: layer/infra
Tag assignment · test- Gold
- The note calls for `layer/infra`: Build, CI, deployment infrastructure (True)
- ember
- The note has no reason to carry `layer/infra` (False)
- Signal
- P(true) 0.33 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Code/Tools — Vault Audit.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — Vault Audit
The local-first vault knowledge-graph audit pipeline (spec 088). One deterministic mechanical checker (`scripts/ci/vault_audit.py`), plus a semantic drift phase that runs as an agent invocation (not a standalone script).
## Entry Points
| Command | Backend | Cost |
|---|---|---|
| `make vault-audit` | `scripts/ci/vault_audit.py docs/vault` | Free |
| `make vault-audit-apply` | `scripts/ci/vault_audit.py docs/vault --apply` | Free |
| `make vault-audit-semantic` | `opencode run --command vault-audit-semantic` | LLM tokens (model-dependent) |
| `make vault-audit-test` | `tests/vault_audit/run_tests.sh` | Free |
## Mechanical Script (`scripts/ci/vault_audit.py`)
**Dependencies**: Python 3 stdlib + PyYAML only. No other external dependencies.
**Checks performed**:
- Schema validation (FR-002): required fields, type/status/layer tag cardinality, controlled vocab, aliases/source/system on agent notes
- Wikilink resolution (FR-003): case-insensitive NFC-normalized lookup against filename index; duplicate stems → WARN; unresolved → ERROR
- Path checking (FR-004): code-refs/spec-refs existence; src/assets/ paths → SKIPPED when submodule not initialized
- Inventory extraction (FR-005): autoloads from project.godot, signals from events.gd, GDExtension classes from gdext-wrapper/src/*.rs, Rust modules from rust-core/src/*.rs, threshold constants from src/resources/config/*.tres
**Safe auto-fixes** (with `--apply`):
- Missing `aliases:` on agent notes → set to `title:`
- Missing `source:` on agent notes → set to `agent` with inline comment
- `layer:` field/tag mismatch → field wins, tag updated
- Case-mismatched wikilinks → normalized to real filename case
- Broken `code-refs`/`spec-refs` → `stale: true` + `stale_reason:` + `> [!warning]` callout
- Missing spec stubs → auto-generated from `_meta/templates/spec.md`
**Output files** (all committed except metadata):
- `docs/vault/_meta/audit/last_audit.json` — deterministic mechanical report
- `docs/vault/_meta/audit/inventories.json` — code inventories for semantic phase
- `docs/vault/_meta/audit/audit_run_metadata.json` — gitignored; run timestamps
**Critical implementation notes**:
- Filename index excludes `_meta/` and `.obsidian/` to avoid false duplicate collisions with template files
- All file walks use `sorted()` for cross-platform determinism
- Sort key on findings: `(note_path, rule, message)` — tertiary message key required for idempotency when multiple findings share same note+rule
- `--output` flag always wins over `--no-write` (test harness requirement)
## Semantic Drift Phase (agent-invoked)
**Not a standalone Python script.** Semantic drift analysis runs through `opencode run --command vault-audit-semantic`, which dispatches the `/vault-audit-semantic` slash command defined under `.opencode/command/`. The command loads the `introspection` skill (`.opencode/skills/introspection/SKILL.md`) as its system prompt, reads `last_audit.json` + `inventories.json` from `docs/vault/_meta/audit/`, walks the in-scope notes directly, and writes `docs/vault/_meta/audit/last_semantic.json`.
**Model selection**: The Makefile target passes `--model openrouter/anthropic/claude-opus-4.8` by default. Override by editing the Makefile target or invoking `opencode run` manually with a different `--model` flag. There is no pinned `DEFAULT_MODEL` constant — model choice is a CLI argument.
**No anthropic Python dependency**: The agent runs inside the opencode runtime, which handles LLM HTTP itself. `tools/vault_audit/` contains only an empty `__init__.py`; there is no `semantic_drift.py`, no `requirements.txt`, and no batch-size CLI flag at this layer (batching is the agent's internal concern).
**Scope gate** (enforced by the agent via the introspection skill): Only `type/system`, `type/code`, `type/decision`, `type/discovery` notes with ≥1 valid `code-refs:` entry. All others logged as "needs refs" and skipped.
**Claim taxonomy** (applied by the agent via the introspection skill system prompt):
- `exact` → drift if actual value differs
- `range` → drift only if outside the range
- `approximation` → never drift
- `list` → drift if membership differs
- `description` → drift only if confident behavior no longer matches
**Auto-run behaviour**: The slash command will run the mechanical script first if `last_audit.json` is missing or stale relative to vault content.
## Test Suite
Located at `tests/vault_audit/`. Run via `make vault-audit-test`.
**Fixture structure**:
- `fixtures/malformed/` — 5 seeded error notes (missing type, broken wikilink, broken code-ref, invalid tag, duplicate stem)
- `fixtures/clean/` — 2 valid notes including unicode em-dash (SC-006)
- `fixtures/claim_taxonomy/` — 15 fixtures (3 per claim type: drift/no_drift/boundary)
- `fixtures/mock_last_audit.json` + `fixtures/mock_inventories.json` — fixture inputs for semantic tests
**Test cases in `run_tests.sh`**:
1. Malformed vault → exit 1, specific error messages present
2. Clean vault → exit 0, "Vault audit: 0 errors, 0 warnings"
3. Idempotency — two runs produce byte-identical JSON
4. Unicode em-dash round-trip — file unchanged after scan (SC-006)
5. Submodule SKIPPED detection (SC-007)
6. Performance — full audit < 30 seconds (NFR-002)
## Controlled Vocabulary Reference
Full vocabulary in `docs/vault/_meta/tags.md`. The script enforces:
- `type/*`: 10 values (principle, design, system, code, spec, reference, moc, decision, discovery, session-log)
- `status/*`: 6 values (draft, wip, stale, superseded, reviewed, canonical) — NOT `status/done` or `status/waiting`
- `layer/*`: 6 values (gdscript, rust-core, gdext, python, content, infra)
- `domain/*` (system: field): 9 values (world, audio, vehicle, player, anomalies, narrative, art, tooling, governance)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-vault-audit: status/reviewed
Tag assignment · test- Gold
- The note calls for `status/reviewed`: Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `status/reviewed` (False)
- Signal
- P(true) 0.10 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Code/Tools — Vault Audit.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — Vault Audit
The local-first vault knowledge-graph audit pipeline (spec 088). One deterministic mechanical checker (`scripts/ci/vault_audit.py`), plus a semantic drift phase that runs as an agent invocation (not a standalone script).
## Entry Points
| Command | Backend | Cost |
|---|---|---|
| `make vault-audit` | `scripts/ci/vault_audit.py docs/vault` | Free |
| `make vault-audit-apply` | `scripts/ci/vault_audit.py docs/vault --apply` | Free |
| `make vault-audit-semantic` | `opencode run --command vault-audit-semantic` | LLM tokens (model-dependent) |
| `make vault-audit-test` | `tests/vault_audit/run_tests.sh` | Free |
## Mechanical Script (`scripts/ci/vault_audit.py`)
**Dependencies**: Python 3 stdlib + PyYAML only. No other external dependencies.
**Checks performed**:
- Schema validation (FR-002): required fields, type/status/layer tag cardinality, controlled vocab, aliases/source/system on agent notes
- Wikilink resolution (FR-003): case-insensitive NFC-normalized lookup against filename index; duplicate stems → WARN; unresolved → ERROR
- Path checking (FR-004): code-refs/spec-refs existence; src/assets/ paths → SKIPPED when submodule not initialized
- Inventory extraction (FR-005): autoloads from project.godot, signals from events.gd, GDExtension classes from gdext-wrapper/src/*.rs, Rust modules from rust-core/src/*.rs, threshold constants from src/resources/config/*.tres
**Safe auto-fixes** (with `--apply`):
- Missing `aliases:` on agent notes → set to `title:`
- Missing `source:` on agent notes → set to `agent` with inline comment
- `layer:` field/tag mismatch → field wins, tag updated
- Case-mismatched wikilinks → normalized to real filename case
- Broken `code-refs`/`spec-refs` → `stale: true` + `stale_reason:` + `> [!warning]` callout
- Missing spec stubs → auto-generated from `_meta/templates/spec.md`
**Output files** (all committed except metadata):
- `docs/vault/_meta/audit/last_audit.json` — deterministic mechanical report
- `docs/vault/_meta/audit/inventories.json` — code inventories for semantic phase
- `docs/vault/_meta/audit/audit_run_metadata.json` — gitignored; run timestamps
**Critical implementation notes**:
- Filename index excludes `_meta/` and `.obsidian/` to avoid false duplicate collisions with template files
- All file walks use `sorted()` for cross-platform determinism
- Sort key on findings: `(note_path, rule, message)` — tertiary message key required for idempotency when multiple findings share same note+rule
- `--output` flag always wins over `--no-write` (test harness requirement)
## Semantic Drift Phase (agent-invoked)
**Not a standalone Python script.** Semantic drift analysis runs through `opencode run --command vault-audit-semantic`, which dispatches the `/vault-audit-semantic` slash command defined under `.opencode/command/`. The command loads the `introspection` skill (`.opencode/skills/introspection/SKILL.md`) as its system prompt, reads `last_audit.json` + `inventories.json` from `docs/vault/_meta/audit/`, walks the in-scope notes directly, and writes `docs/vault/_meta/audit/last_semantic.json`.
**Model selection**: The Makefile target passes `--model openrouter/anthropic/claude-opus-4.8` by default. Override by editing the Makefile target or invoking `opencode run` manually with a different `--model` flag. There is no pinned `DEFAULT_MODEL` constant — model choice is a CLI argument.
**No anthropic Python dependency**: The agent runs inside the opencode runtime, which handles LLM HTTP itself. `tools/vault_audit/` contains only an empty `__init__.py`; there is no `semantic_drift.py`, no `requirements.txt`, and no batch-size CLI flag at this layer (batching is the agent's internal concern).
**Scope gate** (enforced by the agent via the introspection skill): Only `type/system`, `type/code`, `type/decision`, `type/discovery` notes with ≥1 valid `code-refs:` entry. All others logged as "needs refs" and skipped.
**Claim taxonomy** (applied by the agent via the introspection skill system prompt):
- `exact` → drift if actual value differs
- `range` → drift only if outside the range
- `approximation` → never drift
- `list` → drift if membership differs
- `description` → drift only if confident behavior no longer matches
**Auto-run behaviour**: The slash command will run the mechanical script first if `last_audit.json` is missing or stale relative to vault content.
## Test Suite
Located at `tests/vault_audit/`. Run via `make vault-audit-test`.
**Fixture structure**:
- `fixtures/malformed/` — 5 seeded error notes (missing type, broken wikilink, broken code-ref, invalid tag, duplicate stem)
- `fixtures/clean/` — 2 valid notes including unicode em-dash (SC-006)
- `fixtures/claim_taxonomy/` — 15 fixtures (3 per claim type: drift/no_drift/boundary)
- `fixtures/mock_last_audit.json` + `fixtures/mock_inventories.json` — fixture inputs for semantic tests
**Test cases in `run_tests.sh`**:
1. Malformed vault → exit 1, specific error messages present
2. Clean vault → exit 0, "Vault audit: 0 errors, 0 warnings"
3. Idempotency — two runs produce byte-identical JSON
4. Unicode em-dash round-trip — file unchanged after scan (SC-006)
5. Submodule SKIPPED detection (SC-007)
6. Performance — full audit < 30 seconds (NFR-002)
## Controlled Vocabulary Reference
Full vocabulary in `docs/vault/_meta/tags.md`. The script enforces:
- `type/*`: 10 values (principle, design, system, code, spec, reference, moc, decision, discovery, session-log)
- `status/*`: 6 values (draft, wip, stale, superseded, reviewed, canonical) — NOT `status/done` or `status/waiting`
- `layer/*`: 6 values (gdscript, rust-core, gdext, python, content, infra)
- `domain/*` (system: field): 9 values (world, audio, vehicle, player, anomalies, narrative, art, tooling, governance)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-vault-audit: type/code
Tag assignment · test- Gold
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- ember
- The note has no reason to carry `type/code` (False)
- Signal
- P(true) 0.46 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Code/Tools — Vault Audit.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — Vault Audit
The local-first vault knowledge-graph audit pipeline (spec 088). One deterministic mechanical checker (`scripts/ci/vault_audit.py`), plus a semantic drift phase that runs as an agent invocation (not a standalone script).
## Entry Points
| Command | Backend | Cost |
|---|---|---|
| `make vault-audit` | `scripts/ci/vault_audit.py docs/vault` | Free |
| `make vault-audit-apply` | `scripts/ci/vault_audit.py docs/vault --apply` | Free |
| `make vault-audit-semantic` | `opencode run --command vault-audit-semantic` | LLM tokens (model-dependent) |
| `make vault-audit-test` | `tests/vault_audit/run_tests.sh` | Free |
## Mechanical Script (`scripts/ci/vault_audit.py`)
**Dependencies**: Python 3 stdlib + PyYAML only. No other external dependencies.
**Checks performed**:
- Schema validation (FR-002): required fields, type/status/layer tag cardinality, controlled vocab, aliases/source/system on agent notes
- Wikilink resolution (FR-003): case-insensitive NFC-normalized lookup against filename index; duplicate stems → WARN; unresolved → ERROR
- Path checking (FR-004): code-refs/spec-refs existence; src/assets/ paths → SKIPPED when submodule not initialized
- Inventory extraction (FR-005): autoloads from project.godot, signals from events.gd, GDExtension classes from gdext-wrapper/src/*.rs, Rust modules from rust-core/src/*.rs, threshold constants from src/resources/config/*.tres
**Safe auto-fixes** (with `--apply`):
- Missing `aliases:` on agent notes → set to `title:`
- Missing `source:` on agent notes → set to `agent` with inline comment
- `layer:` field/tag mismatch → field wins, tag updated
- Case-mismatched wikilinks → normalized to real filename case
- Broken `code-refs`/`spec-refs` → `stale: true` + `stale_reason:` + `> [!warning]` callout
- Missing spec stubs → auto-generated from `_meta/templates/spec.md`
**Output files** (all committed except metadata):
- `docs/vault/_meta/audit/last_audit.json` — deterministic mechanical report
- `docs/vault/_meta/audit/inventories.json` — code inventories for semantic phase
- `docs/vault/_meta/audit/audit_run_metadata.json` — gitignored; run timestamps
**Critical implementation notes**:
- Filename index excludes `_meta/` and `.obsidian/` to avoid false duplicate collisions with template files
- All file walks use `sorted()` for cross-platform determinism
- Sort key on findings: `(note_path, rule, message)` — tertiary message key required for idempotency when multiple findings share same note+rule
- `--output` flag always wins over `--no-write` (test harness requirement)
## Semantic Drift Phase (agent-invoked)
**Not a standalone Python script.** Semantic drift analysis runs through `opencode run --command vault-audit-semantic`, which dispatches the `/vault-audit-semantic` slash command defined under `.opencode/command/`. The command loads the `introspection` skill (`.opencode/skills/introspection/SKILL.md`) as its system prompt, reads `last_audit.json` + `inventories.json` from `docs/vault/_meta/audit/`, walks the in-scope notes directly, and writes `docs/vault/_meta/audit/last_semantic.json`.
**Model selection**: The Makefile target passes `--model openrouter/anthropic/claude-opus-4.8` by default. Override by editing the Makefile target or invoking `opencode run` manually with a different `--model` flag. There is no pinned `DEFAULT_MODEL` constant — model choice is a CLI argument.
**No anthropic Python dependency**: The agent runs inside the opencode runtime, which handles LLM HTTP itself. `tools/vault_audit/` contains only an empty `__init__.py`; there is no `semantic_drift.py`, no `requirements.txt`, and no batch-size CLI flag at this layer (batching is the agent's internal concern).
**Scope gate** (enforced by the agent via the introspection skill): Only `type/system`, `type/code`, `type/decision`, `type/discovery` notes with ≥1 valid `code-refs:` entry. All others logged as "needs refs" and skipped.
**Claim taxonomy** (applied by the agent via the introspection skill system prompt):
- `exact` → drift if actual value differs
- `range` → drift only if outside the range
- `approximation` → never drift
- `list` → drift if membership differs
- `description` → drift only if confident behavior no longer matches
**Auto-run behaviour**: The slash command will run the mechanical script first if `last_audit.json` is missing or stale relative to vault content.
## Test Suite
Located at `tests/vault_audit/`. Run via `make vault-audit-test`.
**Fixture structure**:
- `fixtures/malformed/` — 5 seeded error notes (missing type, broken wikilink, broken code-ref, invalid tag, duplicate stem)
- `fixtures/clean/` — 2 valid notes including unicode em-dash (SC-006)
- `fixtures/claim_taxonomy/` — 15 fixtures (3 per claim type: drift/no_drift/boundary)
- `fixtures/mock_last_audit.json` + `fixtures/mock_inventories.json` — fixture inputs for semantic tests
**Test cases in `run_tests.sh`**:
1. Malformed vault → exit 1, specific error messages present
2. Clean vault → exit 0, "Vault audit: 0 errors, 0 warnings"
3. Idempotency — two runs produce byte-identical JSON
4. Unicode em-dash round-trip — file unchanged after scan (SC-006)
5. Submodule SKIPPED detection (SC-007)
6. Performance — full audit < 30 seconds (NFR-002)
## Controlled Vocabulary Reference
Full vocabulary in `docs/vault/_meta/tags.md`. The script enforces:
- `type/*`: 10 values (principle, design, system, code, spec, reference, moc, decision, discovery, session-log)
- `status/*`: 6 values (draft, wip, stale, superseded, reviewed, canonical) — NOT `status/done` or `status/waiting`
- `layer/*`: 6 values (gdscript, rust-core, gdext, python, content, infra)
- `domain/*` (system: field): 9 values (world, audio, vehicle, player, anomalies, narrative, art, tooling, governance)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-autonomous-company-agent-runtime-selection: domain/governance
Tag assignment · test- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.07 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/Autonomous Company Agent Runtime Selection.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Autonomous Company Agent Runtime Selection
> **Persona roster update 2026-05-08**: persona names in this Decision were updated to match the redesigned roster from [[Role Personas]] (Coordinator, Engineer, Narrative Director, Playtest Coordinator, GTM, replacing the original CEO/CTO/GTM/PM frame). The architectural conclusions are unchanged and the `status: reviewed` ratification stands — the runtime choice is downstream of the persona roster, not upstream of it, and the Coordinator-as-routing-hub topology in the redesign vindicates Letta's per-channel-shared-memory architecture.
This is the second of three decisions that supersede Backlog item 1 (Tool Stack Decision ADR) in the autonomous-company hub. It selects the persistent-agent runtime — the long-running process layer where the persona roster lives, holds memory, and connects to Slack channels. This Decision was revised across four research waves between 2026-05-08 morning and afternoon: the second-wave research pass added Slack as the confirmed internal-ops chat surface and corrected LettaBot to Letta Code Channels; the third-wave research established OpenCode + omo as the worker harness (replacing the brainstorm's Claude Code references) and specified the Letta-to-OpenCode HTTP API integration contract; the fourth-wave research committed the all-local v0.1 deployment topology, Amphetamine mitigation, and creative-tooling MCP integration. **The four-way runtime choice narrowed to Letta + Letta Code Channels as the v0.1 commitment, with Hermes Agent v0.13.0 "Tenacity" as the documented first fallback. Anthropic Claude Code Channels was eliminated (no Slack support; not production-ready for autonomous twenty-four-by-seven operation regardless of platform); OpenClaw + Honcho plugin was deprioritised due to three open Slack-specific regressions in v2026.5.7.** **This Decision was promoted to `status: reviewed` on 2026-05-08 by human ratification** — the v0.1 implementation work (persona authoring per [[Backlog]] items 4–7, the Letta-to-OpenCode bridge implementation, the per-persona Slack-app registration) can now proceed against this Decision as the committed architecture.
## Trigger
The autonomous-company brainstorm in [[SDLC Orchestration Whiteboard]] Layer 1 captured a provisional preference for OpenClaw as the persistent-agent runtime. The first-wave research recorded in [[Autonomous Company Tool Stack Verification]] identified three issues with that preference: OpenClaw's per-channel memory model is broken in core (Issue #64311, closed as plugin-path; bare OpenClaw isolates each chat channel as a separate session with zero continuity); OpenClaw's security posture between January and March 2026 included thirty-one CVEs, a CVSS 8.8 WebSocket one-click remote code execution, the ClawHavoc supply-chain incident, and one hundred thirty-five thousand or more internet-exposed instances; and the project's founder departed for OpenAI in February 2026 with the project transferred to a foundation whose operational governance is credible but untested.
The second-wave research adds three further findings that revise the runtime ranking. First, LettaBot is archived; the current Letta product for multi-channel chat bridging is **Letta Code Channels** (the channels system inside the Letta Code CLI), which has first-class Slack support architecturally identical to its Discord support — Socket Mode via `@slack/bolt`, per-channel routing, and the same Conversations API per-channel-shared-memory mechanism. Second, OpenClaw v2026.5.7 carries three open Slack-specific regressions: socket connection permanently dead after event-loop starvation with manuallyStopped suppressing auto-reconnect (Issue 77651); outbound thread reply does not promote session key to thread, so the next inbound thread message lands in a fresh session (Issue 78505); and bot-started threads do not require @mention to trigger agent action (Issue 77927). For an always-on persona system, these are material risks. Third, Anthropic Claude Code Channels does not support Slack — only Telegram, Discord, and iMessage are on the research-preview allowlist as of 2026-05-08 — and the architecture (Claude Code session must remain open on a persistent machine) is not production-ready for autonomous twenty-four-by-seven multi-agent operation regardless of platform.
## Decision (reviewed — ratified by human 2026-05-08)
The committed v0.1 runtime is **Letta with Letta Code Channels** as the Slack bridge. This Decision was promoted from `status: draft` to `status: reviewed` by human ratification on 2026-05-08, after four research waves narrowed the four-way choice and specified the Letta-to-OpenCode integration contract plus the all-local deployment topology. The implementation work (persona authoring, the Letta-to-OpenCode bridge, per-persona Slack-app registration) is now unblocked.
If the recommendation is ratified, the v0.1 stack is:
- **Letta server** (Apache-2.0, self-hosted or Letta Cloud) as the persistent-agent runtime.
- **Letta Code Channels** (the Channels system inside the Letta Code CLI) as the Slack bridge for the four personas. `FIRST_PARTY_CHANNEL_IDS` lists `slack` alongside `telegram` and `discord`; the Slack adapter uses `@slack/bolt` with Socket Mode (no public HTTP endpoint required, no reverse proxy, no tunnel — friendly for a self-hosted solo-dev setup).
- **OpenCode + omo as the execution worker** dispatched by personas via the `opencode serve` HTTP API for code-touching tasks (matching the "PM coordinates, OpenCode + omo executes" pattern in [[SDLC Orchestration Whiteboard]]). OpenCode + omo is the project's existing agentic harness — the brainstorm's earlier "Claude Code as worker" framing was a brainstorm error and is corrected throughout this commit wave per the third research wave recorded in [[Autonomous Company Tool Stack Verification]] addendum.
- **Letta Conversations API** as the per-channel-shared-memory mechanism — single agent per persona role, multiple concurrent conversation threads (one per Slack channel binding), shared memory across all threads.
- Memory blocks per persona: a `core` block for the persona's identity and immutable contract, an `archival` block (vector-backed) for accumulated experience, and a `recall` block for full conversation history.
- Slack routing: @mentions in channels create new agent conversations bound to the thread; subsequent messages in that thread route without further mentions; DMs map 1:1 to a conversation; auto-provisioning on first interaction (per Letta Code Channels documentation).
Why Letta over the alternatives:
- Letta's Conversations API directly implements the per-channel-shared-memory architecture the brainstorm describes. This is a first-class feature, not a workaround. Bare OpenClaw requires the Honcho plugin to deliver the same shape; Hermes Agent requires custom memory-bridging code.
- Letta Code Channels has first-class Slack support — `FIRST_PARTY_CHANNEL_IDS` includes `slack`; the Slack adapter is shipped as a core feature, not a plugin; Socket Mode is the default transport.
- Letta's typed memory blocks (core, archival, recall) map naturally to the SOUL.md / CHARTER.md / INTENT.md three-tier structure documented in [[Drift Detection and Audit Mechanics]] and the SoulSpec convention.
- Letta is YC-backed with stable funding and Apache-2.0 licensing; the bus-factor and license-stability concerns that apply to OpenClaw do not apply here.
- Letta is model-agnostic; persona memory survives model swaps, which is forward-compatible with the [[Autonomous Company LLM Provider Selection]] decision (cheap-tier-default routing through OpenRouter with Anthropic-direct foundational escalation).
What this decision does *not* settle:
- The exact Letta hosting topology (self-hosted on a personal server versus Letta Cloud) — defer until after ratification because hosting cost and operational burden trade off differently for v0.1 versus steady-state operation.
- Whether OpenCode is invoked via the `opencode serve` HTTP API (the v0.1 default per the integration contract below), the ACP stdio interface, or a per-task subprocess spawn — the v0.1 default is HTTP API, but reversal to ACP or subprocess remains low-cost if the HTTP API path encounters operational issues.
## Letta-to-OpenCode integration contract (v0.1)
The runtime selected here (Letta + Letta Code Channels) hosts the persistent persona personas with Slack-app bridging and per-channel-shared-memory. The execution worker (OpenCode + omo) is the project's existing agentic harness, dispatched via the `opencode serve` HTTP API. The contract for v0.1:
**Topology.** A single `opencode serve` instance runs on the autonomous-company host, bound to localhost on a configured port. The Letta server (or per-persona Slack-app processes hosting Letta agents) and the OpenCode server are separate processes on the same host or composing through localhost networking. The same `opencode serve` instance is also reachable by the human's interactive sessions (TUI, web, `--attach`) — one OpenCode installation, multiple invocation paths.
**Dispatch.** When a Letta persona decides on a code-touching task, the persona's tool layer invokes the OpenCode session API:
- `POST /session` to create a new session for the task, or `POST /session/:id/fork` to branch from a stable base session that has the project context cache-warmed.
- `POST /session/:id/message` with a structured prompt that includes (a) the persona identity tag, (b) the task specification, (c) relevant memory-block context the persona has accumulated, and (d) optional category hint for omo (`<task-category>code-light</task-category>`, `<task-category>visual-engineering</task-category>`, etc.).
- omo's category-based routing handles the model selection inside OpenCode per [[Autonomous Company LLM Provider Selection]]: cheap-tier defaults (MiniMax M2.5 default, Gemini 2.5 Flash classification, Qwen3-Coder Plus code-light) with Anthropic-direct foundational escalation only on explicit triggers.
**Asynchrony.** The dispatch is asynchronous. Letta does not block the persona's Slack conversation flow on the OpenCode session. Two response paths are supported, with the first preferred for v0.1:
- **Server-Sent Events**. The persona's tool layer subscribes to the OpenCode session's SSE stream and processes tool-call events, message events, and the final session-complete event. The persona can post in-progress updates to Slack (per the chat-streaming pattern from [[Community Layer]]) before the final result.
- **Callback URL**. The dispatch prompt includes a callback URL that the OpenCode session hits when it completes. The persona's Letta agent receives the callback as an event and synthesises the result.
**Result handling.** The persona reads the OpenCode session's final message, validates against its CHARTER constraints, accumulates the result into the persona's `archival` memory block per [[Drift Detection and Audit Mechanics]], and synthesises a Slack-facing response that flows through the per-persona output-filtering middleware (per Community Layer) before posting via `chat.postMessage`.
**Budget enforcement.** OpenCode's invocation runs through the same OpenRouter account as the persona's direct LLM calls per Autonomous Company LLM Provider Selection. The budget-enforcement middleware reserves projected cost before each LLM call inside the OpenCode session, not just before the dispatch. omo's category-based routing fits naturally because the cheap-tier defaults match the routing chains documented in Decision 1.
**Reverse compatibility.** The human's interactive Sisyphus sessions on the same `opencode serve` instance use the OpenCode TUI, web client, or `--attach` mode. Persona-dispatched sessions and human-interactive sessions share the same OpenCode server and the same omo plugin configuration but are separate sessions with their own message histories. Cross-contamination between personas, between sessions, and between the human's work and persona work is prevented by session-id scoping at the OpenCode layer.
**Failure modes.** If the OpenCode session hangs (per the documented Issue #6573 — REST-API-spawned subagents can stick in `busy` state), the Letta persona times out the call after a configured threshold (recommended sixty seconds for code-light tasks, three hundred seconds for foundational-escalation tasks) and either retries with a fresh session or escalates the task to the human. If the OpenCode server is unavailable, the persona refuses the dispatch and posts a maintenance-mode notice to its primary Slack channel; the budget-enforcement middleware also serves as the kill-switch surface in this case (refusing further dispatch attempts until the operator re-enables).
**v0.1 implementation work.** The bridge is custom code. Estimated effort: one to two engineering days for the dispatch layer (HTTP client, SSE consumer or callback receiver, session-id scoping, category-hint emission), plus one engineering day for the timeout-and-retry policy and the budget-middleware integration. This is roughly fifty to one hundred lines of code on top of [[Backlog]] item 28 (Cheap-Tier Routing Layer Spec) and item 30 (Slack-App Kill-Switch Runbook). A new Backlog item documents the Letta-to-OpenCode bridge spec explicitly.
**Reversal.** If the HTTP API path encounters operational issues during v0.1, two fallback paths exist: the `opencode acp` stdio interface (Letta spawns a subprocess per task and communicates via JSON-RPC over stdio) or per-task subprocess spawn (`opencode run --command ...` synchronous invocation). Both are documented patterns; the HTTP API is preferred for v0.1 because it composes with the parallel human-interactive use case and supports the asynchronous dispatch model.
## v0.1 deployment topology — all-local on the dev machine
For the v0.1 trial period, **every component runs locally on the developer's machine**. This includes the Letta server, the `opencode serve` instance hosting OpenCode + omo, the per-persona Slack-app processes that bridge to the Letta agents, and all developer MCPs and Old-Growth-specific creative tooling (Blender, Godot, ComfyUI, Obsidian vault). The only cloud-hosted components are the LLM providers themselves (OpenRouter cheap-tier; Anthropic-direct foundational escalation per [[Autonomous Company LLM Provider Selection]]) and the Slack workspace (Slack Pro at $7.25/user/month, hosted by Slack as always).
This resolves two open hosting questions from the first-wave research: Letta hosting topology (self-hosted on dev machine for v0.1; not Letta Cloud, not VPS) and the Letta Code Channels server-hosting line item in [[Cost and Budget Model]] (zero dollars per month for v0.1; the Letta server is the dev machine itself).
### Dev-machine availability is the kill-switch surface
The previous research wave disqualified Anthropic Claude Code Channels in part because its execution model required the Claude Code session to be actively running on a persistent machine — "if the operator's laptop sleeps, messages go unanswered." The all-local v0.1 topology accepts the same constraint **knowingly**, with explicit mitigations and accepted residual risks. **Persona availability equals dev machine uptime.** When the dev machine sleeps, the autonomous company stops. When it restarts, in-flight tasks are lost unless persisted to local disk before the restart.
Mitigations in place for v0.1:
- **Amphetamine** (the macOS keep-awake utility) prevents the dev machine from sleeping during persona-active hours. This handles the most common reliability failure (laptop closed or auto-sleep timer firing).
- The dev machine remains plugged into power and connected to network. Slack-Socket-Mode connections, OpenCode `serve` traffic, and LLM API calls all assume continuous network availability.
- Local-disk persistence: Letta memory blocks, OpenCode session storage, Valkey Streams for `audit.failed` durability per [[Autonomous Company PM Surface and Event Bus]] all write to local disk and survive a clean restart. They do not survive a power-loss-during-write event.
Residual risks accepted for v0.1:
- **Power outage** kills the dev machine. v0.1 does not have UPS (uninterruptible power supply) protection. A short outage may corrupt in-flight Letta memory writes or OpenCode session state. Recovery requires the kill-switch runbook plus a fresh persona startup.
- **Network outage** kills the Slack Socket Mode connection, the LLM API calls, and the OpenCode session traffic. Personas pause and the runtime exhibits the same maintenance-mode behaviour as if the OpenCode server were unavailable (per the failure-modes section above).
- **Dev-machine restart** (OS update, Amphetamine misconfiguration, manual reboot) loses any in-flight persona work that has not been persisted.
These residual risks are accepted for the v0.1 trial because the trial is two weeks of supervised operation, not 24/7 unattended production. The probability of an unrecoverable failure during a two-week period under the Amphetamine-plus-power-plus-network mitigations is low enough that an additional UPS or hosted-infrastructure spend cannot be justified for v0.1.
### Migration path post-trial
The all-local topology is explicitly v0.1-scoped. If v0.1 succeeds and the autonomous-company experiment continues, **v0.2 migrates to hosted infrastructure**. The migration is staged so that no single component change is destabilising:
- **Stage 1 — Letta server moves first**. To a small VPS (DigitalOcean five-to-ten-dollar droplet) or to Letta Cloud (pricing TBD when v0.2 is scoped). Letta agents and their memory blocks export-import cleanly between local and remote Letta instances. The Letta-to-OpenCode HTTP API contract stays unchanged at the contract level; only the URL and auth credentials change. OpenCode + omo remains on the dev machine in this stage; the persona dispatches Letta-to-OpenCode-on-dev-machine via remote-to-local networking.
- **Stage 2 — OpenCode + omo moves second** if Stage 1 surfaces operational issues with the Letta-to-dev-machine round-trip. To the same VPS or to its own host. The integration contract URL changes again; the contract itself is unchanged.
- **Stage 3 — Per-persona Slack-app processes move with whichever stage hosts the Letta agents** (Stage 1 if Letta moves alone). Slack tokens (`xoxb-`, `xapp-`) are environment-portable.
- **Creative-tooling MCPs** are the most complex migration because they are local-resource-intensive (GPU for ComfyUI; the Godot editor for the gdai MCP server; Blender's native UI). v0.2 may keep these on the dev machine while the persona runtime moves to a VPS, with the persona dispatching back to the dev machine for creative-tooling tasks via a tunnelled MCP connection (Tailscale, Cloudflare Tunnel, or SSH reverse-tunnel). Or v0.2 may consolidate creative tooling onto a small home server with a GPU. The decision belongs in the v0.2 hosted-deployment migration spec ([[Backlog]] item TBD added in this commit wave).
The migration triggers are explicit and recorded in this Decision's verification triggers below.
## Creative-tooling MCP integration
The v0.1 dev machine **already runs the creative-tooling and developer-tooling MCPs the personas need**. The OpenCode session that the human uses today (per [[SDLC Orchestration Whiteboard]] and the existing `opencode.json` configuration) has access to all of the following MCPs, and the same `opencode serve` instance makes them accessible to persona-dispatched OpenCode sessions through the integration contract above:
| MCP | Capabilities | Persona use cases |
|---|---|---|
| **Blender MCP** | Scene control, object creation and inspection, viewport screenshots, Polyhaven / Sketchfab / Hyper3D / Hunyuan3D asset-generation integration, Python execution in Blender, scene-file content retrieval | GTM persona generating environmental visuals for devlog posts; Engineer persona inspecting 3D scene state when reviewing world-generation specs; creative-asset generation for the game world (per `audio-assets` skill conventions for non-audio assets) |
| **Godot AI MCP server (`gdai-mcp-server`)** | Scene tree inspection, script viewing and editing, runtime state querying, scene editor and game-window screenshots, input simulation, node properties, file search across the project | Engineer persona reviewing game state during architecture discussions; Playtest Coordinator persona checking that a spec's runtime expectations are met and capturing diagnostic screenshots for regression reports; release-readiness QA before the GTM persona drafts a release announcement |
| **ComfyUI MCP** | Image, video, and audio generation pipelines; PBR texture generation; image-to-image workflows; asset publishing to project paths; workflow listing, regeneration, and metadata retrieval | GTM persona generating devlog visuals; Narrative Director persona commissioning audio-pipeline assets per `audio-assets` skill; PBR texture generation for the world-generation pipeline (Engineer dispatches via OpenCode session) |
| **Obsidian vault MCP (`@bitbonsai/mcpvault`)** | Read, write, search, and manipulate vault notes; tag management; frontmatter operations; multi-note batch reads | Every persona's vault writes (the personas' primary persistent-document surface). Engineer writes to `Decisions/`, `Discoveries/`, and `Specs/NNN Title/`; Narrative Director writes to `Decisions/` (canon proposals) and `Design/` (world-bible expansions); Playtest Coordinator writes to `Discoveries/` (regression patterns) and `Sessions/` (QA-pass audit-log entries); GTM writes to `Discoveries/` when community feedback (when public-community surface exists) reveals constraints; Coordinator writes to `Sessions/` (routing decisions) |
| **context7** | Documentation discovery for libraries the personas reference | Engineer reviewing a library choice; any persona needing current library docs (Engineer is the primary consumer because Article III Rule 10 dependency proposals route through Engineer per the redesigned roster) |
| **websearch (Exa)** | Web search for current information not in any vault note or library doc | Librarian-equivalent fallback; current-events lookup; verification of external claims |
| **grep_app** | GitHub-wide code search for usage examples and reference implementations | Engineer finding precedent for an architectural pattern; persona-authored task delegation when reference implementation is needed |
These MCPs are not new infrastructure to build for v0.1. They are the existing developer-tooling surface that the personas access via the shared `opencode serve` instance. Persona task prompts to OpenCode either declare which MCPs are needed (explicit) or omo's category routing infers from the task category (implicit — `visual-engineering` likely needs Blender or ComfyUI; `deep` may need context7 plus grep_app; `quick` likely needs none beyond the vault). The persona has no direct access to the MCPs — only through the OpenCode session that the dispatch creates. This composes cleanly with the privilege-separation principle from [[Off-limits Functions]]: the persona's authority to act through an MCP is mediated by the OpenCode session's tool-call layer, which can be restricted, audited, and revoked at the dispatch level.
The MCP catalogue above is also the answer to "what tools do the personas have available beyond LLM calls" — not an additional thing to build, but the existing tooling surface the personas inherit from the dev machine's `opencode serve` configuration. Per the all-local topology in this Decision, every MCP listed runs on the same dev machine as the personas; there is no remote-MCP networking concern for v0.1.
## Rejected and considered alternatives
### Option A — Hermes Agent (Nous Research) — first fallback
Hermes Agent at v0.13.0 "Tenacity" (2026-05-07) has the **strongest Slack feature density across the four runtimes**: Socket Mode only (no public HTTP endpoint required), native Slack slash command auto-generation from a Slack manifest (`hermes slack manifest --write`), per-channel ephemeral prompts and per-channel skill bindings, multi-workspace support via comma-separated bot tokens, voice-message transcription (incoming audio → STT), home-channel proactive notifications, allowed-users allowlist by Member ID, and a security-first default profile with all P0/P1 Slack-specific bugs closed in the latest release. MIT-licensed, NousResearch-backed with three hundred eighty contributors, and the Kanban-plus-`/goal`-Ralph-loop pattern matches the "PM coordinates, Claude Code executes" workflow natively.
Documented as the **first fallback** if Letta Code Channels' Slack adapter encounters operational issues during v0.1, or if Slack-specific feature density (per-channel prompts, native slash commands, voice transcription) turns out to matter more for the human review workflow than Letta's Conversations API per-channel-shared-memory architecture.
Rejected as the v0.1 default because Hermes does not have a first-class equivalent to Letta's Conversations API for per-channel shared memory across one agent. The architecture would require either custom memory-bridging code or a workaround similar to the OpenClaw `shared-context.md` pattern. Reconsider if Letta's Slack adapter encounters issues, if security-default discipline matters more than multi-channel memory architecture, or if the per-channel feature density (slash commands, voice, per-channel prompts) becomes a significant operational advantage during v0.1.
### Option B — OpenClaw with the Honcho plugin
OpenClaw remains a real and viable runtime if the operator accepts four explicit risks. First, ongoing security-monitoring responsibility (thirty-one CVEs in eight weeks during peak founder activity, no full-time stewardship after February 2026 founder departure). Second, the Honcho plugin as a hard dependency for the per-channel-shared-memory architecture (since bare OpenClaw isolates each Slack channel as a separate session and Issue #64311 was closed as plugin-path). Third, the foundation-transition unknown (the OpenClaw Foundation is structurally credible — MIT licence permanently locked, TSC supermajority required to change — but operationally unproven for sustained CVE response). Fourth, three open Slack-specific regressions in v2026.5.7: socket connection permanently dead after event-loop starvation (Issue 77651); thread session keys not promoted on outbound replies (Issue 78505); bot-started threads bypass @mention policy (Issue 77927).
Rejected as the v0.1 default because for a solo developer operating without dedicated security-response capacity, OpenClaw shifts the security burden onto the operator. The Honcho plugin requirement adds a third-party dependency to a critical-path architectural feature. The three open Slack-specific bugs each individually represent risk for an always-on persona system. Reconsider if Letta and Hermes both prove operationally infeasible for some other reason, or if OpenClaw's foundation demonstrates sustained CVE response and closes the three open Slack bugs over the next quarter.
### Option C — Anthropic Claude Code Channels with the ClawCode plugin (eliminated)
Claude Code Channels launched 2026-03-20 as Anthropic's research preview of always-on Claude Code reachable via Telegram, Discord, and iMessage MCP bridges. **Slack is not on the research-preview allowlist and there is no Slack plugin in the `claude-plugins-official` repository as of 2026-05-08.** A separate "Claude in Slack" product exists but it spawns cloud sessions on @mention rather than running a persistent agent in Slack — orthogonal to the Channels research preview.
Architecturally, Claude Code Channels requires `claude --channels plugin:telegram@...` to be **actively running** — events only arrive while the session is open. For an autonomous-company experiment with four persistent agent personas running twenty-four-by-seven, Claude Code Channels would require four background terminal sessions with the channels flag, which is architecturally fragile for a solo dev's autonomous workflow. There is no Slack plugin and the plugin API is allowlist-restricted during research preview.
Eliminated as a runtime candidate. It is better thought of as a **human-in-the-loop personal interface** (text your terminal session, get responses) rather than a runtime hosting persistent persona personas. Reconsider only if Anthropic graduates Claude Code Channels to general availability with a stable plugin API, ships a Slack plugin, and removes the always-running-session requirement.
### Option D — Bare OpenClaw without the Honcho plugin
Rejected outright. Bare OpenClaw cannot deliver the per-channel-shared-memory architecture the brainstorm describes; Issue #64311 closed this as a plugin-path concern. Selecting bare OpenClaw would mean either accepting per-channel-isolated personas (a different and weaker architecture than the brainstorm) or implementing the Honcho equivalent ourselves. Neither matches the v0.1 scope.
### Option E — Letta with LettaBot (deprecated)
LettaBot was the original multi-channel bridge for Telegram, Slack, Discord, WhatsApp, and Signal. As of 2026-05-08 it is **archived/deprecated**; the README carries a banner directing all new development to Letta Code Channels. Existing LettaBot deployments will continue to function but will receive no security patches or feature updates. The first-pass Decision named LettaBot as the Discord bridge product; the corrected naming is Letta Code Channels.
Rejected for any new work. Use Letta Code Channels (the current product) instead.
### Option F — Roll our own runtime
Rejected outright for v0.1. The two-week experiment scope assumes existing tooling. Building a persistent-agent runtime from scratch is months of work, not a v0.1 candidate. Reconsider only if all three viable runtimes above prove unworkable in production, which is not what the verification research suggests.
### Option G — CrewAI / AutoGen / LangGraph as the runtime
Rejected as wrong-abstraction. These are agent-orchestration libraries, not persistent-process hosts with multi-channel chat-bridge integration. The autonomous company already has Sisyphus and Spec Kit at the orchestration layer; a library-level orchestrator would duplicate that and provide nothing for the always-on persona-in-channel use case.
## Affected files
- [[SDLC Orchestration Whiteboard]] — Layer 1 (persistent-agent-runtime), Layer 4 (chat surface), the role-mapping table, and event-flow examples revised across this commit wave. The `agent_launch Claude Code` references are corrected to the OpenCode + omo dispatch via `opencode serve` HTTP API per the integration contract above.
- [[Autonomous Company PM Surface and Event Bus]] — confirms Slack as the chat surface, which composes with this Decision's runtime choice.
- The persona-authoring work in [[Backlog]] items 4–7b (Coordinator, Engineer, Narrative Director, Playtest Coordinator, GTM SOUL.md drafts per the [[Role Personas]] redesigned five-persona roster shipping incrementally per the v0.1 → v0.2 → v0.5 growth path) inherits the Letta typed-memory-block structure (core / archival / recall) from this decision once ratified, plus the per-persona Slack-app registration model (each persona is a separately-registered Slack app with its own `xoxb-` bot token and `xapp-` app-level token for Socket Mode), plus the OpenCode session API contract for code-touching task dispatch.
- Role Personas — Nebula precedent disambiguator added in this commit wave (Nebula uses Claude Code/Codex/Gemini as their body; we use OpenCode + omo for the equivalent worker harness).
- [[Cost and Budget Model]] — human-driven Sisyphus session line revised in this commit wave to reference OpenCode + omo rather than Claude Code/Codex; Nebula precedent gets the same disambiguator. Letta hosting line item resolved to "$0/month — local on dev machine for v0.1" per the all-local deployment topology section in this Decision.
- The drift-detection middleware in [[Drift Detection and Audit Mechanics]] writes to and reads from Letta memory blocks rather than free-floating persona files; this affects the SyncScore precomputation location and the per-persona reference-distribution storage. OpenCode session dispatches are scoped under the persona's `archival` memory accumulation per the integration contract above.
- A new Backlog item documents the v0.2 hosted-deployment migration spec (when v0.1 trial period ends and the autonomous-company experiment continues, components migrate from the dev machine to a VPS or Letta Cloud per the migration triggers below).
## Reversal cost
Medium. Persona system prompts are markdown and are runtime-portable. Memory contents (core, archival, recall blocks) are structured data and would need to be exported and re-imported in the new runtime's memory model. Slack app tokens (`xoxb-` bot, `xapp-` app-level) are runtime-independent — they remain valid when switching runtimes. The Slack-channel binding configuration, MCP server connections, and the OpenCode session API integration code are runtime-specific and would need re-implementation. Estimated effort to reverse mid-experiment: three to five engineering days; reversing after persona memory has accumulated: one to two weeks.
The OpenCode worker integration itself is more reversible than the runtime choice: switching from `opencode serve` HTTP API to the `opencode acp` stdio interface or to per-task subprocess spawn is a one-engineering-day change in the bridge layer and does not affect persona memory or Slack-app configuration. Switching the worker harness off OpenCode entirely (back to Claude Code, or to Codex, or to a custom worker) is a larger change because the persona's task prompt format and category-hint convention are calibrated to omo's category routing.
The reversal cost was the primary reason this decision was originally recorded as `draft` pending ratification — a wrong choice was more expensive than a careful choice. The four research waves on 2026-05-08 narrowed the choice through verified evidence (Slack-vs-Discord feature comparison, OpenCode + omo verification, Architecture B rejection on three deal-breakers, all-local deployment topology committed); human ratification followed the same day, promoting the Decision to `status: reviewed`. The reversal cost numbers above remain accurate as the cost of changing the runtime choice mid-experiment if v0.1 surfaces an issue not caught by the verification.
## Verification trigger for revisit
Revisit this decision when any of the following occur:
- Letta ships a breaking change to the Conversations API or memory-block model that affects the four-persona shared-memory architecture.
- Letta Code Channels' Slack adapter encounters operational issues during v0.1 (Socket Mode reliability, conversation routing edge cases, rate limit handling).
- The Letta-to-OpenCode HTTP API bridge encounters operational issues during v0.1 (the documented OpenCode Issue #6573 where REST-API-spawned subagents stick in `busy` state; OpenCode's `@opencode-ai/slack` Issue #20075 if it affects the parallel human-interactive use case; or unexpected timeout-and-retry behaviour). Fall back to the ACP stdio interface or per-task subprocess spawn before reconsidering the runtime choice.
- OpenCode + omo ships a feature that materially changes the Architecture B feasibility verdict (e.g. native per-persona memory across channels, fix for Issue #20075 enabling remote `opencode serve` connection from `@opencode-ai/slack`, autonomous-dispatch trigger layer). At that point Architecture B (collapsing Letta and using OpenCode + omo as the runtime) becomes worth re-evaluating.
- An OpenClaw Foundation security-response demonstration over an extended period (no new high-severity CVE without a 24-hour patch) closes the gap on the security-maturity argument, AND the three open Slack-specific bugs (#77651, #78505, #77927) are closed in a stable release.
- Anthropic graduates Claude Code Channels from research preview to general availability with a stable plugin API and ships a Slack plugin.
- Hermes Agent ships an equivalent of Letta's Conversations API as a first-class feature, OR Slack-specific feature density (per-channel prompts, native slash commands, voice) becomes a significant operational advantage that outweighs Letta's Conversations API benefit.
- A new entrant (one not surveyed in [[Autonomous Company Tool Stack Verification]] in May 2026) ships a runtime that addresses both per-channel-shared-memory and security maturity simultaneously.
Migration triggers for the v0.1-to-v0.2 hosted-deployment transition (per the all-local deployment topology section above):
- v0.1 succeeds and the human commits to v0.2 (extended autonomous-company operation beyond the two-week trial). At that point the all-local topology migrates to hosted infrastructure starting with the Letta server.
- A power-outage or network-outage incident during v0.1 demonstrates the reliability cost of all-local at higher impact than expected. v0.2 prioritises hosted Letta plus OpenCode plus Slack-app processes.
- The persona load increases such that the dev machine cannot reliably host the persona runtime alongside the human's interactive Sisyphus sessions (CPU contention, RAM exhaustion, disk-space pressure on local Letta memory blocks plus OpenCode session storage plus Valkey Streams plus the creative-tooling MCPs' resource demands).
- A second human operator joins the autonomous company. At that point the dev machine cannot be the single host because two people cannot both have keep-awake on their own laptops without conflict over the persona state.
- Amphetamine fails to prevent sleep (macOS Big Sur or later "Power Nap" interactions, OS update changing keep-awake semantics, etc.) and the failure produces a persona-availability incident.
## References
- [[Autonomous Company Tool Stack Verification]] — the original research; the second-wave Slack-pivot addendum that corrected the LettaBot product naming; the third-wave addendum that confirms OpenCode + omo as the worker harness and rejects the Architecture B replacement of Letta; the fourth-wave addendum that records the all-local deployment topology for v0.1 and the migration path to v0.2 hosted infrastructure
- [[Autonomous Company LLM Provider Selection]] — the LLM-provider decision this runtime composes with
- [[Autonomous Company PM Surface and Event Bus]] — the chat-surface (Slack) and per-persona Slack-app model that compose with this Decision
- [[SDLC Orchestration Whiteboard]] — Layer 1 (runtime) and Layer 4 (chat surface) preferences this Decision supersedes
- [[Role Personas]] — Coordinator, Engineer, Narrative Director, Playtest Coordinator, GTM persona definitions that the runtime hosts (redesigned 2026-05-08; this Decision's architectural conclusions are unchanged because the runtime choice is downstream of the roster decomposition)
- [[Drift Detection and Audit Mechanics]] — SyncScore and behavioral-invariant requirements that compose with the runtime's memory model
- Letta documentation (docs.letta.com)
- Letta Code Channels documentation (docs.letta.com/letta-code/channels)
- Letta Conversations API blog (letta.com/blog/conversations)
- Letta Code FIRST_PARTY_CHANNEL_IDS (`telegram`, `slack`, `discord`)
- LettaBot deprecation banner (github.com/letta-ai/lettabot)
- OpenClaw Issue #64311 — per-channel memory isolation, closed as plugin-path
- OpenClaw Issue #77651 — Slack socket permanently dead after event-loop starvation (open, v2026.5.7)
- OpenClaw Issue #78505 — Slack outbound thread reply does not promote session key (open, v2026.5.7)
- OpenClaw Issue #77927 — Slack bot-started threads should require @mention (open, v2026.5.7)
- Hermes Agent v2026.5.7 "Tenacity" release notes (github.com/NousResearch/hermes-agent)
- Hermes Agent Slack documentation (github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/messaging/slack.md)
- Honcho plugin documentation (the v0.1 mitigation if Option B is selected)
- Anthropic Claude Code Channels research-preview announcement (2026-03-20)
- Anthropic Claude Code Channels documentation (docs.anthropic.com/en/docs/claude-code/channels)
- ClawCode plugin (github.com/AdamSriko/ClawCode)
- OpenCode documentation (opencode.ai/docs)
- OpenCode server documentation (opencode.ai/docs/server) — `opencode serve` HTTP API used for the Letta-to-OpenCode integration contract
- OpenCode `@opencode-ai/slack` package documentation (Socket Mode bridge for human-interactive sessions)
- OpenCode Issue #6573 — REST-API-spawned subagents can stick in `busy` state (documented failure mode for the v0.1 timeout-and-retry policy)
- OpenCode Issue #20075 — `@opencode-ai/slack` cannot connect to remote `opencode serve` (open, labelled `core`, blocks Architecture B and is a watch-item for Architecture A's parallel human-interactive use case)
- OpenCode Agent Teams design doc and PRs (#12711 design, #12730 core, #12731 tools, #12732 TUI; merged 2026-02-08, gated behind `OPENCODE_EXPERIMENTAL_AGENT_TEAMS=1`)
- omo (oh-my-openagent) repository (github.com/code-yeongyu/oh-my-openagent) — v4.0.0 Team Mode release 2026-05-07; the project's `~/.config/opencode/oh-my-openagent.json` model-config file
- Amphetamine (macOS keep-awake utility, App Store) — the v0.1 mitigation against laptop sleep that would otherwise stop the autonomous company
- Blender MCP — in-repo configuration; provides scene control, viewport screenshots, and Polyhaven / Sketchfab / Hyper3D / Hunyuan3D asset generation to persona-dispatched OpenCode sessions
- Godot AI MCP server (`gdai-mcp-server`) — in-repo configuration; provides scene-tree inspection, script editing, runtime state, and screenshot capture
- ComfyUI MCP — in-repo configuration; provides image / video / audio generation pipelines, PBR texture generation, and asset publishing
- Obsidian vault MCP (`@bitbonsai/mcpvault`) — in-repo `opencode.json` configuration as the `obsidian` MCP key; provides vault read / write / search / tag-management operations to all personas as their primary persistent-document surface
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-autonomous-company-agent-runtime-selection: domain/tooling
Tag assignment · test- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.14 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/Autonomous Company Agent Runtime Selection.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Autonomous Company Agent Runtime Selection
> **Persona roster update 2026-05-08**: persona names in this Decision were updated to match the redesigned roster from [[Role Personas]] (Coordinator, Engineer, Narrative Director, Playtest Coordinator, GTM, replacing the original CEO/CTO/GTM/PM frame). The architectural conclusions are unchanged and the `status: reviewed` ratification stands — the runtime choice is downstream of the persona roster, not upstream of it, and the Coordinator-as-routing-hub topology in the redesign vindicates Letta's per-channel-shared-memory architecture.
This is the second of three decisions that supersede Backlog item 1 (Tool Stack Decision ADR) in the autonomous-company hub. It selects the persistent-agent runtime — the long-running process layer where the persona roster lives, holds memory, and connects to Slack channels. This Decision was revised across four research waves between 2026-05-08 morning and afternoon: the second-wave research pass added Slack as the confirmed internal-ops chat surface and corrected LettaBot to Letta Code Channels; the third-wave research established OpenCode + omo as the worker harness (replacing the brainstorm's Claude Code references) and specified the Letta-to-OpenCode HTTP API integration contract; the fourth-wave research committed the all-local v0.1 deployment topology, Amphetamine mitigation, and creative-tooling MCP integration. **The four-way runtime choice narrowed to Letta + Letta Code Channels as the v0.1 commitment, with Hermes Agent v0.13.0 "Tenacity" as the documented first fallback. Anthropic Claude Code Channels was eliminated (no Slack support; not production-ready for autonomous twenty-four-by-seven operation regardless of platform); OpenClaw + Honcho plugin was deprioritised due to three open Slack-specific regressions in v2026.5.7.** **This Decision was promoted to `status: reviewed` on 2026-05-08 by human ratification** — the v0.1 implementation work (persona authoring per [[Backlog]] items 4–7, the Letta-to-OpenCode bridge implementation, the per-persona Slack-app registration) can now proceed against this Decision as the committed architecture.
## Trigger
The autonomous-company brainstorm in [[SDLC Orchestration Whiteboard]] Layer 1 captured a provisional preference for OpenClaw as the persistent-agent runtime. The first-wave research recorded in [[Autonomous Company Tool Stack Verification]] identified three issues with that preference: OpenClaw's per-channel memory model is broken in core (Issue #64311, closed as plugin-path; bare OpenClaw isolates each chat channel as a separate session with zero continuity); OpenClaw's security posture between January and March 2026 included thirty-one CVEs, a CVSS 8.8 WebSocket one-click remote code execution, the ClawHavoc supply-chain incident, and one hundred thirty-five thousand or more internet-exposed instances; and the project's founder departed for OpenAI in February 2026 with the project transferred to a foundation whose operational governance is credible but untested.
The second-wave research adds three further findings that revise the runtime ranking. First, LettaBot is archived; the current Letta product for multi-channel chat bridging is **Letta Code Channels** (the channels system inside the Letta Code CLI), which has first-class Slack support architecturally identical to its Discord support — Socket Mode via `@slack/bolt`, per-channel routing, and the same Conversations API per-channel-shared-memory mechanism. Second, OpenClaw v2026.5.7 carries three open Slack-specific regressions: socket connection permanently dead after event-loop starvation with manuallyStopped suppressing auto-reconnect (Issue 77651); outbound thread reply does not promote session key to thread, so the next inbound thread message lands in a fresh session (Issue 78505); and bot-started threads do not require @mention to trigger agent action (Issue 77927). For an always-on persona system, these are material risks. Third, Anthropic Claude Code Channels does not support Slack — only Telegram, Discord, and iMessage are on the research-preview allowlist as of 2026-05-08 — and the architecture (Claude Code session must remain open on a persistent machine) is not production-ready for autonomous twenty-four-by-seven multi-agent operation regardless of platform.
## Decision (reviewed — ratified by human 2026-05-08)
The committed v0.1 runtime is **Letta with Letta Code Channels** as the Slack bridge. This Decision was promoted from `status: draft` to `status: reviewed` by human ratification on 2026-05-08, after four research waves narrowed the four-way choice and specified the Letta-to-OpenCode integration contract plus the all-local deployment topology. The implementation work (persona authoring, the Letta-to-OpenCode bridge, per-persona Slack-app registration) is now unblocked.
If the recommendation is ratified, the v0.1 stack is:
- **Letta server** (Apache-2.0, self-hosted or Letta Cloud) as the persistent-agent runtime.
- **Letta Code Channels** (the Channels system inside the Letta Code CLI) as the Slack bridge for the four personas. `FIRST_PARTY_CHANNEL_IDS` lists `slack` alongside `telegram` and `discord`; the Slack adapter uses `@slack/bolt` with Socket Mode (no public HTTP endpoint required, no reverse proxy, no tunnel — friendly for a self-hosted solo-dev setup).
- **OpenCode + omo as the execution worker** dispatched by personas via the `opencode serve` HTTP API for code-touching tasks (matching the "PM coordinates, OpenCode + omo executes" pattern in [[SDLC Orchestration Whiteboard]]). OpenCode + omo is the project's existing agentic harness — the brainstorm's earlier "Claude Code as worker" framing was a brainstorm error and is corrected throughout this commit wave per the third research wave recorded in [[Autonomous Company Tool Stack Verification]] addendum.
- **Letta Conversations API** as the per-channel-shared-memory mechanism — single agent per persona role, multiple concurrent conversation threads (one per Slack channel binding), shared memory across all threads.
- Memory blocks per persona: a `core` block for the persona's identity and immutable contract, an `archival` block (vector-backed) for accumulated experience, and a `recall` block for full conversation history.
- Slack routing: @mentions in channels create new agent conversations bound to the thread; subsequent messages in that thread route without further mentions; DMs map 1:1 to a conversation; auto-provisioning on first interaction (per Letta Code Channels documentation).
Why Letta over the alternatives:
- Letta's Conversations API directly implements the per-channel-shared-memory architecture the brainstorm describes. This is a first-class feature, not a workaround. Bare OpenClaw requires the Honcho plugin to deliver the same shape; Hermes Agent requires custom memory-bridging code.
- Letta Code Channels has first-class Slack support — `FIRST_PARTY_CHANNEL_IDS` includes `slack`; the Slack adapter is shipped as a core feature, not a plugin; Socket Mode is the default transport.
- Letta's typed memory blocks (core, archival, recall) map naturally to the SOUL.md / CHARTER.md / INTENT.md three-tier structure documented in [[Drift Detection and Audit Mechanics]] and the SoulSpec convention.
- Letta is YC-backed with stable funding and Apache-2.0 licensing; the bus-factor and license-stability concerns that apply to OpenClaw do not apply here.
- Letta is model-agnostic; persona memory survives model swaps, which is forward-compatible with the [[Autonomous Company LLM Provider Selection]] decision (cheap-tier-default routing through OpenRouter with Anthropic-direct foundational escalation).
What this decision does *not* settle:
- The exact Letta hosting topology (self-hosted on a personal server versus Letta Cloud) — defer until after ratification because hosting cost and operational burden trade off differently for v0.1 versus steady-state operation.
- Whether OpenCode is invoked via the `opencode serve` HTTP API (the v0.1 default per the integration contract below), the ACP stdio interface, or a per-task subprocess spawn — the v0.1 default is HTTP API, but reversal to ACP or subprocess remains low-cost if the HTTP API path encounters operational issues.
## Letta-to-OpenCode integration contract (v0.1)
The runtime selected here (Letta + Letta Code Channels) hosts the persistent persona personas with Slack-app bridging and per-channel-shared-memory. The execution worker (OpenCode + omo) is the project's existing agentic harness, dispatched via the `opencode serve` HTTP API. The contract for v0.1:
**Topology.** A single `opencode serve` instance runs on the autonomous-company host, bound to localhost on a configured port. The Letta server (or per-persona Slack-app processes hosting Letta agents) and the OpenCode server are separate processes on the same host or composing through localhost networking. The same `opencode serve` instance is also reachable by the human's interactive sessions (TUI, web, `--attach`) — one OpenCode installation, multiple invocation paths.
**Dispatch.** When a Letta persona decides on a code-touching task, the persona's tool layer invokes the OpenCode session API:
- `POST /session` to create a new session for the task, or `POST /session/:id/fork` to branch from a stable base session that has the project context cache-warmed.
- `POST /session/:id/message` with a structured prompt that includes (a) the persona identity tag, (b) the task specification, (c) relevant memory-block context the persona has accumulated, and (d) optional category hint for omo (`<task-category>code-light</task-category>`, `<task-category>visual-engineering</task-category>`, etc.).
- omo's category-based routing handles the model selection inside OpenCode per [[Autonomous Company LLM Provider Selection]]: cheap-tier defaults (MiniMax M2.5 default, Gemini 2.5 Flash classification, Qwen3-Coder Plus code-light) with Anthropic-direct foundational escalation only on explicit triggers.
**Asynchrony.** The dispatch is asynchronous. Letta does not block the persona's Slack conversation flow on the OpenCode session. Two response paths are supported, with the first preferred for v0.1:
- **Server-Sent Events**. The persona's tool layer subscribes to the OpenCode session's SSE stream and processes tool-call events, message events, and the final session-complete event. The persona can post in-progress updates to Slack (per the chat-streaming pattern from [[Community Layer]]) before the final result.
- **Callback URL**. The dispatch prompt includes a callback URL that the OpenCode session hits when it completes. The persona's Letta agent receives the callback as an event and synthesises the result.
**Result handling.** The persona reads the OpenCode session's final message, validates against its CHARTER constraints, accumulates the result into the persona's `archival` memory block per [[Drift Detection and Audit Mechanics]], and synthesises a Slack-facing response that flows through the per-persona output-filtering middleware (per Community Layer) before posting via `chat.postMessage`.
**Budget enforcement.** OpenCode's invocation runs through the same OpenRouter account as the persona's direct LLM calls per Autonomous Company LLM Provider Selection. The budget-enforcement middleware reserves projected cost before each LLM call inside the OpenCode session, not just before the dispatch. omo's category-based routing fits naturally because the cheap-tier defaults match the routing chains documented in Decision 1.
**Reverse compatibility.** The human's interactive Sisyphus sessions on the same `opencode serve` instance use the OpenCode TUI, web client, or `--attach` mode. Persona-dispatched sessions and human-interactive sessions share the same OpenCode server and the same omo plugin configuration but are separate sessions with their own message histories. Cross-contamination between personas, between sessions, and between the human's work and persona work is prevented by session-id scoping at the OpenCode layer.
**Failure modes.** If the OpenCode session hangs (per the documented Issue #6573 — REST-API-spawned subagents can stick in `busy` state), the Letta persona times out the call after a configured threshold (recommended sixty seconds for code-light tasks, three hundred seconds for foundational-escalation tasks) and either retries with a fresh session or escalates the task to the human. If the OpenCode server is unavailable, the persona refuses the dispatch and posts a maintenance-mode notice to its primary Slack channel; the budget-enforcement middleware also serves as the kill-switch surface in this case (refusing further dispatch attempts until the operator re-enables).
**v0.1 implementation work.** The bridge is custom code. Estimated effort: one to two engineering days for the dispatch layer (HTTP client, SSE consumer or callback receiver, session-id scoping, category-hint emission), plus one engineering day for the timeout-and-retry policy and the budget-middleware integration. This is roughly fifty to one hundred lines of code on top of [[Backlog]] item 28 (Cheap-Tier Routing Layer Spec) and item 30 (Slack-App Kill-Switch Runbook). A new Backlog item documents the Letta-to-OpenCode bridge spec explicitly.
**Reversal.** If the HTTP API path encounters operational issues during v0.1, two fallback paths exist: the `opencode acp` stdio interface (Letta spawns a subprocess per task and communicates via JSON-RPC over stdio) or per-task subprocess spawn (`opencode run --command ...` synchronous invocation). Both are documented patterns; the HTTP API is preferred for v0.1 because it composes with the parallel human-interactive use case and supports the asynchronous dispatch model.
## v0.1 deployment topology — all-local on the dev machine
For the v0.1 trial period, **every component runs locally on the developer's machine**. This includes the Letta server, the `opencode serve` instance hosting OpenCode + omo, the per-persona Slack-app processes that bridge to the Letta agents, and all developer MCPs and Old-Growth-specific creative tooling (Blender, Godot, ComfyUI, Obsidian vault). The only cloud-hosted components are the LLM providers themselves (OpenRouter cheap-tier; Anthropic-direct foundational escalation per [[Autonomous Company LLM Provider Selection]]) and the Slack workspace (Slack Pro at $7.25/user/month, hosted by Slack as always).
This resolves two open hosting questions from the first-wave research: Letta hosting topology (self-hosted on dev machine for v0.1; not Letta Cloud, not VPS) and the Letta Code Channels server-hosting line item in [[Cost and Budget Model]] (zero dollars per month for v0.1; the Letta server is the dev machine itself).
### Dev-machine availability is the kill-switch surface
The previous research wave disqualified Anthropic Claude Code Channels in part because its execution model required the Claude Code session to be actively running on a persistent machine — "if the operator's laptop sleeps, messages go unanswered." The all-local v0.1 topology accepts the same constraint **knowingly**, with explicit mitigations and accepted residual risks. **Persona availability equals dev machine uptime.** When the dev machine sleeps, the autonomous company stops. When it restarts, in-flight tasks are lost unless persisted to local disk before the restart.
Mitigations in place for v0.1:
- **Amphetamine** (the macOS keep-awake utility) prevents the dev machine from sleeping during persona-active hours. This handles the most common reliability failure (laptop closed or auto-sleep timer firing).
- The dev machine remains plugged into power and connected to network. Slack-Socket-Mode connections, OpenCode `serve` traffic, and LLM API calls all assume continuous network availability.
- Local-disk persistence: Letta memory blocks, OpenCode session storage, Valkey Streams for `audit.failed` durability per [[Autonomous Company PM Surface and Event Bus]] all write to local disk and survive a clean restart. They do not survive a power-loss-during-write event.
Residual risks accepted for v0.1:
- **Power outage** kills the dev machine. v0.1 does not have UPS (uninterruptible power supply) protection. A short outage may corrupt in-flight Letta memory writes or OpenCode session state. Recovery requires the kill-switch runbook plus a fresh persona startup.
- **Network outage** kills the Slack Socket Mode connection, the LLM API calls, and the OpenCode session traffic. Personas pause and the runtime exhibits the same maintenance-mode behaviour as if the OpenCode server were unavailable (per the failure-modes section above).
- **Dev-machine restart** (OS update, Amphetamine misconfiguration, manual reboot) loses any in-flight persona work that has not been persisted.
These residual risks are accepted for the v0.1 trial because the trial is two weeks of supervised operation, not 24/7 unattended production. The probability of an unrecoverable failure during a two-week period under the Amphetamine-plus-power-plus-network mitigations is low enough that an additional UPS or hosted-infrastructure spend cannot be justified for v0.1.
### Migration path post-trial
The all-local topology is explicitly v0.1-scoped. If v0.1 succeeds and the autonomous-company experiment continues, **v0.2 migrates to hosted infrastructure**. The migration is staged so that no single component change is destabilising:
- **Stage 1 — Letta server moves first**. To a small VPS (DigitalOcean five-to-ten-dollar droplet) or to Letta Cloud (pricing TBD when v0.2 is scoped). Letta agents and their memory blocks export-import cleanly between local and remote Letta instances. The Letta-to-OpenCode HTTP API contract stays unchanged at the contract level; only the URL and auth credentials change. OpenCode + omo remains on the dev machine in this stage; the persona dispatches Letta-to-OpenCode-on-dev-machine via remote-to-local networking.
- **Stage 2 — OpenCode + omo moves second** if Stage 1 surfaces operational issues with the Letta-to-dev-machine round-trip. To the same VPS or to its own host. The integration contract URL changes again; the contract itself is unchanged.
- **Stage 3 — Per-persona Slack-app processes move with whichever stage hosts the Letta agents** (Stage 1 if Letta moves alone). Slack tokens (`xoxb-`, `xapp-`) are environment-portable.
- **Creative-tooling MCPs** are the most complex migration because they are local-resource-intensive (GPU for ComfyUI; the Godot editor for the gdai MCP server; Blender's native UI). v0.2 may keep these on the dev machine while the persona runtime moves to a VPS, with the persona dispatching back to the dev machine for creative-tooling tasks via a tunnelled MCP connection (Tailscale, Cloudflare Tunnel, or SSH reverse-tunnel). Or v0.2 may consolidate creative tooling onto a small home server with a GPU. The decision belongs in the v0.2 hosted-deployment migration spec ([[Backlog]] item TBD added in this commit wave).
The migration triggers are explicit and recorded in this Decision's verification triggers below.
## Creative-tooling MCP integration
The v0.1 dev machine **already runs the creative-tooling and developer-tooling MCPs the personas need**. The OpenCode session that the human uses today (per [[SDLC Orchestration Whiteboard]] and the existing `opencode.json` configuration) has access to all of the following MCPs, and the same `opencode serve` instance makes them accessible to persona-dispatched OpenCode sessions through the integration contract above:
| MCP | Capabilities | Persona use cases |
|---|---|---|
| **Blender MCP** | Scene control, object creation and inspection, viewport screenshots, Polyhaven / Sketchfab / Hyper3D / Hunyuan3D asset-generation integration, Python execution in Blender, scene-file content retrieval | GTM persona generating environmental visuals for devlog posts; Engineer persona inspecting 3D scene state when reviewing world-generation specs; creative-asset generation for the game world (per `audio-assets` skill conventions for non-audio assets) |
| **Godot AI MCP server (`gdai-mcp-server`)** | Scene tree inspection, script viewing and editing, runtime state querying, scene editor and game-window screenshots, input simulation, node properties, file search across the project | Engineer persona reviewing game state during architecture discussions; Playtest Coordinator persona checking that a spec's runtime expectations are met and capturing diagnostic screenshots for regression reports; release-readiness QA before the GTM persona drafts a release announcement |
| **ComfyUI MCP** | Image, video, and audio generation pipelines; PBR texture generation; image-to-image workflows; asset publishing to project paths; workflow listing, regeneration, and metadata retrieval | GTM persona generating devlog visuals; Narrative Director persona commissioning audio-pipeline assets per `audio-assets` skill; PBR texture generation for the world-generation pipeline (Engineer dispatches via OpenCode session) |
| **Obsidian vault MCP (`@bitbonsai/mcpvault`)** | Read, write, search, and manipulate vault notes; tag management; frontmatter operations; multi-note batch reads | Every persona's vault writes (the personas' primary persistent-document surface). Engineer writes to `Decisions/`, `Discoveries/`, and `Specs/NNN Title/`; Narrative Director writes to `Decisions/` (canon proposals) and `Design/` (world-bible expansions); Playtest Coordinator writes to `Discoveries/` (regression patterns) and `Sessions/` (QA-pass audit-log entries); GTM writes to `Discoveries/` when community feedback (when public-community surface exists) reveals constraints; Coordinator writes to `Sessions/` (routing decisions) |
| **context7** | Documentation discovery for libraries the personas reference | Engineer reviewing a library choice; any persona needing current library docs (Engineer is the primary consumer because Article III Rule 10 dependency proposals route through Engineer per the redesigned roster) |
| **websearch (Exa)** | Web search for current information not in any vault note or library doc | Librarian-equivalent fallback; current-events lookup; verification of external claims |
| **grep_app** | GitHub-wide code search for usage examples and reference implementations | Engineer finding precedent for an architectural pattern; persona-authored task delegation when reference implementation is needed |
These MCPs are not new infrastructure to build for v0.1. They are the existing developer-tooling surface that the personas access via the shared `opencode serve` instance. Persona task prompts to OpenCode either declare which MCPs are needed (explicit) or omo's category routing infers from the task category (implicit — `visual-engineering` likely needs Blender or ComfyUI; `deep` may need context7 plus grep_app; `quick` likely needs none beyond the vault). The persona has no direct access to the MCPs — only through the OpenCode session that the dispatch creates. This composes cleanly with the privilege-separation principle from [[Off-limits Functions]]: the persona's authority to act through an MCP is mediated by the OpenCode session's tool-call layer, which can be restricted, audited, and revoked at the dispatch level.
The MCP catalogue above is also the answer to "what tools do the personas have available beyond LLM calls" — not an additional thing to build, but the existing tooling surface the personas inherit from the dev machine's `opencode serve` configuration. Per the all-local topology in this Decision, every MCP listed runs on the same dev machine as the personas; there is no remote-MCP networking concern for v0.1.
## Rejected and considered alternatives
### Option A — Hermes Agent (Nous Research) — first fallback
Hermes Agent at v0.13.0 "Tenacity" (2026-05-07) has the **strongest Slack feature density across the four runtimes**: Socket Mode only (no public HTTP endpoint required), native Slack slash command auto-generation from a Slack manifest (`hermes slack manifest --write`), per-channel ephemeral prompts and per-channel skill bindings, multi-workspace support via comma-separated bot tokens, voice-message transcription (incoming audio → STT), home-channel proactive notifications, allowed-users allowlist by Member ID, and a security-first default profile with all P0/P1 Slack-specific bugs closed in the latest release. MIT-licensed, NousResearch-backed with three hundred eighty contributors, and the Kanban-plus-`/goal`-Ralph-loop pattern matches the "PM coordinates, Claude Code executes" workflow natively.
Documented as the **first fallback** if Letta Code Channels' Slack adapter encounters operational issues during v0.1, or if Slack-specific feature density (per-channel prompts, native slash commands, voice transcription) turns out to matter more for the human review workflow than Letta's Conversations API per-channel-shared-memory architecture.
Rejected as the v0.1 default because Hermes does not have a first-class equivalent to Letta's Conversations API for per-channel shared memory across one agent. The architecture would require either custom memory-bridging code or a workaround similar to the OpenClaw `shared-context.md` pattern. Reconsider if Letta's Slack adapter encounters issues, if security-default discipline matters more than multi-channel memory architecture, or if the per-channel feature density (slash commands, voice, per-channel prompts) becomes a significant operational advantage during v0.1.
### Option B — OpenClaw with the Honcho plugin
OpenClaw remains a real and viable runtime if the operator accepts four explicit risks. First, ongoing security-monitoring responsibility (thirty-one CVEs in eight weeks during peak founder activity, no full-time stewardship after February 2026 founder departure). Second, the Honcho plugin as a hard dependency for the per-channel-shared-memory architecture (since bare OpenClaw isolates each Slack channel as a separate session and Issue #64311 was closed as plugin-path). Third, the foundation-transition unknown (the OpenClaw Foundation is structurally credible — MIT licence permanently locked, TSC supermajority required to change — but operationally unproven for sustained CVE response). Fourth, three open Slack-specific regressions in v2026.5.7: socket connection permanently dead after event-loop starvation (Issue 77651); thread session keys not promoted on outbound replies (Issue 78505); bot-started threads bypass @mention policy (Issue 77927).
Rejected as the v0.1 default because for a solo developer operating without dedicated security-response capacity, OpenClaw shifts the security burden onto the operator. The Honcho plugin requirement adds a third-party dependency to a critical-path architectural feature. The three open Slack-specific bugs each individually represent risk for an always-on persona system. Reconsider if Letta and Hermes both prove operationally infeasible for some other reason, or if OpenClaw's foundation demonstrates sustained CVE response and closes the three open Slack bugs over the next quarter.
### Option C — Anthropic Claude Code Channels with the ClawCode plugin (eliminated)
Claude Code Channels launched 2026-03-20 as Anthropic's research preview of always-on Claude Code reachable via Telegram, Discord, and iMessage MCP bridges. **Slack is not on the research-preview allowlist and there is no Slack plugin in the `claude-plugins-official` repository as of 2026-05-08.** A separate "Claude in Slack" product exists but it spawns cloud sessions on @mention rather than running a persistent agent in Slack — orthogonal to the Channels research preview.
Architecturally, Claude Code Channels requires `claude --channels plugin:telegram@...` to be **actively running** — events only arrive while the session is open. For an autonomous-company experiment with four persistent agent personas running twenty-four-by-seven, Claude Code Channels would require four background terminal sessions with the channels flag, which is architecturally fragile for a solo dev's autonomous workflow. There is no Slack plugin and the plugin API is allowlist-restricted during research preview.
Eliminated as a runtime candidate. It is better thought of as a **human-in-the-loop personal interface** (text your terminal session, get responses) rather than a runtime hosting persistent persona personas. Reconsider only if Anthropic graduates Claude Code Channels to general availability with a stable plugin API, ships a Slack plugin, and removes the always-running-session requirement.
### Option D — Bare OpenClaw without the Honcho plugin
Rejected outright. Bare OpenClaw cannot deliver the per-channel-shared-memory architecture the brainstorm describes; Issue #64311 closed this as a plugin-path concern. Selecting bare OpenClaw would mean either accepting per-channel-isolated personas (a different and weaker architecture than the brainstorm) or implementing the Honcho equivalent ourselves. Neither matches the v0.1 scope.
### Option E — Letta with LettaBot (deprecated)
LettaBot was the original multi-channel bridge for Telegram, Slack, Discord, WhatsApp, and Signal. As of 2026-05-08 it is **archived/deprecated**; the README carries a banner directing all new development to Letta Code Channels. Existing LettaBot deployments will continue to function but will receive no security patches or feature updates. The first-pass Decision named LettaBot as the Discord bridge product; the corrected naming is Letta Code Channels.
Rejected for any new work. Use Letta Code Channels (the current product) instead.
### Option F — Roll our own runtime
Rejected outright for v0.1. The two-week experiment scope assumes existing tooling. Building a persistent-agent runtime from scratch is months of work, not a v0.1 candidate. Reconsider only if all three viable runtimes above prove unworkable in production, which is not what the verification research suggests.
### Option G — CrewAI / AutoGen / LangGraph as the runtime
Rejected as wrong-abstraction. These are agent-orchestration libraries, not persistent-process hosts with multi-channel chat-bridge integration. The autonomous company already has Sisyphus and Spec Kit at the orchestration layer; a library-level orchestrator would duplicate that and provide nothing for the always-on persona-in-channel use case.
## Affected files
- [[SDLC Orchestration Whiteboard]] — Layer 1 (persistent-agent-runtime), Layer 4 (chat surface), the role-mapping table, and event-flow examples revised across this commit wave. The `agent_launch Claude Code` references are corrected to the OpenCode + omo dispatch via `opencode serve` HTTP API per the integration contract above.
- [[Autonomous Company PM Surface and Event Bus]] — confirms Slack as the chat surface, which composes with this Decision's runtime choice.
- The persona-authoring work in [[Backlog]] items 4–7b (Coordinator, Engineer, Narrative Director, Playtest Coordinator, GTM SOUL.md drafts per the [[Role Personas]] redesigned five-persona roster shipping incrementally per the v0.1 → v0.2 → v0.5 growth path) inherits the Letta typed-memory-block structure (core / archival / recall) from this decision once ratified, plus the per-persona Slack-app registration model (each persona is a separately-registered Slack app with its own `xoxb-` bot token and `xapp-` app-level token for Socket Mode), plus the OpenCode session API contract for code-touching task dispatch.
- Role Personas — Nebula precedent disambiguator added in this commit wave (Nebula uses Claude Code/Codex/Gemini as their body; we use OpenCode + omo for the equivalent worker harness).
- [[Cost and Budget Model]] — human-driven Sisyphus session line revised in this commit wave to reference OpenCode + omo rather than Claude Code/Codex; Nebula precedent gets the same disambiguator. Letta hosting line item resolved to "$0/month — local on dev machine for v0.1" per the all-local deployment topology section in this Decision.
- The drift-detection middleware in [[Drift Detection and Audit Mechanics]] writes to and reads from Letta memory blocks rather than free-floating persona files; this affects the SyncScore precomputation location and the per-persona reference-distribution storage. OpenCode session dispatches are scoped under the persona's `archival` memory accumulation per the integration contract above.
- A new Backlog item documents the v0.2 hosted-deployment migration spec (when v0.1 trial period ends and the autonomous-company experiment continues, components migrate from the dev machine to a VPS or Letta Cloud per the migration triggers below).
## Reversal cost
Medium. Persona system prompts are markdown and are runtime-portable. Memory contents (core, archival, recall blocks) are structured data and would need to be exported and re-imported in the new runtime's memory model. Slack app tokens (`xoxb-` bot, `xapp-` app-level) are runtime-independent — they remain valid when switching runtimes. The Slack-channel binding configuration, MCP server connections, and the OpenCode session API integration code are runtime-specific and would need re-implementation. Estimated effort to reverse mid-experiment: three to five engineering days; reversing after persona memory has accumulated: one to two weeks.
The OpenCode worker integration itself is more reversible than the runtime choice: switching from `opencode serve` HTTP API to the `opencode acp` stdio interface or to per-task subprocess spawn is a one-engineering-day change in the bridge layer and does not affect persona memory or Slack-app configuration. Switching the worker harness off OpenCode entirely (back to Claude Code, or to Codex, or to a custom worker) is a larger change because the persona's task prompt format and category-hint convention are calibrated to omo's category routing.
The reversal cost was the primary reason this decision was originally recorded as `draft` pending ratification — a wrong choice was more expensive than a careful choice. The four research waves on 2026-05-08 narrowed the choice through verified evidence (Slack-vs-Discord feature comparison, OpenCode + omo verification, Architecture B rejection on three deal-breakers, all-local deployment topology committed); human ratification followed the same day, promoting the Decision to `status: reviewed`. The reversal cost numbers above remain accurate as the cost of changing the runtime choice mid-experiment if v0.1 surfaces an issue not caught by the verification.
## Verification trigger for revisit
Revisit this decision when any of the following occur:
- Letta ships a breaking change to the Conversations API or memory-block model that affects the four-persona shared-memory architecture.
- Letta Code Channels' Slack adapter encounters operational issues during v0.1 (Socket Mode reliability, conversation routing edge cases, rate limit handling).
- The Letta-to-OpenCode HTTP API bridge encounters operational issues during v0.1 (the documented OpenCode Issue #6573 where REST-API-spawned subagents stick in `busy` state; OpenCode's `@opencode-ai/slack` Issue #20075 if it affects the parallel human-interactive use case; or unexpected timeout-and-retry behaviour). Fall back to the ACP stdio interface or per-task subprocess spawn before reconsidering the runtime choice.
- OpenCode + omo ships a feature that materially changes the Architecture B feasibility verdict (e.g. native per-persona memory across channels, fix for Issue #20075 enabling remote `opencode serve` connection from `@opencode-ai/slack`, autonomous-dispatch trigger layer). At that point Architecture B (collapsing Letta and using OpenCode + omo as the runtime) becomes worth re-evaluating.
- An OpenClaw Foundation security-response demonstration over an extended period (no new high-severity CVE without a 24-hour patch) closes the gap on the security-maturity argument, AND the three open Slack-specific bugs (#77651, #78505, #77927) are closed in a stable release.
- Anthropic graduates Claude Code Channels from research preview to general availability with a stable plugin API and ships a Slack plugin.
- Hermes Agent ships an equivalent of Letta's Conversations API as a first-class feature, OR Slack-specific feature density (per-channel prompts, native slash commands, voice) becomes a significant operational advantage that outweighs Letta's Conversations API benefit.
- A new entrant (one not surveyed in [[Autonomous Company Tool Stack Verification]] in May 2026) ships a runtime that addresses both per-channel-shared-memory and security maturity simultaneously.
Migration triggers for the v0.1-to-v0.2 hosted-deployment transition (per the all-local deployment topology section above):
- v0.1 succeeds and the human commits to v0.2 (extended autonomous-company operation beyond the two-week trial). At that point the all-local topology migrates to hosted infrastructure starting with the Letta server.
- A power-outage or network-outage incident during v0.1 demonstrates the reliability cost of all-local at higher impact than expected. v0.2 prioritises hosted Letta plus OpenCode plus Slack-app processes.
- The persona load increases such that the dev machine cannot reliably host the persona runtime alongside the human's interactive Sisyphus sessions (CPU contention, RAM exhaustion, disk-space pressure on local Letta memory blocks plus OpenCode session storage plus Valkey Streams plus the creative-tooling MCPs' resource demands).
- A second human operator joins the autonomous company. At that point the dev machine cannot be the single host because two people cannot both have keep-awake on their own laptops without conflict over the persona state.
- Amphetamine fails to prevent sleep (macOS Big Sur or later "Power Nap" interactions, OS update changing keep-awake semantics, etc.) and the failure produces a persona-availability incident.
## References
- [[Autonomous Company Tool Stack Verification]] — the original research; the second-wave Slack-pivot addendum that corrected the LettaBot product naming; the third-wave addendum that confirms OpenCode + omo as the worker harness and rejects the Architecture B replacement of Letta; the fourth-wave addendum that records the all-local deployment topology for v0.1 and the migration path to v0.2 hosted infrastructure
- [[Autonomous Company LLM Provider Selection]] — the LLM-provider decision this runtime composes with
- [[Autonomous Company PM Surface and Event Bus]] — the chat-surface (Slack) and per-persona Slack-app model that compose with this Decision
- [[SDLC Orchestration Whiteboard]] — Layer 1 (runtime) and Layer 4 (chat surface) preferences this Decision supersedes
- [[Role Personas]] — Coordinator, Engineer, Narrative Director, Playtest Coordinator, GTM persona definitions that the runtime hosts (redesigned 2026-05-08; this Decision's architectural conclusions are unchanged because the runtime choice is downstream of the roster decomposition)
- [[Drift Detection and Audit Mechanics]] — SyncScore and behavioral-invariant requirements that compose with the runtime's memory model
- Letta documentation (docs.letta.com)
- Letta Code Channels documentation (docs.letta.com/letta-code/channels)
- Letta Conversations API blog (letta.com/blog/conversations)
- Letta Code FIRST_PARTY_CHANNEL_IDS (`telegram`, `slack`, `discord`)
- LettaBot deprecation banner (github.com/letta-ai/lettabot)
- OpenClaw Issue #64311 — per-channel memory isolation, closed as plugin-path
- OpenClaw Issue #77651 — Slack socket permanently dead after event-loop starvation (open, v2026.5.7)
- OpenClaw Issue #78505 — Slack outbound thread reply does not promote session key (open, v2026.5.7)
- OpenClaw Issue #77927 — Slack bot-started threads should require @mention (open, v2026.5.7)
- Hermes Agent v2026.5.7 "Tenacity" release notes (github.com/NousResearch/hermes-agent)
- Hermes Agent Slack documentation (github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/messaging/slack.md)
- Honcho plugin documentation (the v0.1 mitigation if Option B is selected)
- Anthropic Claude Code Channels research-preview announcement (2026-03-20)
- Anthropic Claude Code Channels documentation (docs.anthropic.com/en/docs/claude-code/channels)
- ClawCode plugin (github.com/AdamSriko/ClawCode)
- OpenCode documentation (opencode.ai/docs)
- OpenCode server documentation (opencode.ai/docs/server) — `opencode serve` HTTP API used for the Letta-to-OpenCode integration contract
- OpenCode `@opencode-ai/slack` package documentation (Socket Mode bridge for human-interactive sessions)
- OpenCode Issue #6573 — REST-API-spawned subagents can stick in `busy` state (documented failure mode for the v0.1 timeout-and-retry policy)
- OpenCode Issue #20075 — `@opencode-ai/slack` cannot connect to remote `opencode serve` (open, labelled `core`, blocks Architecture B and is a watch-item for Architecture A's parallel human-interactive use case)
- OpenCode Agent Teams design doc and PRs (#12711 design, #12730 core, #12731 tools, #12732 TUI; merged 2026-02-08, gated behind `OPENCODE_EXPERIMENTAL_AGENT_TEAMS=1`)
- omo (oh-my-openagent) repository (github.com/code-yeongyu/oh-my-openagent) — v4.0.0 Team Mode release 2026-05-07; the project's `~/.config/opencode/oh-my-openagent.json` model-config file
- Amphetamine (macOS keep-awake utility, App Store) — the v0.1 mitigation against laptop sleep that would otherwise stop the autonomous company
- Blender MCP — in-repo configuration; provides scene control, viewport screenshots, and Polyhaven / Sketchfab / Hyper3D / Hunyuan3D asset generation to persona-dispatched OpenCode sessions
- Godot AI MCP server (`gdai-mcp-server`) — in-repo configuration; provides scene-tree inspection, script editing, runtime state, and screenshot capture
- ComfyUI MCP — in-repo configuration; provides image / video / audio generation pipelines, PBR texture generation, and asset publishing
- Obsidian vault MCP (`@bitbonsai/mcpvault`) — in-repo `opencode.json` configuration as the `obsidian` MCP key; provides vault read / write / search / tag-management operations to all personas as their primary persistent-document surface
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-vault-graph-health-auto-fix-indentation-and-inline-related-constraint: domain/tooling
Tag assignment · test- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.46 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Vault Graph Health Auto-Fix Indentation and Inline Related Constraint.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Vault Graph Health Auto-Fix Indentation and Inline Related Constraint
Two non-obvious constraints in the vault graph health auto-fix (`apply_fix` in `prediction.py`) were discovered during a full vault health pass. Both caused YAML parse errors in `related:` frontmatter when auto-fix ran — one from hardcoded indentation, one from inline `related:` values. Neither was caught by the existing mechanical audit because they only surface when `--apply` triggers the link prediction fix, not during a dry-run audit.
## Indentation Mismatch
The `apply_fix` function in `scripts/ci/graph_health/prediction.py` hardcoded a 2-space indentation for new `related:` list items (`new_link = " - '[[source]]'"`). Notes whose existing `related:` entries were at column 0 (no leading whitespace) would get a new item at 2-space indentation, creating a YAML nested-sequence error. The fix (applied 2026-06-06) dynamically detects the leading whitespace of the first existing list item and uses that indentation for the new entry.
**Affected pattern**: Any `related:` list with items at column 0 (the majority of vault notes).
## Inline Related Values
Six notes under `Reference/Autonomous Company/` used inline `related:` values (`related: [[Link]]`) instead of YAML list format (`related:\n- '[[Link]]'`). The auto-fix's `if "related:" in frontmatter:` detection matched these, but the subsequent item-insertion logic expected a multi-line list structure and failed. The notes had to be converted to list format manually before the auto-fix could add reciprocal links to them.
**Affected files** (now fixed):
- `Reference/Autonomous Company/Ngrok Tunnel Setup.md`
- `Reference/Autonomous Company/Slack App Kill-Switch Runbook.md`
- `Reference/Autonomous Company/personas/_shared/forbidden-phrases.md`
- `Reference/Autonomous Company/personas/gtm/GTM Charter.md`
- `Reference/Autonomous Company/personas/gtm/GTM Soul.md`
- `Reference/Autonomous Company/slack/GTM Class-B Approval Workflow.md`
## Batch Reciprocal Operation
Running `apply_fix` with a threshold of 0.0 (by modifying `FIX_THRESHOLD` in `prediction.py`) will add every missing reciprocal link in the vault, not just the high-confidence candidates. This is a one-off operation that requires: (a) bypassing the dirty-working-tree guard (the `_is_working_tree_dirty()` check at the top of `apply_fix`), and (b) restoring both the threshold and the guard afterward. The tool is not designed for this mode — it produces correct YAML but the output is voluminous.
## References
- `scripts/ci/graph_health/prediction.py` — the `apply_fix` function (lines 380-439), `FIX_THRESHOLD` (line 31), and `_is_working_tree_dirty` guard (line 354)
- `scripts/ci/vault_audit.py` — the `--apply` orchestrator that triggers graph health fix
- `docs/vault/_meta/tags.md` — `related:` field specification in frontmatter schema
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-vault-graph-health-auto-fix-indentation-and-inline-related-constraint: status/reviewed
Tag assignment · test- Gold
- The note calls for `status/reviewed`: Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `status/reviewed` (False)
- Signal
- P(true) 0.11 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Discoveries/Vault Graph Health Auto-Fix Indentation and Inline Related Constraint.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Vault Graph Health Auto-Fix Indentation and Inline Related Constraint
Two non-obvious constraints in the vault graph health auto-fix (`apply_fix` in `prediction.py`) were discovered during a full vault health pass. Both caused YAML parse errors in `related:` frontmatter when auto-fix ran — one from hardcoded indentation, one from inline `related:` values. Neither was caught by the existing mechanical audit because they only surface when `--apply` triggers the link prediction fix, not during a dry-run audit.
## Indentation Mismatch
The `apply_fix` function in `scripts/ci/graph_health/prediction.py` hardcoded a 2-space indentation for new `related:` list items (`new_link = " - '[[source]]'"`). Notes whose existing `related:` entries were at column 0 (no leading whitespace) would get a new item at 2-space indentation, creating a YAML nested-sequence error. The fix (applied 2026-06-06) dynamically detects the leading whitespace of the first existing list item and uses that indentation for the new entry.
**Affected pattern**: Any `related:` list with items at column 0 (the majority of vault notes).
## Inline Related Values
Six notes under `Reference/Autonomous Company/` used inline `related:` values (`related: [[Link]]`) instead of YAML list format (`related:\n- '[[Link]]'`). The auto-fix's `if "related:" in frontmatter:` detection matched these, but the subsequent item-insertion logic expected a multi-line list structure and failed. The notes had to be converted to list format manually before the auto-fix could add reciprocal links to them.
**Affected files** (now fixed):
- `Reference/Autonomous Company/Ngrok Tunnel Setup.md`
- `Reference/Autonomous Company/Slack App Kill-Switch Runbook.md`
- `Reference/Autonomous Company/personas/_shared/forbidden-phrases.md`
- `Reference/Autonomous Company/personas/gtm/GTM Charter.md`
- `Reference/Autonomous Company/personas/gtm/GTM Soul.md`
- `Reference/Autonomous Company/slack/GTM Class-B Approval Workflow.md`
## Batch Reciprocal Operation
Running `apply_fix` with a threshold of 0.0 (by modifying `FIX_THRESHOLD` in `prediction.py`) will add every missing reciprocal link in the vault, not just the high-confidence candidates. This is a one-off operation that requires: (a) bypassing the dirty-working-tree guard (the `_is_working_tree_dirty()` check at the top of `apply_fix`), and (b) restoring both the threshold and the guard afterward. The tool is not designed for this mode — it produces correct YAML but the output is voluminous.
## References
- `scripts/ci/graph_health/prediction.py` — the `apply_fix` function (lines 380-439), `FIX_THRESHOLD` (line 31), and `_is_working_tree_dirty` guard (line 354)
- `scripts/ci/vault_audit.py` — the `--apply` orchestrator that triggers graph health fix
- `docs/vault/_meta/tags.md` — `related:` field specification in frontmatter schema
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-vehicle-destruction: type/code
Tag assignment · test- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.52 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Vehicle — Destruction.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Vehicle — Destruction
The panel-level destruction system for the Samba. The Samba is a 60-panel body where each panel is an independent damage target with three states: INTACT, DAMAGED, DESTROYED. Damage events flow through the `Events` signal bus; downstream systems (audio, dashboard, HUD) react independently.
## Overview
Damage in Old Growth is granular: individual panels dent and crack. Impact direction and force determine which panel takes the hit. Destroyed panels expose the vehicle interior to exterior conditions (noise, weather, contamination). The damage state at any moment is a visible running record of what the run has been.
## Design Intent
Per the World Bible: the Samba "features a 60-panel destructible body system that registers every scrape and impact." This granularity is a commitment to the player-as-vehicle framing (see [[Tone and Horror]]) — the bus's body is the player's body, and damage accumulation is a visible argument for caution.
Per Article IV Rule 8 (post-2.19.0): vehicle configuration (installed components and their damage states) is a persistent-data category. Damage persists across runs. Repair kits address it; nothing else does.
## Key Decisions
### 60 panels with Resource-defined properties
Panel definitions live in `src/resources/panels/`. Each panel has:
- ID (StringName)
- Position on the Samba (for spatial damage resolution)
- Health (0 → DESTROYED)
- DAMAGED threshold (typically mid-health)
- Associated damage visuals (deformation, cracks, missing-panel exposure)
### Three-state model
| State | Health | Visual |
|-------|--------|--------|
| INTACT | Above DAMAGED threshold | Clean panel |
| DAMAGED | Below DAMAGED threshold, above 0 | Dents, cracks |
| DESTROYED | Zero | Panel missing; interior exposed |
State transitions emit `Events.bus_panel_state_changed(panel_id, new_state)`. Transitioning to DESTROYED additionally emits `Events.bus_panel_destroyed(panel_id)`.
### Exposure zones
A set of destroyed panels together produces "exposure zones" — logical groups that tell other systems (audio mix, weather, contamination) which parts of the interior are now exposed. `Events.bus_exposure_changed(zones: PackedStringArray)` emits when the active exposure set changes.
### Collision → damage mapping
`Events.bus_collision(force, position)` fires for any collision above the minimum damage-force threshold. The damage system maps (force, position) to the specific panel(s) that should take damage. This lets the collision detection stay generic while the damage logic stays specific.
### Damage accumulation is persistent
Damage persists across runs per Article IV Rule 7. Repair kits restore panel health. Nothing else does. The player's run strategy includes managing damage because the Samba carries history.
### Interior exposure consequences
When a panel is DESTROYED, the corresponding exposure zone affects:
- **Audio mix** — the low-pass interior filter is bypassed in that zone (see [[Audio — World Audio]])
- **Weather** — rain, wind, spore haze, calcite dust can penetrate (see [[Audio — Car Radio]] and weather system)
- **Contamination** — the World Bible's "contamination in the corners" logic. Petrifold dust, Fungal spore residue, mycelium threads accumulate in exposed interior zones across runs (Article IV Rule 7 — vehicle configuration includes damage state)
## Implementation
Primary code references (all under `src/scripts/bus/`):
- `src/scripts/bus/bus_damage_system.gd` — the top-level destruction system
- `src/scripts/bus/bus_panel.gd` — the panel class (state, health, exposure)
- `src/scripts/bus/tire_damage_driver.gd` — tire damage subsystem
- `src/resources/panels/panel_definitions.gd` — panel definitions (GDScript)
- `src/resources/panels/*.tres` — per-panel Resource files
Signals (all via `Events`):
- `bus_panel_state_changed(panel_id, new_state)`
- `bus_panel_destroyed(panel_id)`
- `bus_exposure_changed(zones)`
- `bus_collision(force, position)`
- `bus_enemy_hit(enemy, force)`
## Test Coverage
- `src/tests/unit/test_bus_damage_system.gd` — panel damage, state transitions, exposure zone tracking
- `src/tests/unit/test_bus_damage_effects.gd` — damage visual/audio effects
- `src/tests/unit/test_bus_panel.gd` — panel class unit tests
- `src/tests/unit/test_tire_damage_driver.gd` — tire damage subsystem
> **⚠️ PRE-068 NOTE — panel count and damage model will change when spec 068 (van asset integration) ships.**
> **✅ 068 STATUS UPDATE (2026-05-03)** — Active scene has **44 mesh-active panel nodes** (not 20–30). All 60 panel IDs remain in `panel_definitions.gd` for damage logic; 16 are logical-only or merged. Spec 068 ships 2 visual states (DAMAGED, DESTROYED) via blend-mask shader. Spec 082 will reconcile post-merge.
## Agent Notes (backlinks)
- [[Vehicle Systems and Production Specifications Discovery]] — structural panel HP values (150–200), cascade child lists per structural panel, accessory HP 30 / `no_rear_view` effect, tire HP 80 / `speed_reduction` + `steering_pull`, `acid_exposure` from floor failure; `door_rear_left` and `door_rear_right` are BOTH on passenger side (bi-parting halves, not vehicle sides)
## Contamination Config Resource
When the per-panel contamination system is implemented, all thresholds and tuning values should be committed as a single `@export`-annotated Resource at `res://resources/contamination/contamination_config.tres` — consistent with the pattern established by `res://resources/config/disturbance_config.tres`.
See [[Art/Vehicle — Contamination]] for the visual tier specifications (coverage, opacity, silhouette, normal displacement) and panel zone mapping. The GDScript template below is the implementation target:
```gdscript
## Contamination progression tuning — all values @export for art-director adjustment.
## Authority: docs/vault/Art/Vehicle — Contamination.md
class_name ContaminationConfig
extends Resource
# --- Light tier thresholds (seconds of biome time) -------
@export var moss_light_threshold: float = 180.0
@export var lichen_light_threshold: float = 300.0
@export var spore_film_light_threshold: float = 120.0
@export var fungal_bloom_light_threshold: float = 240.0
@export var calcite_light_threshold: float = 180.0
@export var mineral_accretion_light_threshold: float = 360.0
# --- Moderate tier thresholds -------
@export var moss_moderate_threshold: float = 900.0
@export var lichen_moderate_threshold: float = 1500.0
@export var spore_film_moderate_threshold: float = 600.0
@export var fungal_bloom_moderate_threshold: float = 1200.0
@export var calcite_moderate_threshold: float = 900.0
@export var mineral_accretion_moderate_threshold: float = 1800.0
# --- Severe tier thresholds -------
@export var moss_severe_threshold: float = 2400.0
@export var lichen_severe_threshold: float = 4000.0
@export var spore_film_severe_threshold: float = 1800.0
@export var fungal_bloom_severe_threshold: float = 3000.0
@export var calcite_severe_threshold: float = 2400.0
@export var mineral_accretion_severe_threshold: float = 5000.0
# --- HP acceleration -------
## If panel HP is below this fraction, contamination thresholds scale down by hp_acceleration_strength
@export var hp_acceleration_fraction: float = 0.5
@export var hp_acceleration_strength: float = 0.7
# --- Neighbor proximity acceleration -------
## For each adjacent panel at severe tier, scale current panel thresholds by this factor
@export var neighbor_severe_acceleration: float = 0.85
```
> [!note] This resource does not yet exist. It is a Phase 3 implementation target. Parallels `res://resources/config/disturbance_config.tres`.
## Related
- [[Vehicle — Samba]] — the vehicle this system runs on
- [[Vehicle — Dashboard]] — dashboard displays damage state
- [[Core Mechanics]] — damage mechanic overview
- [[Audio — World Audio]] — exposure-driven audio mix
- [[Autoloads — Events]] — signal bus
- [[Systems]] — folder MOC
- [[Vehicle — Domain MOC]] — domain MOC
- [[Constitution]] — Article IV Rule 7 (persistent data), Article II (Car Is Core)
## Spec 156 Updates (2026-05-29)
The following values were updated by [[156 Bus Damage Rebalance]]:
### HP Ranges (panel_definitions.gd)
- Body: from 80–100 to 120–150 (midpoint 135.0 used)
- Structural: from 150–200 to 200–300 (midpoint 250.0 used)
- Glass: from 40–150 to 80–200 (windshield 200, side windows 150, door windows 120, skylights 100)
- Accessory: from 30 to 50
- Tire: from 80 to 120
### Constants (bus_damage_system.gd)
- `FORCE_TO_DAMAGE_SCALE`: 0.005 → 0.0025
- `cascade_fraction`: 0.25 → 0.10
- `offroad_damage_rate`: 0.5 → 0.2
### Tire Tuning (tire_damage_driver.gd, panel_definitions.gd)
- Tire `damaged_threshold`: 0.3 → 0.15
- `offroad_tire_damage_rate`: 1.5 → 0.4
- `catastrophic_force_threshold`: 15000 → 20000
- `spawn_grace_frames`: 300 → 600
### Collision-Type Discrimination (samba_bus.gd, new)
- `CollisionType` enum: `COLLISION_SOFT` and `COLLISION_HARD`
- Hard groups: `"Tree"`, `"enemy"`; Soft groups: `"Road"`, `"Grass"`; ungrouped defaults to HARD
- Soft collisions apply 0.35× damage multiplier; Hard = 1.0×
- Velocity-based damage gate: below 5 km/h = zero damage; quadratic curve 0.0× at ≤8 km/h → 1.0× at ≥25 km/h
- New export: `soft_filter_force_threshold` = 5000.0 (filters low-force soft contacts)
- Road-seam filter extended from Road-only to all soft collision groups
### Grace System (samba_bus.gd, refactored)
- Two-stage grace: stage 1 = 240 frames (full immunity), stage 2 = 120 frames (50% mitigation), then normal
- New export: `settling_grace_stage2_frames` = 120
### Disturbance Scaling (samba_bus.gd, new)
- New export: `collision_disturbance_scale` = 0.0001
- Disturbance = `collision_disturbance_scale × force` (continuous, replaces fixed 2.0)
- Old `collision_disturbance` export removed
- `disturbance_increase` for door_left_front: 1.15× → 1.06×
- Disturbance is NOT gated by grace — accumulates during settling
### Deferred
- FR-014 (structural damage resistance bonus) — deferred
- FR-017 (spare tire) — deferred
- FR-018 (gear-dependent damage scaling) — deferred
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-vehicle-destruction: type/system
Tag assignment · test- Gold
- The note calls for `type/system`: A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`) (True)
- ember
- The note has no reason to carry `type/system` (False)
- Signal
- P(true) 0.28 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Vehicle — Destruction.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Vehicle — Destruction
The panel-level destruction system for the Samba. The Samba is a 60-panel body where each panel is an independent damage target with three states: INTACT, DAMAGED, DESTROYED. Damage events flow through the `Events` signal bus; downstream systems (audio, dashboard, HUD) react independently.
## Overview
Damage in Old Growth is granular: individual panels dent and crack. Impact direction and force determine which panel takes the hit. Destroyed panels expose the vehicle interior to exterior conditions (noise, weather, contamination). The damage state at any moment is a visible running record of what the run has been.
## Design Intent
Per the World Bible: the Samba "features a 60-panel destructible body system that registers every scrape and impact." This granularity is a commitment to the player-as-vehicle framing (see [[Tone and Horror]]) — the bus's body is the player's body, and damage accumulation is a visible argument for caution.
Per Article IV Rule 8 (post-2.19.0): vehicle configuration (installed components and their damage states) is a persistent-data category. Damage persists across runs. Repair kits address it; nothing else does.
## Key Decisions
### 60 panels with Resource-defined properties
Panel definitions live in `src/resources/panels/`. Each panel has:
- ID (StringName)
- Position on the Samba (for spatial damage resolution)
- Health (0 → DESTROYED)
- DAMAGED threshold (typically mid-health)
- Associated damage visuals (deformation, cracks, missing-panel exposure)
### Three-state model
| State | Health | Visual |
|-------|--------|--------|
| INTACT | Above DAMAGED threshold | Clean panel |
| DAMAGED | Below DAMAGED threshold, above 0 | Dents, cracks |
| DESTROYED | Zero | Panel missing; interior exposed |
State transitions emit `Events.bus_panel_state_changed(panel_id, new_state)`. Transitioning to DESTROYED additionally emits `Events.bus_panel_destroyed(panel_id)`.
### Exposure zones
A set of destroyed panels together produces "exposure zones" — logical groups that tell other systems (audio mix, weather, contamination) which parts of the interior are now exposed. `Events.bus_exposure_changed(zones: PackedStringArray)` emits when the active exposure set changes.
### Collision → damage mapping
`Events.bus_collision(force, position)` fires for any collision above the minimum damage-force threshold. The damage system maps (force, position) to the specific panel(s) that should take damage. This lets the collision detection stay generic while the damage logic stays specific.
### Damage accumulation is persistent
Damage persists across runs per Article IV Rule 7. Repair kits restore panel health. Nothing else does. The player's run strategy includes managing damage because the Samba carries history.
### Interior exposure consequences
When a panel is DESTROYED, the corresponding exposure zone affects:
- **Audio mix** — the low-pass interior filter is bypassed in that zone (see [[Audio — World Audio]])
- **Weather** — rain, wind, spore haze, calcite dust can penetrate (see [[Audio — Car Radio]] and weather system)
- **Contamination** — the World Bible's "contamination in the corners" logic. Petrifold dust, Fungal spore residue, mycelium threads accumulate in exposed interior zones across runs (Article IV Rule 7 — vehicle configuration includes damage state)
## Implementation
Primary code references (all under `src/scripts/bus/`):
- `src/scripts/bus/bus_damage_system.gd` — the top-level destruction system
- `src/scripts/bus/bus_panel.gd` — the panel class (state, health, exposure)
- `src/scripts/bus/tire_damage_driver.gd` — tire damage subsystem
- `src/resources/panels/panel_definitions.gd` — panel definitions (GDScript)
- `src/resources/panels/*.tres` — per-panel Resource files
Signals (all via `Events`):
- `bus_panel_state_changed(panel_id, new_state)`
- `bus_panel_destroyed(panel_id)`
- `bus_exposure_changed(zones)`
- `bus_collision(force, position)`
- `bus_enemy_hit(enemy, force)`
## Test Coverage
- `src/tests/unit/test_bus_damage_system.gd` — panel damage, state transitions, exposure zone tracking
- `src/tests/unit/test_bus_damage_effects.gd` — damage visual/audio effects
- `src/tests/unit/test_bus_panel.gd` — panel class unit tests
- `src/tests/unit/test_tire_damage_driver.gd` — tire damage subsystem
> **⚠️ PRE-068 NOTE — panel count and damage model will change when spec 068 (van asset integration) ships.**
> **✅ 068 STATUS UPDATE (2026-05-03)** — Active scene has **44 mesh-active panel nodes** (not 20–30). All 60 panel IDs remain in `panel_definitions.gd` for damage logic; 16 are logical-only or merged. Spec 068 ships 2 visual states (DAMAGED, DESTROYED) via blend-mask shader. Spec 082 will reconcile post-merge.
## Agent Notes (backlinks)
- [[Vehicle Systems and Production Specifications Discovery]] — structural panel HP values (150–200), cascade child lists per structural panel, accessory HP 30 / `no_rear_view` effect, tire HP 80 / `speed_reduction` + `steering_pull`, `acid_exposure` from floor failure; `door_rear_left` and `door_rear_right` are BOTH on passenger side (bi-parting halves, not vehicle sides)
## Contamination Config Resource
When the per-panel contamination system is implemented, all thresholds and tuning values should be committed as a single `@export`-annotated Resource at `res://resources/contamination/contamination_config.tres` — consistent with the pattern established by `res://resources/config/disturbance_config.tres`.
See [[Art/Vehicle — Contamination]] for the visual tier specifications (coverage, opacity, silhouette, normal displacement) and panel zone mapping. The GDScript template below is the implementation target:
```gdscript
## Contamination progression tuning — all values @export for art-director adjustment.
## Authority: docs/vault/Art/Vehicle — Contamination.md
class_name ContaminationConfig
extends Resource
# --- Light tier thresholds (seconds of biome time) -------
@export var moss_light_threshold: float = 180.0
@export var lichen_light_threshold: float = 300.0
@export var spore_film_light_threshold: float = 120.0
@export var fungal_bloom_light_threshold: float = 240.0
@export var calcite_light_threshold: float = 180.0
@export var mineral_accretion_light_threshold: float = 360.0
# --- Moderate tier thresholds -------
@export var moss_moderate_threshold: float = 900.0
@export var lichen_moderate_threshold: float = 1500.0
@export var spore_film_moderate_threshold: float = 600.0
@export var fungal_bloom_moderate_threshold: float = 1200.0
@export var calcite_moderate_threshold: float = 900.0
@export var mineral_accretion_moderate_threshold: float = 1800.0
# --- Severe tier thresholds -------
@export var moss_severe_threshold: float = 2400.0
@export var lichen_severe_threshold: float = 4000.0
@export var spore_film_severe_threshold: float = 1800.0
@export var fungal_bloom_severe_threshold: float = 3000.0
@export var calcite_severe_threshold: float = 2400.0
@export var mineral_accretion_severe_threshold: float = 5000.0
# --- HP acceleration -------
## If panel HP is below this fraction, contamination thresholds scale down by hp_acceleration_strength
@export var hp_acceleration_fraction: float = 0.5
@export var hp_acceleration_strength: float = 0.7
# --- Neighbor proximity acceleration -------
## For each adjacent panel at severe tier, scale current panel thresholds by this factor
@export var neighbor_severe_acceleration: float = 0.85
```
> [!note] This resource does not yet exist. It is a Phase 3 implementation target. Parallels `res://resources/config/disturbance_config.tres`.
## Related
- [[Vehicle — Samba]] — the vehicle this system runs on
- [[Vehicle — Dashboard]] — dashboard displays damage state
- [[Core Mechanics]] — damage mechanic overview
- [[Audio — World Audio]] — exposure-driven audio mix
- [[Autoloads — Events]] — signal bus
- [[Systems]] — folder MOC
- [[Vehicle — Domain MOC]] — domain MOC
- [[Constitution]] — Article IV Rule 7 (persistent data), Article II (Car Is Core)
## Spec 156 Updates (2026-05-29)
The following values were updated by [[156 Bus Damage Rebalance]]:
### HP Ranges (panel_definitions.gd)
- Body: from 80–100 to 120–150 (midpoint 135.0 used)
- Structural: from 150–200 to 200–300 (midpoint 250.0 used)
- Glass: from 40–150 to 80–200 (windshield 200, side windows 150, door windows 120, skylights 100)
- Accessory: from 30 to 50
- Tire: from 80 to 120
### Constants (bus_damage_system.gd)
- `FORCE_TO_DAMAGE_SCALE`: 0.005 → 0.0025
- `cascade_fraction`: 0.25 → 0.10
- `offroad_damage_rate`: 0.5 → 0.2
### Tire Tuning (tire_damage_driver.gd, panel_definitions.gd)
- Tire `damaged_threshold`: 0.3 → 0.15
- `offroad_tire_damage_rate`: 1.5 → 0.4
- `catastrophic_force_threshold`: 15000 → 20000
- `spawn_grace_frames`: 300 → 600
### Collision-Type Discrimination (samba_bus.gd, new)
- `CollisionType` enum: `COLLISION_SOFT` and `COLLISION_HARD`
- Hard groups: `"Tree"`, `"enemy"`; Soft groups: `"Road"`, `"Grass"`; ungrouped defaults to HARD
- Soft collisions apply 0.35× damage multiplier; Hard = 1.0×
- Velocity-based damage gate: below 5 km/h = zero damage; quadratic curve 0.0× at ≤8 km/h → 1.0× at ≥25 km/h
- New export: `soft_filter_force_threshold` = 5000.0 (filters low-force soft contacts)
- Road-seam filter extended from Road-only to all soft collision groups
### Grace System (samba_bus.gd, refactored)
- Two-stage grace: stage 1 = 240 frames (full immunity), stage 2 = 120 frames (50% mitigation), then normal
- New export: `settling_grace_stage2_frames` = 120
### Disturbance Scaling (samba_bus.gd, new)
- New export: `collision_disturbance_scale` = 0.0001
- Disturbance = `collision_disturbance_scale × force` (continuous, replaces fixed 2.0)
- Old `collision_disturbance` export removed
- `disturbance_increase` for door_left_front: 1.15× → 1.06×
- Disturbance is NOT gated by grace — accumulates during settling
### Deferred
- FR-014 (structural damage resistance bonus) — deferred
- FR-017 (spare tire) — deferred
- FR-018 (gear-dependent damage scaling) — deferred
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-build-system: type/code
Tag assignment · test- Gold
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- ember
- The note has no reason to carry `type/code` (False)
- Signal
- P(true) 0.31 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Code/Build System.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Build System
Old Growth's build system is Make-driven. All CI checks, lint, test, and release workflows are Make targets at the repo root. This note is the canonical target reference + pre-commit hook documentation + the macOS release pipeline overview.
## Cross-Platform Dispatch
The Makefile targets both macOS and Windows (spec 156). At the top of the Makefile, `ifeq ($(OS),Windows_NT)` detection sets platform-conditional variables:
| Variable | macOS | Windows |
|----------|-------|---------|
| `SCRIPT_EXT` | `sh` | `ps1` |
| `SCRIPT_RUNNER` | `bash` | `pwsh -File` |
| `VENV_BIN` | `.venv/bin` | `.venv/Scripts` |
| `PYTHON` | `.venv/bin/python3` | `.venv/Scripts/python.exe` |
| `GODOT_APP` | `/Applications/Godot.app/Contents/MacOS` | `$(shell where godot)` |
| `PATHSEP` | `:` | `;` |
All targets use `$(SCRIPT_RUNNER)` with `$(SCRIPT_EXT)` to dispatch to platform-specific scripts. macOS uses existing `.sh` scripts; Windows uses parallel `.ps1` scripts in the same directories. macOS-only targets (codesign, dmg, Rust universal binary, launchd-managed personas) print a clear `[SKIP]` message when run on Windows.
## Overview
A single `Makefile` at the repo root orchestrates everything: GDScript lint/format, Rust build/test, component SemVer enforcement, Godot headless sanity, export smoke tests, release bumps, and the full CI pipeline. Prerequisites and target dependencies are documented in AGENTS.md; this note is the authored reference.
## Key Targets
### Composite targets
| Target | Purpose |
|--------|---------|
| `make all` | Full CI pipeline (stops on first failure) |
| `make pre-commit` | Fast, non-Godot checks run by the pre-commit hook |
| `make gdscript-quality` | `gdformat --check` + `gdlint` combined |
| `make test` | Run ALL GUT unit tests headless |
### GDScript quality
| Target | Purpose |
|--------|---------|
| `make format-check` | `gdformat --check` on first-party `.gd` files |
| `make lint` | `gdlint` on first-party `.gd` files |
### Enforcement
| Target | Purpose |
|--------|---------|
| `make semver` | Component SemVer enforcement (see [[Component SemVer Policy]]) |
| `make gdignore-check` | Verify non-shippable asset dirs have `.gdignore` |
### Godot sanity
| Target | Purpose |
|--------|---------|
| `make headless` | Godot headless project sanity check |
| `make macos-smoke` | macOS headless smoke test (lightweight, no LFS) |
| `make export-smoke` | macOS export smoke test (requires full LFS assets) |
| `make export-linux` | Linux export (cross-compile from macOS) — builds Rust .so, runs Godot headless export with Linux preset.2, validates with `validate_linux_export.sh` |
### Rust
| Target | Purpose |
|--------|---------|
| `make rust-build` | Compile Rust workspace (debug), copy dylib to `src/addons/oldgrowth-rust/bin/` |
| `make rust-test` | Run `cargo test --workspace` |
| `make rust-clippy` | Run `cargo clippy -- -D warnings` |
| `make rust-release` | Build Universal Binary (arm64 + x86_64) for release |
### Release
| Target | Purpose |
|--------|---------|
| `make bump-patch` | Bump game version patch (e.g., `0.9.1 → 0.9.2`) |
| `make bump-minor` | Bump game version minor (e.g., `0.9.2 → 0.10.0`) |
| `make bump-major` | Bump game version major |
### Misc
| Target | Purpose |
|--------|---------|
| `make setup` | Create `.venv/`, add `godot` to PATH, copy `.env.example` → `.env` and `tools/comfyui/.env.example` → `tools/comfyui/.env` |
| `make comfyui-setup` | One-shot ComfyUI pipeline setup: clone MCP server, Python venv, custom nodes, models, `.env` |
| `make comfyui-models` | Download SDXL + RealESRGAN upscaler models only (idempotent) |
| `make comfyui-status` | Verify full ComfyUI pipeline readiness |
| `make blender-run` | Run full T011–T015 Blender pipeline headlessly (panel sep → UV → transforms → LOD → export) |
| `make blender-run STAGE=t011` | Run a single pipeline stage |
| `make blender-status` | Check Blender binary, source blend, and GLB export outputs |
| `make clean` | Remove local test artifacts |
## Pre-Commit Hook
The pre-commit hook runs `make pre-commit` before every commit. This runs:
1. Component SemVer check (auto-bumps patches on drift)
2. Game version format check
3. Asset `.gdignore` coverage check
4. GDScript formatting check
5. GDScript linting
6. Rust tests + clippy (optional, can be skipped with env var)
On commit, the hook auto-bumps patch versions for components whose files have changed without a bump. This catches the common case where a developer forgot. More significant bumps must be applied manually.
## CI Pipeline
The CI basics workflow (`ci-basics.yml`) aligns with `make pre-commit` plus the additional Godot + export checks that require a Godot installation. A Linux export smoke test (`linux-export-smoke`) validates the export pipeline on every PR — builds the Rust .so, runs Godot headless export with the Linux preset, and runs `validate_linux_export.sh` on the output. This job runs independently of `linux-tests` (GUT + headless) and is included in the `all-checks` aggregate gate.
A separate `release.yml` watches for `config/version=` changes on `main` and publishes a GitHub Release. The release workflow has three parallel export jobs:
- `export-universal` (macOS, `macos-latest`) — builds universal Rust dylib, exports signed/notarized DMG
- `export-linux` (Linux, `ubuntu-latest`) — builds Linux Rust .so, wraps export in AppImage via appimagetool, uploads to R2 independently
The `sign-and-release` job aggregates both platforms with `if: always()` — one platform failure does not block the other. It generates multi-platform release notes and attaches all available artifacts to the GitHub Release.
### Godot Version Consistency
All CI workflows reference a pinned `GODOT_VERSION` from `.github/ci-vars.yml`. A `version-consistency` job in `ci-fast.yml` verifies that every workflow using `GODOT_VERSION` matches the canonical value, preventing silent drift between the release and PR CI pipelines. The Makefile does not hardcode a Godot version — it auto-detects the installed binary at runtime.
## Article III Rule 15 — The Single Source of Truth
The game version lives exclusively in `src/project.godot` (`config/version=`). Bumping it in a PR that merges to `main` is the sole release trigger. No manual tagging; no manual upload.
Agents must run `make bump-*` as part of any PR that touches game-affecting files (`src/**` or `rust/**`). The bump level follows these rules:
- `bump-patch` — bug fixes, config refactors, hardening (no new gameplay)
- `bump-minor` — completed phase spec (new gameplay capability)
- `bump-major` — full epic complete
## CI Workflow Suppression Pitfall
**GitHub suppresses `push`-based workflow triggers when a merge commit modifies `.github/workflows/` files.** This is a platform security constraint, not a bug.
**Symptom**: PR merges to `main`, no Release or Auto Bump workflow runs appear in Actions.
**Prevention**: Keep `.github/workflows/` changes in a dedicated PR that does not touch `src/` or `rust/`. Merge the workflow PR first; then merge the version bump PR separately.
**Recovery**: Actions → select the workflow → Run workflow → `main`.
## Related
- [[Component SemVer Policy]] — the policy `make semver` enforces
- [[Project Topology]] — the repo structure Make operates on
- [[Source Tree]] — the files Make touches
- [[Rust — Core]] — built by `make rust-build`
- [[Rust — GDExt Wrapper]] — built by `make rust-build`
- [[Code]] — folder MOC
- [[Constitution]] — Article III Rule 15 (game version bump obligation)
- [[Tools — ComfyUI]] — ComfyUI pipeline targets (`make comfyui-setup/models/status`)
- [[Tools — Blender]] — Blender pipeline targets (`make blender-run/status`)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-reciprocal-exemption-for-bidirectional-ratio: status/draft
Tag assignment · test- Gold
- The note calls for `status/draft`: Authoring in progress, not yet verified (True)
- ember
- The note has no reason to carry `status/draft` (False)
- Signal
- P(true) 0.08 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/Reciprocal Exemption for Bidirectional Ratio.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Reciprocal Exemption for Bidirectional Ratio
The vault graph health report computes a bidirectional ratio (percentage of linked note pairs that have reciprocal wikilinks). A naive count includes many links that are intentionally one-way, making the ratio misleadingly low.
Three categories are excluded from the ratio:
1. Constitution cited as a target — the Constitution is a foundational governance document referenced by 80+ notes as a formal citation. Expecting it to reciprocate all of them would produce meaningless backlinks.
2. Thread/essay notes as a target — outward-facing essays like The Disturbance Thread link to concepts they discuss through the concepts should not reciprocate 15+ thread backlinks.
3. Spec sub-artifacts as a source — template-generated plan, task, and data-model files link to their parent spec by design. This is a documentation pattern, not navigational linking.
These categories accounted for roughly half of the missing-reciprocal count (833 of 1698 pairs). Excluding them moved the bidirectional ratio from ~25% into the healthy range (~38%) without adding any new actual reciprocals.
The exemption is implemented in `scripts/ci/graph_health/scanner.py` as the function `_is_reciprocal_exempt()`. It is checked during the bidirectional completeness pass (FR-006) before a pair enters the seen-pairs set or the missing-reciprocals list.
## References
- `scripts/ci/graph_health/scanner.py` — `_is_reciprocal_exempt()` at line 178
- `docs/vault/Specs/148 Link Prediction Ensemble/148 Link Prediction Ensemble.md`
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-reciprocal-exemption-for-bidirectional-ratio: type/decision
Tag assignment · test- Gold
- The note calls for `type/decision`: Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/decision` (False)
- Signal
- P(true) 0.06 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/Reciprocal Exemption for Bidirectional Ratio.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Reciprocal Exemption for Bidirectional Ratio
The vault graph health report computes a bidirectional ratio (percentage of linked note pairs that have reciprocal wikilinks). A naive count includes many links that are intentionally one-way, making the ratio misleadingly low.
Three categories are excluded from the ratio:
1. Constitution cited as a target — the Constitution is a foundational governance document referenced by 80+ notes as a formal citation. Expecting it to reciprocate all of them would produce meaningless backlinks.
2. Thread/essay notes as a target — outward-facing essays like The Disturbance Thread link to concepts they discuss through the concepts should not reciprocate 15+ thread backlinks.
3. Spec sub-artifacts as a source — template-generated plan, task, and data-model files link to their parent spec by design. This is a documentation pattern, not navigational linking.
These categories accounted for roughly half of the missing-reciprocal count (833 of 1698 pairs). Excluding them moved the bidirectional ratio from ~25% into the healthy range (~38%) without adding any new actual reciprocals.
The exemption is implemented in `scripts/ci/graph_health/scanner.py` as the function `_is_reciprocal_exempt()`. It is checked during the bidirectional completeness pass (FR-006) before a pair enters the seen-pairs set or the missing-reciprocals list.
## References
- `scripts/ci/graph_health/scanner.py` — `_is_reciprocal_exempt()` at line 178
- `docs/vault/Specs/148 Link Prediction Ensemble/148 Link Prediction Ensemble.md`
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-appimage-action-deprecation-and-ci-portability-fixes: status/draft
Tag assignment · test- Gold
- The note calls for `status/draft`: Authoring in progress, not yet verified (True)
- ember
- The note has no reason to carry `status/draft` (False)
- Signal
- P(true) 0.03 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Discoveries/AppImage Action Deprecation and CI Portability Fixes.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
AppImage Action Deprecation and CI Portability Fixes
The Linux release pipeline broke silently for ~4.5 weeks (v0.17.11 through v0.18.8) because the AppImage packaging action was deleted and the export validation script had portability bugs that only manifested on ubuntu-24.04 CI runners.
## AppImage/AppImageKit-action Removal
The `AppImage/AppImageKit-action@v1` GitHub Action was removed from the Marketplace — the repository was deleted. The old repository's final release notes redirect to the new standalone project at `github.com/AppImage/appimagetool`.
**Replacement**: Download the `appimagetool-x86_64.AppImage` directly from the continuous release and set `APPIMAGE_EXTRACT_AND_RUN=1` in the environment. GitHub Actions ubuntu runners do not have FUSE, so the AppImage runtime must auto-extract on each invocation. The `$GITHUB_PATH` approach was unreliable — using `$GITHUB_ENV` with an explicit env var (`IMTOOL=/tmp/appimagetool`) and referencing `${IMTOOL}` directly in the wrapping step was more robust.
**Affected file**: `.github/workflows/release.yml` — the `Install appimagetool` step and the `Wrap export into AppImage` step.
## Validate Script Portability
The `scripts/ci/validate_linux_export.sh` script had two portability issues that caused CI Basics `linux-export-smoke` to fail on ubuntu-24.04 runners while passing locally on macOS:
**Non-portable head invocation**: The script used `head -n -2` to trim footer lines from `unzip -l` output. The negative-count syntax is a GNU coreutils extension — BSD `head` (macOS) rejects it with `illegal line count`. The CI Basics `linux-export-smoke` job runs on `ubuntu-24.04` which ships GNU coreutils, but the script's parent shell uses `bash -e`, and the failing `head` triggered `set -e` before the variable assignment completed. **Fix**: Replaced with `grep -vE` pattern matching that works on both GNU and BSD systems.
**Fragile binary detection**: The script matched binary filenames by the `\.x86_64$` suffix. Godot 4.6 Linux exports on ubuntu-24.04 may produce binaries with different naming conventions. **Fix**: Replaced filename-pattern matching with actual ELF magic byte detection — iterating candidate files, extracting them to temp, and running `file` to check for `ELF` in the output.
**set -e interaction**: The script used `set -euo pipefail` internally (redundant with the GitHub Actions shell wrapper's `-e`). `grep` returning exit code 1 (no match) within a pipeline combined with `set -o pipefail` could cause unexpected exits even inside `$()` command substitutions on some bash versions. **Fix**: Replaced with explicit `FAILED` counter pattern and `|| true` guards on grep pipelines. The script no longer relies on `set -e` for flow control.
## References
- `.github/workflows/release.yml` — Install appimagetool step (uses direct download + APPIMAGE_EXTRACT_AND_RUN)
- `scripts/ci/validate_linux_export.sh` — Rewritten binary detection and cross-platform parsing
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-appimage-action-deprecation-and-ci-portability-fixes: type/discovery
Tag assignment · test- Gold
- The note calls for `type/discovery`: Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/discovery` (False)
- Signal
- P(true) 0.05 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Discoveries/AppImage Action Deprecation and CI Portability Fixes.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
AppImage Action Deprecation and CI Portability Fixes
The Linux release pipeline broke silently for ~4.5 weeks (v0.17.11 through v0.18.8) because the AppImage packaging action was deleted and the export validation script had portability bugs that only manifested on ubuntu-24.04 CI runners.
## AppImage/AppImageKit-action Removal
The `AppImage/AppImageKit-action@v1` GitHub Action was removed from the Marketplace — the repository was deleted. The old repository's final release notes redirect to the new standalone project at `github.com/AppImage/appimagetool`.
**Replacement**: Download the `appimagetool-x86_64.AppImage` directly from the continuous release and set `APPIMAGE_EXTRACT_AND_RUN=1` in the environment. GitHub Actions ubuntu runners do not have FUSE, so the AppImage runtime must auto-extract on each invocation. The `$GITHUB_PATH` approach was unreliable — using `$GITHUB_ENV` with an explicit env var (`IMTOOL=/tmp/appimagetool`) and referencing `${IMTOOL}` directly in the wrapping step was more robust.
**Affected file**: `.github/workflows/release.yml` — the `Install appimagetool` step and the `Wrap export into AppImage` step.
## Validate Script Portability
The `scripts/ci/validate_linux_export.sh` script had two portability issues that caused CI Basics `linux-export-smoke` to fail on ubuntu-24.04 runners while passing locally on macOS:
**Non-portable head invocation**: The script used `head -n -2` to trim footer lines from `unzip -l` output. The negative-count syntax is a GNU coreutils extension — BSD `head` (macOS) rejects it with `illegal line count`. The CI Basics `linux-export-smoke` job runs on `ubuntu-24.04` which ships GNU coreutils, but the script's parent shell uses `bash -e`, and the failing `head` triggered `set -e` before the variable assignment completed. **Fix**: Replaced with `grep -vE` pattern matching that works on both GNU and BSD systems.
**Fragile binary detection**: The script matched binary filenames by the `\.x86_64$` suffix. Godot 4.6 Linux exports on ubuntu-24.04 may produce binaries with different naming conventions. **Fix**: Replaced filename-pattern matching with actual ELF magic byte detection — iterating candidate files, extracting them to temp, and running `file` to check for `ELF` in the output.
**set -e interaction**: The script used `set -euo pipefail` internally (redundant with the GitHub Actions shell wrapper's `-e`). `grep` returning exit code 1 (no match) within a pipeline combined with `set -o pipefail` could cause unexpected exits even inside `$()` command substitutions on some bash versions. **Fix**: Replaced with explicit `FAILED` counter pattern and `|| true` guards on grep pipelines. The script no longer relies on `set -e` for flow control.
## References
- `.github/workflows/release.yml` — Install appimagetool step (uses direct download + APPIMAGE_EXTRACT_AND_RUN)
- `scripts/ci/validate_linux_export.sh` — Rewritten binary detection and cross-platform parsing
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-art-vehicle-samba-interior: type/reference
Tag assignment · dev- Gold
- The note calls for `type/reference`: Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`) (True)
- ember
- The note has no reason to carry `type/reference` (False)
- Signal
- P(true) 0.04 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Art/Vehicle — Samba Interior.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Vehicle — Samba Interior
> **⚠️ PRE-068 NOTE — dashboard layout will be superseded by actual geometry when spec 068 ships.**
> The control sequence and spatial layout here reflect Phase 2 SVG diagrams — intended layout, not the current placeholder geometry. After 068 ships the purchased model, the committed geometry is the authority. See [[Vehicle Palette Materials and Dimensions Discovery]] for the full authoritative interior note.
For the full interior layout, control sequence, speaker routing, and design intent see Vehicle Palette Materials and Dimensions Discovery. This note focuses on the PBR material specs and Godot resource targets for each interior surface.
## Interior dashboard diagram
![[_meta/attachments/vehicle/dashboard_interaction_callout.svg]]
## Critical design rule — dashboard is body-coloured
The 1963 Samba dashboard is painted **the same colour as the lower exterior body paint** (`hero_teal_lower`). The interior-facing dash continues the exterior paint scheme into the cabin. `interior_dash_body` (`#2D4A45`) is not an independent colour — it is `hero_teal_lower` under cabin shadow. **Do NOT introduce a separate dashboard paint colour.**
## Interior palette — 5 swatches
| Slot | Name | Role | sRGB hex | Linear RGB |
|---|---|---|---|---|
| 1 | `interior_dash_body` | Dashboard fascia, door card metal, interior panels | `#2D4A45` | `(0.025, 0.073, 0.063)` |
| 2 | `interior_headliner` | Cloth headliner spanning cabin ceiling between skylights | `#E8DDBF` | `(0.783, 0.702, 0.520)` |
| 3 | `interior_seat_vinyl` | Seat upholstery, door card vinyl — Como Green | `#4A3826` | `(0.068, 0.038, 0.016)` |
| 4 | `interior_bakelite` | Steering wheel rim, knobs, radio bezel, gear knob | `#1A1714` | `(0.009, 0.007, 0.005)` |
| 5 | `interior_amber_glow` | All incandescent emissive surfaces | `#E8A838` | `(0.783, 0.374, 0.036)` |
## Godot material targets
| Resource | Albedo | Roughness | Metalness | Notes |
|---|---|---|---|---|
| `van_interior_dash_painted.tres` | `interior_dash_body` | 0.70 | 0.0 | Same paint as exterior lower, dimmed for cabin shadow |
| `van_interior_metal_trim.tres` | `vw_roundel_chrome` (reuse from exterior) | 0.35 | 0.9 | Window frames, interior chrome bright trim |
| `van_interior_headliner.tres` | `interior_headliner` | 0.95 | 0.0 | Lambertian cloth — no specular |
| `van_interior_seat.tres` | `interior_seat_vinyl` | 0.55 | 0.0 | Slight anisotropy along seam direction if seam normal map is authored |
## Dashboard metal surfaces — 3 wear states
### Clean
- Albedo: `interior_dash_body` (#2D4A45)
- Roughness: 0.65 — interior paint slightly less polished than exterior
- Metalness: 0.0
### Worn (default)
- Albedo: same + scuffing at high-contact points (door arm rests, dash centre near radio)
- Roughness: 0.75
- Metalness: 0.0
### Damaged
- Albedo: scuff marks exposing primer; minor surface rust at lower door corners from rain intrusion
- Roughness: 0.85
- Metalness: 0.0
Interior metal reads noticeably darker than exterior same-colour paint — cabin shadow means the in-engine ambient contribution is significantly lower than exterior sky ambient.
## Bakelite plastics — 3 wear states
Steering wheel rim (ivory-cream), steering column switches, gear knob, radio knobs, dashboard switches, glove box latch, door pulls, window winder handles, ashtray flap.
Two bakelite colours:
- **Black bakelite**: `interior_bakelite` (#1A1714) — near-black with warm brown undertone
- **Ivory bakelite** (steering wheel rim): `#E4DFC8` — period cream
| State | Roughness | Notes |
|---|---|---|
| Clean | 0.40 | Moderate gloss — bakelite has more sheen than modern plastic |
| Worn (default) | 0.55 | Black bakelite browns slightly; ivory items yellow |
| Damaged | 0.65 | Cracks showing lighter interior; oil-stain darkening at high-use knobs |
Bakelite is warmer and more tactile than modern plastic. Avoid plastic-y sheen — this is a proto-polymer.
## Headliner
Pale cream Masonite-backed cloth (`interior_headliner`, `#E8DDBF`). Spans the cabin ceiling between the 8 skylights. The headliner is the single largest warm-value surface in the interior. Together with the amber glow, it defines the "sanctuary" read when parked in dark forest.
Roughness: 0.95 (essentially Lambertian). No anisotropy. No specular.
## Seat upholstery — 3 wear states
Default: Como Green period vinyl (`interior_seat_vinyl`, `#4A3826`). Reads warm brown in normal light, not distinctly green except under strong neutral illumination.
| State | Albedo | Roughness | Notes |
|---|---|---|
| Clean | `interior_seat_vinyl` | 0.55 | Slight anisotropy along seam |
| Worn (default) | Darker at seat bolsters from hand and body oils | 0.65 | |
| Damaged | Cracked vinyl with white foam showing through splits | 0.70 | |
Headliner worn state: slightly yellowed (`#D8CFA8`). Headliner damaged state: stained yellow-brown from smoke/sun.
Period-correct seat vinyl alternates (confirm against TheSamba.com upholstery archive before final authoring):
| Factory code | Approx hex | Paired exterior |
|---|---|---|
| Como Green (default) | `#4A3826` | Option B (Turquoise/White) |
| Mesa Brown | `#5C4428` | Option A (Red/Beige) |
| Alabaster | `#C9B896` | Option C (Pearl White/Mouse Grey) |
| Chestnut | `#6B4A2E` | Multiple |
## Amber glow — emissive roles and energy targets
`interior_amber_glow` (`#E8A838`, 2700K tungsten equivalent) serves four distinct emissive roles. **Keep emission energy LOW** — the goal is "warm amber presence" not "bright glow":
| Role | Node type | Energy range | Active when |
|---|---|---|---|
| Dome light | `OmniLight3D`, ~3 m range | 0.4–0.8 | `dome_light_control.gd` toggled on |
| Gauge illumination | Emissive material on gauge faces | 0.3–0.6 | Headlights on at night |
| Dashboard indicator lamps | Emissive material on indicator spots | 0.5–1.0 (0 when inactive) | Respective system active |
| Radio dial illumination | Emissive material on frequency scale | 0.2–0.4 | Radio powered on |
The moodboard interior warm glow visible through the windshield and side windows from outside is primarily dome light + reflected light off the cream headliner. Test with dome light ON and OFF under Petrifold ambient — this is where the amber glow does its heaviest lifting (exterior chroma is desaturated; amber is the only warm point in the scene).
## Bed slats / cargo area
Floor slats in the rear cargo area. Unfinished or oiled wood with visible grain.
| State | Roughness | Metalness | Notes |
|---|---|---|---|
| Clean | 0.50–0.65 | 0.0 | Natural wood grain readable |
| Worn | 0.60–0.70 | 0.0 | Scuffing, dark oil staining at load edges |
| Damaged | 0.65–0.75 | 0.0 | Splits, exposed raw wood at break points |
## Related
- [[Systems/Vehicle — Dashboard]] — dashboard design intent and diegetic HUD
- [[Systems/Vehicle — Samba]] — implementation context
- [[Vehicle Palette Materials and Dimensions Discovery]] — full interior layout, control sequence, speaker routing, amber glow emissive values (authoritative Discovery note)
- [[Vehicle Interaction and Physics Implementation Discovery]] — keybinds, 3-speaker routing, emissive energy values
- [[Art/Vehicle — Samba Exterior]] — exterior palette (interior_dash_body is shadow-shifted hero_teal_lower)
- [[Art/Vehicle — Samba Acceptance Checklist]] — Phase 3 pass/fail criteria
- [[Art]] — folder MOC
- [[Vehicle — Domain MOC]] — domain MOC
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-persona-thread-echo-and-vault-mcp-debugging-may-31-2026: domain/tooling
Tag assignment · dev- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.13 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Persona Thread Echo and Vault MCP Debugging — May 31 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Persona Thread Echo and Vault MCP Debugging — May 31 2026
Debugging session spanning May 31 2026. Five interconnected issues were diagnosed and fixed across the persona runtime.
## Issues Found
### 1. GTM Not Responding in Thread (coordinator.py nested correlation_id access)
`dispatch_route_to()` returns `{"ok": true, "result": {"correlation_id": "..."}}` but `coordinator_response_hook` was reading `result.get("correlation_id", "")` at the top level — always empty. Same bug existed in `gtm.py`.
**Fix**: Access `result["result"]["correlation_id"]`.
### 2. Thread Mapping Race — Save After Dispatch
Even after fixing the correlation_id extraction, the mapping was saved to disk *after* `dispatch_route_to()` sent the inter-agent message to GTM's Letta agent. GTM's poller detected the message before the mapping existed on disk.
**Fix**: Generate correlation_id and call `save_threads()` *before* `dispatch_route_to()`. Pass the pre-generated correlation_id into the dispatch call.
### 3. Vault MCP Server Not Returning Results (mcpvault CLI + Docker path)
Two problems:
- The MCP manifest used `--vault-path /repo/docs/vault` but mcpvault v0.11.0 takes the vault path as a positional argument, not a `--vault-path` flag.
- The Docker container mounts the repo at `/repo` — paths like `/Users/joshburt/...` are not visible inside the container. The correct path is `/repo/docs/vault`.
**Fix**: Changed manifest args to `["-y", "@bitbonsai/mcpvault@latest", "/repo/docs/vault"]` and re-registered the MCP server.
### 4. GTM Not Using Vault MCP Tools (send_to_letta response ordering)
`send_to_letta()` returned the *first* `assistant_message` from the Letta response. When the LLM called a tool, the message sequence was: preliminary text → tool call → tool result → final response. The preliminary text was returned instead of the final draft.
**Fix**: Return the *last* `assistant_message` after iterating all messages.
### 5. GTM Responding Twice (dual-path dedup gap)
The same Slack message reached GTM through two independent paths — its own Socket Mode app_mention handler and the coordinator's human injection. No shared dedup mechanism existed.
**Fix**: Added shared dedup state in `slack_echo.py` (spec 007). Socket Mode handler checks Slack `ts`, human injection handler checks Letta `msg_id`.
## Key Files Touched
- `personas/coordinator.py` — correlation_id extraction + save-before-dispatch reorder
- `personas/gtm.py` — same fixes + silent except logging
- `personas/letta_bridge.py` — last assistant_message instead of first
- `personas/prompt.py` — manifest-driven capabilities block
- `personas/runtime.py` — strengthened processing prompt, dedup checks
- `personas/slack_echo.py` — dedup state + helpers
- `docs/vault/Reference/Autonomous Company/trusted_agent_mcp_manifest.json` — vault path args
- Various vault notes — fixed 4 broken wikilinks (spec renumbering)
## PRs
- #469: Persona thread echo race, vault MCP, tool response extraction, capabilities block, wikilink fixes
- #470: Message deduplication (spec 007)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-persona-thread-echo-and-vault-mcp-debugging-may-31-2026: type/code
Tag assignment · dev- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.56 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Persona Thread Echo and Vault MCP Debugging — May 31 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Persona Thread Echo and Vault MCP Debugging — May 31 2026
Debugging session spanning May 31 2026. Five interconnected issues were diagnosed and fixed across the persona runtime.
## Issues Found
### 1. GTM Not Responding in Thread (coordinator.py nested correlation_id access)
`dispatch_route_to()` returns `{"ok": true, "result": {"correlation_id": "..."}}` but `coordinator_response_hook` was reading `result.get("correlation_id", "")` at the top level — always empty. Same bug existed in `gtm.py`.
**Fix**: Access `result["result"]["correlation_id"]`.
### 2. Thread Mapping Race — Save After Dispatch
Even after fixing the correlation_id extraction, the mapping was saved to disk *after* `dispatch_route_to()` sent the inter-agent message to GTM's Letta agent. GTM's poller detected the message before the mapping existed on disk.
**Fix**: Generate correlation_id and call `save_threads()` *before* `dispatch_route_to()`. Pass the pre-generated correlation_id into the dispatch call.
### 3. Vault MCP Server Not Returning Results (mcpvault CLI + Docker path)
Two problems:
- The MCP manifest used `--vault-path /repo/docs/vault` but mcpvault v0.11.0 takes the vault path as a positional argument, not a `--vault-path` flag.
- The Docker container mounts the repo at `/repo` — paths like `/Users/joshburt/...` are not visible inside the container. The correct path is `/repo/docs/vault`.
**Fix**: Changed manifest args to `["-y", "@bitbonsai/mcpvault@latest", "/repo/docs/vault"]` and re-registered the MCP server.
### 4. GTM Not Using Vault MCP Tools (send_to_letta response ordering)
`send_to_letta()` returned the *first* `assistant_message` from the Letta response. When the LLM called a tool, the message sequence was: preliminary text → tool call → tool result → final response. The preliminary text was returned instead of the final draft.
**Fix**: Return the *last* `assistant_message` after iterating all messages.
### 5. GTM Responding Twice (dual-path dedup gap)
The same Slack message reached GTM through two independent paths — its own Socket Mode app_mention handler and the coordinator's human injection. No shared dedup mechanism existed.
**Fix**: Added shared dedup state in `slack_echo.py` (spec 007). Socket Mode handler checks Slack `ts`, human injection handler checks Letta `msg_id`.
## Key Files Touched
- `personas/coordinator.py` — correlation_id extraction + save-before-dispatch reorder
- `personas/gtm.py` — same fixes + silent except logging
- `personas/letta_bridge.py` — last assistant_message instead of first
- `personas/prompt.py` — manifest-driven capabilities block
- `personas/runtime.py` — strengthened processing prompt, dedup checks
- `personas/slack_echo.py` — dedup state + helpers
- `docs/vault/Reference/Autonomous Company/trusted_agent_mcp_manifest.json` — vault path args
- Various vault notes — fixed 4 broken wikilinks (spec renumbering)
## PRs
- #469: Persona thread echo race, vault MCP, tool response extraction, capabilities block, wikilink fixes
- #470: Message deduplication (spec 007)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-overview: type/moc
Tag assignment · dev- Gold
- The note has no reason to carry `type/moc` (False)
- ember
- The note calls for `type/moc`: Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`) (True)
- Signal
- P(true) 0.64 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Audio — Overview.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — Overview
The top-level architecture of Old Growth's audio systems. Audio is the primary horror delivery mechanism in Old Growth — the forest communicates through sound before it communicates through visuals. This note is the high-level map; [[Audio — World Audio]] carries the full per-subsystem specification.
## Overview
Sound design defines the horror experience (World Bible §16). Every anomaly is heard before it is seen. Every biome has a distinct acoustic identity. The disturbance state manifests audibly long before any visual cue. Silence is itself a weapon — the Petrifold and The Silence anomaly both weaponize audio absence.
## Design Intent — Five Pillars
1. **Audio-first danger communication** — every anomaly and enemy has an identifiable audio signature that arrives before visual contact is possible
2. **Layered atmospheric density** — no biome is a single loop; each is a stack of independent layers mixed dynamically
3. **Interior/exterior as two distinct worlds** — the van's cab is a sanctuary expressed in audio; stepping outside removes that protection audibly
4. **Disturbance as a musical arc** — ambient audio evolves across the four disturbance states, building dread without UI narration
5. **Silence as a weapon** — absence of expected sound is the most unsettling signal
## Key Decisions
### Planned Bus Architecture (constitution Appendix A)
The planned audio bus architecture routes through seven buses. All audio routes through these — never Master directly.
| Bus | Purpose | Key Effects |
|-----|---------|-------------|
| Master | Final mix output | — |
| Ambience | Biome ambient layers, weather beds | `AudioEffectReverb` (large room), `AudioEffectFilter` (low-pass for interior occlusion) |
| Proximity | Positional anomaly and creature audio (3D spatialized) | `AudioEffectReverb` (matched to biome), distance-based attenuation |
| Interior | Audio heard from inside the Samba — muffled, low-pass filtered | `AudioEffectFilter` low-pass @ 800 Hz, resonance 0.3; `AudioEffectReverb` (small room) |
| Horror | Stingers, tension drones, disturbance audio cues, The Storm, The Silence | No EQ — raw, present, unprocessed |
| Radio | Clean radio broadcast (DJ, music, forest transmissions) | — |
| RadioDegraded | Degraded radio signal (static, interference, procedural audio) | — |
When the player is inside the Samba, `Ambience` and `Proximity` buses route through `Interior` (applying low-pass @ 800 Hz, resonance 0.3). When outside, they route directly to Master.
**Interior occlusion filter**: simulates the van's body mass absorbing high-frequency content from outside. Rain becomes a low thud. Wind drops out entirely. Creature sounds become muffled and distant. The bus sounds like safety.
> [!attention] CONFLICT
> **Planned architecture vs. current state**: Constitution Appendix A and this note describe 7 buses. As of spec 099, `src/default_bus_layout.tres` defines 5 buses: Master, Radio, RadioDegraded, VehicleSFX, SFX. The 4 buses required by the world audio system (Ambience, Proximity, Interior, Horror) are not yet present. [[111 Audio Bus Architecture]] gates this expansion and is a hard prerequisite for specs 104–110. Indexed in [[Conflicts]].
### Biome ambient system — four layers
Each biome runs four simultaneous ambient layers that crossfade independently:
| Layer | Description | Node Type |
|-------|-------------|-----------|
| L1 Bed | Foundational tonal bed — deepest, lowest, longest loop | `AudioStreamPlayer` → Ambience |
| L2 Wind | Wind and air movement through canopy | `AudioStreamPlayer` → Ambience |
| L3 Organic | Biome-specific organic sounds (creak, drip, spore, crack) | `AudioStreamPlayer` → Ambience |
| L4 Detail | Sparse randomized events (bird, branch, footstep echo) | `AudioStreamPlayer3D` (randomized positions) → Proximity |
All layers except L4 are non-positional (ambient surround). L4 is spatialized to give the world directionality and suggest life (or absence of life) in specific directions. A weather change might swap only the wind layer while canopy creak continues uninterrupted.
### Radio as separate system
Dedicated Radio + RadioDegraded buses with its own content pipeline — see [[Audio — Car Radio]] and [[Audio — Radio Stations]].
### Four-state disturbance arc
Audio evolves continuously across the four disturbance states (Calm → Uneasy → Agitated → Storm) — see [[Disturbance System]] and [[The Disturbance Thread]]. Stinger cues mark threshold crossings on the Horror bus. The final state (Storm = 85) spawns the Storm weather event — see [[The Player Causes the Storm]].
### Silence as weapon
Two systems weaponize audio absence:
- **The Petrifold** biome has no wind layer. L1 is near-silent. L3 cracks are isolated events with long reverb tails dying into silence.
- **The Silence anomaly** hard-cuts ALL environmental audio on zone entry. Only the player's heartbeat remains. `Ambience` and `Proximity` buses are muted entirely when inside a Silence zone.
### Gain staging and LUFS compliance
Per Article XII-D: audio levels set at source before bus mixing. Each bus has headroom for the master bus. All shipped audio is normalized to consistent LUFS target documented in the audio spec family. Sub-Nyquist audio (<44.1 kHz) is never shipped.
### Godot implementation
Audio primitives are entirely Godot native (`AudioStreamPlayer`, `AudioStreamPlayer3D`, `AudioBus`, `AudioEffect*`) — no external plugin dependencies. The approved bus layout lives in `src/default_bus_layout.tres`. Biome-specific audio content lives in `src/assets/audio/` (submodule). The top-level orchestration is `world_audio_manager.gd` (see [[Audio — World Audio]] for full scene structure).
## Test Coverage
- `src/tests/unit/test_bus_radio.gd`, `test_radio_channel.gd` — radio runtime + per-channel behavior
- `src/tests/unit/test_engine_audio.gd` — engine audio
- `src/tests/unit/test_disturbance.gd` — disturbance autoload (upstream of audio arc)
- Individual subsystem tests as listed on per-subsystem notes ([[Audio — Car Radio]], etc.)
## Related
- [[Audio — World Audio]] — full per-subsystem specifications (biome audio, weather, disturbance arc, Storm, anomaly audio, Hollows, interior/exterior mix, footsteps, vehicle audio, player audio, interaction audio, prop audio, combat audio, garage audio, exit gate audio, UI audio, scene structure)
- [[Audio — Buses]] — current bus layout, effect chains, and the pending Ambience/Proximity/Interior/Horror expansion
- [[Audio — Car Radio]] — radio signal and degradation
- [[Audio — Pipeline]] — dev-time audio processing pipeline
- [[Audio — Radio Stations]] — per-station content
- [[Audio — Forest Transmissions]] — what comes through after signal dies
- [[Audio — Domain MOC]] — domain MOC
- [[Diegesis Thread]] — audio is fundamentally diegetic
- [[The Disturbance Thread]] — audio arc tracks disturbance state
- [[Constitution]] — Article XII-D, Appendix A bus architecture
### Phase 4 Implementation Specs
- [[111 Audio Bus Architecture]] — prerequisite for all world audio specs
- [[104 Biome Ambient Audio]], [[105 Disturbance Arc Audio]], [[106 Anomaly Audio Signatures]]
- [[107 Storm Audio System]], [[108 Garage And Exit Gate Audio]], [[109 Weather Bed Layer]]
- [[110 Time Of Day Audio Variants]], [[112 Environmental Presence System]], [[113 Player And World Interaction Audio]]
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-overview: type/system
Tag assignment · dev- Gold
- The note calls for `type/system`: A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`) (True)
- ember
- The note has no reason to carry `type/system` (False)
- Signal
- P(true) 0.13 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Systems/Audio — Overview.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — Overview
The top-level architecture of Old Growth's audio systems. Audio is the primary horror delivery mechanism in Old Growth — the forest communicates through sound before it communicates through visuals. This note is the high-level map; [[Audio — World Audio]] carries the full per-subsystem specification.
## Overview
Sound design defines the horror experience (World Bible §16). Every anomaly is heard before it is seen. Every biome has a distinct acoustic identity. The disturbance state manifests audibly long before any visual cue. Silence is itself a weapon — the Petrifold and The Silence anomaly both weaponize audio absence.
## Design Intent — Five Pillars
1. **Audio-first danger communication** — every anomaly and enemy has an identifiable audio signature that arrives before visual contact is possible
2. **Layered atmospheric density** — no biome is a single loop; each is a stack of independent layers mixed dynamically
3. **Interior/exterior as two distinct worlds** — the van's cab is a sanctuary expressed in audio; stepping outside removes that protection audibly
4. **Disturbance as a musical arc** — ambient audio evolves across the four disturbance states, building dread without UI narration
5. **Silence as a weapon** — absence of expected sound is the most unsettling signal
## Key Decisions
### Planned Bus Architecture (constitution Appendix A)
The planned audio bus architecture routes through seven buses. All audio routes through these — never Master directly.
| Bus | Purpose | Key Effects |
|-----|---------|-------------|
| Master | Final mix output | — |
| Ambience | Biome ambient layers, weather beds | `AudioEffectReverb` (large room), `AudioEffectFilter` (low-pass for interior occlusion) |
| Proximity | Positional anomaly and creature audio (3D spatialized) | `AudioEffectReverb` (matched to biome), distance-based attenuation |
| Interior | Audio heard from inside the Samba — muffled, low-pass filtered | `AudioEffectFilter` low-pass @ 800 Hz, resonance 0.3; `AudioEffectReverb` (small room) |
| Horror | Stingers, tension drones, disturbance audio cues, The Storm, The Silence | No EQ — raw, present, unprocessed |
| Radio | Clean radio broadcast (DJ, music, forest transmissions) | — |
| RadioDegraded | Degraded radio signal (static, interference, procedural audio) | — |
When the player is inside the Samba, `Ambience` and `Proximity` buses route through `Interior` (applying low-pass @ 800 Hz, resonance 0.3). When outside, they route directly to Master.
**Interior occlusion filter**: simulates the van's body mass absorbing high-frequency content from outside. Rain becomes a low thud. Wind drops out entirely. Creature sounds become muffled and distant. The bus sounds like safety.
> [!attention] CONFLICT
> **Planned architecture vs. current state**: Constitution Appendix A and this note describe 7 buses. As of spec 099, `src/default_bus_layout.tres` defines 5 buses: Master, Radio, RadioDegraded, VehicleSFX, SFX. The 4 buses required by the world audio system (Ambience, Proximity, Interior, Horror) are not yet present. [[111 Audio Bus Architecture]] gates this expansion and is a hard prerequisite for specs 104–110. Indexed in [[Conflicts]].
### Biome ambient system — four layers
Each biome runs four simultaneous ambient layers that crossfade independently:
| Layer | Description | Node Type |
|-------|-------------|-----------|
| L1 Bed | Foundational tonal bed — deepest, lowest, longest loop | `AudioStreamPlayer` → Ambience |
| L2 Wind | Wind and air movement through canopy | `AudioStreamPlayer` → Ambience |
| L3 Organic | Biome-specific organic sounds (creak, drip, spore, crack) | `AudioStreamPlayer` → Ambience |
| L4 Detail | Sparse randomized events (bird, branch, footstep echo) | `AudioStreamPlayer3D` (randomized positions) → Proximity |
All layers except L4 are non-positional (ambient surround). L4 is spatialized to give the world directionality and suggest life (or absence of life) in specific directions. A weather change might swap only the wind layer while canopy creak continues uninterrupted.
### Radio as separate system
Dedicated Radio + RadioDegraded buses with its own content pipeline — see [[Audio — Car Radio]] and [[Audio — Radio Stations]].
### Four-state disturbance arc
Audio evolves continuously across the four disturbance states (Calm → Uneasy → Agitated → Storm) — see [[Disturbance System]] and [[The Disturbance Thread]]. Stinger cues mark threshold crossings on the Horror bus. The final state (Storm = 85) spawns the Storm weather event — see [[The Player Causes the Storm]].
### Silence as weapon
Two systems weaponize audio absence:
- **The Petrifold** biome has no wind layer. L1 is near-silent. L3 cracks are isolated events with long reverb tails dying into silence.
- **The Silence anomaly** hard-cuts ALL environmental audio on zone entry. Only the player's heartbeat remains. `Ambience` and `Proximity` buses are muted entirely when inside a Silence zone.
### Gain staging and LUFS compliance
Per Article XII-D: audio levels set at source before bus mixing. Each bus has headroom for the master bus. All shipped audio is normalized to consistent LUFS target documented in the audio spec family. Sub-Nyquist audio (<44.1 kHz) is never shipped.
### Godot implementation
Audio primitives are entirely Godot native (`AudioStreamPlayer`, `AudioStreamPlayer3D`, `AudioBus`, `AudioEffect*`) — no external plugin dependencies. The approved bus layout lives in `src/default_bus_layout.tres`. Biome-specific audio content lives in `src/assets/audio/` (submodule). The top-level orchestration is `world_audio_manager.gd` (see [[Audio — World Audio]] for full scene structure).
## Test Coverage
- `src/tests/unit/test_bus_radio.gd`, `test_radio_channel.gd` — radio runtime + per-channel behavior
- `src/tests/unit/test_engine_audio.gd` — engine audio
- `src/tests/unit/test_disturbance.gd` — disturbance autoload (upstream of audio arc)
- Individual subsystem tests as listed on per-subsystem notes ([[Audio — Car Radio]], etc.)
## Related
- [[Audio — World Audio]] — full per-subsystem specifications (biome audio, weather, disturbance arc, Storm, anomaly audio, Hollows, interior/exterior mix, footsteps, vehicle audio, player audio, interaction audio, prop audio, combat audio, garage audio, exit gate audio, UI audio, scene structure)
- [[Audio — Buses]] — current bus layout, effect chains, and the pending Ambience/Proximity/Interior/Horror expansion
- [[Audio — Car Radio]] — radio signal and degradation
- [[Audio — Pipeline]] — dev-time audio processing pipeline
- [[Audio — Radio Stations]] — per-station content
- [[Audio — Forest Transmissions]] — what comes through after signal dies
- [[Audio — Domain MOC]] — domain MOC
- [[Diegesis Thread]] — audio is fundamentally diegetic
- [[The Disturbance Thread]] — audio arc tracks disturbance state
- [[Constitution]] — Article XII-D, Appendix A bus architecture
### Phase 4 Implementation Specs
- [[111 Audio Bus Architecture]] — prerequisite for all world audio specs
- [[104 Biome Ambient Audio]], [[105 Disturbance Arc Audio]], [[106 Anomaly Audio Signatures]]
- [[107 Storm Audio System]], [[108 Garage And Exit Gate Audio]], [[109 Weather Bed Layer]]
- [[110 Time Of Day Audio Variants]], [[112 Environmental Presence System]], [[113 Player And World Interaction Audio]]
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-rust-core: domain/tooling
Tag assignment · test- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.11 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Code/Rust — Core.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Rust — Core
The engine-agnostic Rust core crate at `rust/rust-core/`. Contains performance-critical, deterministic logic with **zero Godot dependencies** — it compiles and tests in isolation. [[Rust — GDExt Wrapper]] bridges this crate to Godot types at the other side. This split is mandated by constitution Article III Rule 17.
## Overview
`rust-core` is where Old Growth's procedural generation, biome blending, road math, vegetation rejection, and deterministic state transitions live. Every module is pure in the sense that: inputs are simple data structures (tuples, arrays, custom structs); outputs are simple data structures; no Godot types are referenced. This lets the crate be unit-tested via `cargo test` without any game engine involvement.
## Key Decisions
### Engine-agnostic boundary
The crate's Cargo.toml has no Godot dependencies. All public APIs use standard Rust types (`[f32; 3]` instead of `Vector3`, etc.). The bridge crate is responsible for type conversion at the FFI boundary.
### Module layout
Each domain has its own module file:
| Module | Purpose |
|--------|---------|
| `biome.rs` | Biome blending — spec 070. Computes per-position biome weights from noise-sampled biome map |
| `disturbance.rs` | Disturbance meter arithmetic and state transitions — see [[Autoloads — Disturbance]] |
| `exit_points.rs` | Exit-gate discovery and placement logic |
| `game_state.rs` | Change-detection for tracked state fields — see [[Autoloads — GameState]] |
| `road/` | Road network generation (spec 053). Submodules: `density.rs` (biome density rules), `generator.rs` (pipeline), `graph.rs` (petgraph connectivity), `intersection.rs` (snap/merge), `mesh_geometry.rs`, `path_utils.rs`, `spline.rs` (Catmull-Rom), `terrain.rs` (conformance), `types.rs` (RoadNetwork, RoadNode, RoadSegment, profiles) |
| `structures.rs` | Structure placement logic (ranger stations, research trailers, cabins) |
| `terrain.rs` | Terrain math — height sampling, constraint validation |
| `vegetation.rs` | Vegetation scatter rejection (spec 070) — the biome-aware rejection that ships in rust-core as of spec 070 |
| `math.rs` | Mathematical utilities — noise sampling helpers, interpolation functions shared across modules |
| `utils.rs` | Shared utility functions used across rust-core modules |
| `weather/` | Weather simulation submodules (biome hysteresis, exposure, fog, zones, traction, wetness) |
### Deterministic RNG throughout
Every function that uses randomness takes an explicit seed or a `&mut SmallRng` reference. No function consumes OS entropy. This is mandated by Article IV Rule 3 (seed determinism).
### Newtype pattern for domain IDs
Per Article XII-C (Rust idioms): domain identifiers (biome IDs, road segment IDs, seed values) are wrapped in newtypes, not passed as raw `u32`/`i64`. A `BiomeId(u32)` and a `RoadSegmentId(u32)` are not interchangeable. Invalid state is unrepresentable at the type level with zero runtime cost.
### Approved dependencies
Per constitution Appendix A:
- `gdext` — handled by the wrapper crate, not here
- `rand 0.8` (SmallRng) — deterministic RNG
- `fastnoise-lite 1.1.1` — noise functions
- `petgraph 0.6` — road network connectivity (BFS, spatial traversal)
- `uniform-cubic-splines 0.5` — Catmull-Rom spline sampling
- `rustc-hash 2.1` — spatial hash grids (intersection snap/merge)
Adding a new dependency requires updating constitution Appendix A.
### Result/Option over panics
Per Article XII-C: `unwrap()` and `expect()` only appear in tests or explicitly unreachable branches with `// SAFETY:` comments. Public API functions return `Result<T, E>` for fallible operations.
## Implementation Notes
### Module structure (one level deeper than most modules)
`road/` is the only module that itself contains submodules — the road generation pipeline is complex enough (biome density rules, graph connectivity, spline curves, intersection snap/merge, terrain conformance, mesh geometry, types) that flat organization would exceed readable scope.
### Testing
`cargo test --workspace` runs all tests in rust-core. CI runs `make rust-test`, which includes this. Article III Rule 15 requires component SemVer bumps for rust-core and rust-gdext components when their paths change.
### Building
`make rust-build` compiles debug and copies the dylib to `src/addons/oldgrowth-rust/bin/`. Godot loads the dylib at editor startup; GDExtension libraries cannot hot-reload — any Rust change requires restarting the Godot editor.
Universal Binary (arm64 + x86_64) is built via `make rust-release` for macOS distribution.
## Test Coverage
- `cargo test --workspace` — unit tests for every module
- Integration tests exercise the bridge crate ([[Rust — GDExt Wrapper]])
- SC-001 baseline (tree counts) validates vegetation module output against golden seed
## Related
- [[Rust — GDExt Wrapper]] — the bridge crate
- [[World — Procedural Generation]] — a consumer of rust-core
- [[Autoloads — Disturbance]] — delegates to disturbance.rs
- [[Autoloads — GameState]] — delegates to game_state.rs
- [[Source Tree]] — repo topology
- [[Code]] — folder MOC
- [[Constitution]] — Article III Rule 17, Article XII-C (Rust idioms), Appendix A
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-host-tools-mcp-server-launchd-lifecycle: domain/tooling
Tag assignment · dev- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.20 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Host-Tools MCP Server Launchd Lifecycle.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Host-Tools MCP Server Launchd Lifecycle
The host-tools MCP server on port 9105 is owned by launchd via `com.oldgrowth.host-mcp.plist`. The `mcp-setup` command must never attempt to check port availability or start its own server instance — it should verify server health and proceed with tool attachment.
## Background
The `cmd_mcp_setup()` function in `scripts/personas/system-manager.py` originally used `socket.bind()` to check if port 9105 was free. On a clean boot, launchd starts the host-tools MCP server via `com.oldgrowth.host-mcp.plist` (RunAtLoad + KeepAlive), so the port is always in use — causing `socket.bind()` to fail and `make recovery` to exit with "Port 9105 is already in use."
This was a design mismatch: launchd owns the server lifecycle (start, keep alive, restart on crash), and `mcp-setup` only needs to verify the server is healthy and attach MCP tools to Letta agents. The port-conflict check was an artifact from an earlier design where `mcp-setup` was expected to start its own server.
## Fix
The port-conflict check was replaced with a two-phase health check:
1. Hit `GET /health` on port 9105. If the server responds, skip to tool attachment.
2. If unreachable, attempt `launchctl bootstrap` (or `load`) against the launchd plist, wait up to ~6 seconds for the server to come up, then proceed.
This handles every scenario: normal boot (launchd has it running), plist unloaded (auto-start via launchctl), server crashed (auto-restart via launchd).
## Related
- `scripts/personas/system-manager.py` — `cmd_mcp_setup()` and `_ensure_host_mcp_server()`
- `personas/host_mcp_server.py` — the server itself, listens on 9105
- `personas/launchd/com.oldgrowth.host-mcp.plist` — launchd plist managing the server
- `personas/config.py` — `HOST_TOOLS_CALLBACK_PORT = 9105` constant
## References
- `scripts/personas/system-manager.py` — `_ensure_host_mcp_server()` function (lines ~302-380)
- `personas/host_mcp_server.py` — `/health` endpoint implementation (lines 1049-1069)
- `personas/launchd/com.oldgrowth.host-mcp.plist` — launchd plist with RunAtLoad + KeepAlive
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-host-tools-mcp-server-launchd-lifecycle: status/draft
Tag assignment · dev- Gold
- The note calls for `status/draft`: Authoring in progress, not yet verified (True)
- ember
- The note has no reason to carry `status/draft` (False)
- Signal
- P(true) 0.10 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Discoveries/Host-Tools MCP Server Launchd Lifecycle.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Host-Tools MCP Server Launchd Lifecycle
The host-tools MCP server on port 9105 is owned by launchd via `com.oldgrowth.host-mcp.plist`. The `mcp-setup` command must never attempt to check port availability or start its own server instance — it should verify server health and proceed with tool attachment.
## Background
The `cmd_mcp_setup()` function in `scripts/personas/system-manager.py` originally used `socket.bind()` to check if port 9105 was free. On a clean boot, launchd starts the host-tools MCP server via `com.oldgrowth.host-mcp.plist` (RunAtLoad + KeepAlive), so the port is always in use — causing `socket.bind()` to fail and `make recovery` to exit with "Port 9105 is already in use."
This was a design mismatch: launchd owns the server lifecycle (start, keep alive, restart on crash), and `mcp-setup` only needs to verify the server is healthy and attach MCP tools to Letta agents. The port-conflict check was an artifact from an earlier design where `mcp-setup` was expected to start its own server.
## Fix
The port-conflict check was replaced with a two-phase health check:
1. Hit `GET /health` on port 9105. If the server responds, skip to tool attachment.
2. If unreachable, attempt `launchctl bootstrap` (or `load`) against the launchd plist, wait up to ~6 seconds for the server to come up, then proceed.
This handles every scenario: normal boot (launchd has it running), plist unloaded (auto-start via launchctl), server crashed (auto-restart via launchd).
## Related
- `scripts/personas/system-manager.py` — `cmd_mcp_setup()` and `_ensure_host_mcp_server()`
- `personas/host_mcp_server.py` — the server itself, listens on 9105
- `personas/launchd/com.oldgrowth.host-mcp.plist` — launchd plist managing the server
- `personas/config.py` — `HOST_TOOLS_CALLBACK_PORT = 9105` constant
## References
- `scripts/personas/system-manager.py` — `_ensure_host_mcp_server()` function (lines ~302-380)
- `personas/host_mcp_server.py` — `/health` endpoint implementation (lines 1049-1069)
- `personas/launchd/com.oldgrowth.host-mcp.plist` — launchd plist with RunAtLoad + KeepAlive
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-host-tools-mcp-server-launchd-lifecycle: type/code
Tag assignment · dev- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.74 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Host-Tools MCP Server Launchd Lifecycle.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Host-Tools MCP Server Launchd Lifecycle
The host-tools MCP server on port 9105 is owned by launchd via `com.oldgrowth.host-mcp.plist`. The `mcp-setup` command must never attempt to check port availability or start its own server instance — it should verify server health and proceed with tool attachment.
## Background
The `cmd_mcp_setup()` function in `scripts/personas/system-manager.py` originally used `socket.bind()` to check if port 9105 was free. On a clean boot, launchd starts the host-tools MCP server via `com.oldgrowth.host-mcp.plist` (RunAtLoad + KeepAlive), so the port is always in use — causing `socket.bind()` to fail and `make recovery` to exit with "Port 9105 is already in use."
This was a design mismatch: launchd owns the server lifecycle (start, keep alive, restart on crash), and `mcp-setup` only needs to verify the server is healthy and attach MCP tools to Letta agents. The port-conflict check was an artifact from an earlier design where `mcp-setup` was expected to start its own server.
## Fix
The port-conflict check was replaced with a two-phase health check:
1. Hit `GET /health` on port 9105. If the server responds, skip to tool attachment.
2. If unreachable, attempt `launchctl bootstrap` (or `load`) against the launchd plist, wait up to ~6 seconds for the server to come up, then proceed.
This handles every scenario: normal boot (launchd has it running), plist unloaded (auto-start via launchctl), server crashed (auto-restart via launchd).
## Related
- `scripts/personas/system-manager.py` — `cmd_mcp_setup()` and `_ensure_host_mcp_server()`
- `personas/host_mcp_server.py` — the server itself, listens on 9105
- `personas/launchd/com.oldgrowth.host-mcp.plist` — launchd plist managing the server
- `personas/config.py` — `HOST_TOOLS_CALLBACK_PORT = 9105` constant
## References
- `scripts/personas/system-manager.py` — `_ensure_host_mcp_server()` function (lines ~302-380)
- `personas/host_mcp_server.py` — `/health` endpoint implementation (lines 1049-1069)
- `personas/launchd/com.oldgrowth.host-mcp.plist` — launchd plist with RunAtLoad + KeepAlive
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-host-tools-mcp-server-launchd-lifecycle: type/discovery
Tag assignment · dev- Gold
- The note calls for `type/discovery`: Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/discovery` (False)
- Signal
- P(true) 0.25 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Host-Tools MCP Server Launchd Lifecycle.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Host-Tools MCP Server Launchd Lifecycle
The host-tools MCP server on port 9105 is owned by launchd via `com.oldgrowth.host-mcp.plist`. The `mcp-setup` command must never attempt to check port availability or start its own server instance — it should verify server health and proceed with tool attachment.
## Background
The `cmd_mcp_setup()` function in `scripts/personas/system-manager.py` originally used `socket.bind()` to check if port 9105 was free. On a clean boot, launchd starts the host-tools MCP server via `com.oldgrowth.host-mcp.plist` (RunAtLoad + KeepAlive), so the port is always in use — causing `socket.bind()` to fail and `make recovery` to exit with "Port 9105 is already in use."
This was a design mismatch: launchd owns the server lifecycle (start, keep alive, restart on crash), and `mcp-setup` only needs to verify the server is healthy and attach MCP tools to Letta agents. The port-conflict check was an artifact from an earlier design where `mcp-setup` was expected to start its own server.
## Fix
The port-conflict check was replaced with a two-phase health check:
1. Hit `GET /health` on port 9105. If the server responds, skip to tool attachment.
2. If unreachable, attempt `launchctl bootstrap` (or `load`) against the launchd plist, wait up to ~6 seconds for the server to come up, then proceed.
This handles every scenario: normal boot (launchd has it running), plist unloaded (auto-start via launchctl), server crashed (auto-restart via launchd).
## Related
- `scripts/personas/system-manager.py` — `cmd_mcp_setup()` and `_ensure_host_mcp_server()`
- `personas/host_mcp_server.py` — the server itself, listens on 9105
- `personas/launchd/com.oldgrowth.host-mcp.plist` — launchd plist managing the server
- `personas/config.py` — `HOST_TOOLS_CALLBACK_PORT = 9105` constant
## References
- `scripts/personas/system-manager.py` — `_ensure_host_mcp_server()` function (lines ~302-380)
- `personas/host_mcp_server.py` — `/health` endpoint implementation (lines 1049-1069)
- `personas/launchd/com.oldgrowth.host-mcp.plist` — launchd plist with RunAtLoad + KeepAlive
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-art-vehicle-samba-exterior: type/reference
Tag assignment · test- Gold
- The note calls for `type/reference`: Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`) (True)
- ember
- The note has no reason to carry `type/reference` (False)
- Signal
- P(true) 0.03 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Art/Vehicle — Samba Exterior.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Vehicle — Samba Exterior
> **⚠️ PRE-068 NOTE — asset paths and material resource names will be verified when spec 068 ships.**
> Material `.tres` file paths are proposed targets. After 068 ships the purchased model, confirm resource names against the actual committed geometry. Spec 082 will reconcile.
This note covers PBR material specs for all exterior surfaces. For the palette derivation rationale and archival paint code research, see [[Vehicle Palette Materials and Dimensions Discovery]]. For dimensional anchors (panel locations, window sizes), see [[Art/Vehicle — Samba Dimensions]].
## Exterior panel diagram
![[_meta/attachments/vehicle/panels_exterior_callout.svg]]
## Hero palette (exterior)
| Slot | Name | sRGB hex | Linear RGB | Purpose |
|---|---|---|---|---|
| 1 | `hero_cream_upper` | `#D9D3BE` | `(0.675, 0.615, 0.513)` | Primary upper body paint |
| 2 | `hero_teal_lower` | `#3F6E66` | `(0.047, 0.154, 0.138)` | Primary lower body paint |
| 3 | `weathered_cream_upper` | `#C9C2AB` | `(0.581, 0.529, 0.418)` | Sun-faded upper (roof peaks, raised surfaces) |
| 4 | `weathered_teal_lower` | `#355A56` | `(0.032, 0.103, 0.097)` | Sun-faded lower (hood front, running boards, rockers) |
| 5 | `trim_beltline` | `#B5B0A3` | `(0.453, 0.421, 0.363)` | Chrome decorative strip with tarnish |
| 6 | `vw_roundel_chrome` | `#D4D0C5` | `(0.646, 0.613, 0.546)` | VW badge and bright chrome trim |
| 7 | `shadow_deep` | `#1B2E2A` | `(0.009, 0.028, 0.024)` | AO reference / deep crevice shadow colour |
**Archival anchor**: Option B — L289 Blue White / L380 Turquoise. Hero palette is intentionally adjusted from archival codes for moodboard alignment; values above are the production target, not the factory codes. See [[Vehicle Palette Materials and Dimensions Discovery]] for the full archival baseline.
**Two-tone boundary rule**: The paint boundary is a hard seam at the chrome beltline strip. Paint does NOT gradient-blend. Model the beltline as a separate bevelled feature; assign upper and lower paint as two distinct material slots.
## Painted body
Covers: nose cone, hood front, all fenders, all side panels, roof panels, engine hatch, all doors, running boards where painted.
**Panel IDs**: `nose_cone`, `hood_front`, `engine_hatch`, `panel_front_lower`, `fender_front_left`, `fender_front_right`, `fender_rear_left`, `fender_rear_right`, `panel_side_left_front/mid/rear`, `panel_side_right_front/mid/rear`, `door_left_front`, `door_right_front`, `door_rear_left`, `door_rear_right`, `panel_rear_upper`, `roof_panel_front/mid/rear`, `running_board_left`, `running_board_right`.
### PBR rules (all states)
- **Metalness**: 0.0 — non-metallic nitrocellulose lacquer. No metallic flake.
- **Clearcoat**: DISABLED — 1963 paint was not clearcoated.
- **Two-tone**: hard seam at chrome beltline; two separate material instances above and below.
### Clean state
| Property | Upper | Lower |
|---|---|---|
| Albedo | `hero_cream_upper` | `hero_teal_lower` |
| Roughness | 0.55–0.65 | 0.55–0.65 |
| Normal | Subtle panel-seam + minor hand-beaten imperfections (period-correct) | Same |
| AO | Panel-seam and wheel-arch bake | Same |
### Worn state (default Old Growth game state)
| Property | Upper | Lower |
|---|---|---|
| Albedo | `hero_cream_upper` → `weathered_cream_upper` blend (60/40 worn) | `hero_teal_lower` → `weathered_teal_lower` blend |
| Roughness | 0.65–0.80 (lost gloss; worse on horizontal surfaces) | Same |
| Normal | Paint chips at panel edges, fender tops, hood corners; minor hail-type dimples on horizontal surfaces | Same |
| Weathering mask | World-up normal + panel edge proximity + wheel-well proximity | Same |
### Damaged state (heavy wear)
| Property | Upper | Lower |
|---|---|---|
| Albedo | `weathered_cream_upper` + primer `#8A7E68` in exposed spots + rust `#7A4A2C` at edges | `weathered_teal_lower` + exposed metal |
| Roughness | 0.80–0.95 (matte, oxidised) | Same |
| Metalness | 0.0 on paint; 0.7 on bare metal | Same |
| Normal | Deep paint loss patches, real dents, bent panels, visible panel seam displacement | Same |
| Rust layer | Colour `#7A4A2C`, roughness 0.95, metalness 0.2 (oxidised iron has partial metallic character) | Same |
**Wet state modifier**: roughness drops ~0.10; specular rises. See `src/assets/shaders/rain_on_glass.gdshader` for wet-surface shader reference.
## Chrome trim
Decorative chrome bright trim: beltline strip, window surrounds, VW roundel badge, door handles, headlight bezels, bumper overriders, side mirror housings, radio fascia bezel.
| State | Albedo | Roughness | Metalness | Notes |
|---|---|---|---|---|
| Clean | `vw_roundel_chrome` (#D4D0C5), slightly cooler | 0.05–0.15 | 1.0 | Mirror polish |
| Worn (default) | Same + subtle yellowed patches | 0.20–0.40 | 0.85 | Light oxidation |
| Damaged | `#A09A88` (dull yellowed); rust blooms `#7A4A2C` at pitting | 0.60–0.85 | 0.55 | Pitted chrome |
Period chrome is triple-plated (copper-nickel-chrome). When pitted, copper undertones appear — add warm pink hint at pitting damage sites.
## Bumpers
Front and rear bumpers: painted steel tube body with chrome overrider bars. The bumper tube is upper-body-colour; overriders are chrome-plated.
| Part | Clean | Worn | Damaged |
|---|---|---|---|
| Tube albedo | `hero_cream_upper` | Scratches exposing primer `#3A342C` | Heavy scratches, exposed metal, rust |
| Tube roughness | 0.55 | 0.70 | 0.85 |
| Tube metalness | 0.0 | 0.0 | 0.0 (+ 0.7 on exposed metal) |
| Overriders albedo | `vw_roundel_chrome` | Dull | Pitted chrome + oxide |
| Overriders roughness | 0.08 | 0.25 | 0.70 |
| Overriders metalness | 1.0 | 0.9 | 0.5 |
Bumper mount brackets behind the tube are unpainted black-phosphate steel and can show through damaged areas.
## Rubber seals
Window gaskets, door weatherstripping, windshield perimeter seal.
| State | Albedo | Roughness | Metalness | Notes |
|---|---|---|---|---|
| Clean | `#1A1A1A` deep matte black | 0.90–0.95 | 0.0 | Essentially Lambertian rubber |
| Worn | `#252525` grey-brown from UV | 0.85 | 0.0 | Slight hardening, surface craze |
| Damaged | Cracked rubber, white chalking visible | 0.80 | 0.0 | Perished rubber look |
> [!note] Moss accumulation priority: rubber seals are high-priority contamination zones in the Verdant biome — seams and gaskets trap moisture and organic material. See [[Art/Vehicle — Contamination]].
## Glass (19 panels)
Windshield (upper + lower split panes), 6 side windows, 8 skylights, 2 rear corner windows, 1 rear window. All flat or mildly curved automotive glass.
| State | Albedo alpha | Roughness | Notes |
|---|---|---|---|
| Clean | 0.08 (nearly transparent, subtle green tint from period iron-content glass) | 0.0 | Mirror-smooth |
| Worn (default) | 0.12 (internal haze + water spot overlay) | 0.05 | Water spots, dirt streaks |
| Damaged (cracked) | Crack-pattern overlay as albedo (white lines on alpha) | 0.15 at cracks | |
| Damaged (shattered) | Crack pattern + additional displacement | 0.15 | |
> [!warning] **Do NOT replace the existing rain/wiper shader stack.**
> `src/scripts/bus/windshield_rain.gd` and `src/resources/windshield_rain_material.tres` are live in the codebase. Phase 3 glass materials must integrate with these, not replace them. The rain shader handles dynamic water droplets and wiper interaction. See [[Systems/Vehicle — Samba]] for the art integration constraint.
Period laminated windshield glass has a slight green tint from iron content in the glass — this is period correct. Apply a subtle green-cast transparency.
## Tyres
Four 5.60-15 bias-ply tyres on 15 in steel rims with painted hubcaps.
| State | Albedo | Roughness | Metalness | Notes |
|---|---|---|---|---|
| Clean | `#1A1A1A` deep matte black | 0.88 | 0.0 | Narrow bias-ply tread (NOT modern aggressive tread) |
| Worn (default) | `#202020` greyed from road dust | 0.85 | 0.0 | |
| Damaged (flat/detached) | Shredded rubber texture; exposed steel belt | 0.80 | 0.3 (steel belt) | At `damaged_threshold = 0.3` the tyre visually deflates |
Period tyre sidewalls show "5.60-15" or "165R15" branding plus manufacturer name. **Avoid modern all-terrain or aggressive tread.** This is a narrow-rib bias-ply profile.
## Texture variety sets (Phase 3)
Five full PBR variety sets (BaseColor, Roughness, Metallic, NormalMap) needed for the exterior panel groups:
| Variety | Description |
|---|---|
| `weathered` | Sun fading, chalky paint, minor surface rust at panel edges |
| `forest_worn` | Moss at seals, lichen on lower panels, sap streaks, bark scrapes |
| `muddy` | Mud splatter on lower panels and wheel arches |
| `rain_wet` | Wet sheen, water droplets, rain streaks, dark wet wheel arches |
| `deep_forest` | Heavy moss, fungal patches, years of accumulation |
## Godot material targets (proposed)
| Resource | Albedo slot | Roughness | Metalness |
|---|---|---|---|
| `van_body_upper.tres` | `hero_cream_upper` | 0.65 | 0.0 |
| `van_body_lower.tres` | `hero_teal_lower` | 0.65 | 0.0 |
| `van_chrome_trim.tres` | `vw_roundel_chrome` | 0.10 | 1.0 |
| `van_bumper_tube.tres` | `hero_cream_upper` | 0.55 | 0.0 |
| `van_glass.tres` | Color(1,1,1,0.08) | 0.0 | 0.0 |
| `van_tyre.tres` | `#1A1A1A` | 0.88 | 0.0 |
| `van_rubber_seal.tres` | `#1A1A1A` | 0.92 | 0.0 |
## Related
- [[Systems/Vehicle — Samba]] — implementation context; art integration constraint for glass shader
- [[Vehicle Palette Materials and Dimensions Discovery]] — palette derivation, archival paint codes, full PBR category list
- [[Art/Vehicle — Samba Dimensions]] — panel locations, window sizes, proportion anchors
- [[Art/Vehicle — Samba Interior]] — interior material targets
- [[Art/Vehicle — Contamination]] — contamination zones keyed to these exterior panels
- [[Art/Vehicle — Samba Acceptance Checklist]] — Phase 3 pass/fail criteria
- [[Art/Biome Ambient Palette]] — how exterior colours read under each biome ambient
- [[Art]] — folder MOC
- [[Vehicle — Domain MOC]] — domain MOC
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-art-vehicle-samba-exterior: type/spec
Tag assignment · test- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.66 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Art/Vehicle — Samba Exterior.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Vehicle — Samba Exterior
> **⚠️ PRE-068 NOTE — asset paths and material resource names will be verified when spec 068 ships.**
> Material `.tres` file paths are proposed targets. After 068 ships the purchased model, confirm resource names against the actual committed geometry. Spec 082 will reconcile.
This note covers PBR material specs for all exterior surfaces. For the palette derivation rationale and archival paint code research, see [[Vehicle Palette Materials and Dimensions Discovery]]. For dimensional anchors (panel locations, window sizes), see [[Art/Vehicle — Samba Dimensions]].
## Exterior panel diagram
![[_meta/attachments/vehicle/panels_exterior_callout.svg]]
## Hero palette (exterior)
| Slot | Name | sRGB hex | Linear RGB | Purpose |
|---|---|---|---|---|
| 1 | `hero_cream_upper` | `#D9D3BE` | `(0.675, 0.615, 0.513)` | Primary upper body paint |
| 2 | `hero_teal_lower` | `#3F6E66` | `(0.047, 0.154, 0.138)` | Primary lower body paint |
| 3 | `weathered_cream_upper` | `#C9C2AB` | `(0.581, 0.529, 0.418)` | Sun-faded upper (roof peaks, raised surfaces) |
| 4 | `weathered_teal_lower` | `#355A56` | `(0.032, 0.103, 0.097)` | Sun-faded lower (hood front, running boards, rockers) |
| 5 | `trim_beltline` | `#B5B0A3` | `(0.453, 0.421, 0.363)` | Chrome decorative strip with tarnish |
| 6 | `vw_roundel_chrome` | `#D4D0C5` | `(0.646, 0.613, 0.546)` | VW badge and bright chrome trim |
| 7 | `shadow_deep` | `#1B2E2A` | `(0.009, 0.028, 0.024)` | AO reference / deep crevice shadow colour |
**Archival anchor**: Option B — L289 Blue White / L380 Turquoise. Hero palette is intentionally adjusted from archival codes for moodboard alignment; values above are the production target, not the factory codes. See [[Vehicle Palette Materials and Dimensions Discovery]] for the full archival baseline.
**Two-tone boundary rule**: The paint boundary is a hard seam at the chrome beltline strip. Paint does NOT gradient-blend. Model the beltline as a separate bevelled feature; assign upper and lower paint as two distinct material slots.
## Painted body
Covers: nose cone, hood front, all fenders, all side panels, roof panels, engine hatch, all doors, running boards where painted.
**Panel IDs**: `nose_cone`, `hood_front`, `engine_hatch`, `panel_front_lower`, `fender_front_left`, `fender_front_right`, `fender_rear_left`, `fender_rear_right`, `panel_side_left_front/mid/rear`, `panel_side_right_front/mid/rear`, `door_left_front`, `door_right_front`, `door_rear_left`, `door_rear_right`, `panel_rear_upper`, `roof_panel_front/mid/rear`, `running_board_left`, `running_board_right`.
### PBR rules (all states)
- **Metalness**: 0.0 — non-metallic nitrocellulose lacquer. No metallic flake.
- **Clearcoat**: DISABLED — 1963 paint was not clearcoated.
- **Two-tone**: hard seam at chrome beltline; two separate material instances above and below.
### Clean state
| Property | Upper | Lower |
|---|---|---|
| Albedo | `hero_cream_upper` | `hero_teal_lower` |
| Roughness | 0.55–0.65 | 0.55–0.65 |
| Normal | Subtle panel-seam + minor hand-beaten imperfections (period-correct) | Same |
| AO | Panel-seam and wheel-arch bake | Same |
### Worn state (default Old Growth game state)
| Property | Upper | Lower |
|---|---|---|
| Albedo | `hero_cream_upper` → `weathered_cream_upper` blend (60/40 worn) | `hero_teal_lower` → `weathered_teal_lower` blend |
| Roughness | 0.65–0.80 (lost gloss; worse on horizontal surfaces) | Same |
| Normal | Paint chips at panel edges, fender tops, hood corners; minor hail-type dimples on horizontal surfaces | Same |
| Weathering mask | World-up normal + panel edge proximity + wheel-well proximity | Same |
### Damaged state (heavy wear)
| Property | Upper | Lower |
|---|---|---|
| Albedo | `weathered_cream_upper` + primer `#8A7E68` in exposed spots + rust `#7A4A2C` at edges | `weathered_teal_lower` + exposed metal |
| Roughness | 0.80–0.95 (matte, oxidised) | Same |
| Metalness | 0.0 on paint; 0.7 on bare metal | Same |
| Normal | Deep paint loss patches, real dents, bent panels, visible panel seam displacement | Same |
| Rust layer | Colour `#7A4A2C`, roughness 0.95, metalness 0.2 (oxidised iron has partial metallic character) | Same |
**Wet state modifier**: roughness drops ~0.10; specular rises. See `src/assets/shaders/rain_on_glass.gdshader` for wet-surface shader reference.
## Chrome trim
Decorative chrome bright trim: beltline strip, window surrounds, VW roundel badge, door handles, headlight bezels, bumper overriders, side mirror housings, radio fascia bezel.
| State | Albedo | Roughness | Metalness | Notes |
|---|---|---|---|---|
| Clean | `vw_roundel_chrome` (#D4D0C5), slightly cooler | 0.05–0.15 | 1.0 | Mirror polish |
| Worn (default) | Same + subtle yellowed patches | 0.20–0.40 | 0.85 | Light oxidation |
| Damaged | `#A09A88` (dull yellowed); rust blooms `#7A4A2C` at pitting | 0.60–0.85 | 0.55 | Pitted chrome |
Period chrome is triple-plated (copper-nickel-chrome). When pitted, copper undertones appear — add warm pink hint at pitting damage sites.
## Bumpers
Front and rear bumpers: painted steel tube body with chrome overrider bars. The bumper tube is upper-body-colour; overriders are chrome-plated.
| Part | Clean | Worn | Damaged |
|---|---|---|---|
| Tube albedo | `hero_cream_upper` | Scratches exposing primer `#3A342C` | Heavy scratches, exposed metal, rust |
| Tube roughness | 0.55 | 0.70 | 0.85 |
| Tube metalness | 0.0 | 0.0 | 0.0 (+ 0.7 on exposed metal) |
| Overriders albedo | `vw_roundel_chrome` | Dull | Pitted chrome + oxide |
| Overriders roughness | 0.08 | 0.25 | 0.70 |
| Overriders metalness | 1.0 | 0.9 | 0.5 |
Bumper mount brackets behind the tube are unpainted black-phosphate steel and can show through damaged areas.
## Rubber seals
Window gaskets, door weatherstripping, windshield perimeter seal.
| State | Albedo | Roughness | Metalness | Notes |
|---|---|---|---|---|
| Clean | `#1A1A1A` deep matte black | 0.90–0.95 | 0.0 | Essentially Lambertian rubber |
| Worn | `#252525` grey-brown from UV | 0.85 | 0.0 | Slight hardening, surface craze |
| Damaged | Cracked rubber, white chalking visible | 0.80 | 0.0 | Perished rubber look |
> [!note] Moss accumulation priority: rubber seals are high-priority contamination zones in the Verdant biome — seams and gaskets trap moisture and organic material. See [[Art/Vehicle — Contamination]].
## Glass (19 panels)
Windshield (upper + lower split panes), 6 side windows, 8 skylights, 2 rear corner windows, 1 rear window. All flat or mildly curved automotive glass.
| State | Albedo alpha | Roughness | Notes |
|---|---|---|---|
| Clean | 0.08 (nearly transparent, subtle green tint from period iron-content glass) | 0.0 | Mirror-smooth |
| Worn (default) | 0.12 (internal haze + water spot overlay) | 0.05 | Water spots, dirt streaks |
| Damaged (cracked) | Crack-pattern overlay as albedo (white lines on alpha) | 0.15 at cracks | |
| Damaged (shattered) | Crack pattern + additional displacement | 0.15 | |
> [!warning] **Do NOT replace the existing rain/wiper shader stack.**
> `src/scripts/bus/windshield_rain.gd` and `src/resources/windshield_rain_material.tres` are live in the codebase. Phase 3 glass materials must integrate with these, not replace them. The rain shader handles dynamic water droplets and wiper interaction. See [[Systems/Vehicle — Samba]] for the art integration constraint.
Period laminated windshield glass has a slight green tint from iron content in the glass — this is period correct. Apply a subtle green-cast transparency.
## Tyres
Four 5.60-15 bias-ply tyres on 15 in steel rims with painted hubcaps.
| State | Albedo | Roughness | Metalness | Notes |
|---|---|---|---|---|
| Clean | `#1A1A1A` deep matte black | 0.88 | 0.0 | Narrow bias-ply tread (NOT modern aggressive tread) |
| Worn (default) | `#202020` greyed from road dust | 0.85 | 0.0 | |
| Damaged (flat/detached) | Shredded rubber texture; exposed steel belt | 0.80 | 0.3 (steel belt) | At `damaged_threshold = 0.3` the tyre visually deflates |
Period tyre sidewalls show "5.60-15" or "165R15" branding plus manufacturer name. **Avoid modern all-terrain or aggressive tread.** This is a narrow-rib bias-ply profile.
## Texture variety sets (Phase 3)
Five full PBR variety sets (BaseColor, Roughness, Metallic, NormalMap) needed for the exterior panel groups:
| Variety | Description |
|---|---|
| `weathered` | Sun fading, chalky paint, minor surface rust at panel edges |
| `forest_worn` | Moss at seals, lichen on lower panels, sap streaks, bark scrapes |
| `muddy` | Mud splatter on lower panels and wheel arches |
| `rain_wet` | Wet sheen, water droplets, rain streaks, dark wet wheel arches |
| `deep_forest` | Heavy moss, fungal patches, years of accumulation |
## Godot material targets (proposed)
| Resource | Albedo slot | Roughness | Metalness |
|---|---|---|---|
| `van_body_upper.tres` | `hero_cream_upper` | 0.65 | 0.0 |
| `van_body_lower.tres` | `hero_teal_lower` | 0.65 | 0.0 |
| `van_chrome_trim.tres` | `vw_roundel_chrome` | 0.10 | 1.0 |
| `van_bumper_tube.tres` | `hero_cream_upper` | 0.55 | 0.0 |
| `van_glass.tres` | Color(1,1,1,0.08) | 0.0 | 0.0 |
| `van_tyre.tres` | `#1A1A1A` | 0.88 | 0.0 |
| `van_rubber_seal.tres` | `#1A1A1A` | 0.92 | 0.0 |
## Related
- [[Systems/Vehicle — Samba]] — implementation context; art integration constraint for glass shader
- [[Vehicle Palette Materials and Dimensions Discovery]] — palette derivation, archival paint codes, full PBR category list
- [[Art/Vehicle — Samba Dimensions]] — panel locations, window sizes, proportion anchors
- [[Art/Vehicle — Samba Interior]] — interior material targets
- [[Art/Vehicle — Contamination]] — contamination zones keyed to these exterior panels
- [[Art/Vehicle — Samba Acceptance Checklist]] — Phase 3 pass/fail criteria
- [[Art/Biome Ambient Palette]] — how exterior colours read under each biome ambient
- [[Art]] — folder MOC
- [[Vehicle — Domain MOC]] — domain MOC
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-098-weather-effects-implementation-apr-25-2026: layer/gdscript
Tag assignment · test- Gold
- The note calls for `layer/gdscript`: GDScript (Godot scene-tier logic) (True)
- ember
- The note has no reason to carry `layer/gdscript` (False)
- Signal
- P(true) 0.23 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spec 098 Weather Effects Implementation — Apr 25 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 098 Weather Effects Implementation — Apr 25 2026
Implementation session for spec 098 (Weather Effects Completion). All 12 phases executed via parallel agent delegation in a single session.
## What Was Built
### Windshield SOLID Hierarchy
- `windshield_overlay_base.gd` — abstract base (push_error guards on 5 abstract methods)
- `rain_overlay.gd` — migrated all logic from deleted `windshield_rain.gd`
- `snow_overlay.gd` — opaque white accumulation (LIGHT_SNOW→0.3, HEAVY_SNOW→0.7, BLIZZARD→1.0)
- `spore_film_overlay.gd` + `spore_film.gdshader` — amber-blue film, smear-not-clear wiper behavior
- `frost_overlay.gd` + `windshield_frost.gdshader` — 60s delay, edge-inward growth, wiper-immune
- `windshield_controller.gd` — coordinator routing all 4 overlays
### Particle Systems
- `weather_effects_controller.gd` — Node3D with QualityTier enum; snow/spore/ash GPUParticles3D
- `on_foot_weather_particles.gd` — mirrors on_foot_rain_particles.gd for snow/spore/ash/hail
### Audio Controllers
- `wind_audio_controller.gd` — interior/exterior split, volume = lerpf(WIND_SILENT_DB, WIND_MAX_DB, wind)
- `dead_calm_audio_controller.gd` — sub-bass fade-in/-out at -45 dB on Ambience bus
- `instrument_disruption_handler.gd` — FLICKER/OSCILLATE patterns, graceful no-op when needles absent
### Game Systems
- `lightning_controller.gd` — seeded RNG (seed + 9173), discharge timer, WorldEnvironment flash, thunder audio, instrument_disruption_changed emission, ELECTRICAL_STORM crackle + radio static
- `on_foot_controller.gd` — wind_speed_penalty_scale + _wind_mult, get_target_speed() × _wind_mult
- `rain_audio_controller.gd` — additive hail audio branch (is_hail + THUNDERSTORM)
- `rain_particles_controller.gd` — hail particle swap during THUNDERSTORM+is_hail
- `fog_system.gd` — ELECTRICAL_STORM_SKY_COLOR tween (Color(0.25, 0.0, 0.4)) over 3.0s
- `wet_road_controller.gd` — ash road coating via albedo lerp + roughness delta
- `disturbance_storm_audio.gd` — preloaded _STORM_ROAR_STREAM const
### Assets
- 16 audio placeholder .ogg files in `src/assets/audio/ambient/weather/`
- `src/assets/audio/ambient/weather/gold/.gdignore` — non-shippable gold masters excluded
- `src/resources/windshield_snow_material.tres` — snow shader material
## CI Results
- `make gdscript-quality`: ✅ 146 files, no violations
- `make test`: ✅ 934/935 passing (1 pre-existing risky)
- `make headless`: ✅ clean
- `make all`: ✅ including export smoke test
## SemVer Bumps Applied
All 8 required bumps exceeded: core-autoloads→0.24.0, bus→0.33.2, weather→0.19.2, on-foot-controller→0.10.1, assets→0.22.2, terrain→2.15.1, world-generation→0.53.5, tests→0.60.3
## Post-Implementation Work (same session)
### Audio Pipeline (completed)
- 14 fake placeholder OGGs removed from submodule; 2 real blizzard wind OGGs retained
- 18 Suno provenance JSON sidecars written to `src/assets/audio/ambient/weather/gold/` (16 spec-098 + 2 pre-existing rain OGGs co-located)
- `tools/tts/ambient_process.sh` — new ffmpeg pipeline for Suno ambient audio (loop/oneshot modes)
- `tools/tts/validate_ambient_naming.sh` + `validate_ambient_provenance.sh` — CI validation for 18 assets
- `tools/tts/Makefile` extended with `ambient-weather-*` targets; `validate-names`/`validate-provenance` now cover ambient/weather
- `rain_heavy_interior.ogg` + `rain_light_interior.ogg` moved from `ambient/` to `ambient/weather/` and renamed with `weather_` prefix for consistency
- AGENTS.md + `audio-assets` SKILL.md updated with full ambient weather production workflow
- Vault discovery note written: `Discoveries/Audio Generation Pipeline Constraints & Patterns.md` (consolidated note)
### Scene Wiring via Godot MCP (completed)
All previously-deferred scene wiring completed via Godot editor MCP:
**`generated_world.tscn`:**
- `WeatherEffectsController` (Node3D + script) with `SnowParticles`, `SporeParticles`, `AshParticles` (GPUParticles3D, particle materials assigned, NodePaths wired)
- `LightningController` (Node + script)
**`samba_bus.tscn`:**
- `HailParticles` (GPUParticles3D, wired to `RainParticlesController.hail_particles_path`)
- `WindAudioController` (Node + script) with `WindInterior` (Interior bus) + `WindExterior` (Ambience bus)
- `InstrumentDisruptionHandler` (Node + script, `needle_paths=[]` — graceful no-op until spec 033)
- `DeadCalmAudioController` (Node + script) with `SubBassPlayer` (Ambience bus)
**`on_foot_player.tscn`:**
- `OnFootWeatherParticles` (Node + script) with `SnowParticlesOnFoot`, `SporeParticlesOnFoot`, `AshParticlesOnFoot`, `HailParticlesOnFoot`
**`src/resources/particles/`:** 4 new `ParticleProcessMaterial` .tres resources (snow, spore, ash, hail)
### Bug Fixes (discovered during scene wiring)
- 3 audio controllers had `const preload()` for pending Suno OGGs — fixed to `ResourceLoader.exists()` + runtime `load()` with `Debug.warn()` fallback: `dead_calm_audio_controller.gd`, `disturbance_storm_audio.gd`, `rain_audio_controller.gd`
- 11 Oracle-identified bugs fixed post-review (spore/frost delta=0, hail null guard, material ownership, wind tween split, etc.)
## Still Pending (human tasks)
- ~~14 Suno audio assets not yet produced (code gracefully silent until installed)~~ — ✅ complete: all 18 ElevenLabs SFX V2 OGGs produced and installed (pipeline switched from Suno to ElevenLabs; see [[Audio Generation Pipeline Constraints & Patterns]]); `validate-ambient-naming` and `validate-ambient-provenance` pass clean
- T085: Game version bump (human decision per Art III R15)
- spec 033 dashboard needles (for `InstrumentDisruptionHandler` to have actual needle NodePaths)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-098-weather-effects-implementation-apr-25-2026: status/reviewed
Tag assignment · test- Gold
- The note calls for `status/reviewed`: Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `status/reviewed` (False)
- Signal
- P(true) 0.48 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spec 098 Weather Effects Implementation — Apr 25 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 098 Weather Effects Implementation — Apr 25 2026
Implementation session for spec 098 (Weather Effects Completion). All 12 phases executed via parallel agent delegation in a single session.
## What Was Built
### Windshield SOLID Hierarchy
- `windshield_overlay_base.gd` — abstract base (push_error guards on 5 abstract methods)
- `rain_overlay.gd` — migrated all logic from deleted `windshield_rain.gd`
- `snow_overlay.gd` — opaque white accumulation (LIGHT_SNOW→0.3, HEAVY_SNOW→0.7, BLIZZARD→1.0)
- `spore_film_overlay.gd` + `spore_film.gdshader` — amber-blue film, smear-not-clear wiper behavior
- `frost_overlay.gd` + `windshield_frost.gdshader` — 60s delay, edge-inward growth, wiper-immune
- `windshield_controller.gd` — coordinator routing all 4 overlays
### Particle Systems
- `weather_effects_controller.gd` — Node3D with QualityTier enum; snow/spore/ash GPUParticles3D
- `on_foot_weather_particles.gd` — mirrors on_foot_rain_particles.gd for snow/spore/ash/hail
### Audio Controllers
- `wind_audio_controller.gd` — interior/exterior split, volume = lerpf(WIND_SILENT_DB, WIND_MAX_DB, wind)
- `dead_calm_audio_controller.gd` — sub-bass fade-in/-out at -45 dB on Ambience bus
- `instrument_disruption_handler.gd` — FLICKER/OSCILLATE patterns, graceful no-op when needles absent
### Game Systems
- `lightning_controller.gd` — seeded RNG (seed + 9173), discharge timer, WorldEnvironment flash, thunder audio, instrument_disruption_changed emission, ELECTRICAL_STORM crackle + radio static
- `on_foot_controller.gd` — wind_speed_penalty_scale + _wind_mult, get_target_speed() × _wind_mult
- `rain_audio_controller.gd` — additive hail audio branch (is_hail + THUNDERSTORM)
- `rain_particles_controller.gd` — hail particle swap during THUNDERSTORM+is_hail
- `fog_system.gd` — ELECTRICAL_STORM_SKY_COLOR tween (Color(0.25, 0.0, 0.4)) over 3.0s
- `wet_road_controller.gd` — ash road coating via albedo lerp + roughness delta
- `disturbance_storm_audio.gd` — preloaded _STORM_ROAR_STREAM const
### Assets
- 16 audio placeholder .ogg files in `src/assets/audio/ambient/weather/`
- `src/assets/audio/ambient/weather/gold/.gdignore` — non-shippable gold masters excluded
- `src/resources/windshield_snow_material.tres` — snow shader material
## CI Results
- `make gdscript-quality`: ✅ 146 files, no violations
- `make test`: ✅ 934/935 passing (1 pre-existing risky)
- `make headless`: ✅ clean
- `make all`: ✅ including export smoke test
## SemVer Bumps Applied
All 8 required bumps exceeded: core-autoloads→0.24.0, bus→0.33.2, weather→0.19.2, on-foot-controller→0.10.1, assets→0.22.2, terrain→2.15.1, world-generation→0.53.5, tests→0.60.3
## Post-Implementation Work (same session)
### Audio Pipeline (completed)
- 14 fake placeholder OGGs removed from submodule; 2 real blizzard wind OGGs retained
- 18 Suno provenance JSON sidecars written to `src/assets/audio/ambient/weather/gold/` (16 spec-098 + 2 pre-existing rain OGGs co-located)
- `tools/tts/ambient_process.sh` — new ffmpeg pipeline for Suno ambient audio (loop/oneshot modes)
- `tools/tts/validate_ambient_naming.sh` + `validate_ambient_provenance.sh` — CI validation for 18 assets
- `tools/tts/Makefile` extended with `ambient-weather-*` targets; `validate-names`/`validate-provenance` now cover ambient/weather
- `rain_heavy_interior.ogg` + `rain_light_interior.ogg` moved from `ambient/` to `ambient/weather/` and renamed with `weather_` prefix for consistency
- AGENTS.md + `audio-assets` SKILL.md updated with full ambient weather production workflow
- Vault discovery note written: `Discoveries/Audio Generation Pipeline Constraints & Patterns.md` (consolidated note)
### Scene Wiring via Godot MCP (completed)
All previously-deferred scene wiring completed via Godot editor MCP:
**`generated_world.tscn`:**
- `WeatherEffectsController` (Node3D + script) with `SnowParticles`, `SporeParticles`, `AshParticles` (GPUParticles3D, particle materials assigned, NodePaths wired)
- `LightningController` (Node + script)
**`samba_bus.tscn`:**
- `HailParticles` (GPUParticles3D, wired to `RainParticlesController.hail_particles_path`)
- `WindAudioController` (Node + script) with `WindInterior` (Interior bus) + `WindExterior` (Ambience bus)
- `InstrumentDisruptionHandler` (Node + script, `needle_paths=[]` — graceful no-op until spec 033)
- `DeadCalmAudioController` (Node + script) with `SubBassPlayer` (Ambience bus)
**`on_foot_player.tscn`:**
- `OnFootWeatherParticles` (Node + script) with `SnowParticlesOnFoot`, `SporeParticlesOnFoot`, `AshParticlesOnFoot`, `HailParticlesOnFoot`
**`src/resources/particles/`:** 4 new `ParticleProcessMaterial` .tres resources (snow, spore, ash, hail)
### Bug Fixes (discovered during scene wiring)
- 3 audio controllers had `const preload()` for pending Suno OGGs — fixed to `ResourceLoader.exists()` + runtime `load()` with `Debug.warn()` fallback: `dead_calm_audio_controller.gd`, `disturbance_storm_audio.gd`, `rain_audio_controller.gd`
- 11 Oracle-identified bugs fixed post-review (spore/frost delta=0, hail null guard, material ownership, wind tween split, etc.)
## Still Pending (human tasks)
- ~~14 Suno audio assets not yet produced (code gracefully silent until installed)~~ — ✅ complete: all 18 ElevenLabs SFX V2 OGGs produced and installed (pipeline switched from Suno to ElevenLabs; see [[Audio Generation Pipeline Constraints & Patterns]]); `validate-ambient-naming` and `validate-ambient-provenance` pass clean
- T085: Game version bump (human decision per Art III R15)
- spec 033 dashboard needles (for `InstrumentDisruptionHandler` to have actual needle NodePaths)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-vehicle-dashboard: type/design
Tag assignment · test- Gold
- The note has no reason to carry `type/design` (False)
- ember
- The note calls for `type/design`: Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`) (True)
- Signal
- P(true) 0.61 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Vehicle — Dashboard.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Vehicle — Dashboard
The Samba's dashboard is Old Growth's HUD. Fuel gauge, speedometer, tachometer, gear indicator, battery gauge, and the radio — all amber-glow instruments mounted on the physical dashboard geometry. There is no non-diegetic UI overlay during driving. The dashboard *is* the UI. Per Article XII-G.
## Overview
The dashboard implements the complete driving-mode information layer: vehicle state (speed, RPM, gear, fuel, battery), vehicle systems (headlights, wipers, dome light), and the radio (see [[Audio — Car Radio]]). Amber glow, analog needles, and tactile diegetic interaction — this is the entire HUD.
## Design Intent
Per Article XII-G and [[Diegesis Thread]]: **diegetic UI is the commitment**. Any non-diegetic UI element breaks the frame the Samba provides. The dashboard exists to carry every piece of information the player needs without breaking diegesis.
The amber glow is an identity mark (World Bible Section 3). It signals *inside the bus* to the player — warm, familiar, close. In contrast to the forest's colors (see [[Palettes]]), amber is the Samba's color. The Amber Bleed anomaly (see [[Anomalies]]) deliberately borrows this visual language for diegetic confusion.
## Key Decisions
### Instruments — analog, not digital
- **Fuel gauge** — needle, amber backlight
- **Speedometer** — needle, analog dial
- **Tachometer** — needle, redline marked
- **Gear indicator** — discrete indicator (forward / reverse)
- **Battery gauge** — needle; goes yellow under electrical stress
### Dashboard lights
- **Headlights indicator** — on when headlights active
- **High beam indicator** — distinct from normal headlight
- **Wiper state indicator** — off / slow / fast
- **Dome light** — interior, toggleable when focused (`E` per [[Controls]])
### Radio as part of the dashboard
The radio is physically on the dashboard. Look at it to activate tuning controls. All radio state (power, station, volume) is shown via its physical knobs and dial — no overlay, no menu.
### No overlay during driving
No healthbar overlay. No minimap. No quest marker. No disturbance meter (Article X intentionally hides it). No ammo counter. Driving is pure diegesis.
### Exception: loading and menu
The main menu and the loading screen ARE non-diegetic (Article XII-G explicitly permits this — diegesis applies to in-world gameplay, not to the frame around it). Once the run begins, diegesis takes over.
### Electrical system is fragile
The World Bible notes: "The electrical system is old and prone to failure when exposed to extreme stress." Under stress (water intrusion via destroyed panels, anomaly proximity like Hollow Trees), dashboard instruments can drift, flicker, or temporarily fail. The needles go first — they "drift to meaningless positions, not zeroing out but genuinely confused, pointing at things that aren't there."
### Dashboard-radio interaction flow
Looking at the radio (raycast from camera hits the radio mesh) activates the radio input context. Controls (`E` toggle power, `T`/`Y` tune, `=`/`-` volume) affect the radio while the look is held. Looking away deactivates the context; general driving inputs resume.
## Implementation
Primary code references (all under `src/scripts/bus/`):
- `src/scripts/bus/bus_radio.gd` — radio (see [[Audio — Car Radio]])
- `src/scripts/bus/radio_knob_power.gd`, `src/scripts/bus/radio_knob_tuning.gd` — diegetic radio controls
- `src/scripts/bus/mirror_system.gd` — rearview mirrors
- `src/scripts/bus/steering_wheel.gd` — steering wheel animation
- `src/scripts/bus/headlight_system.gd` — headlights + high beams
- `src/scripts/bus/wiper_system.gd` — wipers + state machine
- `src/scripts/bus/dome_light_control.gd` — interior dome light
- `src/scripts/bus/engine_audio.gd` — engine audio tied to RPM/gear
- `src/scripts/bus/windshield_rain.gd`, `rain_particles_controller.gd`, `rain_audio_controller.gd`, `splash_particles.gd` — windshield rain + splash
- `src/scripts/bus/interior_camera.gd`, `chase_camera.gd` — cameras
- `src/scenes/bus/samba_bus.tscn` — dashboard geometry lives in the Samba scene
- `src/scripts/ui/` — UI elements that sit on top of the dashboard (menu, loading)
Signals consumed:
- `Events.headlight_state_changed`
- `Events.wiper_state_changed`
- `Events.radio_power_changed`, `Events.radio_station_changed`
- Various vehicle-state signals from GEVP
## Test Coverage
- `src/tests/unit/test_bus_panel.gd` — panel data integrity
- `src/tests/unit/test_headlight_system.gd`, `test_wiper_system.gd`, `test_windshield_rain.gd` — peripherals
- `src/tests/unit/test_mirror_system.gd` — mirrors
- `src/tests/unit/test_steering_wheel.gd` — steering wheel
- `src/tests/unit/test_engine_audio.gd` — engine audio
## Agent Notes (backlinks)
- [[Vehicle Interaction and Physics Implementation Discovery]] — complete keybind map, 3-speaker routing (center_dash / left_a_pillar / right_a_pillar), emissive energy values, dashboard material hex codes, Deluxe clock instrument, radio focus-gate drift note, cabin plan layout
- [[Vehicle Palette Materials and Dimensions Discovery]]
- [[Interior Interaction Detection (ShapeCast3D)]] — decision to use volume-based detection
- [[Interior Interaction Spatial Constraint]] — geometry that broke the raycast for dashboard interactables — 5 interior swatches with Godot material targets, critical design rule (dashboard IS exterior lower body paint), headliner #E8DDBF, seat vinyl Como Green #4A3826, amber glow emissive roles, full left-to-right control sequence, steering wheel 2-spoke (confirmed), handbrake/gear shift/sun visor layout
## Related
- [[Vehicle — Samba]] — the vehicle the dashboard is part of
- [[Vehicle — Destruction]] — damage state that affects dashboard (e.g., electrical)
- [[Audio — Car Radio]] — the radio as part of the dashboard
- [[Controls]] — input map
- [[Palettes]] — amber as Samba identity
- [[Anomalies]] — Hollow Trees affect dashboard needles
- [[Systems]] — folder MOC
- [[Vehicle — Domain MOC]] — domain MOC
- [[Diegesis Thread]] — the dashboard is the central diegetic commitment
- [[Constitution]] — Article XII-G (Visual Design)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-vehicle-dashboard: type/system
Tag assignment · test- Gold
- The note calls for `type/system`: A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`) (True)
- ember
- The note has no reason to carry `type/system` (False)
- Signal
- P(true) 0.25 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Vehicle — Dashboard.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Vehicle — Dashboard
The Samba's dashboard is Old Growth's HUD. Fuel gauge, speedometer, tachometer, gear indicator, battery gauge, and the radio — all amber-glow instruments mounted on the physical dashboard geometry. There is no non-diegetic UI overlay during driving. The dashboard *is* the UI. Per Article XII-G.
## Overview
The dashboard implements the complete driving-mode information layer: vehicle state (speed, RPM, gear, fuel, battery), vehicle systems (headlights, wipers, dome light), and the radio (see [[Audio — Car Radio]]). Amber glow, analog needles, and tactile diegetic interaction — this is the entire HUD.
## Design Intent
Per Article XII-G and [[Diegesis Thread]]: **diegetic UI is the commitment**. Any non-diegetic UI element breaks the frame the Samba provides. The dashboard exists to carry every piece of information the player needs without breaking diegesis.
The amber glow is an identity mark (World Bible Section 3). It signals *inside the bus* to the player — warm, familiar, close. In contrast to the forest's colors (see [[Palettes]]), amber is the Samba's color. The Amber Bleed anomaly (see [[Anomalies]]) deliberately borrows this visual language for diegetic confusion.
## Key Decisions
### Instruments — analog, not digital
- **Fuel gauge** — needle, amber backlight
- **Speedometer** — needle, analog dial
- **Tachometer** — needle, redline marked
- **Gear indicator** — discrete indicator (forward / reverse)
- **Battery gauge** — needle; goes yellow under electrical stress
### Dashboard lights
- **Headlights indicator** — on when headlights active
- **High beam indicator** — distinct from normal headlight
- **Wiper state indicator** — off / slow / fast
- **Dome light** — interior, toggleable when focused (`E` per [[Controls]])
### Radio as part of the dashboard
The radio is physically on the dashboard. Look at it to activate tuning controls. All radio state (power, station, volume) is shown via its physical knobs and dial — no overlay, no menu.
### No overlay during driving
No healthbar overlay. No minimap. No quest marker. No disturbance meter (Article X intentionally hides it). No ammo counter. Driving is pure diegesis.
### Exception: loading and menu
The main menu and the loading screen ARE non-diegetic (Article XII-G explicitly permits this — diegesis applies to in-world gameplay, not to the frame around it). Once the run begins, diegesis takes over.
### Electrical system is fragile
The World Bible notes: "The electrical system is old and prone to failure when exposed to extreme stress." Under stress (water intrusion via destroyed panels, anomaly proximity like Hollow Trees), dashboard instruments can drift, flicker, or temporarily fail. The needles go first — they "drift to meaningless positions, not zeroing out but genuinely confused, pointing at things that aren't there."
### Dashboard-radio interaction flow
Looking at the radio (raycast from camera hits the radio mesh) activates the radio input context. Controls (`E` toggle power, `T`/`Y` tune, `=`/`-` volume) affect the radio while the look is held. Looking away deactivates the context; general driving inputs resume.
## Implementation
Primary code references (all under `src/scripts/bus/`):
- `src/scripts/bus/bus_radio.gd` — radio (see [[Audio — Car Radio]])
- `src/scripts/bus/radio_knob_power.gd`, `src/scripts/bus/radio_knob_tuning.gd` — diegetic radio controls
- `src/scripts/bus/mirror_system.gd` — rearview mirrors
- `src/scripts/bus/steering_wheel.gd` — steering wheel animation
- `src/scripts/bus/headlight_system.gd` — headlights + high beams
- `src/scripts/bus/wiper_system.gd` — wipers + state machine
- `src/scripts/bus/dome_light_control.gd` — interior dome light
- `src/scripts/bus/engine_audio.gd` — engine audio tied to RPM/gear
- `src/scripts/bus/windshield_rain.gd`, `rain_particles_controller.gd`, `rain_audio_controller.gd`, `splash_particles.gd` — windshield rain + splash
- `src/scripts/bus/interior_camera.gd`, `chase_camera.gd` — cameras
- `src/scenes/bus/samba_bus.tscn` — dashboard geometry lives in the Samba scene
- `src/scripts/ui/` — UI elements that sit on top of the dashboard (menu, loading)
Signals consumed:
- `Events.headlight_state_changed`
- `Events.wiper_state_changed`
- `Events.radio_power_changed`, `Events.radio_station_changed`
- Various vehicle-state signals from GEVP
## Test Coverage
- `src/tests/unit/test_bus_panel.gd` — panel data integrity
- `src/tests/unit/test_headlight_system.gd`, `test_wiper_system.gd`, `test_windshield_rain.gd` — peripherals
- `src/tests/unit/test_mirror_system.gd` — mirrors
- `src/tests/unit/test_steering_wheel.gd` — steering wheel
- `src/tests/unit/test_engine_audio.gd` — engine audio
## Agent Notes (backlinks)
- [[Vehicle Interaction and Physics Implementation Discovery]] — complete keybind map, 3-speaker routing (center_dash / left_a_pillar / right_a_pillar), emissive energy values, dashboard material hex codes, Deluxe clock instrument, radio focus-gate drift note, cabin plan layout
- [[Vehicle Palette Materials and Dimensions Discovery]]
- [[Interior Interaction Detection (ShapeCast3D)]] — decision to use volume-based detection
- [[Interior Interaction Spatial Constraint]] — geometry that broke the raycast for dashboard interactables — 5 interior swatches with Godot material targets, critical design rule (dashboard IS exterior lower body paint), headliner #E8DDBF, seat vinyl Como Green #4A3826, amber glow emissive roles, full left-to-right control sequence, steering wheel 2-spoke (confirmed), handbrake/gear shift/sun visor layout
## Related
- [[Vehicle — Samba]] — the vehicle the dashboard is part of
- [[Vehicle — Destruction]] — damage state that affects dashboard (e.g., electrical)
- [[Audio — Car Radio]] — the radio as part of the dashboard
- [[Controls]] — input map
- [[Palettes]] — amber as Samba identity
- [[Anomalies]] — Hollow Trees affect dashboard needles
- [[Systems]] — folder MOC
- [[Vehicle — Domain MOC]] — domain MOC
- [[Diegesis Thread]] — the dashboard is the central diegetic commitment
- [[Constitution]] — Article XII-G (Visual Design)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-core-mechanics: domain/world
Tag assignment · dev- Gold
- The note calls for `domain/world`: World model, biomes, terrain, procedural generation, weather (True)
- ember
- The note has no reason to carry `domain/world` (False)
- Signal
- P(true) 0.07 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Design/Core Mechanics.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Core Mechanics
The mechanical and systems spine of Old Growth: the run loop, player stats, vehicle specifications, resources, garage systems, on-foot handling, UI philosophy, save system, and progression framework. Where [[World Model]] establishes structure and [[Tone and Horror]] establishes register, this note captures the authoritative mechanical values. All values marked `[PENDING]` require playtesting to finalize.
## Overview
Every mechanic serves three design pillars. When a proposed system conflicts, the system changes, not the pillars.
- **Flesh as Horror.** Player body and bus body are both subject to irreversible change. Damage cumulates and is visible. The condition crosses run boundaries in both — the bus through contamination, the player through exposure.
- **The Unknowable Condition.** The forest is never explained. No mechanic makes it legible or masterable. Configuring the bus changes *how* the player meets the condition, not *whether* they can defeat it.
- **Sealed In With It.** Escape is always available in principle and always costly in practice. Ridges, the Storm, fuel economy, and disturbance all pressure toward the exit.
## Key Decisions
### Game Loop
```
GARAGE → SEED SELECTION → WORLD LOAD → DRIVE OUT →
EXPLORE / SCAVENGE → DISTURBANCE ACCUMULATES →
[STORM TRIGGERS if threshold reached] → EXIT ATTEMPT →
[EXTRACTION or DEATH] → GARAGE
```
**Lore framing**: The loop is not a game abstraction imposed on a world. It is the topology of the structure the construct is actively reshaping. The garage is the vertex: the one point all faces share. The exit corridors are edges that fold back to the vertex. Extraction and death both return the player to the garage because all paths inside the closed geometry return to the center. The player has never left. (Post-convergence: death still returns to the vertex, but extraction crosses the rim into the outside. See spec 127.) See [[The Crater]] and [[The Temporal Fracture]].
Engine states:
| State | Description |
|---|---|
| GARAGE | Player on foot in garage. Van accessible. No disturbance accumulation. |
| LOADING | World generation in progress. Seed locked. |
| DRIVING | Player in vehicle, world active. Disturbance accumulates. |
| ON_FOOT | Player outside vehicle. Disturbance accumulates. Maximum vulnerability. |
| STORM_ACTIVE | Storm spawned. Prior states continue. Escape window open. |
| EXTRACTED | Player reached exit gate. Run success. Return to garage. |
| DEAD | Player HP to zero or petrification completed. Run failure. Return to garage. |
Run success: vehicle crosses exit gate with player inside. Run failure: player HP zero, Storm catches player in open, petrification completes on foot, or engine seizure in Petrified Zone with no recovery window. Both outcomes return the player to the garage — the vertex. The difference is how much damage the circuit did on the way through. No campaign end state in v1.0 — the garage contamination arc and player exposure arc provide long-term narrative progression toward the convergence endpoint, which is not a conclusion but a phase transition: after convergence, death still returns to the vertex, but extraction crosses the rim into the outside. The game rules change. See The Temporal Fracture and spec 127.
### Player — Health
| Property | Value |
|---|---|
| Maximum health | 100 HP |
| Starting health (per run) | 100 HP |
| Medkit restore | 25 HP |
| Regen | None |
| Death threshold | 0 HP |
Health does not regenerate. Only restoration is medkit use. Tracked via breathing audio, screen-edge vignette (dark, not colored), and interaction speed reduction at low health. No numeric display.
### Player — Stamina
| Property | Value |
|---|---|
| Maximum stamina | 100 units |
| Sprint drain | 20 units/sec |
| Interact drain | 5 units/action |
| Contact escape drain | 30 units |
| Recharge (walking) | 10 units/sec |
| Recharge (crouching/stationary) | 15 units/sec |
| Recharge (inside van) | 20 units/sec |
| Minimum for sprint | 10 units |
No HUD. Read through movement-speed degradation and audible breathing. Sprint locks at <10 units.
### Player — Exposure (Cross-Run)
Persistent value tracking cumulative contact with the condition across all runs. Does not reset on death or extraction. Does not decrease. Accumulates in the player the same way garage contamination accumulates in the garage — one direction, no instrument, no remedy.
| Threshold | Symptom |
|---|---|
| 0–20 | No symptoms |
| 21–40 | Persistent cough audio, occasional sound delay (50ms) |
| 41–60 | Warmth sensation in hands (haptic/audio), smell sensitivity (audio mix) |
| 61–80 | Vision film at edges, 10% stamina-capacity reduction |
| 81–100 | `[PENDING]` — endpoint design required before implementation |
Exposure sources:
- Spore Cloud on foot: +5 per 10s of contact
- Mycelium Network on foot: +10 per contact event
- Extended Fungal Depths on foot: +2 per minute
- Deep Fungal runs (>50% run time in Fungal): +5 on extraction
- Extended on-foot time at Agitated+ disturbance: +1 per minute
No direct display. Symptoms are designed to feel ordinary — cough, warmth, slight unreliability — so players do not immediately understand what is accumulating.
### Player — Movement
| Mode | Noise Radius | Speed | Notes |
|---|---|---|---|
| Crouch | 0m (silent) | 2 m/s | Only fully silent movement |
| Walk | 3m | 4 m/s | Standard |
| Sprint | 20m | 7 m/s | Stamina drain; noise travels mycelium |
| Fall impact | 15m | — | One-time event on hard landing |
Noise radius is the distance at which the condition registers a signal. Signal strength decreases with distance within the radius.
### Player — Flashlight
| Property | Value |
|---|---|
| Battery life | 5 minutes continuous; 2 minutes recharge in bus |
| Batteries found per world | 0–2 `[PENDING]` |
| Verdant beam | Normal |
| Fungal Depths beam | Scattered by haze; −40% effective range |
| Petrifold beam | High reflection off calcite surfaces |
Only on-foot tool other than throwable rocks and flare gun. No combat applications.
### Vehicle — Base Specifications
| Property | Value |
|---|---|
| Model | 1963 VW Type 2 (T1) 23-Window Deluxe Samba |
| Weight | ~1,100 kg |
| Engine | 50 HP, rear-mounted |
| Drive | Rear-wheel drive |
| Handling feel | Lumbering, floaty, momentum-heavy |
| Max fuel | 100 units |
| Starting fuel (per run) | 80 units |
| Max vehicle health | 100 HP |
| Destructible panels | 56 |
### Vehicle — Fuel Consumption
| Biome | Rate | Notes |
|---|---|---|
| Verdant Old Growth | 1 unit/km | Baseline |
| Fungal Depths | 1.6 units/km | Spore haze affects combustion |
| Petrifold | 0.7 units/km | Engine runs lean and cold |
| Idling | 0.2 units/min | All biomes |
Petrifold's improved fuel economy is not explained by any in-world mechanic the player is told. It is noticed, not announced.
### Vehicle — Health and Damage
| Damage Source | HP Loss |
|---|---|
| Low-speed collision (<20 km/h) | 2–5 HP |
| Medium collision (20–50 km/h) | 8–15 HP |
| High-speed collision (>50 km/h) | 20–40 HP |
| Deadfall impact | 25 HP |
| Mycelium Network spore burst | 10 HP (exterior panels) |
| Storm exposure | 15 HP/sec |
| Presence contact (on exterior) | 0 HP — condition presence does not damage bus mechanically |
Panel damage distributes across the 60-panel system (56 body panels + 4 tires). Each panel tracks its own damage state. Panels at 0 HP do not fall off — they deform and remain, accumulating visual damage. Aggregate panel damage contributes to overall vehicle health. Repair kits restore 20 HP per kit, targetable at specific panel groups or the drivetrain.
### Vehicle — Electrical System
The electrical system is old and unstable. Stress events cause transient failures.
| Stress Source | Effect | Recovery |
|---|---|---|
| Hollow Tree (20m) | Instrument drift, radio static | Auto on exit from radius |
| Hollow Tree (10m) | Engine power −40%, lights flicker | Auto on exit |
| Hollow Tree (5m) | Engine near-failure, instruments dead | Auto on exit |
| Storm proximity (<50m) | Full electrical failure | Auto if Storm not caught |
| High disturbance (Agitated+) | Intermittent instrument failure | Auto; frequency increases |
Electrical failure does not strand the bus unless combined with Petrified Zone seizure. Engine will restart after exit from stress radius.
### Vehicle — Dashboard Instruments
All vehicle-state feedback is diegetic. No HUD overlays.
| Instrument | Normal | Degraded (Agitated) | Failed |
|---|---|---|---|
| Fuel gauge | Accurate | ±10% error | Stuck at last reading |
| Speedometer | Accurate | Minor drift | Inaccurate |
| Tachometer | Accurate | Jittery | Inaccurate |
| Gear indicator | Accurate | Delayed | Dark |
| Battery gauge | Stable | Flickering | Dark |
At Agitated, instruments become unreliable enough that the player must estimate fuel and speed from engine audio and road feel.
### Vehicle — The Bus as Force
At speed, the bus makes contact with whatever is standing in the road. What is in the road is not an entity with a health pool — it is the condition at a local concentration, present and not moving. Contact at speed disperses that concentration. It does not kill anything.
| Speed at Contact | Bus Damage | Notes |
|---|---|---|
| <10 km/h | 0 | Contact registered, no panel damage |
| 10–30 km/h | 2–8 HP (exterior panels) | Low-speed brush |
| 30–60 km/h | 10–20 HP (exterior panels) | Standard impact |
| >60 km/h | 25–40 HP (exterior panels) | High-speed, significant panel damage |
Contact at higher disturbance produces denser concentrations and proportionally greater panel damage.
### Resources
| Item | Effect | Collection | Disturbance Cost |
|---|---|---|---|
| Fuel canister | +25 fuel | Drive-by or on foot | +3 |
| Repair kit | +20 vehicle HP (targeted) | Drive-by or on foot | +4 |
| Medkit | +25 player HP | On foot only (stop required) | +5 |
| Flare gun | Distraction tool, single use | On foot only (stop required) | +3 to find |
| Flashlight battery | +5 min flashlight | On foot only | +3 |
| Rock | Throwable distraction | Passive (always available) | +0.5/throw |
Spawn rates: fuel canister common (gas stations, ranger stations, roadsides); repair kit common (ranger stations, trailers, cargo containers); medkit uncommon (ranger stations, first aid cabinets); flare gun rare (research trailers, one per world max); flashlight battery uncommon (structures, vehicle gloveboxes).
Carry limits (field): fuel canister ×2, repair kit ×2, medkit ×2, flare gun ×1, flashlight battery ×3, rock unlimited. Garage storage: unlimited for fuel/repair/battery; medkit cabinet holds 3; flare gun not storable (found in field only); rocks N/A.
Fuel canisters and repair kits can be collected from inside a moving vehicle at speeds up to 20 km/h. Player must pass within 2m. Collection automatic on proximity — primary method for minimizing on-foot time. On-foot items require a 3-second interact animation; player is stationary and vulnerable during this time.
### Flare Gun Mechanics
| Property | Value |
|---|---|
| Uses | 1 (single shot, found item) |
| Carry limit | 1 at a time |
| Draw radius | 80m |
| Burn duration | 45 seconds |
| Effect | Condition attention redirects toward flare location for burn duration |
| Post-burn | Attention dissipates from flare location over ~10 seconds |
| Disturbance cost (fire) | +6 |
Not a weapon. Creates a temporary attention sink — redirecting the condition's local concentration toward heat and light, useful for clearing a path to the bus or to an exit, never for confrontation.
### Garage Systems
Single-room structure. Player explores on foot. Bus occupies center bay. All interaction points peripheral.
| Element | Function |
|---|---|
| Repair workbench | Spend repair kits on panel groups, drivetrain, or installed components |
| Fitting station | Install, remove, inspect vehicle components `[PENDING spec 027]` |
| First-aid cabinet | Use or store medkits (max 3) |
| Wall map | Seed selection. Displays biome zones for selected seeds |
| Inventory shelf | Stored items: batteries, spare repair kits, uninstalled components |
| Run notebook | Passive display near workbench. Per-run statistics, death/extraction counts, per-biome time. Read-only |
| Van (exterior) | Inspect panel damage and installed component condition visually |
| Van (interior) | Glovebox (battery storage), radio controls, footwell loading. Enter to begin run |
Repair workbench allocates repair kits to: exterior panels (front/rear/left/right/roof), mechanical systems (engine, drivetrain), electrical systems (instruments, headlights, radio). Each kit restores 20 HP, distributed across targeted system. Multiple kits can be applied to same system.
### Garage Contamination Progression
Persistent, cross-run. Does not decrease.
| Threshold | Visual Change | Notes |
|---|---|---|
| 0 | Clean | Starting state |
| 10 | Moss in northwest corner | First visible sign |
| 25 | Fungal bloom on floor near vehicle | Matches Fungal Depths wheel well bloom |
| 40 | Mineral deposit at base of treeline wall | Matches Petrifold calcite |
| 60 | Mycelium thread on first-aid cabinet hinge | Recognizable to attentive players |
| 80 | Amber light visible through window at night | The forest is producing its signal inside the safe space |
| 100 | `[PENDING]` — epic event | |
Accumulates by: run completed (extraction) +3; deep Fungal run +5; Petrifold entry +3; death +2; Mycelium contact +1 per contact (cumulative).
### The Window
The single garage window faces the treeline. Always present. Cannot be boarded or obscured. Provides ambient exterior audio (forest at reduced volume) and, at high contamination, Amber Bleed is visible through it from inside the garage. First indication that the process has entered the safe space in a meaningful way.
### On-Foot Systems
All on-foot interactions have a 3-second animation with no skip. Player is stationary and vulnerable. Cancellable-by-movement: resource collection, structure entry.
**Vehicle entry/exit**: ~1.5-second camera interpolation. Player is vulnerable throughout — neither fully inside nor fully outside; the condition does not distinguish this transition from full on-foot exposure. Intentional.
**On-foot noise** (additive with bus noise when player is outside and bus is idling nearby):
| Action | Radius | Duration |
|---|---|---|
| Crouch | 0m | — |
| Walking | 3m | Continuous |
| Sprinting | 20m | Continuous |
| Fall impact | 15m | 3s |
| Interaction (pickup) | 5m | 1s |
| Grab escape | 20m | 2s |
### UI Philosophy — No HUD
No health bar. No fuel %. No disturbance meter. No minimap. No objective markers.
Information available through:
- Dashboard instruments (vehicle state)
- Van audio (engine, damage, electrical)
- Environmental audio (disturbance state, condition presence)
- Visual environment (biome, weather, anomaly signatures)
- The player's body (breathing, movement capability)
**Permitted UI surfaces**:
| Surface | Context | Content |
|---|---|---|
| Garage wall map | Garage only, on foot | Seed selection, biome zones |
| Inventory screen | Garage or stopped bus | Current inventory, management |
| Dashboard instruments | Always (in van) | Fuel, speed, RPM, gear, battery |
| Interaction prompts | Proximity-triggered | Minimal single verb (TAKE/OPEN/ENTER) |
Prompts appear only within interaction range. No waypoints, quest markers, objective text.
**Death screen**: hard cut to black; single audio event (silence, or forest transmission). No in-moment stats. Stats available next garage return via run notebook.
**Extraction screen**: exit gate closes behind bus; wind-chime resonance fading. Cut to garage with bus in bay. No score screen.
### Save & Persistence
**Persistent**: vehicle HP, per-panel damage states, vehicle configuration (installed components + damage), fuel level, inventory contents, player gear (once equipped, permanent), total runs, total extractions, total deaths, biome preference (last selected), per-biome statistics (time, runs entered, extractions from), garage contamination level (0–100, never decreases), player exposure level (0–100, never decreases).
**Does not persist**: world layout (regenerated per seed per run), condition concentration zones (influenced by seed but not fixed), resource positions (randomized within structure/road rules each run), anomaly activation states (reset each run), disturbance meter (resets to 0 each run), player health mid-run (resets to persistent value on garage return).
**Save trigger**: auto-save fires exactly once per run cycle on garage return after extraction or death. No manual save. No mid-run save. Application closed mid-run = treated as death on next launch.
### Progression — Tradeoff-First Framework
The original prohibition on numerical upgrades (pre-2.19.0 constitution) is replaced by a tradeoff-first framework. Numerical progression is permitted iff all three:
1. **Diegetically grounded.** Change is a physical thing on the bus or worn by the player. No abstract stat increases.
2. **Genuine tradeoff.** No component is a pure upgrade. Every gain carries a corresponding cost.
3. **Bounded.** Components have fixed stats. No escalating tiers, upgrade trees, or compounding bonuses.
The condition cannot be overcome. A fully-configured bus with optimal tradeoffs is still subject to the same disturbance rules, Storm, and condition. Configuration is playstyle expression, not power escalation.
**Valid progression types**:
| Type | Description | Examples |
|---|---|---|
| Informational | Player gains knowledge, not statistics | Wall map annotations, notebook records |
| Narrative | Progression against the player | Garage contamination, player exposure |
| Physical (tradeoff) | Diegetic, bounded, with genuine cost | Vehicle components, worn gear |
**Prohibited**: abstract stat upgrades (not diegetically grounded); pure upgrades (gain without cost); escalating tiers (not bounded); fuel capacity upgrades (no physical basis in van configuration); HP upgrades without physical component.
### Vehicle Configuration System (§16a)
The bus can be fitted with physical components altering behavior. All components durable, degrade with use, require workbench repair. Installed components persist across runs. Destroyed beyond repair = gone permanently. Full specification (stat tables, crafting rules, workbench UI, epic unlocks) deferred to spec 027.
**Categories**: body panels, bumpers, lights, accessories, spoilers, roll cages, other fittings.
**Tradeoff examples** (illustrative, not final):
| Component | Benefit | Cost |
|---|---|---|
| Heavy steel bumper | +impact force, −bus damage on road contact | +noise on impact, −top speed |
| Rear stabilizer wing | +stability at speed | +wind noise (disturbance) |
| Full roll cage | +van HP pool | +rattle audio signature (disturbance) |
| Roof-mounted aux lights | +forward visibility range | +electrical draw, +disturbance when active |
| Reinforced door skins | +panel HP (doors) | +weight, −acceleration |
| External fuel mount | +fuel carry capacity | +fire risk on collision, +noise on rough terrain |
No component combination produces a net advantage. Tradeoffs cumulative.
**Degradation**: components degrade independently from 60-panel body. Degraded components retain tradeoff *costs* while losing *benefits*. Damaged roll cage still rattles but no longer contributes full HP. Repair at workbench targeting the component directly.
**Persistence**:
| State | Behavior |
|---|---|
| Installed, undamaged | Persists across runs, full effect active |
| Installed, degraded | Persists, reduced benefit, full cost |
| Installed, destroyed | Removed permanently. Cannot be recovered |
| Uninstalled (storage) | Persists in garage inventory |
**Acquisition**: `[PENDING]` — three channels proposed: scavenged (found in structures, road debris, wrecked vehicles), crafted (crafting TBD), unlocked via epics (epic system TBD). Dedicated spec required.
### Player Gear (§16b)
Player can find and equip gear (clothing, protective layers). Once equipped, permanent. Player does not manage between runs.
| Property | Value |
|---|---|
| Acquisition | `[PENDING]` |
| Equip behavior | Permanent on first equip. Cannot be removed |
| Mechanical effects | `[PENDING]` — exposure resistance, stamina modifiers |
| Current scope | Cosmetic only |
Intent: keep player's attention on the van, not on their character. Gear is a visible record of what the player has found, not a system to manage.
### Camera & Controls
**In-vehicle**: first-person, locked to driver seat. FOV 75° default. Player can look left/right/mirrors. Full 360° head turn at low speed. Dashboard occupies lower quarter, always visible, readable at a glance. No UI overlay on dashboard.
**On-foot**: standard first-person. FOV 80° default. No weapon viewmodel (flashlight only when active).
**Vehicle entry/exit**: 1.5-second interpolation, not skippable. Physical animation, not cut.
### Out of Scope for v1.0
- Additional biomes (architecture supports expansion)
- Multiplayer (not in scope or architecture)
- New vehicle types (Samba is the only vehicle)
- Voiced narrative (Gary TTS system is audio spec only)
- Garage narrative events (contamination epic events at 80 and 100 `[PENDING]`)
- Environmental presence system (replaces Hollow AI — spec required)
- Player/bus convergence endpoint (Exposure 81–100 and garage endpoint `[PENDING]`)
- Endpoint visual and mechanical design (World Bible direction set; implementation open)
### Accessibility Considerations (to resolve pre-ship)
- **No HUD**: players relying on visual clarity for HP/resource state have no fallback. Consider optional accessibility mode with minimal diegetically-consistent overlays (e.g. faint gauge texture rendered as part of the dashboard, not a screen overlay).
- **Audio-dependent systems** (The Silence, presence audio, disturbance shifts, radio degradation): deaf/HoH players need visual equivalents. Suggestions: subtle screen-edge coloration for disturbance state; visual indicator for Silence zone entry; captioning for radio.
- **Low-visibility environments** (night Petrifold + The Silence): players with visual impairments face compounded difficulty. Consider optional navigation audio (subtle spatial tone for exit gate direction).
Solutions must maintain the horror register.
## How It Appears In-Game
The player learns the loop empirically. The first run is likely lost — not punitively, but because the mechanics take time to internalize. A player who survives long enough learns that the loop is not theirs. The forest was running this cycle before the first road was cut.
## Related
- [[The Crater]] — the origin event; the higher-dimensional geometry that makes the loop topological
- [[The Temporal Fracture]] — extraction as circuit completion; the loop is the shape of the space
- [[World Model]] — the world the mechanics operate within
- [[Biomes]] — fuel, weather, and audio regimes per biome
- [[Disturbance System]] — the tension mechanic these costs feed
- [[Anomalies]] — the hazards in the world
- [[Controls]] — the input layer
- [[Tone and Horror]] — the tonal register mechanics serve
- [[Vehicle — Samba]] — implementation of the vehicle specs
- [[Vehicle — Destruction]] — implementation of panel damage
- [[Vehicle — Dashboard]] — the diegetic instrument implementation
- [[Ephemerality Thread]] — the run-reset rule
- [[The Player Causes the Storm]] — endgame consequence
- [[Constitution]] — Article II (Car Is Core), Article IV Rules 6–8 (ephemeral worlds + tradeoff progression)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-core-mechanics: type/design
Tag assignment · dev- Gold
- The note calls for `type/design`: Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`) (True)
- ember
- The note has no reason to carry `type/design` (False)
- Signal
- P(true) 0.37 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Design/Core Mechanics.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Core Mechanics
The mechanical and systems spine of Old Growth: the run loop, player stats, vehicle specifications, resources, garage systems, on-foot handling, UI philosophy, save system, and progression framework. Where [[World Model]] establishes structure and [[Tone and Horror]] establishes register, this note captures the authoritative mechanical values. All values marked `[PENDING]` require playtesting to finalize.
## Overview
Every mechanic serves three design pillars. When a proposed system conflicts, the system changes, not the pillars.
- **Flesh as Horror.** Player body and bus body are both subject to irreversible change. Damage cumulates and is visible. The condition crosses run boundaries in both — the bus through contamination, the player through exposure.
- **The Unknowable Condition.** The forest is never explained. No mechanic makes it legible or masterable. Configuring the bus changes *how* the player meets the condition, not *whether* they can defeat it.
- **Sealed In With It.** Escape is always available in principle and always costly in practice. Ridges, the Storm, fuel economy, and disturbance all pressure toward the exit.
## Key Decisions
### Game Loop
```
GARAGE → SEED SELECTION → WORLD LOAD → DRIVE OUT →
EXPLORE / SCAVENGE → DISTURBANCE ACCUMULATES →
[STORM TRIGGERS if threshold reached] → EXIT ATTEMPT →
[EXTRACTION or DEATH] → GARAGE
```
**Lore framing**: The loop is not a game abstraction imposed on a world. It is the topology of the structure the construct is actively reshaping. The garage is the vertex: the one point all faces share. The exit corridors are edges that fold back to the vertex. Extraction and death both return the player to the garage because all paths inside the closed geometry return to the center. The player has never left. (Post-convergence: death still returns to the vertex, but extraction crosses the rim into the outside. See spec 127.) See [[The Crater]] and [[The Temporal Fracture]].
Engine states:
| State | Description |
|---|---|
| GARAGE | Player on foot in garage. Van accessible. No disturbance accumulation. |
| LOADING | World generation in progress. Seed locked. |
| DRIVING | Player in vehicle, world active. Disturbance accumulates. |
| ON_FOOT | Player outside vehicle. Disturbance accumulates. Maximum vulnerability. |
| STORM_ACTIVE | Storm spawned. Prior states continue. Escape window open. |
| EXTRACTED | Player reached exit gate. Run success. Return to garage. |
| DEAD | Player HP to zero or petrification completed. Run failure. Return to garage. |
Run success: vehicle crosses exit gate with player inside. Run failure: player HP zero, Storm catches player in open, petrification completes on foot, or engine seizure in Petrified Zone with no recovery window. Both outcomes return the player to the garage — the vertex. The difference is how much damage the circuit did on the way through. No campaign end state in v1.0 — the garage contamination arc and player exposure arc provide long-term narrative progression toward the convergence endpoint, which is not a conclusion but a phase transition: after convergence, death still returns to the vertex, but extraction crosses the rim into the outside. The game rules change. See The Temporal Fracture and spec 127.
### Player — Health
| Property | Value |
|---|---|
| Maximum health | 100 HP |
| Starting health (per run) | 100 HP |
| Medkit restore | 25 HP |
| Regen | None |
| Death threshold | 0 HP |
Health does not regenerate. Only restoration is medkit use. Tracked via breathing audio, screen-edge vignette (dark, not colored), and interaction speed reduction at low health. No numeric display.
### Player — Stamina
| Property | Value |
|---|---|
| Maximum stamina | 100 units |
| Sprint drain | 20 units/sec |
| Interact drain | 5 units/action |
| Contact escape drain | 30 units |
| Recharge (walking) | 10 units/sec |
| Recharge (crouching/stationary) | 15 units/sec |
| Recharge (inside van) | 20 units/sec |
| Minimum for sprint | 10 units |
No HUD. Read through movement-speed degradation and audible breathing. Sprint locks at <10 units.
### Player — Exposure (Cross-Run)
Persistent value tracking cumulative contact with the condition across all runs. Does not reset on death or extraction. Does not decrease. Accumulates in the player the same way garage contamination accumulates in the garage — one direction, no instrument, no remedy.
| Threshold | Symptom |
|---|---|
| 0–20 | No symptoms |
| 21–40 | Persistent cough audio, occasional sound delay (50ms) |
| 41–60 | Warmth sensation in hands (haptic/audio), smell sensitivity (audio mix) |
| 61–80 | Vision film at edges, 10% stamina-capacity reduction |
| 81–100 | `[PENDING]` — endpoint design required before implementation |
Exposure sources:
- Spore Cloud on foot: +5 per 10s of contact
- Mycelium Network on foot: +10 per contact event
- Extended Fungal Depths on foot: +2 per minute
- Deep Fungal runs (>50% run time in Fungal): +5 on extraction
- Extended on-foot time at Agitated+ disturbance: +1 per minute
No direct display. Symptoms are designed to feel ordinary — cough, warmth, slight unreliability — so players do not immediately understand what is accumulating.
### Player — Movement
| Mode | Noise Radius | Speed | Notes |
|---|---|---|---|
| Crouch | 0m (silent) | 2 m/s | Only fully silent movement |
| Walk | 3m | 4 m/s | Standard |
| Sprint | 20m | 7 m/s | Stamina drain; noise travels mycelium |
| Fall impact | 15m | — | One-time event on hard landing |
Noise radius is the distance at which the condition registers a signal. Signal strength decreases with distance within the radius.
### Player — Flashlight
| Property | Value |
|---|---|
| Battery life | 5 minutes continuous; 2 minutes recharge in bus |
| Batteries found per world | 0–2 `[PENDING]` |
| Verdant beam | Normal |
| Fungal Depths beam | Scattered by haze; −40% effective range |
| Petrifold beam | High reflection off calcite surfaces |
Only on-foot tool other than throwable rocks and flare gun. No combat applications.
### Vehicle — Base Specifications
| Property | Value |
|---|---|
| Model | 1963 VW Type 2 (T1) 23-Window Deluxe Samba |
| Weight | ~1,100 kg |
| Engine | 50 HP, rear-mounted |
| Drive | Rear-wheel drive |
| Handling feel | Lumbering, floaty, momentum-heavy |
| Max fuel | 100 units |
| Starting fuel (per run) | 80 units |
| Max vehicle health | 100 HP |
| Destructible panels | 56 |
### Vehicle — Fuel Consumption
| Biome | Rate | Notes |
|---|---|---|
| Verdant Old Growth | 1 unit/km | Baseline |
| Fungal Depths | 1.6 units/km | Spore haze affects combustion |
| Petrifold | 0.7 units/km | Engine runs lean and cold |
| Idling | 0.2 units/min | All biomes |
Petrifold's improved fuel economy is not explained by any in-world mechanic the player is told. It is noticed, not announced.
### Vehicle — Health and Damage
| Damage Source | HP Loss |
|---|---|
| Low-speed collision (<20 km/h) | 2–5 HP |
| Medium collision (20–50 km/h) | 8–15 HP |
| High-speed collision (>50 km/h) | 20–40 HP |
| Deadfall impact | 25 HP |
| Mycelium Network spore burst | 10 HP (exterior panels) |
| Storm exposure | 15 HP/sec |
| Presence contact (on exterior) | 0 HP — condition presence does not damage bus mechanically |
Panel damage distributes across the 60-panel system (56 body panels + 4 tires). Each panel tracks its own damage state. Panels at 0 HP do not fall off — they deform and remain, accumulating visual damage. Aggregate panel damage contributes to overall vehicle health. Repair kits restore 20 HP per kit, targetable at specific panel groups or the drivetrain.
### Vehicle — Electrical System
The electrical system is old and unstable. Stress events cause transient failures.
| Stress Source | Effect | Recovery |
|---|---|---|
| Hollow Tree (20m) | Instrument drift, radio static | Auto on exit from radius |
| Hollow Tree (10m) | Engine power −40%, lights flicker | Auto on exit |
| Hollow Tree (5m) | Engine near-failure, instruments dead | Auto on exit |
| Storm proximity (<50m) | Full electrical failure | Auto if Storm not caught |
| High disturbance (Agitated+) | Intermittent instrument failure | Auto; frequency increases |
Electrical failure does not strand the bus unless combined with Petrified Zone seizure. Engine will restart after exit from stress radius.
### Vehicle — Dashboard Instruments
All vehicle-state feedback is diegetic. No HUD overlays.
| Instrument | Normal | Degraded (Agitated) | Failed |
|---|---|---|---|
| Fuel gauge | Accurate | ±10% error | Stuck at last reading |
| Speedometer | Accurate | Minor drift | Inaccurate |
| Tachometer | Accurate | Jittery | Inaccurate |
| Gear indicator | Accurate | Delayed | Dark |
| Battery gauge | Stable | Flickering | Dark |
At Agitated, instruments become unreliable enough that the player must estimate fuel and speed from engine audio and road feel.
### Vehicle — The Bus as Force
At speed, the bus makes contact with whatever is standing in the road. What is in the road is not an entity with a health pool — it is the condition at a local concentration, present and not moving. Contact at speed disperses that concentration. It does not kill anything.
| Speed at Contact | Bus Damage | Notes |
|---|---|---|
| <10 km/h | 0 | Contact registered, no panel damage |
| 10–30 km/h | 2–8 HP (exterior panels) | Low-speed brush |
| 30–60 km/h | 10–20 HP (exterior panels) | Standard impact |
| >60 km/h | 25–40 HP (exterior panels) | High-speed, significant panel damage |
Contact at higher disturbance produces denser concentrations and proportionally greater panel damage.
### Resources
| Item | Effect | Collection | Disturbance Cost |
|---|---|---|---|
| Fuel canister | +25 fuel | Drive-by or on foot | +3 |
| Repair kit | +20 vehicle HP (targeted) | Drive-by or on foot | +4 |
| Medkit | +25 player HP | On foot only (stop required) | +5 |
| Flare gun | Distraction tool, single use | On foot only (stop required) | +3 to find |
| Flashlight battery | +5 min flashlight | On foot only | +3 |
| Rock | Throwable distraction | Passive (always available) | +0.5/throw |
Spawn rates: fuel canister common (gas stations, ranger stations, roadsides); repair kit common (ranger stations, trailers, cargo containers); medkit uncommon (ranger stations, first aid cabinets); flare gun rare (research trailers, one per world max); flashlight battery uncommon (structures, vehicle gloveboxes).
Carry limits (field): fuel canister ×2, repair kit ×2, medkit ×2, flare gun ×1, flashlight battery ×3, rock unlimited. Garage storage: unlimited for fuel/repair/battery; medkit cabinet holds 3; flare gun not storable (found in field only); rocks N/A.
Fuel canisters and repair kits can be collected from inside a moving vehicle at speeds up to 20 km/h. Player must pass within 2m. Collection automatic on proximity — primary method for minimizing on-foot time. On-foot items require a 3-second interact animation; player is stationary and vulnerable during this time.
### Flare Gun Mechanics
| Property | Value |
|---|---|
| Uses | 1 (single shot, found item) |
| Carry limit | 1 at a time |
| Draw radius | 80m |
| Burn duration | 45 seconds |
| Effect | Condition attention redirects toward flare location for burn duration |
| Post-burn | Attention dissipates from flare location over ~10 seconds |
| Disturbance cost (fire) | +6 |
Not a weapon. Creates a temporary attention sink — redirecting the condition's local concentration toward heat and light, useful for clearing a path to the bus or to an exit, never for confrontation.
### Garage Systems
Single-room structure. Player explores on foot. Bus occupies center bay. All interaction points peripheral.
| Element | Function |
|---|---|
| Repair workbench | Spend repair kits on panel groups, drivetrain, or installed components |
| Fitting station | Install, remove, inspect vehicle components `[PENDING spec 027]` |
| First-aid cabinet | Use or store medkits (max 3) |
| Wall map | Seed selection. Displays biome zones for selected seeds |
| Inventory shelf | Stored items: batteries, spare repair kits, uninstalled components |
| Run notebook | Passive display near workbench. Per-run statistics, death/extraction counts, per-biome time. Read-only |
| Van (exterior) | Inspect panel damage and installed component condition visually |
| Van (interior) | Glovebox (battery storage), radio controls, footwell loading. Enter to begin run |
Repair workbench allocates repair kits to: exterior panels (front/rear/left/right/roof), mechanical systems (engine, drivetrain), electrical systems (instruments, headlights, radio). Each kit restores 20 HP, distributed across targeted system. Multiple kits can be applied to same system.
### Garage Contamination Progression
Persistent, cross-run. Does not decrease.
| Threshold | Visual Change | Notes |
|---|---|---|
| 0 | Clean | Starting state |
| 10 | Moss in northwest corner | First visible sign |
| 25 | Fungal bloom on floor near vehicle | Matches Fungal Depths wheel well bloom |
| 40 | Mineral deposit at base of treeline wall | Matches Petrifold calcite |
| 60 | Mycelium thread on first-aid cabinet hinge | Recognizable to attentive players |
| 80 | Amber light visible through window at night | The forest is producing its signal inside the safe space |
| 100 | `[PENDING]` — epic event | |
Accumulates by: run completed (extraction) +3; deep Fungal run +5; Petrifold entry +3; death +2; Mycelium contact +1 per contact (cumulative).
### The Window
The single garage window faces the treeline. Always present. Cannot be boarded or obscured. Provides ambient exterior audio (forest at reduced volume) and, at high contamination, Amber Bleed is visible through it from inside the garage. First indication that the process has entered the safe space in a meaningful way.
### On-Foot Systems
All on-foot interactions have a 3-second animation with no skip. Player is stationary and vulnerable. Cancellable-by-movement: resource collection, structure entry.
**Vehicle entry/exit**: ~1.5-second camera interpolation. Player is vulnerable throughout — neither fully inside nor fully outside; the condition does not distinguish this transition from full on-foot exposure. Intentional.
**On-foot noise** (additive with bus noise when player is outside and bus is idling nearby):
| Action | Radius | Duration |
|---|---|---|
| Crouch | 0m | — |
| Walking | 3m | Continuous |
| Sprinting | 20m | Continuous |
| Fall impact | 15m | 3s |
| Interaction (pickup) | 5m | 1s |
| Grab escape | 20m | 2s |
### UI Philosophy — No HUD
No health bar. No fuel %. No disturbance meter. No minimap. No objective markers.
Information available through:
- Dashboard instruments (vehicle state)
- Van audio (engine, damage, electrical)
- Environmental audio (disturbance state, condition presence)
- Visual environment (biome, weather, anomaly signatures)
- The player's body (breathing, movement capability)
**Permitted UI surfaces**:
| Surface | Context | Content |
|---|---|---|
| Garage wall map | Garage only, on foot | Seed selection, biome zones |
| Inventory screen | Garage or stopped bus | Current inventory, management |
| Dashboard instruments | Always (in van) | Fuel, speed, RPM, gear, battery |
| Interaction prompts | Proximity-triggered | Minimal single verb (TAKE/OPEN/ENTER) |
Prompts appear only within interaction range. No waypoints, quest markers, objective text.
**Death screen**: hard cut to black; single audio event (silence, or forest transmission). No in-moment stats. Stats available next garage return via run notebook.
**Extraction screen**: exit gate closes behind bus; wind-chime resonance fading. Cut to garage with bus in bay. No score screen.
### Save & Persistence
**Persistent**: vehicle HP, per-panel damage states, vehicle configuration (installed components + damage), fuel level, inventory contents, player gear (once equipped, permanent), total runs, total extractions, total deaths, biome preference (last selected), per-biome statistics (time, runs entered, extractions from), garage contamination level (0–100, never decreases), player exposure level (0–100, never decreases).
**Does not persist**: world layout (regenerated per seed per run), condition concentration zones (influenced by seed but not fixed), resource positions (randomized within structure/road rules each run), anomaly activation states (reset each run), disturbance meter (resets to 0 each run), player health mid-run (resets to persistent value on garage return).
**Save trigger**: auto-save fires exactly once per run cycle on garage return after extraction or death. No manual save. No mid-run save. Application closed mid-run = treated as death on next launch.
### Progression — Tradeoff-First Framework
The original prohibition on numerical upgrades (pre-2.19.0 constitution) is replaced by a tradeoff-first framework. Numerical progression is permitted iff all three:
1. **Diegetically grounded.** Change is a physical thing on the bus or worn by the player. No abstract stat increases.
2. **Genuine tradeoff.** No component is a pure upgrade. Every gain carries a corresponding cost.
3. **Bounded.** Components have fixed stats. No escalating tiers, upgrade trees, or compounding bonuses.
The condition cannot be overcome. A fully-configured bus with optimal tradeoffs is still subject to the same disturbance rules, Storm, and condition. Configuration is playstyle expression, not power escalation.
**Valid progression types**:
| Type | Description | Examples |
|---|---|---|
| Informational | Player gains knowledge, not statistics | Wall map annotations, notebook records |
| Narrative | Progression against the player | Garage contamination, player exposure |
| Physical (tradeoff) | Diegetic, bounded, with genuine cost | Vehicle components, worn gear |
**Prohibited**: abstract stat upgrades (not diegetically grounded); pure upgrades (gain without cost); escalating tiers (not bounded); fuel capacity upgrades (no physical basis in van configuration); HP upgrades without physical component.
### Vehicle Configuration System (§16a)
The bus can be fitted with physical components altering behavior. All components durable, degrade with use, require workbench repair. Installed components persist across runs. Destroyed beyond repair = gone permanently. Full specification (stat tables, crafting rules, workbench UI, epic unlocks) deferred to spec 027.
**Categories**: body panels, bumpers, lights, accessories, spoilers, roll cages, other fittings.
**Tradeoff examples** (illustrative, not final):
| Component | Benefit | Cost |
|---|---|---|
| Heavy steel bumper | +impact force, −bus damage on road contact | +noise on impact, −top speed |
| Rear stabilizer wing | +stability at speed | +wind noise (disturbance) |
| Full roll cage | +van HP pool | +rattle audio signature (disturbance) |
| Roof-mounted aux lights | +forward visibility range | +electrical draw, +disturbance when active |
| Reinforced door skins | +panel HP (doors) | +weight, −acceleration |
| External fuel mount | +fuel carry capacity | +fire risk on collision, +noise on rough terrain |
No component combination produces a net advantage. Tradeoffs cumulative.
**Degradation**: components degrade independently from 60-panel body. Degraded components retain tradeoff *costs* while losing *benefits*. Damaged roll cage still rattles but no longer contributes full HP. Repair at workbench targeting the component directly.
**Persistence**:
| State | Behavior |
|---|---|
| Installed, undamaged | Persists across runs, full effect active |
| Installed, degraded | Persists, reduced benefit, full cost |
| Installed, destroyed | Removed permanently. Cannot be recovered |
| Uninstalled (storage) | Persists in garage inventory |
**Acquisition**: `[PENDING]` — three channels proposed: scavenged (found in structures, road debris, wrecked vehicles), crafted (crafting TBD), unlocked via epics (epic system TBD). Dedicated spec required.
### Player Gear (§16b)
Player can find and equip gear (clothing, protective layers). Once equipped, permanent. Player does not manage between runs.
| Property | Value |
|---|---|
| Acquisition | `[PENDING]` |
| Equip behavior | Permanent on first equip. Cannot be removed |
| Mechanical effects | `[PENDING]` — exposure resistance, stamina modifiers |
| Current scope | Cosmetic only |
Intent: keep player's attention on the van, not on their character. Gear is a visible record of what the player has found, not a system to manage.
### Camera & Controls
**In-vehicle**: first-person, locked to driver seat. FOV 75° default. Player can look left/right/mirrors. Full 360° head turn at low speed. Dashboard occupies lower quarter, always visible, readable at a glance. No UI overlay on dashboard.
**On-foot**: standard first-person. FOV 80° default. No weapon viewmodel (flashlight only when active).
**Vehicle entry/exit**: 1.5-second interpolation, not skippable. Physical animation, not cut.
### Out of Scope for v1.0
- Additional biomes (architecture supports expansion)
- Multiplayer (not in scope or architecture)
- New vehicle types (Samba is the only vehicle)
- Voiced narrative (Gary TTS system is audio spec only)
- Garage narrative events (contamination epic events at 80 and 100 `[PENDING]`)
- Environmental presence system (replaces Hollow AI — spec required)
- Player/bus convergence endpoint (Exposure 81–100 and garage endpoint `[PENDING]`)
- Endpoint visual and mechanical design (World Bible direction set; implementation open)
### Accessibility Considerations (to resolve pre-ship)
- **No HUD**: players relying on visual clarity for HP/resource state have no fallback. Consider optional accessibility mode with minimal diegetically-consistent overlays (e.g. faint gauge texture rendered as part of the dashboard, not a screen overlay).
- **Audio-dependent systems** (The Silence, presence audio, disturbance shifts, radio degradation): deaf/HoH players need visual equivalents. Suggestions: subtle screen-edge coloration for disturbance state; visual indicator for Silence zone entry; captioning for radio.
- **Low-visibility environments** (night Petrifold + The Silence): players with visual impairments face compounded difficulty. Consider optional navigation audio (subtle spatial tone for exit gate direction).
Solutions must maintain the horror register.
## How It Appears In-Game
The player learns the loop empirically. The first run is likely lost — not punitively, but because the mechanics take time to internalize. A player who survives long enough learns that the loop is not theirs. The forest was running this cycle before the first road was cut.
## Related
- [[The Crater]] — the origin event; the higher-dimensional geometry that makes the loop topological
- [[The Temporal Fracture]] — extraction as circuit completion; the loop is the shape of the space
- [[World Model]] — the world the mechanics operate within
- [[Biomes]] — fuel, weather, and audio regimes per biome
- [[Disturbance System]] — the tension mechanic these costs feed
- [[Anomalies]] — the hazards in the world
- [[Controls]] — the input layer
- [[Tone and Horror]] — the tonal register mechanics serve
- [[Vehicle — Samba]] — implementation of the vehicle specs
- [[Vehicle — Destruction]] — implementation of panel damage
- [[Vehicle — Dashboard]] — the diegetic instrument implementation
- [[Ephemerality Thread]] — the run-reset rule
- [[The Player Causes the Storm]] — endgame consequence
- [[Constitution]] — Article II (Car Is Core), Article IV Rules 6–8 (ephemeral worlds + tradeoff progression)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-design-core-mechanics: type/spec
Tag assignment · dev- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.62 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Design/Core Mechanics.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Core Mechanics
The mechanical and systems spine of Old Growth: the run loop, player stats, vehicle specifications, resources, garage systems, on-foot handling, UI philosophy, save system, and progression framework. Where [[World Model]] establishes structure and [[Tone and Horror]] establishes register, this note captures the authoritative mechanical values. All values marked `[PENDING]` require playtesting to finalize.
## Overview
Every mechanic serves three design pillars. When a proposed system conflicts, the system changes, not the pillars.
- **Flesh as Horror.** Player body and bus body are both subject to irreversible change. Damage cumulates and is visible. The condition crosses run boundaries in both — the bus through contamination, the player through exposure.
- **The Unknowable Condition.** The forest is never explained. No mechanic makes it legible or masterable. Configuring the bus changes *how* the player meets the condition, not *whether* they can defeat it.
- **Sealed In With It.** Escape is always available in principle and always costly in practice. Ridges, the Storm, fuel economy, and disturbance all pressure toward the exit.
## Key Decisions
### Game Loop
```
GARAGE → SEED SELECTION → WORLD LOAD → DRIVE OUT →
EXPLORE / SCAVENGE → DISTURBANCE ACCUMULATES →
[STORM TRIGGERS if threshold reached] → EXIT ATTEMPT →
[EXTRACTION or DEATH] → GARAGE
```
**Lore framing**: The loop is not a game abstraction imposed on a world. It is the topology of the structure the construct is actively reshaping. The garage is the vertex: the one point all faces share. The exit corridors are edges that fold back to the vertex. Extraction and death both return the player to the garage because all paths inside the closed geometry return to the center. The player has never left. (Post-convergence: death still returns to the vertex, but extraction crosses the rim into the outside. See spec 127.) See [[The Crater]] and [[The Temporal Fracture]].
Engine states:
| State | Description |
|---|---|
| GARAGE | Player on foot in garage. Van accessible. No disturbance accumulation. |
| LOADING | World generation in progress. Seed locked. |
| DRIVING | Player in vehicle, world active. Disturbance accumulates. |
| ON_FOOT | Player outside vehicle. Disturbance accumulates. Maximum vulnerability. |
| STORM_ACTIVE | Storm spawned. Prior states continue. Escape window open. |
| EXTRACTED | Player reached exit gate. Run success. Return to garage. |
| DEAD | Player HP to zero or petrification completed. Run failure. Return to garage. |
Run success: vehicle crosses exit gate with player inside. Run failure: player HP zero, Storm catches player in open, petrification completes on foot, or engine seizure in Petrified Zone with no recovery window. Both outcomes return the player to the garage — the vertex. The difference is how much damage the circuit did on the way through. No campaign end state in v1.0 — the garage contamination arc and player exposure arc provide long-term narrative progression toward the convergence endpoint, which is not a conclusion but a phase transition: after convergence, death still returns to the vertex, but extraction crosses the rim into the outside. The game rules change. See The Temporal Fracture and spec 127.
### Player — Health
| Property | Value |
|---|---|
| Maximum health | 100 HP |
| Starting health (per run) | 100 HP |
| Medkit restore | 25 HP |
| Regen | None |
| Death threshold | 0 HP |
Health does not regenerate. Only restoration is medkit use. Tracked via breathing audio, screen-edge vignette (dark, not colored), and interaction speed reduction at low health. No numeric display.
### Player — Stamina
| Property | Value |
|---|---|
| Maximum stamina | 100 units |
| Sprint drain | 20 units/sec |
| Interact drain | 5 units/action |
| Contact escape drain | 30 units |
| Recharge (walking) | 10 units/sec |
| Recharge (crouching/stationary) | 15 units/sec |
| Recharge (inside van) | 20 units/sec |
| Minimum for sprint | 10 units |
No HUD. Read through movement-speed degradation and audible breathing. Sprint locks at <10 units.
### Player — Exposure (Cross-Run)
Persistent value tracking cumulative contact with the condition across all runs. Does not reset on death or extraction. Does not decrease. Accumulates in the player the same way garage contamination accumulates in the garage — one direction, no instrument, no remedy.
| Threshold | Symptom |
|---|---|
| 0–20 | No symptoms |
| 21–40 | Persistent cough audio, occasional sound delay (50ms) |
| 41–60 | Warmth sensation in hands (haptic/audio), smell sensitivity (audio mix) |
| 61–80 | Vision film at edges, 10% stamina-capacity reduction |
| 81–100 | `[PENDING]` — endpoint design required before implementation |
Exposure sources:
- Spore Cloud on foot: +5 per 10s of contact
- Mycelium Network on foot: +10 per contact event
- Extended Fungal Depths on foot: +2 per minute
- Deep Fungal runs (>50% run time in Fungal): +5 on extraction
- Extended on-foot time at Agitated+ disturbance: +1 per minute
No direct display. Symptoms are designed to feel ordinary — cough, warmth, slight unreliability — so players do not immediately understand what is accumulating.
### Player — Movement
| Mode | Noise Radius | Speed | Notes |
|---|---|---|---|
| Crouch | 0m (silent) | 2 m/s | Only fully silent movement |
| Walk | 3m | 4 m/s | Standard |
| Sprint | 20m | 7 m/s | Stamina drain; noise travels mycelium |
| Fall impact | 15m | — | One-time event on hard landing |
Noise radius is the distance at which the condition registers a signal. Signal strength decreases with distance within the radius.
### Player — Flashlight
| Property | Value |
|---|---|
| Battery life | 5 minutes continuous; 2 minutes recharge in bus |
| Batteries found per world | 0–2 `[PENDING]` |
| Verdant beam | Normal |
| Fungal Depths beam | Scattered by haze; −40% effective range |
| Petrifold beam | High reflection off calcite surfaces |
Only on-foot tool other than throwable rocks and flare gun. No combat applications.
### Vehicle — Base Specifications
| Property | Value |
|---|---|
| Model | 1963 VW Type 2 (T1) 23-Window Deluxe Samba |
| Weight | ~1,100 kg |
| Engine | 50 HP, rear-mounted |
| Drive | Rear-wheel drive |
| Handling feel | Lumbering, floaty, momentum-heavy |
| Max fuel | 100 units |
| Starting fuel (per run) | 80 units |
| Max vehicle health | 100 HP |
| Destructible panels | 56 |
### Vehicle — Fuel Consumption
| Biome | Rate | Notes |
|---|---|---|
| Verdant Old Growth | 1 unit/km | Baseline |
| Fungal Depths | 1.6 units/km | Spore haze affects combustion |
| Petrifold | 0.7 units/km | Engine runs lean and cold |
| Idling | 0.2 units/min | All biomes |
Petrifold's improved fuel economy is not explained by any in-world mechanic the player is told. It is noticed, not announced.
### Vehicle — Health and Damage
| Damage Source | HP Loss |
|---|---|
| Low-speed collision (<20 km/h) | 2–5 HP |
| Medium collision (20–50 km/h) | 8–15 HP |
| High-speed collision (>50 km/h) | 20–40 HP |
| Deadfall impact | 25 HP |
| Mycelium Network spore burst | 10 HP (exterior panels) |
| Storm exposure | 15 HP/sec |
| Presence contact (on exterior) | 0 HP — condition presence does not damage bus mechanically |
Panel damage distributes across the 60-panel system (56 body panels + 4 tires). Each panel tracks its own damage state. Panels at 0 HP do not fall off — they deform and remain, accumulating visual damage. Aggregate panel damage contributes to overall vehicle health. Repair kits restore 20 HP per kit, targetable at specific panel groups or the drivetrain.
### Vehicle — Electrical System
The electrical system is old and unstable. Stress events cause transient failures.
| Stress Source | Effect | Recovery |
|---|---|---|
| Hollow Tree (20m) | Instrument drift, radio static | Auto on exit from radius |
| Hollow Tree (10m) | Engine power −40%, lights flicker | Auto on exit |
| Hollow Tree (5m) | Engine near-failure, instruments dead | Auto on exit |
| Storm proximity (<50m) | Full electrical failure | Auto if Storm not caught |
| High disturbance (Agitated+) | Intermittent instrument failure | Auto; frequency increases |
Electrical failure does not strand the bus unless combined with Petrified Zone seizure. Engine will restart after exit from stress radius.
### Vehicle — Dashboard Instruments
All vehicle-state feedback is diegetic. No HUD overlays.
| Instrument | Normal | Degraded (Agitated) | Failed |
|---|---|---|---|
| Fuel gauge | Accurate | ±10% error | Stuck at last reading |
| Speedometer | Accurate | Minor drift | Inaccurate |
| Tachometer | Accurate | Jittery | Inaccurate |
| Gear indicator | Accurate | Delayed | Dark |
| Battery gauge | Stable | Flickering | Dark |
At Agitated, instruments become unreliable enough that the player must estimate fuel and speed from engine audio and road feel.
### Vehicle — The Bus as Force
At speed, the bus makes contact with whatever is standing in the road. What is in the road is not an entity with a health pool — it is the condition at a local concentration, present and not moving. Contact at speed disperses that concentration. It does not kill anything.
| Speed at Contact | Bus Damage | Notes |
|---|---|---|
| <10 km/h | 0 | Contact registered, no panel damage |
| 10–30 km/h | 2–8 HP (exterior panels) | Low-speed brush |
| 30–60 km/h | 10–20 HP (exterior panels) | Standard impact |
| >60 km/h | 25–40 HP (exterior panels) | High-speed, significant panel damage |
Contact at higher disturbance produces denser concentrations and proportionally greater panel damage.
### Resources
| Item | Effect | Collection | Disturbance Cost |
|---|---|---|---|
| Fuel canister | +25 fuel | Drive-by or on foot | +3 |
| Repair kit | +20 vehicle HP (targeted) | Drive-by or on foot | +4 |
| Medkit | +25 player HP | On foot only (stop required) | +5 |
| Flare gun | Distraction tool, single use | On foot only (stop required) | +3 to find |
| Flashlight battery | +5 min flashlight | On foot only | +3 |
| Rock | Throwable distraction | Passive (always available) | +0.5/throw |
Spawn rates: fuel canister common (gas stations, ranger stations, roadsides); repair kit common (ranger stations, trailers, cargo containers); medkit uncommon (ranger stations, first aid cabinets); flare gun rare (research trailers, one per world max); flashlight battery uncommon (structures, vehicle gloveboxes).
Carry limits (field): fuel canister ×2, repair kit ×2, medkit ×2, flare gun ×1, flashlight battery ×3, rock unlimited. Garage storage: unlimited for fuel/repair/battery; medkit cabinet holds 3; flare gun not storable (found in field only); rocks N/A.
Fuel canisters and repair kits can be collected from inside a moving vehicle at speeds up to 20 km/h. Player must pass within 2m. Collection automatic on proximity — primary method for minimizing on-foot time. On-foot items require a 3-second interact animation; player is stationary and vulnerable during this time.
### Flare Gun Mechanics
| Property | Value |
|---|---|
| Uses | 1 (single shot, found item) |
| Carry limit | 1 at a time |
| Draw radius | 80m |
| Burn duration | 45 seconds |
| Effect | Condition attention redirects toward flare location for burn duration |
| Post-burn | Attention dissipates from flare location over ~10 seconds |
| Disturbance cost (fire) | +6 |
Not a weapon. Creates a temporary attention sink — redirecting the condition's local concentration toward heat and light, useful for clearing a path to the bus or to an exit, never for confrontation.
### Garage Systems
Single-room structure. Player explores on foot. Bus occupies center bay. All interaction points peripheral.
| Element | Function |
|---|---|
| Repair workbench | Spend repair kits on panel groups, drivetrain, or installed components |
| Fitting station | Install, remove, inspect vehicle components `[PENDING spec 027]` |
| First-aid cabinet | Use or store medkits (max 3) |
| Wall map | Seed selection. Displays biome zones for selected seeds |
| Inventory shelf | Stored items: batteries, spare repair kits, uninstalled components |
| Run notebook | Passive display near workbench. Per-run statistics, death/extraction counts, per-biome time. Read-only |
| Van (exterior) | Inspect panel damage and installed component condition visually |
| Van (interior) | Glovebox (battery storage), radio controls, footwell loading. Enter to begin run |
Repair workbench allocates repair kits to: exterior panels (front/rear/left/right/roof), mechanical systems (engine, drivetrain), electrical systems (instruments, headlights, radio). Each kit restores 20 HP, distributed across targeted system. Multiple kits can be applied to same system.
### Garage Contamination Progression
Persistent, cross-run. Does not decrease.
| Threshold | Visual Change | Notes |
|---|---|---|
| 0 | Clean | Starting state |
| 10 | Moss in northwest corner | First visible sign |
| 25 | Fungal bloom on floor near vehicle | Matches Fungal Depths wheel well bloom |
| 40 | Mineral deposit at base of treeline wall | Matches Petrifold calcite |
| 60 | Mycelium thread on first-aid cabinet hinge | Recognizable to attentive players |
| 80 | Amber light visible through window at night | The forest is producing its signal inside the safe space |
| 100 | `[PENDING]` — epic event | |
Accumulates by: run completed (extraction) +3; deep Fungal run +5; Petrifold entry +3; death +2; Mycelium contact +1 per contact (cumulative).
### The Window
The single garage window faces the treeline. Always present. Cannot be boarded or obscured. Provides ambient exterior audio (forest at reduced volume) and, at high contamination, Amber Bleed is visible through it from inside the garage. First indication that the process has entered the safe space in a meaningful way.
### On-Foot Systems
All on-foot interactions have a 3-second animation with no skip. Player is stationary and vulnerable. Cancellable-by-movement: resource collection, structure entry.
**Vehicle entry/exit**: ~1.5-second camera interpolation. Player is vulnerable throughout — neither fully inside nor fully outside; the condition does not distinguish this transition from full on-foot exposure. Intentional.
**On-foot noise** (additive with bus noise when player is outside and bus is idling nearby):
| Action | Radius | Duration |
|---|---|---|
| Crouch | 0m | — |
| Walking | 3m | Continuous |
| Sprinting | 20m | Continuous |
| Fall impact | 15m | 3s |
| Interaction (pickup) | 5m | 1s |
| Grab escape | 20m | 2s |
### UI Philosophy — No HUD
No health bar. No fuel %. No disturbance meter. No minimap. No objective markers.
Information available through:
- Dashboard instruments (vehicle state)
- Van audio (engine, damage, electrical)
- Environmental audio (disturbance state, condition presence)
- Visual environment (biome, weather, anomaly signatures)
- The player's body (breathing, movement capability)
**Permitted UI surfaces**:
| Surface | Context | Content |
|---|---|---|
| Garage wall map | Garage only, on foot | Seed selection, biome zones |
| Inventory screen | Garage or stopped bus | Current inventory, management |
| Dashboard instruments | Always (in van) | Fuel, speed, RPM, gear, battery |
| Interaction prompts | Proximity-triggered | Minimal single verb (TAKE/OPEN/ENTER) |
Prompts appear only within interaction range. No waypoints, quest markers, objective text.
**Death screen**: hard cut to black; single audio event (silence, or forest transmission). No in-moment stats. Stats available next garage return via run notebook.
**Extraction screen**: exit gate closes behind bus; wind-chime resonance fading. Cut to garage with bus in bay. No score screen.
### Save & Persistence
**Persistent**: vehicle HP, per-panel damage states, vehicle configuration (installed components + damage), fuel level, inventory contents, player gear (once equipped, permanent), total runs, total extractions, total deaths, biome preference (last selected), per-biome statistics (time, runs entered, extractions from), garage contamination level (0–100, never decreases), player exposure level (0–100, never decreases).
**Does not persist**: world layout (regenerated per seed per run), condition concentration zones (influenced by seed but not fixed), resource positions (randomized within structure/road rules each run), anomaly activation states (reset each run), disturbance meter (resets to 0 each run), player health mid-run (resets to persistent value on garage return).
**Save trigger**: auto-save fires exactly once per run cycle on garage return after extraction or death. No manual save. No mid-run save. Application closed mid-run = treated as death on next launch.
### Progression — Tradeoff-First Framework
The original prohibition on numerical upgrades (pre-2.19.0 constitution) is replaced by a tradeoff-first framework. Numerical progression is permitted iff all three:
1. **Diegetically grounded.** Change is a physical thing on the bus or worn by the player. No abstract stat increases.
2. **Genuine tradeoff.** No component is a pure upgrade. Every gain carries a corresponding cost.
3. **Bounded.** Components have fixed stats. No escalating tiers, upgrade trees, or compounding bonuses.
The condition cannot be overcome. A fully-configured bus with optimal tradeoffs is still subject to the same disturbance rules, Storm, and condition. Configuration is playstyle expression, not power escalation.
**Valid progression types**:
| Type | Description | Examples |
|---|---|---|
| Informational | Player gains knowledge, not statistics | Wall map annotations, notebook records |
| Narrative | Progression against the player | Garage contamination, player exposure |
| Physical (tradeoff) | Diegetic, bounded, with genuine cost | Vehicle components, worn gear |
**Prohibited**: abstract stat upgrades (not diegetically grounded); pure upgrades (gain without cost); escalating tiers (not bounded); fuel capacity upgrades (no physical basis in van configuration); HP upgrades without physical component.
### Vehicle Configuration System (§16a)
The bus can be fitted with physical components altering behavior. All components durable, degrade with use, require workbench repair. Installed components persist across runs. Destroyed beyond repair = gone permanently. Full specification (stat tables, crafting rules, workbench UI, epic unlocks) deferred to spec 027.
**Categories**: body panels, bumpers, lights, accessories, spoilers, roll cages, other fittings.
**Tradeoff examples** (illustrative, not final):
| Component | Benefit | Cost |
|---|---|---|
| Heavy steel bumper | +impact force, −bus damage on road contact | +noise on impact, −top speed |
| Rear stabilizer wing | +stability at speed | +wind noise (disturbance) |
| Full roll cage | +van HP pool | +rattle audio signature (disturbance) |
| Roof-mounted aux lights | +forward visibility range | +electrical draw, +disturbance when active |
| Reinforced door skins | +panel HP (doors) | +weight, −acceleration |
| External fuel mount | +fuel carry capacity | +fire risk on collision, +noise on rough terrain |
No component combination produces a net advantage. Tradeoffs cumulative.
**Degradation**: components degrade independently from 60-panel body. Degraded components retain tradeoff *costs* while losing *benefits*. Damaged roll cage still rattles but no longer contributes full HP. Repair at workbench targeting the component directly.
**Persistence**:
| State | Behavior |
|---|---|
| Installed, undamaged | Persists across runs, full effect active |
| Installed, degraded | Persists, reduced benefit, full cost |
| Installed, destroyed | Removed permanently. Cannot be recovered |
| Uninstalled (storage) | Persists in garage inventory |
**Acquisition**: `[PENDING]` — three channels proposed: scavenged (found in structures, road debris, wrecked vehicles), crafted (crafting TBD), unlocked via epics (epic system TBD). Dedicated spec required.
### Player Gear (§16b)
Player can find and equip gear (clothing, protective layers). Once equipped, permanent. Player does not manage between runs.
| Property | Value |
|---|---|
| Acquisition | `[PENDING]` |
| Equip behavior | Permanent on first equip. Cannot be removed |
| Mechanical effects | `[PENDING]` — exposure resistance, stamina modifiers |
| Current scope | Cosmetic only |
Intent: keep player's attention on the van, not on their character. Gear is a visible record of what the player has found, not a system to manage.
### Camera & Controls
**In-vehicle**: first-person, locked to driver seat. FOV 75° default. Player can look left/right/mirrors. Full 360° head turn at low speed. Dashboard occupies lower quarter, always visible, readable at a glance. No UI overlay on dashboard.
**On-foot**: standard first-person. FOV 80° default. No weapon viewmodel (flashlight only when active).
**Vehicle entry/exit**: 1.5-second interpolation, not skippable. Physical animation, not cut.
### Out of Scope for v1.0
- Additional biomes (architecture supports expansion)
- Multiplayer (not in scope or architecture)
- New vehicle types (Samba is the only vehicle)
- Voiced narrative (Gary TTS system is audio spec only)
- Garage narrative events (contamination epic events at 80 and 100 `[PENDING]`)
- Environmental presence system (replaces Hollow AI — spec required)
- Player/bus convergence endpoint (Exposure 81–100 and garage endpoint `[PENDING]`)
- Endpoint visual and mechanical design (World Bible direction set; implementation open)
### Accessibility Considerations (to resolve pre-ship)
- **No HUD**: players relying on visual clarity for HP/resource state have no fallback. Consider optional accessibility mode with minimal diegetically-consistent overlays (e.g. faint gauge texture rendered as part of the dashboard, not a screen overlay).
- **Audio-dependent systems** (The Silence, presence audio, disturbance shifts, radio degradation): deaf/HoH players need visual equivalents. Suggestions: subtle screen-edge coloration for disturbance state; visual indicator for Silence zone entry; captioning for radio.
- **Low-visibility environments** (night Petrifold + The Silence): players with visual impairments face compounded difficulty. Consider optional navigation audio (subtle spatial tone for exit gate direction).
Solutions must maintain the horror register.
## How It Appears In-Game
The player learns the loop empirically. The first run is likely lost — not punitively, but because the mechanics take time to internalize. A player who survives long enough learns that the loop is not theirs. The forest was running this cycle before the first road was cut.
## Related
- [[The Crater]] — the origin event; the higher-dimensional geometry that makes the loop topological
- [[The Temporal Fracture]] — extraction as circuit completion; the loop is the shape of the space
- [[World Model]] — the world the mechanics operate within
- [[Biomes]] — fuel, weather, and audio regimes per biome
- [[Disturbance System]] — the tension mechanic these costs feed
- [[Anomalies]] — the hazards in the world
- [[Controls]] — the input layer
- [[Tone and Horror]] — the tonal register mechanics serve
- [[Vehicle — Samba]] — implementation of the vehicle specs
- [[Vehicle — Destruction]] — implementation of panel damage
- [[Vehicle — Dashboard]] — the diegetic instrument implementation
- [[Ephemerality Thread]] — the run-reset rule
- [[The Player Causes the Storm]] — endgame consequence
- [[Constitution]] — Article II (Car Is Core), Article IV Rules 6–8 (ephemeral worlds + tradeoff progression)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-spec-156-ci-tooling-gotchas: domain/governance
Tag assignment · test- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.04 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Discoveries/Spec 156 — CI Tooling Gotchas.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 156 — CI Tooling Gotchas
Two CI pitfalls discovered during spec 156 PR CI debugging: the make bump targets misparse three-component semver, and the Component SemVer CI check fails on project-config even though the pre-commit hook passes locally.
## Discovery 1 — `make bump-patch` and `make bump-minor` Misparse Versions Like 0.17.12
**Context**: After changing game-affecting files (samba_bus.gd, panel_definitions.gd, etc.) in spec 156, CI's Version Bump Check required a game version bump from 0.17.12. Running `make bump-patch` or `make bump-minor` was the prescribed remedy per the CI error message.
**Discovery**: Both `make bump-patch` and `make bump-minor` misparse semver strings with patch versions ≥10 (like 0.17.12). They output "bump version 0.17.1 -> 0.17.2" instead of correctly reading 0.17.12, then fail with `Error 16` ("No tag found to do an incremental changelog"). This means any version above 0.x.y where y ≥ 10 cannot be bumped via the make targets.
**Workaround**: Manually edit the version string in `src/project.godot` (`config/version="0.x.y"`) to the desired value. CI will sync `export_presets.cfg` automatically on merge. The game-version-check CI gate (`make game-version-check`) validates that the new version is valid SemVer.
**Root cause**: Likely in the bump script's version parsing — it interprets `0.17.12` as the first two components (`0.17`) plus the beginning of the third (`1`), treating the version as `0.17.1`.
## Discovery 2 — Component SemVer Passes Pre-Commit but Fails CI for project-config
**Context**: The pre-commit hook's auto-bump logic runs on staged files and commits the updated `component_versions.json`. But when a commit only touches `src/project.godot` (version bump), the pre-commit hook says "Component SemVer check passed" without auto-bumping the `project-config` component version. CI then compares the branch's `component_versions.json` against the base branch baseline and fails because `project-config` is unchanged.
**Discovery**: The pre-commit hook's auto-bump only triggers when it detects first-party component files that are out of date relative to git's index. When `project.godot` is changed in isolation (not alongside other bus/panel changes), the hook treats the project-config component as "in sync" because the file was just staged. But CI compares against the base branch's `component_versions.json` — which has the old project-config version — so the check fails.
**Workaround**: After bumping `project.godot`, manually bump `project-config`'s version in `src/component_versions.json` before pushing. Or combine the project.godot change with another component change so the auto-bump fires.
**Implication**: The pre-commit hook's Component SemVer check is a fast local gate, but it is NOT equivalent to the CI Component SemVer check. CI uses a baseline comparison (base branch vs PR branch) that is stricter. Relying solely on the pre-commit hook for semver can lead to CI failures.
## References
- src/project.godot — game version config
- src/component_versions.json — component version tracking
- Makefile — bump-patch, bump-minor targets (version parsing bug)
- scripts/hooks/pre-commit — auto-bump logic (project-config not always bumped)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-spec-156-ci-tooling-gotchas: type/discovery
Tag assignment · test- Gold
- The note calls for `type/discovery`: Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/discovery` (False)
- Signal
- P(true) 0.48 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Spec 156 — CI Tooling Gotchas.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 156 — CI Tooling Gotchas
Two CI pitfalls discovered during spec 156 PR CI debugging: the make bump targets misparse three-component semver, and the Component SemVer CI check fails on project-config even though the pre-commit hook passes locally.
## Discovery 1 — `make bump-patch` and `make bump-minor` Misparse Versions Like 0.17.12
**Context**: After changing game-affecting files (samba_bus.gd, panel_definitions.gd, etc.) in spec 156, CI's Version Bump Check required a game version bump from 0.17.12. Running `make bump-patch` or `make bump-minor` was the prescribed remedy per the CI error message.
**Discovery**: Both `make bump-patch` and `make bump-minor` misparse semver strings with patch versions ≥10 (like 0.17.12). They output "bump version 0.17.1 -> 0.17.2" instead of correctly reading 0.17.12, then fail with `Error 16` ("No tag found to do an incremental changelog"). This means any version above 0.x.y where y ≥ 10 cannot be bumped via the make targets.
**Workaround**: Manually edit the version string in `src/project.godot` (`config/version="0.x.y"`) to the desired value. CI will sync `export_presets.cfg` automatically on merge. The game-version-check CI gate (`make game-version-check`) validates that the new version is valid SemVer.
**Root cause**: Likely in the bump script's version parsing — it interprets `0.17.12` as the first two components (`0.17`) plus the beginning of the third (`1`), treating the version as `0.17.1`.
## Discovery 2 — Component SemVer Passes Pre-Commit but Fails CI for project-config
**Context**: The pre-commit hook's auto-bump logic runs on staged files and commits the updated `component_versions.json`. But when a commit only touches `src/project.godot` (version bump), the pre-commit hook says "Component SemVer check passed" without auto-bumping the `project-config` component version. CI then compares the branch's `component_versions.json` against the base branch baseline and fails because `project-config` is unchanged.
**Discovery**: The pre-commit hook's auto-bump only triggers when it detects first-party component files that are out of date relative to git's index. When `project.godot` is changed in isolation (not alongside other bus/panel changes), the hook treats the project-config component as "in sync" because the file was just staged. But CI compares against the base branch's `component_versions.json` — which has the old project-config version — so the check fails.
**Workaround**: After bumping `project.godot`, manually bump `project-config`'s version in `src/component_versions.json` before pushing. Or combine the project.godot change with another component change so the auto-bump fires.
**Implication**: The pre-commit hook's Component SemVer check is a fast local gate, but it is NOT equivalent to the CI Component SemVer check. CI uses a baseline comparison (base branch vs PR branch) that is stricter. Relying solely on the pre-commit hook for semver can lead to CI failures.
## References
- src/project.godot — game version config
- src/component_versions.json — component version tracking
- Makefile — bump-patch, bump-minor targets (version parsing bug)
- scripts/hooks/pre-commit — auto-bump logic (project-config not always bumped)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-reference-decision-log: domain/governance
Tag assignment · dev- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.49 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Reference/Decision Log.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Decision Log
The chronological record of significant design and engineering decisions across Old Growth's development. Superseded decisions are retained with rationale. Sourced from Appendix C of the constitution and from decisions captured during authoring.
<!-- Populated in Phase 6. -->
## Entries
Significant decisions across Old Growth's development. Sourced from constitution amendments (the version-change log in the constitution header), Appendix C (superseded design decisions), and significant per-spec decisions.
### Constitutional amendments (from [[Constitution]] sync impact reports)
- **v2.21.0 (2026-04-21)** — Added Article XIV — The Vault. Establishes `docs/vault/` as canonical documentation surface.
- **v2.20.0 (2026-04-20)** — Added explicit game version bump obligation (Article III Rule 15) for `src/**` or `rust/**` changes merging to `main`.
- **v2.19.0 (2026-04-19)** — Replaced Article IV Rule 8's hard prohibition on numerical upgrades with a tradeoff-first progression framework. Numerical progression permitted when diegetically grounded, a genuine tradeoff, and bounded. Vehicle configuration + player gear added to persistent data list.
- **v2.13.0** (earlier) — Major revision (content not recorded in the currently-visible SYNC IMPACT REPORT)
- **Pre-2.19.0 history** — See [[Constitution]] version-change log (lines 1–180) for the full history
### Approved-stack decisions (from [[Tech Stack Review]])
- **Godot 4.6+ over Unity/Unreal/Bevy** — Article III Rule 2 (Godot Lock)
- **Jolt physics (native) over Godot Physics** — more stable vehicle sim
- **GEVP over VehicleBody3D / custom** — raycast-based simcade controller
- **Terrain3D over HTerrain / custom** — GPU clipmap, foliage instancer
- **gdext / Rust over C# / pure GDScript** — Article III Rule 17; performance-critical logic in rust-core
- **Native Godot audio over Sound Manager / FMOD / Wwise** — sufficient for the 7-bus architecture
### Rejected alternatives (Appendix C — loaded via constitution-reference skill)
Full list of rejected design decisions lives in constitution Appendix C. Access via the `constitution-reference` OpenCode skill. Examples:
- Rejection of gradual enemy introduction (anomalies are structural, not scaling)
- Rejection of multi-vehicle designs (Article II — the Samba is the only vehicle)
- Rejection of non-ephemeral worlds (Article IV Rule 6)
### Spec-level significant decisions
- **Spec 067 (Phased Rust migration)** — migration of procedural generation into rust-core for determinism + performance
- **Spec 070 (Biome blending Rust)** — biome blend logic fully moved to Rust; 14 constants promoted to `@export`; `--auto-new-game` CLI added; `veg_counts.json` baseline established
- **Spec 078 (Hardcoded values remediation)** — promoted ~150 Class A values to `@export`; introduced 3 Config Resources (BiomeConfig, DisturbanceConfig, AudioBusConfig); VarDictionary bridge migration
- **Spec 079 (Obsidian vault)** — this spec; added Article XIV
### Governance-level decisions
- **Branch-only workflow** — direct commits to `main` prohibited (Governance / Commit Strategy Rule 6+8)
- **No `git stash`** — multi-agent concurrency (Governance Rule 7)
- **Submodule two-repo workflow** — audio submodule PRs land first; parent repo updates the pointer
- **Component SemVer** — Article III Rule 15; see [[Component SemVer Policy]]
## Related
- [[Constitution]] — Appendix C for full superseded-decisions list; Appendix C is the primary source
- [[Tech Stack Review]] — stack decision rationale
- [[Reference]] — folder MOC
- [[Epics]] — status snapshot
- [[Conflicts]] — unresolved source disagreements
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-storm: domain/world
Tag assignment · dev- Gold
- The note calls for `domain/world`: World model, biomes, terrain, procedural generation, weather (True)
- ember
- The note has no reason to carry `domain/world` (False)
- Signal
- P(true) 0.36 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Storm.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Storm
The Storm is the endgame event: a directional wall that spawns from the ridgeline opposite the nearest exit and advances toward the player when disturbance reaches its Finishing threshold. It is not a difficulty spike, not a timer, and not weather — it is the active terraforming of the construct through physical space, momentarily unfiltered (see [[The Crater]], [[The Temporal Fracture]]).
The construct is still reshaping this space. Normally the temporal fracture diffuses this process into background distortion. When disturbance destabilizes the field past the threshold, the fracture loses coherence and the raw transformation becomes perceptible — a wall of displacement energy advancing from the crater rim. The Storm is indifferent because it is not a response to the player — it is a process that has been running since the construct entered this space, briefly made visible. The storm that kills you is the storm that is still reshaping this place.
The run ends when the player either extracts (`RunPhase.EXTRACTED`) or dies (`RunPhase.DEAD`). Both outcomes return the player to the garage — the vertex. (Post-convergence: death still returns to the vertex, but extraction crosses the rim into the outside. See spec 127.) Extraction is completing a circuit along an edge of the higher-dimensional geometry. Death is completing a circuit through the ongoing displacement. The destination is the same; the cost is different. See [[The Player Causes the Storm]] for the full ludonarrative argument.
## Trigger and State
The Storm is triggered by [[Autoloads — Disturbance]] when `level >= 85` (the Finishing threshold, tunable via `disturbance_config.tres`). At that point:
1. `Autoloads — Disturbance` emits `threshold_changed("storm")` on its own autoload signal, and emits `disturbance_storm_activated(origin, direction)` via `Events.disturbance_storm_activated` for cross-system notification
2. `Autoloads — GameState` transitions `run_phase` to `RunPhase.STORM_ACTIVE`
3. Downstream storm systems begin spawning the visual wall and audio arc
The world's temporal configuration closes; the garage — the vertex — receives the player back. Only the garage persists because it is the one point all faces of the geometry agree on (see [[Ephemerality Thread]]).
## Visual System (⏳ Not yet authored)
The Storm spawns as a directional wall on the ridgeline opposite the nearest exit and advances toward the player. The direction decision is documented in [[Governance/Tech Stack Review]] §41. The visual wall is a Godot particle / shader system — not yet assigned to a dedicated script.
Key visual design constraints:
- The storm must read at driving speed from the first-person interior camera
- It must feel *indifferent* — not targeted. See [[Design/Tone and Horror]] and [[Design/Horror Register]]
- Visibility collapses to <10m inside the storm; on-foot cold exposure is −5 HP/sec in Blizzard conditions
## Audio System (⏳ Spec 107 — waiting)
[[107 Storm Audio System]] defines the 4-phase audio arc:
| Phase | Condition | Character |
|---|---|---|
| **Approach** | Storm spawned, player at distance | Subsonic pressure build, biome ambient collapse |
| **Active** | Storm wall advancing | Full roar; Horror bus at max |
| **Proximity** | Player near storm edge | Disorientation; radio dies entirely |
| **Caught** | Player inside storm | Maximum; extraction pressure absolute |
Post-extraction: a 3-second silence beat as the player passes through the geometry's edge and arrives back at the vertex. For three seconds, time is coherent — all temporal layers agree. Then the next run begins, and the player enters another face of the geometry.
Prerequisite: [[111 Audio Bus Architecture]] must ship first (adds the Horror bus the storm audio routes through).
## Relationship to Disturbance
The Storm is the visible form of disturbance at its maximum. The disturbance meter is hidden — the player never sees a number. The Storm is what the meter looks like when it arrives at a conclusion about the player. It is not a punishment. The forest is not angry. The player is *the largest unfinished input in the watershed.*
See [[The Disturbance Thread]] for the full mechanic thread and [[The Player Causes the Storm]] for the ludonarrative framing.
## Related
- [[The Crater]] — the origin event; the Storm spawns from the crater rim
- [[The Temporal Fracture]] — the Storm as the ongoing insertion momentarily unfiltered
- [[Autoloads — Disturbance]] — the threshold arithmetic that triggers the Storm (level >= 85)
- [[Autoloads — GameState]] — run_phase transitions (STORM_ACTIVE → EXTRACTED / DEAD)
- [[Audio — World Audio]] — the four-state disturbance audio arc that precedes the Storm
- [[107 Storm Audio System]] — the Storm's 4-phase audio spec (⏳ waiting on spec 111)
- [[Systems/World — Procedural Generation]] — Storm direction uses exit point positions computed during generation
- [[Design/Tone and Horror]] — the horror register the Storm must maintain (indifference, not targeting)
- [[The Player Causes the Storm]] — the full ludonarrative thread
- [[The Disturbance Thread]] — the mechanic that builds to the Storm
- [[020 Disturbance Storm]] — original spec that implemented the disturbance → Storm mechanic
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-storm: type/spec
Tag assignment · dev- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.53 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Storm.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Storm
The Storm is the endgame event: a directional wall that spawns from the ridgeline opposite the nearest exit and advances toward the player when disturbance reaches its Finishing threshold. It is not a difficulty spike, not a timer, and not weather — it is the active terraforming of the construct through physical space, momentarily unfiltered (see [[The Crater]], [[The Temporal Fracture]]).
The construct is still reshaping this space. Normally the temporal fracture diffuses this process into background distortion. When disturbance destabilizes the field past the threshold, the fracture loses coherence and the raw transformation becomes perceptible — a wall of displacement energy advancing from the crater rim. The Storm is indifferent because it is not a response to the player — it is a process that has been running since the construct entered this space, briefly made visible. The storm that kills you is the storm that is still reshaping this place.
The run ends when the player either extracts (`RunPhase.EXTRACTED`) or dies (`RunPhase.DEAD`). Both outcomes return the player to the garage — the vertex. (Post-convergence: death still returns to the vertex, but extraction crosses the rim into the outside. See spec 127.) Extraction is completing a circuit along an edge of the higher-dimensional geometry. Death is completing a circuit through the ongoing displacement. The destination is the same; the cost is different. See [[The Player Causes the Storm]] for the full ludonarrative argument.
## Trigger and State
The Storm is triggered by [[Autoloads — Disturbance]] when `level >= 85` (the Finishing threshold, tunable via `disturbance_config.tres`). At that point:
1. `Autoloads — Disturbance` emits `threshold_changed("storm")` on its own autoload signal, and emits `disturbance_storm_activated(origin, direction)` via `Events.disturbance_storm_activated` for cross-system notification
2. `Autoloads — GameState` transitions `run_phase` to `RunPhase.STORM_ACTIVE`
3. Downstream storm systems begin spawning the visual wall and audio arc
The world's temporal configuration closes; the garage — the vertex — receives the player back. Only the garage persists because it is the one point all faces of the geometry agree on (see [[Ephemerality Thread]]).
## Visual System (⏳ Not yet authored)
The Storm spawns as a directional wall on the ridgeline opposite the nearest exit and advances toward the player. The direction decision is documented in [[Governance/Tech Stack Review]] §41. The visual wall is a Godot particle / shader system — not yet assigned to a dedicated script.
Key visual design constraints:
- The storm must read at driving speed from the first-person interior camera
- It must feel *indifferent* — not targeted. See [[Design/Tone and Horror]] and [[Design/Horror Register]]
- Visibility collapses to <10m inside the storm; on-foot cold exposure is −5 HP/sec in Blizzard conditions
## Audio System (⏳ Spec 107 — waiting)
[[107 Storm Audio System]] defines the 4-phase audio arc:
| Phase | Condition | Character |
|---|---|---|
| **Approach** | Storm spawned, player at distance | Subsonic pressure build, biome ambient collapse |
| **Active** | Storm wall advancing | Full roar; Horror bus at max |
| **Proximity** | Player near storm edge | Disorientation; radio dies entirely |
| **Caught** | Player inside storm | Maximum; extraction pressure absolute |
Post-extraction: a 3-second silence beat as the player passes through the geometry's edge and arrives back at the vertex. For three seconds, time is coherent — all temporal layers agree. Then the next run begins, and the player enters another face of the geometry.
Prerequisite: [[111 Audio Bus Architecture]] must ship first (adds the Horror bus the storm audio routes through).
## Relationship to Disturbance
The Storm is the visible form of disturbance at its maximum. The disturbance meter is hidden — the player never sees a number. The Storm is what the meter looks like when it arrives at a conclusion about the player. It is not a punishment. The forest is not angry. The player is *the largest unfinished input in the watershed.*
See [[The Disturbance Thread]] for the full mechanic thread and [[The Player Causes the Storm]] for the ludonarrative framing.
## Related
- [[The Crater]] — the origin event; the Storm spawns from the crater rim
- [[The Temporal Fracture]] — the Storm as the ongoing insertion momentarily unfiltered
- [[Autoloads — Disturbance]] — the threshold arithmetic that triggers the Storm (level >= 85)
- [[Autoloads — GameState]] — run_phase transitions (STORM_ACTIVE → EXTRACTED / DEAD)
- [[Audio — World Audio]] — the four-state disturbance audio arc that precedes the Storm
- [[107 Storm Audio System]] — the Storm's 4-phase audio spec (⏳ waiting on spec 111)
- [[Systems/World — Procedural Generation]] — Storm direction uses exit point positions computed during generation
- [[Design/Tone and Horror]] — the horror register the Storm must maintain (indifference, not targeting)
- [[The Player Causes the Storm]] — the full ludonarrative thread
- [[The Disturbance Thread]] — the mechanic that builds to the Storm
- [[020 Disturbance Storm]] — original spec that implemented the disturbance → Storm mechanic
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-storm: type/system
Tag assignment · dev- Gold
- The note calls for `type/system`: A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`) (True)
- ember
- The note has no reason to carry `type/system` (False)
- Signal
- P(true) 0.27 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Storm.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Storm
The Storm is the endgame event: a directional wall that spawns from the ridgeline opposite the nearest exit and advances toward the player when disturbance reaches its Finishing threshold. It is not a difficulty spike, not a timer, and not weather — it is the active terraforming of the construct through physical space, momentarily unfiltered (see [[The Crater]], [[The Temporal Fracture]]).
The construct is still reshaping this space. Normally the temporal fracture diffuses this process into background distortion. When disturbance destabilizes the field past the threshold, the fracture loses coherence and the raw transformation becomes perceptible — a wall of displacement energy advancing from the crater rim. The Storm is indifferent because it is not a response to the player — it is a process that has been running since the construct entered this space, briefly made visible. The storm that kills you is the storm that is still reshaping this place.
The run ends when the player either extracts (`RunPhase.EXTRACTED`) or dies (`RunPhase.DEAD`). Both outcomes return the player to the garage — the vertex. (Post-convergence: death still returns to the vertex, but extraction crosses the rim into the outside. See spec 127.) Extraction is completing a circuit along an edge of the higher-dimensional geometry. Death is completing a circuit through the ongoing displacement. The destination is the same; the cost is different. See [[The Player Causes the Storm]] for the full ludonarrative argument.
## Trigger and State
The Storm is triggered by [[Autoloads — Disturbance]] when `level >= 85` (the Finishing threshold, tunable via `disturbance_config.tres`). At that point:
1. `Autoloads — Disturbance` emits `threshold_changed("storm")` on its own autoload signal, and emits `disturbance_storm_activated(origin, direction)` via `Events.disturbance_storm_activated` for cross-system notification
2. `Autoloads — GameState` transitions `run_phase` to `RunPhase.STORM_ACTIVE`
3. Downstream storm systems begin spawning the visual wall and audio arc
The world's temporal configuration closes; the garage — the vertex — receives the player back. Only the garage persists because it is the one point all faces of the geometry agree on (see [[Ephemerality Thread]]).
## Visual System (⏳ Not yet authored)
The Storm spawns as a directional wall on the ridgeline opposite the nearest exit and advances toward the player. The direction decision is documented in [[Governance/Tech Stack Review]] §41. The visual wall is a Godot particle / shader system — not yet assigned to a dedicated script.
Key visual design constraints:
- The storm must read at driving speed from the first-person interior camera
- It must feel *indifferent* — not targeted. See [[Design/Tone and Horror]] and [[Design/Horror Register]]
- Visibility collapses to <10m inside the storm; on-foot cold exposure is −5 HP/sec in Blizzard conditions
## Audio System (⏳ Spec 107 — waiting)
[[107 Storm Audio System]] defines the 4-phase audio arc:
| Phase | Condition | Character |
|---|---|---|
| **Approach** | Storm spawned, player at distance | Subsonic pressure build, biome ambient collapse |
| **Active** | Storm wall advancing | Full roar; Horror bus at max |
| **Proximity** | Player near storm edge | Disorientation; radio dies entirely |
| **Caught** | Player inside storm | Maximum; extraction pressure absolute |
Post-extraction: a 3-second silence beat as the player passes through the geometry's edge and arrives back at the vertex. For three seconds, time is coherent — all temporal layers agree. Then the next run begins, and the player enters another face of the geometry.
Prerequisite: [[111 Audio Bus Architecture]] must ship first (adds the Horror bus the storm audio routes through).
## Relationship to Disturbance
The Storm is the visible form of disturbance at its maximum. The disturbance meter is hidden — the player never sees a number. The Storm is what the meter looks like when it arrives at a conclusion about the player. It is not a punishment. The forest is not angry. The player is *the largest unfinished input in the watershed.*
See [[The Disturbance Thread]] for the full mechanic thread and [[The Player Causes the Storm]] for the ludonarrative framing.
## Related
- [[The Crater]] — the origin event; the Storm spawns from the crater rim
- [[The Temporal Fracture]] — the Storm as the ongoing insertion momentarily unfiltered
- [[Autoloads — Disturbance]] — the threshold arithmetic that triggers the Storm (level >= 85)
- [[Autoloads — GameState]] — run_phase transitions (STORM_ACTIVE → EXTRACTED / DEAD)
- [[Audio — World Audio]] — the four-state disturbance audio arc that precedes the Storm
- [[107 Storm Audio System]] — the Storm's 4-phase audio spec (⏳ waiting on spec 111)
- [[Systems/World — Procedural Generation]] — Storm direction uses exit point positions computed during generation
- [[Design/Tone and Horror]] — the horror register the Storm must maintain (indifference, not targeting)
- [[The Player Causes the Storm]] — the full ludonarrative thread
- [[The Disturbance Thread]] — the mechanic that builds to the Storm
- [[020 Disturbance Storm]] — original spec that implemented the disturbance → Storm mechanic
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-agent-skills-mcp: domain/governance
Tag assignment · test- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.09 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Code/Tools — Agent Skills & MCP.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — Agent Skills & MCP
The skill system and the MCP (Model Context Protocol) server set form the two extension surfaces that turn a generic agent into an Old Growth agent. Skills inject domain instructions into the conversation context; MCP servers extend the tool surface with external capabilities. This note inventories both, documents the invocation pattern, and maps which agents consume which surfaces. It complements [[Tools — mcpvault]], which describes the vault MCP server in depth.
## The Two Surfaces, Compared
Skills are bodies of instructions loaded into the conversation context on demand. They contain workflows, decision matrices, and references to bundled scripts. They do not add new tools — they teach the agent how to use existing tools and external systems correctly. Skills live under `.opencode/skills/` and are loaded via the `skill` tool or as the `load_skills` parameter on the `task` tool.
MCP servers are processes (local or remote) that expose tools, resources, and prompts to the agent over the Model Context Protocol. They add new tool calls — for example, `gdai-mcp-server` exposes `play_scene`, `get_running_scene_screenshot`, `simulate_input`, and other Godot runtime operations as agent-callable tools. MCP servers are configured in `opencode.json` (project-local) and `.concourse.json` (Concourse repo-profile hook).
The two surfaces compose: a skill (e.g. `observer-qa`) tells the agent the correct workflow for visual QA; an MCP server (e.g. `gdai-mcp-server`) provides the tools that workflow invokes.
## Skill Inventory
Five skills ship in `.opencode/skills/`. Each is a directory containing a `SKILL.md` file with frontmatter (description, triggers) and a body of instructions; some bundle additional scripts or reference files.
| Skill | Directory | Purpose |
|---|---|---|
| `observer-qa` | `.opencode/skills/SKILL.md` (root) | MCP-first visual QA pipeline. Documents the GDAI MCP screenshot-and-analyse workflow as primary, the Observer CLI as secondary for sequence captures and CI gates, the agent trigger matrix, and the decision matrix that maps `(quality, recommendation)` pairs to ship/block actions. Loaded when working on generation code, world preview, or visual artefacts. |
| `audio-assets` | `.opencode/skills/audio-assets/SKILL.md` | TTS generation tooling, audio submodule workflow, naming conventions, and provenance requirements for Old Growth radio and ambient weather content. Loaded when authoring radio clips, KTLK Gary dialogue, weather ambient SFX, or station music. |
| `introspection` | `.opencode/skills/introspection/SKILL.md` | Vault knowledge-graph audit. Documents the three-phase pipeline (mechanical via `scripts/ci/vault_audit.py`, auto-fix via `--apply`, semantic drift via the `vault-audit-semantic` agent) and the rolling `vault/audit` PR convention. Loaded when running a vault health check, interpreting audit output, or drafting correction PRs. |
| `spec-management` | `.opencode/skills/spec-management/SKILL.md` | Spec creation and repositioning in the numbered sequence. Covers the `.specify/scripts/bash/` tooling, the NNN-Title naming convention, and the directory structure under `docs/vault/Specs/NNN Title/`. Loaded when creating, renumbering, or splitting specs. |
| `blog-posts` | `.opencode/skills/blog-posts/SKILL.md` | Jekyll devlog post authoring for the GitHub Pages site. Covers the time-release scheduling mechanism (`future: false` in frontmatter), the post frontmatter contract, and the deploy-site workflow. Loaded when drafting or scheduling a blog post. |
No skill currently embeds an MCP server in its frontmatter. Skills reference MCP tools descriptively in their bodies (for example, `observer-qa` describes the GDAI MCP screenshot workflow), but the skill loader does not bind MCP servers automatically. Agents must have the relevant MCP server configured in `opencode.json` for the skill's instructions to be actionable.
## MCP Server Inventory
Four MCP servers are wired into the project. All are local processes spawned per session — there are no remote MCP endpoints.
| MCP Server | Type | Entry Point | Purpose |
|---|---|---|---|
| `gdai-mcp-server` | local | `uv run src/addons/gdai-mcp-plugin-godot/gdai_mcp_server.py` | Godot editor and runtime access. Exposes scene control (`play_scene`, `stop_running_scene`), visual capture (`get_running_scene_screenshot`), input injection (`simulate_input`), node search (`search_nodes` with `mode="running_scene"`), and editor script execution (`execute_editor_script`). The server-side Python process talks to the Godot editor through HTTP calls against the paired GDScript plugin (`gdai_mcp_plugin.gd` + `gdai_mcp_runtime.gd`) loaded as a Godot editor plugin. Required for visual QA, runtime verification, and SC-001 baseline capture. The only MCP server in `opencode.json` without an explicit `enabled: true` flag (it relies on the OpenCode default-enabled behaviour). |
| `obsidian` (mcpvault) | local | `npx -y @bitbonsai/mcpvault@0.11.0 docs/vault/` | Vault knowledge-graph read/write. See [[Tools — mcpvault]] for the full tool surface (15 tools including `read_note`, `search_notes`, `write_note`, `append_to_note`, `update_frontmatter`, `manage_tags`, `list_vault`). Required for vault session-start reads and write-back of decisions, discoveries, and session logs. |
| `blender` | local | `uv run --project ~/Workbench/Repositories/blender-mcp blender-mcp` | Blender 3D pipeline automation. Used by the `tools/blender/` pipeline for mesh separation, UV unwrap, LOD generation, and GLB export. Telemetry disabled via environment variable. |
| `comfyui` | local | `comfyui-mcp-server/.venv/bin/python3 server.py --stdio` | ComfyUI workflow execution for AI texture generation. Configured with `COMFYUI_URL=http://localhost:8000`, an output root under the user's ComfyUI install, and a project-local workflow directory at `tools/comfyui/workflows/`. Used by the texture-generation pipeline that turns Blender renders into PBR textures via SDXL img2img and RealESRGAN upscaling. |
The `.concourse.json` file enables `gdai-mcp-server` and the `gdscript` LSP at the Concourse repo-profile level, so any session opened by Concourse against this repo automatically gets Godot runtime access. The full MCP set in `opencode.json` is additive and used by direct OpenCode sessions.
## Invocation Patterns
Skills are loaded one of two ways: directly via the `skill(name="...")` tool, which injects the skill content into the current conversation; or as the `load_skills` parameter on a `task(...)` delegation, which injects the skill into the spawned subagent's context. The `load_skills` parameter is required on every `task` call — pass an empty list (`load_skills=[]`) when no skill applies.
MCP servers are auto-started by OpenCode at session creation based on `opencode.json` and `.concourse.json`. Their tools become available as namespaced calls (for example, `mcp.gdai-mcp-server.play_scene`). Agents do not invoke MCP servers explicitly; they call the exposed tools directly once a session is live.
The two surfaces combine in delegation. A typical generation-code task delegates to a subagent with `load_skills=["observer-qa"]`, expecting that subagent to follow the visual QA workflow using the `gdai-mcp-server` MCP tools. The skill provides the protocol; the MCP provides the capability.
## Skill ↔ MCP ↔ Agent Coupling
The intended runtime coupling is documented below. No code currently enforces it — the bindings are conventional, not configured.
| Skill | Primary MCP | Secondary Tools | Typical Agent |
|---|---|---|---|
| `observer-qa` | `gdai-mcp-server` | `tools/observer/` CLI for sequence captures and CI gates | Sisyphus (orchestrator); subagents working on generation code |
| `audio-assets` | none (filesystem only) | `tools/tts/` scripts, `make tts-*` targets, `make radio-*` targets | Sisyphus-Junior delegated for content tasks |
| `introspection` | `obsidian` (mcpvault) | `scripts/ci/vault_audit.py`, opencode `vault-audit-semantic` agent | Sisyphus + dedicated `vault-audit-semantic` subagent |
| `spec-management` | `obsidian` (mcpvault) | `.specify/scripts/bash/` shell tooling | Sisyphus or human via slash commands |
| `blog-posts` | `obsidian` (mcpvault) | Jekyll site under `site/`, `make site-*` targets | Sisyphus or GTM persona |
The [[Tools — Development Workbench]] note carries the full layer diagram showing how agents, skills, and MCP servers stack on top of the persona runtime, the vault, the spec system, and the CI/CD layer.
## GDAI MCP Plugin — Two-Process Architecture
The `gdai-mcp-server` MCP is unusual: it is not a single process but a paired system. The Python MCP server at `src/addons/gdai-mcp-plugin-godot/gdai_mcp_server.py` is the agent-facing surface; it speaks MCP to OpenCode. The GDScript plugin (`gdai_mcp_plugin.gd` and `gdai_mcp_runtime.gd`) is loaded by the Godot editor as an `EditorPlugin` and stands up an HTTP server inside the Godot process that exposes the actual runtime hooks (scene control, screenshot capture, input simulation). The Python MCP server reaches the editor by issuing HTTP requests via `httpx` against `http://localhost:3571` (the port is overridable via the `GDAI_MCP_SERVER_PORT` environment variable). The plugin must be installed and enabled in the Godot editor for the MCP server's tools to do anything — an MCP server with no editor on the other end returns connection errors. The plugin lives in `src/addons/`, is vendored (not edited), and is excluded from first-party linting and formatting.
## Limitations
The MCP-first model has known constraints documented in the `observer-qa` skill and AGENTS.md:
- **Editor script context** — `execute_editor_script` runs in the editor process, not the running game. It cannot call autoload signals or access running scene nodes via `Engine.get_main_loop()` while the game is playing.
- **Log buffer truncation** — `get_godot_errors(num_lines=200)` returns a rolling tail. Mid-generation log lines are often lost. Use `user://logs/` file writes for durable diagnostics (the `veg_counts.json` and `generation_*.json` files follow this pattern).
- **GDExtension editor instantiation** — Instantiating Rust GDExtension nodes via `ClassDB.instantiate()` in an editor script produces placeholder instances that cannot call Rust methods. Run the full game scene instead.
These limitations push agents toward `play_scene` + `simulate_input` + filesystem reads, which is the SC-001 / vegetation baseline procedure documented in AGENTS.md.
## Related
- [[Tools — mcpvault]] — full surface of the vault MCP server (15 tools)
- [[Tools — Development Workbench]] — meta-architecture showing skills + MCP in context
- [[Tools — Observer]] — the Observer CLI that pairs with the `observer-qa` skill
- [[Tools — TTS]] — TTS pipeline that pairs with the `audio-assets` skill
- [[Tools — Vault Audit]] — vault audit tooling that pairs with the `introspection` skill
- [[Code]] — folder MOC
- [[Tooling — Domain MOC]] — domain MOC
## References
- `opencode.json` — MCP server configuration (gdai-mcp-server, obsidian, blender, comfyui) and GDScript LSP
- `.concourse.json` — Concourse repo-profile hook (gdai-mcp-server + gdscript)
- `.opencode/skills/SKILL.md` — `observer-qa` skill (root)
- `.opencode/skills/audio-assets/SKILL.md`
- `.opencode/skills/introspection/SKILL.md`
- `.opencode/skills/spec-management/SKILL.md`
- `.opencode/skills/blog-posts/SKILL.md`
- `src/addons/gdai-mcp-plugin-godot/gdai_mcp_server.py` — Python MCP server entry point
- `src/addons/gdai-mcp-plugin-godot/gdai_mcp_plugin.gd` — Godot editor plugin
- `src/addons/gdai-mcp-plugin-godot/gdai_mcp_runtime.gd` — runtime GDScript bridge
- `AGENTS.md` — Godot MCP usage protocol and SC-001 baseline procedure
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-code-tools-agent-skills-mcp: type/code
Tag assignment · test- Gold
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- ember
- The note has no reason to carry `type/code` (False)
- Signal
- P(true) 0.40 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Code/Tools — Agent Skills & MCP.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Tools — Agent Skills & MCP
The skill system and the MCP (Model Context Protocol) server set form the two extension surfaces that turn a generic agent into an Old Growth agent. Skills inject domain instructions into the conversation context; MCP servers extend the tool surface with external capabilities. This note inventories both, documents the invocation pattern, and maps which agents consume which surfaces. It complements [[Tools — mcpvault]], which describes the vault MCP server in depth.
## The Two Surfaces, Compared
Skills are bodies of instructions loaded into the conversation context on demand. They contain workflows, decision matrices, and references to bundled scripts. They do not add new tools — they teach the agent how to use existing tools and external systems correctly. Skills live under `.opencode/skills/` and are loaded via the `skill` tool or as the `load_skills` parameter on the `task` tool.
MCP servers are processes (local or remote) that expose tools, resources, and prompts to the agent over the Model Context Protocol. They add new tool calls — for example, `gdai-mcp-server` exposes `play_scene`, `get_running_scene_screenshot`, `simulate_input`, and other Godot runtime operations as agent-callable tools. MCP servers are configured in `opencode.json` (project-local) and `.concourse.json` (Concourse repo-profile hook).
The two surfaces compose: a skill (e.g. `observer-qa`) tells the agent the correct workflow for visual QA; an MCP server (e.g. `gdai-mcp-server`) provides the tools that workflow invokes.
## Skill Inventory
Five skills ship in `.opencode/skills/`. Each is a directory containing a `SKILL.md` file with frontmatter (description, triggers) and a body of instructions; some bundle additional scripts or reference files.
| Skill | Directory | Purpose |
|---|---|---|
| `observer-qa` | `.opencode/skills/SKILL.md` (root) | MCP-first visual QA pipeline. Documents the GDAI MCP screenshot-and-analyse workflow as primary, the Observer CLI as secondary for sequence captures and CI gates, the agent trigger matrix, and the decision matrix that maps `(quality, recommendation)` pairs to ship/block actions. Loaded when working on generation code, world preview, or visual artefacts. |
| `audio-assets` | `.opencode/skills/audio-assets/SKILL.md` | TTS generation tooling, audio submodule workflow, naming conventions, and provenance requirements for Old Growth radio and ambient weather content. Loaded when authoring radio clips, KTLK Gary dialogue, weather ambient SFX, or station music. |
| `introspection` | `.opencode/skills/introspection/SKILL.md` | Vault knowledge-graph audit. Documents the three-phase pipeline (mechanical via `scripts/ci/vault_audit.py`, auto-fix via `--apply`, semantic drift via the `vault-audit-semantic` agent) and the rolling `vault/audit` PR convention. Loaded when running a vault health check, interpreting audit output, or drafting correction PRs. |
| `spec-management` | `.opencode/skills/spec-management/SKILL.md` | Spec creation and repositioning in the numbered sequence. Covers the `.specify/scripts/bash/` tooling, the NNN-Title naming convention, and the directory structure under `docs/vault/Specs/NNN Title/`. Loaded when creating, renumbering, or splitting specs. |
| `blog-posts` | `.opencode/skills/blog-posts/SKILL.md` | Jekyll devlog post authoring for the GitHub Pages site. Covers the time-release scheduling mechanism (`future: false` in frontmatter), the post frontmatter contract, and the deploy-site workflow. Loaded when drafting or scheduling a blog post. |
No skill currently embeds an MCP server in its frontmatter. Skills reference MCP tools descriptively in their bodies (for example, `observer-qa` describes the GDAI MCP screenshot workflow), but the skill loader does not bind MCP servers automatically. Agents must have the relevant MCP server configured in `opencode.json` for the skill's instructions to be actionable.
## MCP Server Inventory
Four MCP servers are wired into the project. All are local processes spawned per session — there are no remote MCP endpoints.
| MCP Server | Type | Entry Point | Purpose |
|---|---|---|---|
| `gdai-mcp-server` | local | `uv run src/addons/gdai-mcp-plugin-godot/gdai_mcp_server.py` | Godot editor and runtime access. Exposes scene control (`play_scene`, `stop_running_scene`), visual capture (`get_running_scene_screenshot`), input injection (`simulate_input`), node search (`search_nodes` with `mode="running_scene"`), and editor script execution (`execute_editor_script`). The server-side Python process talks to the Godot editor through HTTP calls against the paired GDScript plugin (`gdai_mcp_plugin.gd` + `gdai_mcp_runtime.gd`) loaded as a Godot editor plugin. Required for visual QA, runtime verification, and SC-001 baseline capture. The only MCP server in `opencode.json` without an explicit `enabled: true` flag (it relies on the OpenCode default-enabled behaviour). |
| `obsidian` (mcpvault) | local | `npx -y @bitbonsai/mcpvault@0.11.0 docs/vault/` | Vault knowledge-graph read/write. See [[Tools — mcpvault]] for the full tool surface (15 tools including `read_note`, `search_notes`, `write_note`, `append_to_note`, `update_frontmatter`, `manage_tags`, `list_vault`). Required for vault session-start reads and write-back of decisions, discoveries, and session logs. |
| `blender` | local | `uv run --project ~/Workbench/Repositories/blender-mcp blender-mcp` | Blender 3D pipeline automation. Used by the `tools/blender/` pipeline for mesh separation, UV unwrap, LOD generation, and GLB export. Telemetry disabled via environment variable. |
| `comfyui` | local | `comfyui-mcp-server/.venv/bin/python3 server.py --stdio` | ComfyUI workflow execution for AI texture generation. Configured with `COMFYUI_URL=http://localhost:8000`, an output root under the user's ComfyUI install, and a project-local workflow directory at `tools/comfyui/workflows/`. Used by the texture-generation pipeline that turns Blender renders into PBR textures via SDXL img2img and RealESRGAN upscaling. |
The `.concourse.json` file enables `gdai-mcp-server` and the `gdscript` LSP at the Concourse repo-profile level, so any session opened by Concourse against this repo automatically gets Godot runtime access. The full MCP set in `opencode.json` is additive and used by direct OpenCode sessions.
## Invocation Patterns
Skills are loaded one of two ways: directly via the `skill(name="...")` tool, which injects the skill content into the current conversation; or as the `load_skills` parameter on a `task(...)` delegation, which injects the skill into the spawned subagent's context. The `load_skills` parameter is required on every `task` call — pass an empty list (`load_skills=[]`) when no skill applies.
MCP servers are auto-started by OpenCode at session creation based on `opencode.json` and `.concourse.json`. Their tools become available as namespaced calls (for example, `mcp.gdai-mcp-server.play_scene`). Agents do not invoke MCP servers explicitly; they call the exposed tools directly once a session is live.
The two surfaces combine in delegation. A typical generation-code task delegates to a subagent with `load_skills=["observer-qa"]`, expecting that subagent to follow the visual QA workflow using the `gdai-mcp-server` MCP tools. The skill provides the protocol; the MCP provides the capability.
## Skill ↔ MCP ↔ Agent Coupling
The intended runtime coupling is documented below. No code currently enforces it — the bindings are conventional, not configured.
| Skill | Primary MCP | Secondary Tools | Typical Agent |
|---|---|---|---|
| `observer-qa` | `gdai-mcp-server` | `tools/observer/` CLI for sequence captures and CI gates | Sisyphus (orchestrator); subagents working on generation code |
| `audio-assets` | none (filesystem only) | `tools/tts/` scripts, `make tts-*` targets, `make radio-*` targets | Sisyphus-Junior delegated for content tasks |
| `introspection` | `obsidian` (mcpvault) | `scripts/ci/vault_audit.py`, opencode `vault-audit-semantic` agent | Sisyphus + dedicated `vault-audit-semantic` subagent |
| `spec-management` | `obsidian` (mcpvault) | `.specify/scripts/bash/` shell tooling | Sisyphus or human via slash commands |
| `blog-posts` | `obsidian` (mcpvault) | Jekyll site under `site/`, `make site-*` targets | Sisyphus or GTM persona |
The [[Tools — Development Workbench]] note carries the full layer diagram showing how agents, skills, and MCP servers stack on top of the persona runtime, the vault, the spec system, and the CI/CD layer.
## GDAI MCP Plugin — Two-Process Architecture
The `gdai-mcp-server` MCP is unusual: it is not a single process but a paired system. The Python MCP server at `src/addons/gdai-mcp-plugin-godot/gdai_mcp_server.py` is the agent-facing surface; it speaks MCP to OpenCode. The GDScript plugin (`gdai_mcp_plugin.gd` and `gdai_mcp_runtime.gd`) is loaded by the Godot editor as an `EditorPlugin` and stands up an HTTP server inside the Godot process that exposes the actual runtime hooks (scene control, screenshot capture, input simulation). The Python MCP server reaches the editor by issuing HTTP requests via `httpx` against `http://localhost:3571` (the port is overridable via the `GDAI_MCP_SERVER_PORT` environment variable). The plugin must be installed and enabled in the Godot editor for the MCP server's tools to do anything — an MCP server with no editor on the other end returns connection errors. The plugin lives in `src/addons/`, is vendored (not edited), and is excluded from first-party linting and formatting.
## Limitations
The MCP-first model has known constraints documented in the `observer-qa` skill and AGENTS.md:
- **Editor script context** — `execute_editor_script` runs in the editor process, not the running game. It cannot call autoload signals or access running scene nodes via `Engine.get_main_loop()` while the game is playing.
- **Log buffer truncation** — `get_godot_errors(num_lines=200)` returns a rolling tail. Mid-generation log lines are often lost. Use `user://logs/` file writes for durable diagnostics (the `veg_counts.json` and `generation_*.json` files follow this pattern).
- **GDExtension editor instantiation** — Instantiating Rust GDExtension nodes via `ClassDB.instantiate()` in an editor script produces placeholder instances that cannot call Rust methods. Run the full game scene instead.
These limitations push agents toward `play_scene` + `simulate_input` + filesystem reads, which is the SC-001 / vegetation baseline procedure documented in AGENTS.md.
## Related
- [[Tools — mcpvault]] — full surface of the vault MCP server (15 tools)
- [[Tools — Development Workbench]] — meta-architecture showing skills + MCP in context
- [[Tools — Observer]] — the Observer CLI that pairs with the `observer-qa` skill
- [[Tools — TTS]] — TTS pipeline that pairs with the `audio-assets` skill
- [[Tools — Vault Audit]] — vault audit tooling that pairs with the `introspection` skill
- [[Code]] — folder MOC
- [[Tooling — Domain MOC]] — domain MOC
## References
- `opencode.json` — MCP server configuration (gdai-mcp-server, obsidian, blender, comfyui) and GDScript LSP
- `.concourse.json` — Concourse repo-profile hook (gdai-mcp-server + gdscript)
- `.opencode/skills/SKILL.md` — `observer-qa` skill (root)
- `.opencode/skills/audio-assets/SKILL.md`
- `.opencode/skills/introspection/SKILL.md`
- `.opencode/skills/spec-management/SKILL.md`
- `.opencode/skills/blog-posts/SKILL.md`
- `src/addons/gdai-mcp-plugin-godot/gdai_mcp_server.py` — Python MCP server entry point
- `src/addons/gdai-mcp-plugin-godot/gdai_mcp_plugin.gd` — Godot editor plugin
- `src/addons/gdai-mcp-plugin-godot/gdai_mcp_runtime.gd` — runtime GDScript bridge
- `AGENTS.md` — Godot MCP usage protocol and SC-001 baseline procedure
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-garage-map-shows-previous-run: domain/player
Tag assignment · test- Gold
- The note calls for `domain/player`: Player mode, input, controls, camera, exit/entry (True)
- ember
- The note has no reason to carry `domain/player` (False)
- Signal
- P(true) 0.14 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/Garage Map Shows Previous Run.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Garage Map Shows Previous Run
This note records the intentional design decision that the garage map (spec 092) displays the world from the player's previous run rather than generating or previewing the upcoming world.
## The Decision
When the player opens the garage map between runs, the map renders the road network, biome regions, and points of interest from their last completed (or abandoned) run. The upcoming world's seed has already been determined, but the map does not show it.
## The Reasoning
This decision serves the horror register in two ways.
First, it creates a false-safety trap. The player studies the map, plans a route, identifies landmarks. All of that planning is worthless because they are looking at a world that no longer exists. The forest resets between runs. The map is a memento of somewhere you can never return to.
Second, it reinforces the ephemerality and irreversibility themes that run through the game's design. The map is not a planning tool in the conventional sense — it is evidence of a previous encounter. It tells you what you survived (or didn't), not what you are about to face.
## Implementation Constraint
The garage map must read from persisted run data (the previous run's world record), not from the current GameState seed. It must not call any world generation functions. If no previous run exists (first run), the map shows an empty or fog-of-war state — not a generated world.
## References
- docs/vault/Specs/092 Garage Map and Trip Planner/092 Garage Map and Trip Planner.md
- docs/vault/Ephemerality Thread.md
- docs/vault/The Disturbance Thread.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-garage-map-shows-previous-run: layer/gdscript
Tag assignment · test- Gold
- The note calls for `layer/gdscript`: GDScript (Godot scene-tier logic) (True)
- ember
- The note has no reason to carry `layer/gdscript` (False)
- Signal
- P(true) 0.05 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/Garage Map Shows Previous Run.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Garage Map Shows Previous Run
This note records the intentional design decision that the garage map (spec 092) displays the world from the player's previous run rather than generating or previewing the upcoming world.
## The Decision
When the player opens the garage map between runs, the map renders the road network, biome regions, and points of interest from their last completed (or abandoned) run. The upcoming world's seed has already been determined, but the map does not show it.
## The Reasoning
This decision serves the horror register in two ways.
First, it creates a false-safety trap. The player studies the map, plans a route, identifies landmarks. All of that planning is worthless because they are looking at a world that no longer exists. The forest resets between runs. The map is a memento of somewhere you can never return to.
Second, it reinforces the ephemerality and irreversibility themes that run through the game's design. The map is not a planning tool in the conventional sense — it is evidence of a previous encounter. It tells you what you survived (or didn't), not what you are about to face.
## Implementation Constraint
The garage map must read from persisted run data (the previous run's world record), not from the current GameState seed. It must not call any world generation functions. If no previous run exists (first run), the map shows an empty or fog-of-war state — not a generated world.
## References
- docs/vault/Specs/092 Garage Map and Trip Planner/092 Garage Map and Trip Planner.md
- docs/vault/Ephemerality Thread.md
- docs/vault/The Disturbance Thread.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-decisions-garage-map-shows-previous-run: status/reviewed
Tag assignment · test- Gold
- The note calls for `status/reviewed`: Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `status/reviewed` (False)
- Signal
- P(true) 0.19 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Decisions/Garage Map Shows Previous Run.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Garage Map Shows Previous Run
This note records the intentional design decision that the garage map (spec 092) displays the world from the player's previous run rather than generating or previewing the upcoming world.
## The Decision
When the player opens the garage map between runs, the map renders the road network, biome regions, and points of interest from their last completed (or abandoned) run. The upcoming world's seed has already been determined, but the map does not show it.
## The Reasoning
This decision serves the horror register in two ways.
First, it creates a false-safety trap. The player studies the map, plans a route, identifies landmarks. All of that planning is worthless because they are looking at a world that no longer exists. The forest resets between runs. The map is a memento of somewhere you can never return to.
Second, it reinforces the ephemerality and irreversibility themes that run through the game's design. The map is not a planning tool in the conventional sense — it is evidence of a previous encounter. It tells you what you survived (or didn't), not what you are about to face.
## Implementation Constraint
The garage map must read from persisted run data (the previous run's world record), not from the current GameState seed. It must not call any world generation functions. If no previous run exists (first run), the map shows an empty or fog-of-war state — not a generated world.
## References
- docs/vault/Specs/092 Garage Map and Trip Planner/092 Garage Map and Trip Planner.md
- docs/vault/Ephemerality Thread.md
- docs/vault/The Disturbance Thread.md
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-art-visual-direction: domain/tooling
Tag assignment · test- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.24 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Art/Visual Direction.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Visual Direction
The overall visual direction for Old Growth AND the complete agent-driven visual asset generation pipeline — Flux.1 [schnell] + ComfyUI + custom LoRA + "artisan" agent producing textures and concept art. This note captures both the aesthetic framing and the authoritative technical pipeline specification.
## Overview — Aesthetic Direction
Old Growth pursues **stylized realism**. Atmospheric mood takes precedence over graphical fidelity (Article IX). Key references: Simon Stålenhag's paintings, the organic corruption of *Annihilation*, the zone hazards of *Roadside Picnic*, the deep woods of *Twin Peaks*, and the ruined overgrowth of *The Last of Us*.
**Atmosphere is a design constraint, not decoration** (Article IX, Article XII-K). Every new system, asset, and mechanic is evaluated against: *Does this serve or undermine the feeling that this forest is ancient, alive, and hostile?* If it undermines it, it is redesigned or removed.
The visual direction serves the central horror: **the player is very small inside a condition that does not notice them**. Scale must feel overwhelming at all times. An 80-meter tree makes the Samba look like a toy. The player should never feel large in this world.
## Design Intent — Key Decisions
- **Stylized realism, not photorealism**: composition, color, value, light shape are the primary tools. Polygon counts and shader complexity are secondary. Painted, not rendered. Silhouette first — any new asset must read silhouette-first at gameplay distance.
- **Biome as palette gradient** (see [[Palettes]]): color grading shifts per biome. The gradient itself is the storytelling device. Transitions between biomes are gradual — light, sound, and bus behavior shift before the player consciously names the change.
- **Samba silhouette as identity anchor** (Article XII-G): the Samba's silhouette is a primary identity mark. Readable at any time of day or weather. New weather states or lighting conditions must be validated against Samba silhouette readability before shipping.
- **Motivated light**: all scene lighting must appear to come from plausible in-world sources (sun angle, headlights, bioluminescence, fire). Unmotivated fill lights prohibited unless behind a debug-only flag.
- **Night is dark**: not atmospherically dark — *genuinely* dark, the kind that requires headlights for basic navigation. Lets Amber Bleed anomaly's amber glow read clearly from a distance, and makes the garage window's view matter.
- **Visual language is consistent**: players learn anomaly signatures from a distance. Mycelium has a specific luminosity. Hollow Trees have a specific silhouette. Petrified Zones' grey particulate accumulates in recognizable patterns. The world teaches its own grammar (Article XII-G).
- **Value contrast passes greyscale**: all critical visual communication must be tested in greyscale before shipping (Article XII-G). A composition that only works in color is fragile.
- **Accessibility**: color-coded information must be paired with a secondary differentiator (shape, value, icon). Deuteranopia/protanopia simulation must be run before finalizing any color-coded system.
## Visual Asset Pipeline — Overview
The pipeline is an agent-driven visual asset generation workflow producing texture/material candidates and concept art/reference imagery. It mirrors the audio pipeline architecture (submodule + Git LFS + spec-driven agent dispatch) and slots into the Concourse infrastructure alongside existing MLX inference services.
**What this is NOT**: a replacement for hand-crafted or purchased assets. The pipeline produces candidates and reference material, subject to human review and acceptance before integration into Godot.
## Stack
| Component | Role | Details |
|---|---|---|
| **Flux.1 [schnell]** | Primary generation model | Apache 2.0 — fully commercial. 4-step generation. fp16 full precision viable at 128 GB. Quality gap vs [dev] is real but closed significantly by a well-tuned LoRA. |
| **Flux.1 [dev]** | ❌ Excluded | Non-commercial license only. Not viable for a shipping title. |
| **ComfyUI** | Workflow engine | Runs natively on Apple Silicon via MPS + MLX extension. Port 8189 (offset from existing inference on 8001/8002). |
| **ComfyUI-MLX** | Acceleration layer | 50–70% speed improvement over PyTorch MPS on M-series. Installed via Custom Nodes Manager. |
| **comfyui-mcp** | MCP server | `@peleke.s/comfyui-mcp` via npx. Exposes 40 tools (txt2img, img2img, upscale, inpaint, outpaint, ControlNet, LoRA injection). Auto-detects Flux (natural language) vs SDXL (tag) prompting strategy. |
| **artisan agent** | oh-my-openagent agent | Reads biome specs, constructs prompts, dispatches generation jobs via comfyui-mcp, organizes outputs. |
## Infrastructure Setup
### ComfyUI Installation
```bash
cd ~/concourse
git clone https://github.com/comfyanonymous/ComfyUI
cd ComfyUI
python -m venv ../.venv-comfyui # outside repo to avoid git noise
source ../.venv-comfyui/bin/activate
pip install torch torchvision torchaudio # MPS backend, Apple Silicon
pip install -r requirements.txt
# Launch on non-conflicting port
python main.py --port 8189 --listen 127.0.0.1
```
### ComfyUI as Persistent Service (launchd)
Full plist at `~/Library/LaunchAgents/com.concourse.comfyui.plist` runs ComfyUI at login with `RunAtLoad`/`KeepAlive` true, logs to `/tmp/comfyui.{log,err}`. Load with `launchctl load ~/Library/LaunchAgents/com.concourse.comfyui.plist`. Full plist XML in archived spec.
### MLX Extension
ComfyUI Manager → Custom Nodes Manager → search "ComfyUI MLX" → Install. Native MLX inference nodes for Flux models.
### Model Weights Layout
```
ComfyUI/models/
├── checkpoints/
│ ├── flux1-schnell-fp8.safetensors # inference — Apache 2.0, commercial OK
│ └── flux1-schnell.safetensors # full precision — for LoRA training only
├── text_encoders/
│ ├── clip_l.safetensors
│ └── t5xxl_fp16.safetensors # fp16 recommended with 128GB
├── vae/
│ └── ae.safetensors
├── loras/
│ └── pnw-oldgrowth-v1.safetensors # trained PNW style LoRA
└── upscale_models/
└── RealESRGAN_x4plus.pth # 4x upscale for final texture exports
```
### MCP Server Registration
Add to oh-my-openagent MCP config:
```json
{
"comfyui": {
"command": "npx",
"args": ["-y", "@peleke.s/comfyui-mcp"],
"env": {
"COMFYUI_URL": "http://127.0.0.1:8189",
"COMFYUI_MODEL": "flux1-schnell-fp8.safetensors"
}
}
}
```
## Artisan Agent
### Role Definition
```yaml
name: artisan
port: null # stdio MCP, no inference port
model: claude-sonnet-4 # local not needed; task is orchestration not inference
tools:
- comfyui (txt2img, img2img, upscale, list_models, list_loras)
- filesystem (read biome specs, write output manifests)
responsibilities:
- Read canonical biome specs and extract visual descriptors
- Construct Flux-optimized natural language prompts
- Dispatch generation jobs with correct workflow type (texture vs concept)
- Organize outputs into asset submodule structure
- Write generation manifests for reproducibility
out_of_scope:
- Asset acceptance/rejection (human review gate)
- Godot import configuration
- LoRA training
```
### Task Dispatch Schema
```yaml
task_type: generate_assets
biome: verdant_old_growth # canonical biome name
asset_type: texture # texture | concept_art
material_class: bark # see Asset Taxonomy below
batch_size: 4 # candidates per run
seed: 42 # null for random; set for reproducibility
lora:
name: pnw-oldgrowth-v1
strength: 0.75
output_dir: assets/textures/verdant_old_growth/bark/
```
## Biome Prompt Schema
### Canonical Biome Descriptors
Each biome encodes the visual language artisan uses to construct Flux prompts.
**Verdant Old Growth**:
```yaml
palette:
dominant: ["deep forest green", "rich brown", "dark umber"]
accent: ["pale fern green", "moss yellow-green", "filtered golden light"]
temperature: cool-neutral
saturation: high
lighting:
quality: diffused, overcast Pacific Northwest canopy light
shadows: soft, deep, layered
atmosphere: morning mist, light fog between trunks
flora:
dominant: ["Douglas fir bark", "western red cedar", "sword fern", "oxalis"]
ground: ["decomposing leaf litter", "duff", "nurse log"]
micro: ["lichen patches", "liverwort", "club moss"]
mood_keywords: ["ancient", "cathedral quiet", "primordial", "damp"]
negative_prompts: ["sunlit clearing", "dry", "desert", "tropical", "bright sky"]
```
**Fungal Depths**:
```yaml
palette:
dominant: ["near-black brown", "deep slate grey", "dark purple-brown"]
accent: ["pale bioluminescent blue-white", "amber mycelium", "rust orange spore"]
temperature: very cool, desaturated with isolated warm glow
saturation: low overall, high on fungal accents
lighting:
quality: near-darkness with self-illuminating fungal sources
shadows: extremely deep, minimal fill
atmosphere: still air, spore-laden, no wind
flora:
dominant: ["oyster mushroom clusters", "turkey tail fungus", "giant puffball"]
ground: ["dense mycelium network", "decayed wood pulp", "white hyphal threads"]
micro: ["bioluminescent fungal patches", "spore clouds"]
mood_keywords: ["primordial", "alien", "silent", "decomposition", "network"]
negative_prompts: ["sunlight", "bright", "colorful flowers", "living trees"]
```
**The Petrifold**:
```yaml
palette:
dominant: ["grey-blue mineral", "bleached white", "pale ash"]
accent: ["rust iron stain", "pale ochre mineral deposit", "ice blue crack"]
temperature: cold, desaturated
saturation: very low
lighting:
quality: flat grey overcast, harsh shadowless diffusion
shadows: minimal, diffuse
atmosphere: cold mineral stillness, no organic warmth
flora:
dominant: ["petrified tree trunk", "calcified branch", "mineral deposit column"]
ground: ["cracked mineral surface", "calcified root network", "ash grey soil"]
micro: ["crystal inclusions", "iron oxide streaks", "pale lichen on stone"]
mood_keywords: ["ancient death", "geological time", "silence", "crystalline"]
negative_prompts: ["green", "living", "warm", "lush", "brown soil"]
```
### Prompt Construction Rules
Artisan uses these templates. **Flux.1 uses natural language — do NOT use comma-separated tag lists.**
**Texture prompt template**:
```
A seamless tileable texture of [material], [biome.flora.dominant context],
[biome.palette.dominant colors], [biome.lighting.quality],
[biome.mood_keywords], highly detailed surface, macro photography,
physically based rendering material, no horizon, no sky, flat lay perspective
```
**Concept art prompt template**:
```
[subject or scene description], [biome name] forest, Pacific Northwest,
[biome.palette.dominant and accent colors], [biome.lighting.quality],
[biome.atmosphere], [biome.mood_keywords], cinematic composition,
environmental concept art, matte painting style
```
**Negative prompt (all types)**:
```
[biome.negative_prompts], watermark, signature, text, blurry,
low quality, cartoon, anime, illustration style, flat design
```
## Asset Taxonomy
### Textures
Target: 1024×1024 seamless tiles, upscaled to 2048×2048 for Godot import.
**Important**: Flux does not natively generate seamless/tileable textures. The ComfyUI workflow for textures must include a seamless tiling node chain. Use the `SeamlessTile` custom node (available via ComfyUI Manager) or circular padding in the VAE decode step. Without this, textures will show visible seams when tiled.
**PBR map pipeline**: Godot's `StandardMaterial3D` uses albedo, normal, roughness, AO maps. This pipeline generates albedo directly. Derive remaining maps from accepted albedo outputs using:
- **Normal map**: `generate-texture` node in ComfyUI with a normal map ControlNet, or post-process in Materialize / AwesomeBump (free, offline)
- **Roughness/AO**: Materialize or xNormal from accepted albedo — automated batch processing feasible
- Normal/roughness derivation is out of scope for artisan agent — post-acceptance step before Godot import
```
textures/
├── verdant_old_growth/
│ ├── bark/ # douglas_fir, cedar, alder, mossy_bark
│ ├── ground/ # forest_duff, leaf_litter, exposed_root, muddy_soil
│ ├── rock/ # mossy_boulder, exposed_basalt, lichen_rock
│ └── organic/ # fern_frond, sword_fern_bed, oxalis_patch, club_moss
├── fungal_depths/
│ ├── fungal/ # oyster_cluster, turkey_tail, puffball, mycelium_sheet
│ ├── ground/ # decayed_wood_pulp, dense_mycelium, hyphal_mat
│ └── wood/ # heavily_decayed, nurse_log_surface, heartwood_rot
└── the_petrifold/
├── mineral/ # petrified_bark, calcified_surface, mineral_column
├── ground/ # cracked_mineral, ash_soil, calcified_root
└── crystal/ # iron_stain, crystal_inclusion, calcite_vein
```
Each accepted albedo texture gets a sibling set before Godot import:
```
bark_douglas_fir_0042_001_albedo.png
bark_douglas_fir_0042_001_normal.png
bark_douglas_fir_0042_001_roughness.png
bark_douglas_fir_0042_001_ao.png
```
### Concept Art / Reference
Target: 1920×1080 or 1280×720. Not for direct game use — reference and pitch material only.
```
concept/
├── verdant_old_growth/
│ ├── wide/ # full environment establishing shots
│ ├── road/ # forest road reference for driving sections
│ └── detail/ # close-up flora reference
├── fungal_depths/
│ ├── wide/
│ ├── bioluminescence/ # lighting reference for Godot environment shader
│ └── detail/
└── the_petrifold/
├── wide/
├── transition/ # boundary zones between biomes
└── detail/
```
## Output Structure & Git LFS
### Submodule
```bash
git submodule add git@github.com:shapeandshare/old-growth-assets assets
cd assets
git lfs install
```
### LFS Tracking
```bash
git lfs track "*.png"
git lfs track "*.jpg"
git lfs track "*.exr" # for HDR concept exports
git lfs track "*.safetensors" # track LoRA weights in assets repo
git add .gitattributes
```
### Asset Filename Convention
```
{material_slug}_{seed}_{index}.png
# e.g. bark_douglas_fir_0042_001.png
# mycelium_sheet_1337_003.png
# concept_wide_verdant_0099_001.png
```
- `material_slug` — underscore-separated descriptor matching taxonomy leaf node
- `seed` — zero-padded to 4 digits
- `index` — zero-padded batch index (001–NNN)
### Generation Manifest
Each batch writes a `manifest.json` alongside its outputs:
```json
{
"generated_at": "2025-04-16T14:30:00Z",
"model": "flux1-schnell-fp8.safetensors",
"lora": { "name": "pnw-oldgrowth-v1", "strength": 0.75 },
"biome": "verdant_old_growth",
"asset_type": "texture",
"material_class": "bark/douglas_fir",
"prompt": "A seamless tileable texture of douglas fir bark...",
"negative_prompt": "sunlit clearing, dry...",
"seed": 42,
"steps": 4,
"cfg": 1.0,
"resolution": "1024x1024",
"outputs": [
{ "filename": "bark_douglas_fir_0042_001.png", "accepted": null },
{ "filename": "bark_douglas_fir_0042_002.png", "accepted": null }
]
}
```
`steps: 4` is correct for schnell (distilled 4-step model — more steps do not improve quality). `cfg: 1.0` is correct (schnell is guidance-free). `accepted` is `null` until human review sets to `true` or `false`.
## LoRA Strategy
### Why a LoRA is Essential
Flux.1 [schnell] generates competent but generic forests. The specific Pacific Northwest character — the particular quality of light through a doug fir canopy, the exact color of Oregon sword fern, the texture of nurse log decomposition — requires a fine-tuned style anchor. The LoRA is the highest-leverage single investment in this pipeline.
### Training Data Sources
Training data **must be real Pacific Northwest photography, not AI-generated images**. Real photographs give you:
- Authentic PNW light, color, texture rather than an AI approximation
- Legally clean training data with no ToS ambiguity
- Better LoRA quality — the model learns real-world material properties
**Do NOT use Midjourney outputs as training data.** The legal landscape around using AI-generated images to train other AI models is actively litigated and the ToS is ambiguous. Real CC photography is cleaner and produces better results.
**License requirements**: training data for a commercially-released game must be under licenses that permit commercial use. That means CC BY, CC BY-SA, Public Domain, or platform-specific licenses that explicitly cover ML training. CC BY-NC explicitly prohibits commercial use — do not use NC-licensed images.
**Primary sources (commercial-safe)**:
| Source | What to pull | License | Notes |
|---|---|---|---|
| **USFS Photo Library** | PNW national forest photography, old growth, flora | Public Domain | Cleanest possible — no restrictions |
| **Wikimedia Commons** | Douglas fir, cedar, Hoh Rainforest, Olympic NP, fungi, PNW flora | CC BY, CC BY-SA, PD | Filter by license — skip CC BY-NC and CC BY-ND |
| **Flickr CC** | `old growth forest pacific northwest`, `olympic peninsula`, `sword fern`, `douglas fir bark` | CC BY / CC BY-SA only | Must filter explicitly — default search includes NC. Use `license=4,5` URL params to filter to BY and BY-SA only |
| **Unsplash** | Forest, PNW wilderness, macro flora, fungi, bark textures | Unsplash License | Explicitly permits ML training including commercial products |
| **Pexels** | Same subject as Unsplash | Pexels License | Same — permissive, covers commercial ML training |
| **iNaturalist** | ⚠️ Flora macro for PNW species | CC BY-NC (most) | **Avoid for commercial training.** Most observations default to CC BY-NC. Filter to CC BY only but coverage is thin. Use as visual reference, not training data. |
### Dataset Composition
Target: 200–400 images, balanced across biomes.
```
training_data/
├── verdant_old_growth/ ~150 images
│ ├── wide_canopy/ # full environment, dappled light, fog
│ ├── bark_closeup/ # doug fir, cedar, alder — macro texture
│ ├── ground_flora/ # sword fern, oxalis, moss — ground level
│ └── root_nurse_log/ # decomposition, fallen trees
├── fungal_depths/ ~100 images
│ ├── fungi_macro/ # oyster, turkey tail, puffball — closeup
│ ├── mycelium/ # white hyphal networks on wood
│ └── dark_forest/ # heavily shaded, low light conditions
└── the_petrifold/ ~80 images
├── petrified_wood/ # mineral-replaced wood
├── mineral_texture/ # calcite, basalt, grey rock closeup
└── dead_forest/ # standing dead trees, grey/white tones
```
### Training Configuration
**Tool**: AI Toolkit (Kohya-based), compatible with `comfyui-mcp` LoRA upload API.
**Important**: Train on the full precision schnell model (`flux1-schnell.safetensors`), not the fp8 quantized checkpoint. Training on fp8 weights degrades gradient quality. With 128 GB unified memory this is not a constraint — the full ~24 GB model loads comfortably.
```yaml
model: flux1-schnell.safetensors # full precision for training
training_type: lora
rank: 16
alpha: 16
learning_rate: 1e-4
steps: 2000
batch_size: 1 # Apple Silicon MPS constraint
save_every: 500
```
The trained `pnw-oldgrowth-v1.safetensors` is used with the fp8 inference checkpoint at generation time — LoRA weights are precision-independent.
**Trigger words** injected automatically by artisan per biome:
- `verdant_og_style` → Verdant Old Growth
- `fungal_depths_style` → Fungal Depths
- `petrifold_style` → The Petrifold
### Midjourney's Role
MJ remains useful — just not as training data. Use manually through the web UI for:
- Concept exploration before committing to a biome palette direction
- Composition reference for concept art framing
- Prompt language testing — if a scene reads well in MJ, natural language likely translates to Flux prompts
These outputs inform the pipeline but never enter it as training material or generated game assets.
## Godot Import Configuration
### Texture Import Settings
For all albedo textures imported into `res://assets/textures/`:
```ini
# .import file settings (set in Godot Import dock)
compress/mode = 2 # VRAM compressed (BPTC/S3TC)
compress/high_quality = true
flags/repeat = true # REQUIRED for seamless tiling
flags/filter = true
flags/mipmaps = true # REQUIRED — prevents shimmer at distance
process/fix_alpha_border = false
process/premult_alpha = false
```
Normal maps require different settings:
```ini
compress/normal_map = true # tells Godot to treat as normal map
flags/repeat = true
flags/mipmaps = true
```
### Godot Project Asset Path
```
res://
└── assets/
└── textures/
├── verdant_old_growth/
│ ├── bark/
│ │ ├── bark_douglas_fir_0042_001_albedo.png
│ │ ├── bark_douglas_fir_0042_001_normal.png
│ │ ├── bark_douglas_fir_0042_001_roughness.png
│ │ └── bark_douglas_fir_0042_001_ao.png
│ └── ...
└── ...
```
The `old-growth-assets` submodule mounts at `assets/` in the Godot project root. Only `accepted/` contents are symlinked or copied into `res://assets/` — candidates never enter the Godot project.
## Human Review Gate
Generated assets land in the assets submodule with `accepted: null` in their manifests. Before any asset is referenced in the Godot project:
1. Review batch in any image viewer
2. Set `accepted: true` or `accepted: false` in `manifest.json`
3. Accepted albedo PNGs move to `assets/accepted/` via helper script
4. Derive normal/roughness/AO maps from accepted albedos (Materialize or similar)
5. All four map types imported into Godot with correct settings (§9.1)
This keeps generation non-destructive and separates candidates from production assets.
## Phase Integration
Pipeline is addendum Phase 27 and can run in parallel with active development phases. Texture generation is gated only on biome specs being stable (Phase 8+). Concept art can begin immediately.
**Dependencies on existing specs**:
- Canonical biome definitions (in Biome Prompt Schema above)
- World audio spec (biome boundaries and atmosphere inform concept art composition)
- Observer harness (can be extended to run visual QA on generated assets)
## Image Acquisition Pipeline
### Prerequisites
```bash
pip install gallery-dl # Flickr CC batch download
pip install requests Pillow # image validation script
brew install wget # USFS / Wikimedia direct downloads
```
### USFS Photo Library (Public Domain)
Browse https://www.fs.usda.gov/photography and search for:
- `old growth`, `Pacific Northwest`, `Olympic National Forest`, `Mount Baker-Snoqualmie`
- `Douglas fir`, `western red cedar`, `sword fern`, `nurse log`
- `fungi`, `mushroom`, `lichen`
Download manually — no bulk API. Organize into `training_data/` by biome and category per Dataset Composition above.
### Wikimedia Commons (CC BY / CC BY-SA / PD)
Use Wikimedia Commons API to batch-download by category, filtered to CC BY, CC BY-SA, and Public Domain. Full Python script `scripts/wikimedia_fetch.py` with license filtering and minimum-resolution check lives in the archived spec. Example invocation:
```bash
python scripts/wikimedia_fetch.py \
--category "Pseudotsuga menziesii" \
--licenses "CC-BY-4.0,CC-BY-SA-4.0,PD" \
--output training_data/verdant_old_growth/bark_closeup/ \
--min-resolution 512
```
### Flickr CC
`gallery-dl` handles Flickr CC with license filtering. License IDs: 4 = CC BY, 5 = CC BY-SA.
```bash
gallery-dl \
"https://www.flickr.com/search/?text=old+growth+forest+pacific+northwest&license=4,5" \
--directory training_data/verdant_old_growth/wide_canopy/ \
--filter "width >= 512 and height >= 512"
```
Repeat for each biome × subject combination. See archived spec for the full set of canonical Flickr queries.
### Unsplash / Pexels
Both have no usable bulk API for specific searches without an API key. Full `scripts/unsplash_fetch.py` Python script in archived spec — invoke per biome × subject combination from Dataset Composition.
### Dataset Validation
Before captioning, validate the full dataset with `scripts/validate_dataset.py --input training_data/` (full Python source in archived spec). Checks each image is ≥512px minimum edge, mode is RGB or RGBA, and file is not corrupt.
### Caption Generation
AI Toolkit requires a `.txt` caption file alongside each training image. Captions must include the trigger word for the target biome. Full `scripts/caption_dataset.py` (BLIP-2-based auto-captioning) in archived spec. Trigger words:
- `verdant_og_style`
- `fungal_depths_style`
- `petrifold_style`
**Review captions manually after generation.** Auto-captions will be imperfect — correct any that describe the wrong subject or miss the key material.
## Model Download
### HuggingFace Authentication
Flux.1 [schnell] is a gated model requiring HuggingFace account acceptance of the license.
```bash
pip install huggingface_hub
huggingface-cli login # paste your HF token from hf.co/settings/tokens
# Accept license at: https://huggingface.co/black-forest-labs/FLUX.1-schnell
```
### Download Models
```bash
# Inference model
huggingface-cli download black-forest-labs/FLUX.1-schnell \
flux1-schnell.safetensors \
--local-dir ~/concourse/ComfyUI/models/checkpoints/
# fp8 quantized for fast inference (optional, saves ~12GB vs full)
huggingface-cli download Comfy-Org/flux1-schnell \
flux1-schnell-fp8.safetensors \
--local-dir ~/concourse/ComfyUI/models/checkpoints/
# Text encoders (shared between schnell variants)
huggingface-cli download comfyanonymous/flux_text_encoders \
clip_l.safetensors t5xxl_fp16.safetensors \
--local-dir ~/concourse/ComfyUI/models/text_encoders/
# VAE
huggingface-cli download black-forest-labs/FLUX.1-schnell \
ae.safetensors \
--local-dir ~/concourse/ComfyUI/models/vae/
# Upscale model
huggingface-cli download ai-forever/Real-ESRGAN \
RealESRGAN_x4plus.pth \
--local-dir ~/concourse/ComfyUI/models/upscale_models/
```
### Verify Downloads
```bash
ls -lh ~/concourse/ComfyUI/models/checkpoints/
ls -lh ~/concourse/ComfyUI/models/text_encoders/
ls -lh ~/concourse/ComfyUI/models/vae/
ls -lh ~/concourse/ComfyUI/models/upscale_models/
# Verify ComfyUI sees all models
curl -s http://127.0.0.1:8189/object_info | python3 -m json.tool | grep -A5 "CheckpointLoaderSimple"
```
## Fine-Tuning Process
### Fine-Tuning Process
#### AI Toolkit Installation
```bash
cd ~/concourse
git clone https://github.com/ostris/ai-toolkit
cd ai-toolkit
python -m venv ../.venv-aitoolkit
source ../.venv-aitoolkit/bin/activate
pip install torch torchvision torchaudio # MPS backend
pip install -r requirements.txt
```
#### Training Config (`configs/pnw-oldgrowth-v1.yaml`)
```yaml
job: extension
config:
name: pnw-oldgrowth-v1
process:
- type: sd_trainer
training_folder: output/pnw-oldgrowth-v1
device: mps
trigger_word: null # trigger words are per-image in captions
network:
type: lora
linear: 16
linear_alpha: 16
save:
dtype: float16
save_every: 500
max_step_saves_to_keep: 4
datasets:
- folder_path: ../training_data/
caption_ext: txt
caption_dropout_rate: 0.05
shuffle_tokens: false
cache_latents_to_disk: true
resolution: [512, 768, 1024]
train:
batch_size: 1
steps: 2000
gradient_accumulation_steps: 4
train_unet: true
train_text_encoder: false
gradient_checkpointing: true
noise_scheduler: flowmatch
optimizer: adamw8bit
lr: 1e-4
lr_scheduler: cosine_with_restarts
lr_warmup_steps: 100
max_grad_norm: 1.0
dtype: bf16
model:
name_or_path: ~/concourse/ComfyUI/models/checkpoints/flux1-schnell.safetensors
is_flux: true
quantize: false
sample:
sampler: flowmatch
sample_every: 500
width: 1024
height: 1024
prompts:
- "verdant_og_style, seamless tileable texture of douglas fir bark, pacific northwest"
- "fungal_depths_style, seamless tileable texture of oyster mushroom clusters, pacific northwest"
- "petrifold_style, seamless tileable texture of petrified mineral wood surface, pacific northwest"
neg: ""
seed: 42
walk_seed: true
guidance_scale: 1.0
sample_steps: 4
meta:
name: pnw-oldgrowth-v1
version: "1.0"
```
Train on the full precision schnell model (`flux1-schnell.safetensors`), not the fp8 quantized checkpoint. Training on fp8 weights degrades gradient quality.
```bash
cd ~/concourse/ai-toolkit
source ../.venv-aitoolkit/bin/activate
python run.py configs/pnw-oldgrowth-v1.yaml
```
Training produces checkpoint saves every 500 steps in `output/pnw-oldgrowth-v1/`. Sample images generate at each checkpoint — use these to monitor progress.
**Checkpoint evaluation:**
| Checkpoint | What to look for |
|---|---|
| Step 500 | Trigger words beginning to activate; textures roughly on-topic |
| Step 1000 | Biome character emerging; PNW palette starting to appear |
| Step 1500 | Consistent trigger word response; correct materials and lighting |
| Step 2000 | Full convergence; all three trigger words producing distinct biome characters |
Signs of overfit: images look like copies of training photos, loss of diversity, prompt details ignored → stop earlier. Signs of underfit: trigger words produce generic results, no visible biome differentiation → train longer.
```bash
# Export final LoRA to ComfyUI
cp output/pnw-oldgrowth-v1/pnw-oldgrowth-v1_000002000.safetensors \
~/concourse/ComfyUI/models/loras/pnw-oldgrowth-v1.safetensors
```
### Serving the Model
```bash
# Verify LoRA loads in ComfyUI
curl -s http://127.0.0.1:8189/object_info | python3 -c \
"import json,sys; d=json.load(sys.stdin); print(d['LoraLoader']['input']['required']['lora_name'][0])"
# Should include: pnw-oldgrowth-v1.safetensors
```
Smoke test via `http://127.0.0.1:8189` — load Flux schnell workflow, add LoRA loader, run with prompt `verdant_og_style, seamless tileable texture of douglas fir bark, pacific northwest`, LoRA strength `0.75`, steps `4`, CFG `1.0`. Expected: PNW bark character, cool-neutral palette, visible moss/lichen detail. If output is generic, increase LoRA strength to `0.85–0.95`.
### Generation Workflow
The texture workflow: **generate → make seamless → upscale**.
**Stage 1: Generate 1024×1024 albedo** (via comfyui-mcp `txt2img`):
```json
{
"prompt": "verdant_og_style, a seamless tileable texture of douglas fir bark, deep forest green and rich brown tones, diffused Pacific Northwest canopy light, ancient primordial damp, highly detailed surface, macro photography, physically based rendering material, no horizon, no sky, flat lay perspective",
"negative_prompt": "sunlit clearing, dry, desert, tropical, bright sky, watermark, signature, text, blurry, low quality, cartoon",
"width": 1024, "height": 1024, "steps": 4, "cfg": 1.0, "seed": 42,
"model": "flux1-schnell-fp8.safetensors",
"lora": {"name": "pnw-oldgrowth-v1.safetensors", "strength": 0.75},
"batch_size": 4
}
```
**Stage 2: Make seamless** — apply via ComfyUI `SeamlessTile` node. The offset-and-blend approach shifts the image by half its dimensions, inpaints the visible seam line at low denoise (0.2–0.35), then shifts back. Two passes (horizontal + vertical) required.
**Stage 3: Upscale to 2048×2048** — use RealESRGAN_x4plus at 2× scale from 1024 (not 4× from 512, which loses generation detail).
## Related
- [[Palettes]] — per-biome color regimes
- [[Biomes]] — the three-register gradient art serves
- [[Biome Behavior]] — biome identity across systems
- [[Tone and Horror]] — tonal discipline the art must carry
- [[Pacific Drive Reference]] — peer reference for PNW + domestic-vehicle horror
- [[Rock Scatter Design]] — biome tinting applied to rocks
- [[Art]] — folder MOC
- [[Tooling — Domain MOC]] — domain MOC
- [[Constitution]] — Articles IX (Atmosphere) and XII-G (Visual Design) are the authoritative sources
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-art-visual-direction: type/design
Tag assignment · test- Gold
- The note calls for `type/design`: Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`) (True)
- ember
- The note has no reason to carry `type/design` (False)
- Signal
- P(true) 0.19 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Art/Visual Direction.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Visual Direction
The overall visual direction for Old Growth AND the complete agent-driven visual asset generation pipeline — Flux.1 [schnell] + ComfyUI + custom LoRA + "artisan" agent producing textures and concept art. This note captures both the aesthetic framing and the authoritative technical pipeline specification.
## Overview — Aesthetic Direction
Old Growth pursues **stylized realism**. Atmospheric mood takes precedence over graphical fidelity (Article IX). Key references: Simon Stålenhag's paintings, the organic corruption of *Annihilation*, the zone hazards of *Roadside Picnic*, the deep woods of *Twin Peaks*, and the ruined overgrowth of *The Last of Us*.
**Atmosphere is a design constraint, not decoration** (Article IX, Article XII-K). Every new system, asset, and mechanic is evaluated against: *Does this serve or undermine the feeling that this forest is ancient, alive, and hostile?* If it undermines it, it is redesigned or removed.
The visual direction serves the central horror: **the player is very small inside a condition that does not notice them**. Scale must feel overwhelming at all times. An 80-meter tree makes the Samba look like a toy. The player should never feel large in this world.
## Design Intent — Key Decisions
- **Stylized realism, not photorealism**: composition, color, value, light shape are the primary tools. Polygon counts and shader complexity are secondary. Painted, not rendered. Silhouette first — any new asset must read silhouette-first at gameplay distance.
- **Biome as palette gradient** (see [[Palettes]]): color grading shifts per biome. The gradient itself is the storytelling device. Transitions between biomes are gradual — light, sound, and bus behavior shift before the player consciously names the change.
- **Samba silhouette as identity anchor** (Article XII-G): the Samba's silhouette is a primary identity mark. Readable at any time of day or weather. New weather states or lighting conditions must be validated against Samba silhouette readability before shipping.
- **Motivated light**: all scene lighting must appear to come from plausible in-world sources (sun angle, headlights, bioluminescence, fire). Unmotivated fill lights prohibited unless behind a debug-only flag.
- **Night is dark**: not atmospherically dark — *genuinely* dark, the kind that requires headlights for basic navigation. Lets Amber Bleed anomaly's amber glow read clearly from a distance, and makes the garage window's view matter.
- **Visual language is consistent**: players learn anomaly signatures from a distance. Mycelium has a specific luminosity. Hollow Trees have a specific silhouette. Petrified Zones' grey particulate accumulates in recognizable patterns. The world teaches its own grammar (Article XII-G).
- **Value contrast passes greyscale**: all critical visual communication must be tested in greyscale before shipping (Article XII-G). A composition that only works in color is fragile.
- **Accessibility**: color-coded information must be paired with a secondary differentiator (shape, value, icon). Deuteranopia/protanopia simulation must be run before finalizing any color-coded system.
## Visual Asset Pipeline — Overview
The pipeline is an agent-driven visual asset generation workflow producing texture/material candidates and concept art/reference imagery. It mirrors the audio pipeline architecture (submodule + Git LFS + spec-driven agent dispatch) and slots into the Concourse infrastructure alongside existing MLX inference services.
**What this is NOT**: a replacement for hand-crafted or purchased assets. The pipeline produces candidates and reference material, subject to human review and acceptance before integration into Godot.
## Stack
| Component | Role | Details |
|---|---|---|
| **Flux.1 [schnell]** | Primary generation model | Apache 2.0 — fully commercial. 4-step generation. fp16 full precision viable at 128 GB. Quality gap vs [dev] is real but closed significantly by a well-tuned LoRA. |
| **Flux.1 [dev]** | ❌ Excluded | Non-commercial license only. Not viable for a shipping title. |
| **ComfyUI** | Workflow engine | Runs natively on Apple Silicon via MPS + MLX extension. Port 8189 (offset from existing inference on 8001/8002). |
| **ComfyUI-MLX** | Acceleration layer | 50–70% speed improvement over PyTorch MPS on M-series. Installed via Custom Nodes Manager. |
| **comfyui-mcp** | MCP server | `@peleke.s/comfyui-mcp` via npx. Exposes 40 tools (txt2img, img2img, upscale, inpaint, outpaint, ControlNet, LoRA injection). Auto-detects Flux (natural language) vs SDXL (tag) prompting strategy. |
| **artisan agent** | oh-my-openagent agent | Reads biome specs, constructs prompts, dispatches generation jobs via comfyui-mcp, organizes outputs. |
## Infrastructure Setup
### ComfyUI Installation
```bash
cd ~/concourse
git clone https://github.com/comfyanonymous/ComfyUI
cd ComfyUI
python -m venv ../.venv-comfyui # outside repo to avoid git noise
source ../.venv-comfyui/bin/activate
pip install torch torchvision torchaudio # MPS backend, Apple Silicon
pip install -r requirements.txt
# Launch on non-conflicting port
python main.py --port 8189 --listen 127.0.0.1
```
### ComfyUI as Persistent Service (launchd)
Full plist at `~/Library/LaunchAgents/com.concourse.comfyui.plist` runs ComfyUI at login with `RunAtLoad`/`KeepAlive` true, logs to `/tmp/comfyui.{log,err}`. Load with `launchctl load ~/Library/LaunchAgents/com.concourse.comfyui.plist`. Full plist XML in archived spec.
### MLX Extension
ComfyUI Manager → Custom Nodes Manager → search "ComfyUI MLX" → Install. Native MLX inference nodes for Flux models.
### Model Weights Layout
```
ComfyUI/models/
├── checkpoints/
│ ├── flux1-schnell-fp8.safetensors # inference — Apache 2.0, commercial OK
│ └── flux1-schnell.safetensors # full precision — for LoRA training only
├── text_encoders/
│ ├── clip_l.safetensors
│ └── t5xxl_fp16.safetensors # fp16 recommended with 128GB
├── vae/
│ └── ae.safetensors
├── loras/
│ └── pnw-oldgrowth-v1.safetensors # trained PNW style LoRA
└── upscale_models/
└── RealESRGAN_x4plus.pth # 4x upscale for final texture exports
```
### MCP Server Registration
Add to oh-my-openagent MCP config:
```json
{
"comfyui": {
"command": "npx",
"args": ["-y", "@peleke.s/comfyui-mcp"],
"env": {
"COMFYUI_URL": "http://127.0.0.1:8189",
"COMFYUI_MODEL": "flux1-schnell-fp8.safetensors"
}
}
}
```
## Artisan Agent
### Role Definition
```yaml
name: artisan
port: null # stdio MCP, no inference port
model: claude-sonnet-4 # local not needed; task is orchestration not inference
tools:
- comfyui (txt2img, img2img, upscale, list_models, list_loras)
- filesystem (read biome specs, write output manifests)
responsibilities:
- Read canonical biome specs and extract visual descriptors
- Construct Flux-optimized natural language prompts
- Dispatch generation jobs with correct workflow type (texture vs concept)
- Organize outputs into asset submodule structure
- Write generation manifests for reproducibility
out_of_scope:
- Asset acceptance/rejection (human review gate)
- Godot import configuration
- LoRA training
```
### Task Dispatch Schema
```yaml
task_type: generate_assets
biome: verdant_old_growth # canonical biome name
asset_type: texture # texture | concept_art
material_class: bark # see Asset Taxonomy below
batch_size: 4 # candidates per run
seed: 42 # null for random; set for reproducibility
lora:
name: pnw-oldgrowth-v1
strength: 0.75
output_dir: assets/textures/verdant_old_growth/bark/
```
## Biome Prompt Schema
### Canonical Biome Descriptors
Each biome encodes the visual language artisan uses to construct Flux prompts.
**Verdant Old Growth**:
```yaml
palette:
dominant: ["deep forest green", "rich brown", "dark umber"]
accent: ["pale fern green", "moss yellow-green", "filtered golden light"]
temperature: cool-neutral
saturation: high
lighting:
quality: diffused, overcast Pacific Northwest canopy light
shadows: soft, deep, layered
atmosphere: morning mist, light fog between trunks
flora:
dominant: ["Douglas fir bark", "western red cedar", "sword fern", "oxalis"]
ground: ["decomposing leaf litter", "duff", "nurse log"]
micro: ["lichen patches", "liverwort", "club moss"]
mood_keywords: ["ancient", "cathedral quiet", "primordial", "damp"]
negative_prompts: ["sunlit clearing", "dry", "desert", "tropical", "bright sky"]
```
**Fungal Depths**:
```yaml
palette:
dominant: ["near-black brown", "deep slate grey", "dark purple-brown"]
accent: ["pale bioluminescent blue-white", "amber mycelium", "rust orange spore"]
temperature: very cool, desaturated with isolated warm glow
saturation: low overall, high on fungal accents
lighting:
quality: near-darkness with self-illuminating fungal sources
shadows: extremely deep, minimal fill
atmosphere: still air, spore-laden, no wind
flora:
dominant: ["oyster mushroom clusters", "turkey tail fungus", "giant puffball"]
ground: ["dense mycelium network", "decayed wood pulp", "white hyphal threads"]
micro: ["bioluminescent fungal patches", "spore clouds"]
mood_keywords: ["primordial", "alien", "silent", "decomposition", "network"]
negative_prompts: ["sunlight", "bright", "colorful flowers", "living trees"]
```
**The Petrifold**:
```yaml
palette:
dominant: ["grey-blue mineral", "bleached white", "pale ash"]
accent: ["rust iron stain", "pale ochre mineral deposit", "ice blue crack"]
temperature: cold, desaturated
saturation: very low
lighting:
quality: flat grey overcast, harsh shadowless diffusion
shadows: minimal, diffuse
atmosphere: cold mineral stillness, no organic warmth
flora:
dominant: ["petrified tree trunk", "calcified branch", "mineral deposit column"]
ground: ["cracked mineral surface", "calcified root network", "ash grey soil"]
micro: ["crystal inclusions", "iron oxide streaks", "pale lichen on stone"]
mood_keywords: ["ancient death", "geological time", "silence", "crystalline"]
negative_prompts: ["green", "living", "warm", "lush", "brown soil"]
```
### Prompt Construction Rules
Artisan uses these templates. **Flux.1 uses natural language — do NOT use comma-separated tag lists.**
**Texture prompt template**:
```
A seamless tileable texture of [material], [biome.flora.dominant context],
[biome.palette.dominant colors], [biome.lighting.quality],
[biome.mood_keywords], highly detailed surface, macro photography,
physically based rendering material, no horizon, no sky, flat lay perspective
```
**Concept art prompt template**:
```
[subject or scene description], [biome name] forest, Pacific Northwest,
[biome.palette.dominant and accent colors], [biome.lighting.quality],
[biome.atmosphere], [biome.mood_keywords], cinematic composition,
environmental concept art, matte painting style
```
**Negative prompt (all types)**:
```
[biome.negative_prompts], watermark, signature, text, blurry,
low quality, cartoon, anime, illustration style, flat design
```
## Asset Taxonomy
### Textures
Target: 1024×1024 seamless tiles, upscaled to 2048×2048 for Godot import.
**Important**: Flux does not natively generate seamless/tileable textures. The ComfyUI workflow for textures must include a seamless tiling node chain. Use the `SeamlessTile` custom node (available via ComfyUI Manager) or circular padding in the VAE decode step. Without this, textures will show visible seams when tiled.
**PBR map pipeline**: Godot's `StandardMaterial3D` uses albedo, normal, roughness, AO maps. This pipeline generates albedo directly. Derive remaining maps from accepted albedo outputs using:
- **Normal map**: `generate-texture` node in ComfyUI with a normal map ControlNet, or post-process in Materialize / AwesomeBump (free, offline)
- **Roughness/AO**: Materialize or xNormal from accepted albedo — automated batch processing feasible
- Normal/roughness derivation is out of scope for artisan agent — post-acceptance step before Godot import
```
textures/
├── verdant_old_growth/
│ ├── bark/ # douglas_fir, cedar, alder, mossy_bark
│ ├── ground/ # forest_duff, leaf_litter, exposed_root, muddy_soil
│ ├── rock/ # mossy_boulder, exposed_basalt, lichen_rock
│ └── organic/ # fern_frond, sword_fern_bed, oxalis_patch, club_moss
├── fungal_depths/
│ ├── fungal/ # oyster_cluster, turkey_tail, puffball, mycelium_sheet
│ ├── ground/ # decayed_wood_pulp, dense_mycelium, hyphal_mat
│ └── wood/ # heavily_decayed, nurse_log_surface, heartwood_rot
└── the_petrifold/
├── mineral/ # petrified_bark, calcified_surface, mineral_column
├── ground/ # cracked_mineral, ash_soil, calcified_root
└── crystal/ # iron_stain, crystal_inclusion, calcite_vein
```
Each accepted albedo texture gets a sibling set before Godot import:
```
bark_douglas_fir_0042_001_albedo.png
bark_douglas_fir_0042_001_normal.png
bark_douglas_fir_0042_001_roughness.png
bark_douglas_fir_0042_001_ao.png
```
### Concept Art / Reference
Target: 1920×1080 or 1280×720. Not for direct game use — reference and pitch material only.
```
concept/
├── verdant_old_growth/
│ ├── wide/ # full environment establishing shots
│ ├── road/ # forest road reference for driving sections
│ └── detail/ # close-up flora reference
├── fungal_depths/
│ ├── wide/
│ ├── bioluminescence/ # lighting reference for Godot environment shader
│ └── detail/
└── the_petrifold/
├── wide/
├── transition/ # boundary zones between biomes
└── detail/
```
## Output Structure & Git LFS
### Submodule
```bash
git submodule add git@github.com:shapeandshare/old-growth-assets assets
cd assets
git lfs install
```
### LFS Tracking
```bash
git lfs track "*.png"
git lfs track "*.jpg"
git lfs track "*.exr" # for HDR concept exports
git lfs track "*.safetensors" # track LoRA weights in assets repo
git add .gitattributes
```
### Asset Filename Convention
```
{material_slug}_{seed}_{index}.png
# e.g. bark_douglas_fir_0042_001.png
# mycelium_sheet_1337_003.png
# concept_wide_verdant_0099_001.png
```
- `material_slug` — underscore-separated descriptor matching taxonomy leaf node
- `seed` — zero-padded to 4 digits
- `index` — zero-padded batch index (001–NNN)
### Generation Manifest
Each batch writes a `manifest.json` alongside its outputs:
```json
{
"generated_at": "2025-04-16T14:30:00Z",
"model": "flux1-schnell-fp8.safetensors",
"lora": { "name": "pnw-oldgrowth-v1", "strength": 0.75 },
"biome": "verdant_old_growth",
"asset_type": "texture",
"material_class": "bark/douglas_fir",
"prompt": "A seamless tileable texture of douglas fir bark...",
"negative_prompt": "sunlit clearing, dry...",
"seed": 42,
"steps": 4,
"cfg": 1.0,
"resolution": "1024x1024",
"outputs": [
{ "filename": "bark_douglas_fir_0042_001.png", "accepted": null },
{ "filename": "bark_douglas_fir_0042_002.png", "accepted": null }
]
}
```
`steps: 4` is correct for schnell (distilled 4-step model — more steps do not improve quality). `cfg: 1.0` is correct (schnell is guidance-free). `accepted` is `null` until human review sets to `true` or `false`.
## LoRA Strategy
### Why a LoRA is Essential
Flux.1 [schnell] generates competent but generic forests. The specific Pacific Northwest character — the particular quality of light through a doug fir canopy, the exact color of Oregon sword fern, the texture of nurse log decomposition — requires a fine-tuned style anchor. The LoRA is the highest-leverage single investment in this pipeline.
### Training Data Sources
Training data **must be real Pacific Northwest photography, not AI-generated images**. Real photographs give you:
- Authentic PNW light, color, texture rather than an AI approximation
- Legally clean training data with no ToS ambiguity
- Better LoRA quality — the model learns real-world material properties
**Do NOT use Midjourney outputs as training data.** The legal landscape around using AI-generated images to train other AI models is actively litigated and the ToS is ambiguous. Real CC photography is cleaner and produces better results.
**License requirements**: training data for a commercially-released game must be under licenses that permit commercial use. That means CC BY, CC BY-SA, Public Domain, or platform-specific licenses that explicitly cover ML training. CC BY-NC explicitly prohibits commercial use — do not use NC-licensed images.
**Primary sources (commercial-safe)**:
| Source | What to pull | License | Notes |
|---|---|---|---|
| **USFS Photo Library** | PNW national forest photography, old growth, flora | Public Domain | Cleanest possible — no restrictions |
| **Wikimedia Commons** | Douglas fir, cedar, Hoh Rainforest, Olympic NP, fungi, PNW flora | CC BY, CC BY-SA, PD | Filter by license — skip CC BY-NC and CC BY-ND |
| **Flickr CC** | `old growth forest pacific northwest`, `olympic peninsula`, `sword fern`, `douglas fir bark` | CC BY / CC BY-SA only | Must filter explicitly — default search includes NC. Use `license=4,5` URL params to filter to BY and BY-SA only |
| **Unsplash** | Forest, PNW wilderness, macro flora, fungi, bark textures | Unsplash License | Explicitly permits ML training including commercial products |
| **Pexels** | Same subject as Unsplash | Pexels License | Same — permissive, covers commercial ML training |
| **iNaturalist** | ⚠️ Flora macro for PNW species | CC BY-NC (most) | **Avoid for commercial training.** Most observations default to CC BY-NC. Filter to CC BY only but coverage is thin. Use as visual reference, not training data. |
### Dataset Composition
Target: 200–400 images, balanced across biomes.
```
training_data/
├── verdant_old_growth/ ~150 images
│ ├── wide_canopy/ # full environment, dappled light, fog
│ ├── bark_closeup/ # doug fir, cedar, alder — macro texture
│ ├── ground_flora/ # sword fern, oxalis, moss — ground level
│ └── root_nurse_log/ # decomposition, fallen trees
├── fungal_depths/ ~100 images
│ ├── fungi_macro/ # oyster, turkey tail, puffball — closeup
│ ├── mycelium/ # white hyphal networks on wood
│ └── dark_forest/ # heavily shaded, low light conditions
└── the_petrifold/ ~80 images
├── petrified_wood/ # mineral-replaced wood
├── mineral_texture/ # calcite, basalt, grey rock closeup
└── dead_forest/ # standing dead trees, grey/white tones
```
### Training Configuration
**Tool**: AI Toolkit (Kohya-based), compatible with `comfyui-mcp` LoRA upload API.
**Important**: Train on the full precision schnell model (`flux1-schnell.safetensors`), not the fp8 quantized checkpoint. Training on fp8 weights degrades gradient quality. With 128 GB unified memory this is not a constraint — the full ~24 GB model loads comfortably.
```yaml
model: flux1-schnell.safetensors # full precision for training
training_type: lora
rank: 16
alpha: 16
learning_rate: 1e-4
steps: 2000
batch_size: 1 # Apple Silicon MPS constraint
save_every: 500
```
The trained `pnw-oldgrowth-v1.safetensors` is used with the fp8 inference checkpoint at generation time — LoRA weights are precision-independent.
**Trigger words** injected automatically by artisan per biome:
- `verdant_og_style` → Verdant Old Growth
- `fungal_depths_style` → Fungal Depths
- `petrifold_style` → The Petrifold
### Midjourney's Role
MJ remains useful — just not as training data. Use manually through the web UI for:
- Concept exploration before committing to a biome palette direction
- Composition reference for concept art framing
- Prompt language testing — if a scene reads well in MJ, natural language likely translates to Flux prompts
These outputs inform the pipeline but never enter it as training material or generated game assets.
## Godot Import Configuration
### Texture Import Settings
For all albedo textures imported into `res://assets/textures/`:
```ini
# .import file settings (set in Godot Import dock)
compress/mode = 2 # VRAM compressed (BPTC/S3TC)
compress/high_quality = true
flags/repeat = true # REQUIRED for seamless tiling
flags/filter = true
flags/mipmaps = true # REQUIRED — prevents shimmer at distance
process/fix_alpha_border = false
process/premult_alpha = false
```
Normal maps require different settings:
```ini
compress/normal_map = true # tells Godot to treat as normal map
flags/repeat = true
flags/mipmaps = true
```
### Godot Project Asset Path
```
res://
└── assets/
└── textures/
├── verdant_old_growth/
│ ├── bark/
│ │ ├── bark_douglas_fir_0042_001_albedo.png
│ │ ├── bark_douglas_fir_0042_001_normal.png
│ │ ├── bark_douglas_fir_0042_001_roughness.png
│ │ └── bark_douglas_fir_0042_001_ao.png
│ └── ...
└── ...
```
The `old-growth-assets` submodule mounts at `assets/` in the Godot project root. Only `accepted/` contents are symlinked or copied into `res://assets/` — candidates never enter the Godot project.
## Human Review Gate
Generated assets land in the assets submodule with `accepted: null` in their manifests. Before any asset is referenced in the Godot project:
1. Review batch in any image viewer
2. Set `accepted: true` or `accepted: false` in `manifest.json`
3. Accepted albedo PNGs move to `assets/accepted/` via helper script
4. Derive normal/roughness/AO maps from accepted albedos (Materialize or similar)
5. All four map types imported into Godot with correct settings (§9.1)
This keeps generation non-destructive and separates candidates from production assets.
## Phase Integration
Pipeline is addendum Phase 27 and can run in parallel with active development phases. Texture generation is gated only on biome specs being stable (Phase 8+). Concept art can begin immediately.
**Dependencies on existing specs**:
- Canonical biome definitions (in Biome Prompt Schema above)
- World audio spec (biome boundaries and atmosphere inform concept art composition)
- Observer harness (can be extended to run visual QA on generated assets)
## Image Acquisition Pipeline
### Prerequisites
```bash
pip install gallery-dl # Flickr CC batch download
pip install requests Pillow # image validation script
brew install wget # USFS / Wikimedia direct downloads
```
### USFS Photo Library (Public Domain)
Browse https://www.fs.usda.gov/photography and search for:
- `old growth`, `Pacific Northwest`, `Olympic National Forest`, `Mount Baker-Snoqualmie`
- `Douglas fir`, `western red cedar`, `sword fern`, `nurse log`
- `fungi`, `mushroom`, `lichen`
Download manually — no bulk API. Organize into `training_data/` by biome and category per Dataset Composition above.
### Wikimedia Commons (CC BY / CC BY-SA / PD)
Use Wikimedia Commons API to batch-download by category, filtered to CC BY, CC BY-SA, and Public Domain. Full Python script `scripts/wikimedia_fetch.py` with license filtering and minimum-resolution check lives in the archived spec. Example invocation:
```bash
python scripts/wikimedia_fetch.py \
--category "Pseudotsuga menziesii" \
--licenses "CC-BY-4.0,CC-BY-SA-4.0,PD" \
--output training_data/verdant_old_growth/bark_closeup/ \
--min-resolution 512
```
### Flickr CC
`gallery-dl` handles Flickr CC with license filtering. License IDs: 4 = CC BY, 5 = CC BY-SA.
```bash
gallery-dl \
"https://www.flickr.com/search/?text=old+growth+forest+pacific+northwest&license=4,5" \
--directory training_data/verdant_old_growth/wide_canopy/ \
--filter "width >= 512 and height >= 512"
```
Repeat for each biome × subject combination. See archived spec for the full set of canonical Flickr queries.
### Unsplash / Pexels
Both have no usable bulk API for specific searches without an API key. Full `scripts/unsplash_fetch.py` Python script in archived spec — invoke per biome × subject combination from Dataset Composition.
### Dataset Validation
Before captioning, validate the full dataset with `scripts/validate_dataset.py --input training_data/` (full Python source in archived spec). Checks each image is ≥512px minimum edge, mode is RGB or RGBA, and file is not corrupt.
### Caption Generation
AI Toolkit requires a `.txt` caption file alongside each training image. Captions must include the trigger word for the target biome. Full `scripts/caption_dataset.py` (BLIP-2-based auto-captioning) in archived spec. Trigger words:
- `verdant_og_style`
- `fungal_depths_style`
- `petrifold_style`
**Review captions manually after generation.** Auto-captions will be imperfect — correct any that describe the wrong subject or miss the key material.
## Model Download
### HuggingFace Authentication
Flux.1 [schnell] is a gated model requiring HuggingFace account acceptance of the license.
```bash
pip install huggingface_hub
huggingface-cli login # paste your HF token from hf.co/settings/tokens
# Accept license at: https://huggingface.co/black-forest-labs/FLUX.1-schnell
```
### Download Models
```bash
# Inference model
huggingface-cli download black-forest-labs/FLUX.1-schnell \
flux1-schnell.safetensors \
--local-dir ~/concourse/ComfyUI/models/checkpoints/
# fp8 quantized for fast inference (optional, saves ~12GB vs full)
huggingface-cli download Comfy-Org/flux1-schnell \
flux1-schnell-fp8.safetensors \
--local-dir ~/concourse/ComfyUI/models/checkpoints/
# Text encoders (shared between schnell variants)
huggingface-cli download comfyanonymous/flux_text_encoders \
clip_l.safetensors t5xxl_fp16.safetensors \
--local-dir ~/concourse/ComfyUI/models/text_encoders/
# VAE
huggingface-cli download black-forest-labs/FLUX.1-schnell \
ae.safetensors \
--local-dir ~/concourse/ComfyUI/models/vae/
# Upscale model
huggingface-cli download ai-forever/Real-ESRGAN \
RealESRGAN_x4plus.pth \
--local-dir ~/concourse/ComfyUI/models/upscale_models/
```
### Verify Downloads
```bash
ls -lh ~/concourse/ComfyUI/models/checkpoints/
ls -lh ~/concourse/ComfyUI/models/text_encoders/
ls -lh ~/concourse/ComfyUI/models/vae/
ls -lh ~/concourse/ComfyUI/models/upscale_models/
# Verify ComfyUI sees all models
curl -s http://127.0.0.1:8189/object_info | python3 -m json.tool | grep -A5 "CheckpointLoaderSimple"
```
## Fine-Tuning Process
### Fine-Tuning Process
#### AI Toolkit Installation
```bash
cd ~/concourse
git clone https://github.com/ostris/ai-toolkit
cd ai-toolkit
python -m venv ../.venv-aitoolkit
source ../.venv-aitoolkit/bin/activate
pip install torch torchvision torchaudio # MPS backend
pip install -r requirements.txt
```
#### Training Config (`configs/pnw-oldgrowth-v1.yaml`)
```yaml
job: extension
config:
name: pnw-oldgrowth-v1
process:
- type: sd_trainer
training_folder: output/pnw-oldgrowth-v1
device: mps
trigger_word: null # trigger words are per-image in captions
network:
type: lora
linear: 16
linear_alpha: 16
save:
dtype: float16
save_every: 500
max_step_saves_to_keep: 4
datasets:
- folder_path: ../training_data/
caption_ext: txt
caption_dropout_rate: 0.05
shuffle_tokens: false
cache_latents_to_disk: true
resolution: [512, 768, 1024]
train:
batch_size: 1
steps: 2000
gradient_accumulation_steps: 4
train_unet: true
train_text_encoder: false
gradient_checkpointing: true
noise_scheduler: flowmatch
optimizer: adamw8bit
lr: 1e-4
lr_scheduler: cosine_with_restarts
lr_warmup_steps: 100
max_grad_norm: 1.0
dtype: bf16
model:
name_or_path: ~/concourse/ComfyUI/models/checkpoints/flux1-schnell.safetensors
is_flux: true
quantize: false
sample:
sampler: flowmatch
sample_every: 500
width: 1024
height: 1024
prompts:
- "verdant_og_style, seamless tileable texture of douglas fir bark, pacific northwest"
- "fungal_depths_style, seamless tileable texture of oyster mushroom clusters, pacific northwest"
- "petrifold_style, seamless tileable texture of petrified mineral wood surface, pacific northwest"
neg: ""
seed: 42
walk_seed: true
guidance_scale: 1.0
sample_steps: 4
meta:
name: pnw-oldgrowth-v1
version: "1.0"
```
Train on the full precision schnell model (`flux1-schnell.safetensors`), not the fp8 quantized checkpoint. Training on fp8 weights degrades gradient quality.
```bash
cd ~/concourse/ai-toolkit
source ../.venv-aitoolkit/bin/activate
python run.py configs/pnw-oldgrowth-v1.yaml
```
Training produces checkpoint saves every 500 steps in `output/pnw-oldgrowth-v1/`. Sample images generate at each checkpoint — use these to monitor progress.
**Checkpoint evaluation:**
| Checkpoint | What to look for |
|---|---|
| Step 500 | Trigger words beginning to activate; textures roughly on-topic |
| Step 1000 | Biome character emerging; PNW palette starting to appear |
| Step 1500 | Consistent trigger word response; correct materials and lighting |
| Step 2000 | Full convergence; all three trigger words producing distinct biome characters |
Signs of overfit: images look like copies of training photos, loss of diversity, prompt details ignored → stop earlier. Signs of underfit: trigger words produce generic results, no visible biome differentiation → train longer.
```bash
# Export final LoRA to ComfyUI
cp output/pnw-oldgrowth-v1/pnw-oldgrowth-v1_000002000.safetensors \
~/concourse/ComfyUI/models/loras/pnw-oldgrowth-v1.safetensors
```
### Serving the Model
```bash
# Verify LoRA loads in ComfyUI
curl -s http://127.0.0.1:8189/object_info | python3 -c \
"import json,sys; d=json.load(sys.stdin); print(d['LoraLoader']['input']['required']['lora_name'][0])"
# Should include: pnw-oldgrowth-v1.safetensors
```
Smoke test via `http://127.0.0.1:8189` — load Flux schnell workflow, add LoRA loader, run with prompt `verdant_og_style, seamless tileable texture of douglas fir bark, pacific northwest`, LoRA strength `0.75`, steps `4`, CFG `1.0`. Expected: PNW bark character, cool-neutral palette, visible moss/lichen detail. If output is generic, increase LoRA strength to `0.85–0.95`.
### Generation Workflow
The texture workflow: **generate → make seamless → upscale**.
**Stage 1: Generate 1024×1024 albedo** (via comfyui-mcp `txt2img`):
```json
{
"prompt": "verdant_og_style, a seamless tileable texture of douglas fir bark, deep forest green and rich brown tones, diffused Pacific Northwest canopy light, ancient primordial damp, highly detailed surface, macro photography, physically based rendering material, no horizon, no sky, flat lay perspective",
"negative_prompt": "sunlit clearing, dry, desert, tropical, bright sky, watermark, signature, text, blurry, low quality, cartoon",
"width": 1024, "height": 1024, "steps": 4, "cfg": 1.0, "seed": 42,
"model": "flux1-schnell-fp8.safetensors",
"lora": {"name": "pnw-oldgrowth-v1.safetensors", "strength": 0.75},
"batch_size": 4
}
```
**Stage 2: Make seamless** — apply via ComfyUI `SeamlessTile` node. The offset-and-blend approach shifts the image by half its dimensions, inpaints the visible seam line at low denoise (0.2–0.35), then shifts back. Two passes (horizontal + vertical) required.
**Stage 3: Upscale to 2048×2048** — use RealESRGAN_x4plus at 2× scale from 1024 (not 4× from 512, which loses generation detail).
## Related
- [[Palettes]] — per-biome color regimes
- [[Biomes]] — the three-register gradient art serves
- [[Biome Behavior]] — biome identity across systems
- [[Tone and Horror]] — tonal discipline the art must carry
- [[Pacific Drive Reference]] — peer reference for PNW + domestic-vehicle horror
- [[Rock Scatter Design]] — biome tinting applied to rocks
- [[Art]] — folder MOC
- [[Tooling — Domain MOC]] — domain MOC
- [[Constitution]] — Articles IX (Atmosphere) and XII-G (Visual Design) are the authoritative sources
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-art-visual-direction: type/spec
Tag assignment · test- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.71 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Art/Visual Direction.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Visual Direction
The overall visual direction for Old Growth AND the complete agent-driven visual asset generation pipeline — Flux.1 [schnell] + ComfyUI + custom LoRA + "artisan" agent producing textures and concept art. This note captures both the aesthetic framing and the authoritative technical pipeline specification.
## Overview — Aesthetic Direction
Old Growth pursues **stylized realism**. Atmospheric mood takes precedence over graphical fidelity (Article IX). Key references: Simon Stålenhag's paintings, the organic corruption of *Annihilation*, the zone hazards of *Roadside Picnic*, the deep woods of *Twin Peaks*, and the ruined overgrowth of *The Last of Us*.
**Atmosphere is a design constraint, not decoration** (Article IX, Article XII-K). Every new system, asset, and mechanic is evaluated against: *Does this serve or undermine the feeling that this forest is ancient, alive, and hostile?* If it undermines it, it is redesigned or removed.
The visual direction serves the central horror: **the player is very small inside a condition that does not notice them**. Scale must feel overwhelming at all times. An 80-meter tree makes the Samba look like a toy. The player should never feel large in this world.
## Design Intent — Key Decisions
- **Stylized realism, not photorealism**: composition, color, value, light shape are the primary tools. Polygon counts and shader complexity are secondary. Painted, not rendered. Silhouette first — any new asset must read silhouette-first at gameplay distance.
- **Biome as palette gradient** (see [[Palettes]]): color grading shifts per biome. The gradient itself is the storytelling device. Transitions between biomes are gradual — light, sound, and bus behavior shift before the player consciously names the change.
- **Samba silhouette as identity anchor** (Article XII-G): the Samba's silhouette is a primary identity mark. Readable at any time of day or weather. New weather states or lighting conditions must be validated against Samba silhouette readability before shipping.
- **Motivated light**: all scene lighting must appear to come from plausible in-world sources (sun angle, headlights, bioluminescence, fire). Unmotivated fill lights prohibited unless behind a debug-only flag.
- **Night is dark**: not atmospherically dark — *genuinely* dark, the kind that requires headlights for basic navigation. Lets Amber Bleed anomaly's amber glow read clearly from a distance, and makes the garage window's view matter.
- **Visual language is consistent**: players learn anomaly signatures from a distance. Mycelium has a specific luminosity. Hollow Trees have a specific silhouette. Petrified Zones' grey particulate accumulates in recognizable patterns. The world teaches its own grammar (Article XII-G).
- **Value contrast passes greyscale**: all critical visual communication must be tested in greyscale before shipping (Article XII-G). A composition that only works in color is fragile.
- **Accessibility**: color-coded information must be paired with a secondary differentiator (shape, value, icon). Deuteranopia/protanopia simulation must be run before finalizing any color-coded system.
## Visual Asset Pipeline — Overview
The pipeline is an agent-driven visual asset generation workflow producing texture/material candidates and concept art/reference imagery. It mirrors the audio pipeline architecture (submodule + Git LFS + spec-driven agent dispatch) and slots into the Concourse infrastructure alongside existing MLX inference services.
**What this is NOT**: a replacement for hand-crafted or purchased assets. The pipeline produces candidates and reference material, subject to human review and acceptance before integration into Godot.
## Stack
| Component | Role | Details |
|---|---|---|
| **Flux.1 [schnell]** | Primary generation model | Apache 2.0 — fully commercial. 4-step generation. fp16 full precision viable at 128 GB. Quality gap vs [dev] is real but closed significantly by a well-tuned LoRA. |
| **Flux.1 [dev]** | ❌ Excluded | Non-commercial license only. Not viable for a shipping title. |
| **ComfyUI** | Workflow engine | Runs natively on Apple Silicon via MPS + MLX extension. Port 8189 (offset from existing inference on 8001/8002). |
| **ComfyUI-MLX** | Acceleration layer | 50–70% speed improvement over PyTorch MPS on M-series. Installed via Custom Nodes Manager. |
| **comfyui-mcp** | MCP server | `@peleke.s/comfyui-mcp` via npx. Exposes 40 tools (txt2img, img2img, upscale, inpaint, outpaint, ControlNet, LoRA injection). Auto-detects Flux (natural language) vs SDXL (tag) prompting strategy. |
| **artisan agent** | oh-my-openagent agent | Reads biome specs, constructs prompts, dispatches generation jobs via comfyui-mcp, organizes outputs. |
## Infrastructure Setup
### ComfyUI Installation
```bash
cd ~/concourse
git clone https://github.com/comfyanonymous/ComfyUI
cd ComfyUI
python -m venv ../.venv-comfyui # outside repo to avoid git noise
source ../.venv-comfyui/bin/activate
pip install torch torchvision torchaudio # MPS backend, Apple Silicon
pip install -r requirements.txt
# Launch on non-conflicting port
python main.py --port 8189 --listen 127.0.0.1
```
### ComfyUI as Persistent Service (launchd)
Full plist at `~/Library/LaunchAgents/com.concourse.comfyui.plist` runs ComfyUI at login with `RunAtLoad`/`KeepAlive` true, logs to `/tmp/comfyui.{log,err}`. Load with `launchctl load ~/Library/LaunchAgents/com.concourse.comfyui.plist`. Full plist XML in archived spec.
### MLX Extension
ComfyUI Manager → Custom Nodes Manager → search "ComfyUI MLX" → Install. Native MLX inference nodes for Flux models.
### Model Weights Layout
```
ComfyUI/models/
├── checkpoints/
│ ├── flux1-schnell-fp8.safetensors # inference — Apache 2.0, commercial OK
│ └── flux1-schnell.safetensors # full precision — for LoRA training only
├── text_encoders/
│ ├── clip_l.safetensors
│ └── t5xxl_fp16.safetensors # fp16 recommended with 128GB
├── vae/
│ └── ae.safetensors
├── loras/
│ └── pnw-oldgrowth-v1.safetensors # trained PNW style LoRA
└── upscale_models/
└── RealESRGAN_x4plus.pth # 4x upscale for final texture exports
```
### MCP Server Registration
Add to oh-my-openagent MCP config:
```json
{
"comfyui": {
"command": "npx",
"args": ["-y", "@peleke.s/comfyui-mcp"],
"env": {
"COMFYUI_URL": "http://127.0.0.1:8189",
"COMFYUI_MODEL": "flux1-schnell-fp8.safetensors"
}
}
}
```
## Artisan Agent
### Role Definition
```yaml
name: artisan
port: null # stdio MCP, no inference port
model: claude-sonnet-4 # local not needed; task is orchestration not inference
tools:
- comfyui (txt2img, img2img, upscale, list_models, list_loras)
- filesystem (read biome specs, write output manifests)
responsibilities:
- Read canonical biome specs and extract visual descriptors
- Construct Flux-optimized natural language prompts
- Dispatch generation jobs with correct workflow type (texture vs concept)
- Organize outputs into asset submodule structure
- Write generation manifests for reproducibility
out_of_scope:
- Asset acceptance/rejection (human review gate)
- Godot import configuration
- LoRA training
```
### Task Dispatch Schema
```yaml
task_type: generate_assets
biome: verdant_old_growth # canonical biome name
asset_type: texture # texture | concept_art
material_class: bark # see Asset Taxonomy below
batch_size: 4 # candidates per run
seed: 42 # null for random; set for reproducibility
lora:
name: pnw-oldgrowth-v1
strength: 0.75
output_dir: assets/textures/verdant_old_growth/bark/
```
## Biome Prompt Schema
### Canonical Biome Descriptors
Each biome encodes the visual language artisan uses to construct Flux prompts.
**Verdant Old Growth**:
```yaml
palette:
dominant: ["deep forest green", "rich brown", "dark umber"]
accent: ["pale fern green", "moss yellow-green", "filtered golden light"]
temperature: cool-neutral
saturation: high
lighting:
quality: diffused, overcast Pacific Northwest canopy light
shadows: soft, deep, layered
atmosphere: morning mist, light fog between trunks
flora:
dominant: ["Douglas fir bark", "western red cedar", "sword fern", "oxalis"]
ground: ["decomposing leaf litter", "duff", "nurse log"]
micro: ["lichen patches", "liverwort", "club moss"]
mood_keywords: ["ancient", "cathedral quiet", "primordial", "damp"]
negative_prompts: ["sunlit clearing", "dry", "desert", "tropical", "bright sky"]
```
**Fungal Depths**:
```yaml
palette:
dominant: ["near-black brown", "deep slate grey", "dark purple-brown"]
accent: ["pale bioluminescent blue-white", "amber mycelium", "rust orange spore"]
temperature: very cool, desaturated with isolated warm glow
saturation: low overall, high on fungal accents
lighting:
quality: near-darkness with self-illuminating fungal sources
shadows: extremely deep, minimal fill
atmosphere: still air, spore-laden, no wind
flora:
dominant: ["oyster mushroom clusters", "turkey tail fungus", "giant puffball"]
ground: ["dense mycelium network", "decayed wood pulp", "white hyphal threads"]
micro: ["bioluminescent fungal patches", "spore clouds"]
mood_keywords: ["primordial", "alien", "silent", "decomposition", "network"]
negative_prompts: ["sunlight", "bright", "colorful flowers", "living trees"]
```
**The Petrifold**:
```yaml
palette:
dominant: ["grey-blue mineral", "bleached white", "pale ash"]
accent: ["rust iron stain", "pale ochre mineral deposit", "ice blue crack"]
temperature: cold, desaturated
saturation: very low
lighting:
quality: flat grey overcast, harsh shadowless diffusion
shadows: minimal, diffuse
atmosphere: cold mineral stillness, no organic warmth
flora:
dominant: ["petrified tree trunk", "calcified branch", "mineral deposit column"]
ground: ["cracked mineral surface", "calcified root network", "ash grey soil"]
micro: ["crystal inclusions", "iron oxide streaks", "pale lichen on stone"]
mood_keywords: ["ancient death", "geological time", "silence", "crystalline"]
negative_prompts: ["green", "living", "warm", "lush", "brown soil"]
```
### Prompt Construction Rules
Artisan uses these templates. **Flux.1 uses natural language — do NOT use comma-separated tag lists.**
**Texture prompt template**:
```
A seamless tileable texture of [material], [biome.flora.dominant context],
[biome.palette.dominant colors], [biome.lighting.quality],
[biome.mood_keywords], highly detailed surface, macro photography,
physically based rendering material, no horizon, no sky, flat lay perspective
```
**Concept art prompt template**:
```
[subject or scene description], [biome name] forest, Pacific Northwest,
[biome.palette.dominant and accent colors], [biome.lighting.quality],
[biome.atmosphere], [biome.mood_keywords], cinematic composition,
environmental concept art, matte painting style
```
**Negative prompt (all types)**:
```
[biome.negative_prompts], watermark, signature, text, blurry,
low quality, cartoon, anime, illustration style, flat design
```
## Asset Taxonomy
### Textures
Target: 1024×1024 seamless tiles, upscaled to 2048×2048 for Godot import.
**Important**: Flux does not natively generate seamless/tileable textures. The ComfyUI workflow for textures must include a seamless tiling node chain. Use the `SeamlessTile` custom node (available via ComfyUI Manager) or circular padding in the VAE decode step. Without this, textures will show visible seams when tiled.
**PBR map pipeline**: Godot's `StandardMaterial3D` uses albedo, normal, roughness, AO maps. This pipeline generates albedo directly. Derive remaining maps from accepted albedo outputs using:
- **Normal map**: `generate-texture` node in ComfyUI with a normal map ControlNet, or post-process in Materialize / AwesomeBump (free, offline)
- **Roughness/AO**: Materialize or xNormal from accepted albedo — automated batch processing feasible
- Normal/roughness derivation is out of scope for artisan agent — post-acceptance step before Godot import
```
textures/
├── verdant_old_growth/
│ ├── bark/ # douglas_fir, cedar, alder, mossy_bark
│ ├── ground/ # forest_duff, leaf_litter, exposed_root, muddy_soil
│ ├── rock/ # mossy_boulder, exposed_basalt, lichen_rock
│ └── organic/ # fern_frond, sword_fern_bed, oxalis_patch, club_moss
├── fungal_depths/
│ ├── fungal/ # oyster_cluster, turkey_tail, puffball, mycelium_sheet
│ ├── ground/ # decayed_wood_pulp, dense_mycelium, hyphal_mat
│ └── wood/ # heavily_decayed, nurse_log_surface, heartwood_rot
└── the_petrifold/
├── mineral/ # petrified_bark, calcified_surface, mineral_column
├── ground/ # cracked_mineral, ash_soil, calcified_root
└── crystal/ # iron_stain, crystal_inclusion, calcite_vein
```
Each accepted albedo texture gets a sibling set before Godot import:
```
bark_douglas_fir_0042_001_albedo.png
bark_douglas_fir_0042_001_normal.png
bark_douglas_fir_0042_001_roughness.png
bark_douglas_fir_0042_001_ao.png
```
### Concept Art / Reference
Target: 1920×1080 or 1280×720. Not for direct game use — reference and pitch material only.
```
concept/
├── verdant_old_growth/
│ ├── wide/ # full environment establishing shots
│ ├── road/ # forest road reference for driving sections
│ └── detail/ # close-up flora reference
├── fungal_depths/
│ ├── wide/
│ ├── bioluminescence/ # lighting reference for Godot environment shader
│ └── detail/
└── the_petrifold/
├── wide/
├── transition/ # boundary zones between biomes
└── detail/
```
## Output Structure & Git LFS
### Submodule
```bash
git submodule add git@github.com:shapeandshare/old-growth-assets assets
cd assets
git lfs install
```
### LFS Tracking
```bash
git lfs track "*.png"
git lfs track "*.jpg"
git lfs track "*.exr" # for HDR concept exports
git lfs track "*.safetensors" # track LoRA weights in assets repo
git add .gitattributes
```
### Asset Filename Convention
```
{material_slug}_{seed}_{index}.png
# e.g. bark_douglas_fir_0042_001.png
# mycelium_sheet_1337_003.png
# concept_wide_verdant_0099_001.png
```
- `material_slug` — underscore-separated descriptor matching taxonomy leaf node
- `seed` — zero-padded to 4 digits
- `index` — zero-padded batch index (001–NNN)
### Generation Manifest
Each batch writes a `manifest.json` alongside its outputs:
```json
{
"generated_at": "2025-04-16T14:30:00Z",
"model": "flux1-schnell-fp8.safetensors",
"lora": { "name": "pnw-oldgrowth-v1", "strength": 0.75 },
"biome": "verdant_old_growth",
"asset_type": "texture",
"material_class": "bark/douglas_fir",
"prompt": "A seamless tileable texture of douglas fir bark...",
"negative_prompt": "sunlit clearing, dry...",
"seed": 42,
"steps": 4,
"cfg": 1.0,
"resolution": "1024x1024",
"outputs": [
{ "filename": "bark_douglas_fir_0042_001.png", "accepted": null },
{ "filename": "bark_douglas_fir_0042_002.png", "accepted": null }
]
}
```
`steps: 4` is correct for schnell (distilled 4-step model — more steps do not improve quality). `cfg: 1.0` is correct (schnell is guidance-free). `accepted` is `null` until human review sets to `true` or `false`.
## LoRA Strategy
### Why a LoRA is Essential
Flux.1 [schnell] generates competent but generic forests. The specific Pacific Northwest character — the particular quality of light through a doug fir canopy, the exact color of Oregon sword fern, the texture of nurse log decomposition — requires a fine-tuned style anchor. The LoRA is the highest-leverage single investment in this pipeline.
### Training Data Sources
Training data **must be real Pacific Northwest photography, not AI-generated images**. Real photographs give you:
- Authentic PNW light, color, texture rather than an AI approximation
- Legally clean training data with no ToS ambiguity
- Better LoRA quality — the model learns real-world material properties
**Do NOT use Midjourney outputs as training data.** The legal landscape around using AI-generated images to train other AI models is actively litigated and the ToS is ambiguous. Real CC photography is cleaner and produces better results.
**License requirements**: training data for a commercially-released game must be under licenses that permit commercial use. That means CC BY, CC BY-SA, Public Domain, or platform-specific licenses that explicitly cover ML training. CC BY-NC explicitly prohibits commercial use — do not use NC-licensed images.
**Primary sources (commercial-safe)**:
| Source | What to pull | License | Notes |
|---|---|---|---|
| **USFS Photo Library** | PNW national forest photography, old growth, flora | Public Domain | Cleanest possible — no restrictions |
| **Wikimedia Commons** | Douglas fir, cedar, Hoh Rainforest, Olympic NP, fungi, PNW flora | CC BY, CC BY-SA, PD | Filter by license — skip CC BY-NC and CC BY-ND |
| **Flickr CC** | `old growth forest pacific northwest`, `olympic peninsula`, `sword fern`, `douglas fir bark` | CC BY / CC BY-SA only | Must filter explicitly — default search includes NC. Use `license=4,5` URL params to filter to BY and BY-SA only |
| **Unsplash** | Forest, PNW wilderness, macro flora, fungi, bark textures | Unsplash License | Explicitly permits ML training including commercial products |
| **Pexels** | Same subject as Unsplash | Pexels License | Same — permissive, covers commercial ML training |
| **iNaturalist** | ⚠️ Flora macro for PNW species | CC BY-NC (most) | **Avoid for commercial training.** Most observations default to CC BY-NC. Filter to CC BY only but coverage is thin. Use as visual reference, not training data. |
### Dataset Composition
Target: 200–400 images, balanced across biomes.
```
training_data/
├── verdant_old_growth/ ~150 images
│ ├── wide_canopy/ # full environment, dappled light, fog
│ ├── bark_closeup/ # doug fir, cedar, alder — macro texture
│ ├── ground_flora/ # sword fern, oxalis, moss — ground level
│ └── root_nurse_log/ # decomposition, fallen trees
├── fungal_depths/ ~100 images
│ ├── fungi_macro/ # oyster, turkey tail, puffball — closeup
│ ├── mycelium/ # white hyphal networks on wood
│ └── dark_forest/ # heavily shaded, low light conditions
└── the_petrifold/ ~80 images
├── petrified_wood/ # mineral-replaced wood
├── mineral_texture/ # calcite, basalt, grey rock closeup
└── dead_forest/ # standing dead trees, grey/white tones
```
### Training Configuration
**Tool**: AI Toolkit (Kohya-based), compatible with `comfyui-mcp` LoRA upload API.
**Important**: Train on the full precision schnell model (`flux1-schnell.safetensors`), not the fp8 quantized checkpoint. Training on fp8 weights degrades gradient quality. With 128 GB unified memory this is not a constraint — the full ~24 GB model loads comfortably.
```yaml
model: flux1-schnell.safetensors # full precision for training
training_type: lora
rank: 16
alpha: 16
learning_rate: 1e-4
steps: 2000
batch_size: 1 # Apple Silicon MPS constraint
save_every: 500
```
The trained `pnw-oldgrowth-v1.safetensors` is used with the fp8 inference checkpoint at generation time — LoRA weights are precision-independent.
**Trigger words** injected automatically by artisan per biome:
- `verdant_og_style` → Verdant Old Growth
- `fungal_depths_style` → Fungal Depths
- `petrifold_style` → The Petrifold
### Midjourney's Role
MJ remains useful — just not as training data. Use manually through the web UI for:
- Concept exploration before committing to a biome palette direction
- Composition reference for concept art framing
- Prompt language testing — if a scene reads well in MJ, natural language likely translates to Flux prompts
These outputs inform the pipeline but never enter it as training material or generated game assets.
## Godot Import Configuration
### Texture Import Settings
For all albedo textures imported into `res://assets/textures/`:
```ini
# .import file settings (set in Godot Import dock)
compress/mode = 2 # VRAM compressed (BPTC/S3TC)
compress/high_quality = true
flags/repeat = true # REQUIRED for seamless tiling
flags/filter = true
flags/mipmaps = true # REQUIRED — prevents shimmer at distance
process/fix_alpha_border = false
process/premult_alpha = false
```
Normal maps require different settings:
```ini
compress/normal_map = true # tells Godot to treat as normal map
flags/repeat = true
flags/mipmaps = true
```
### Godot Project Asset Path
```
res://
└── assets/
└── textures/
├── verdant_old_growth/
│ ├── bark/
│ │ ├── bark_douglas_fir_0042_001_albedo.png
│ │ ├── bark_douglas_fir_0042_001_normal.png
│ │ ├── bark_douglas_fir_0042_001_roughness.png
│ │ └── bark_douglas_fir_0042_001_ao.png
│ └── ...
└── ...
```
The `old-growth-assets` submodule mounts at `assets/` in the Godot project root. Only `accepted/` contents are symlinked or copied into `res://assets/` — candidates never enter the Godot project.
## Human Review Gate
Generated assets land in the assets submodule with `accepted: null` in their manifests. Before any asset is referenced in the Godot project:
1. Review batch in any image viewer
2. Set `accepted: true` or `accepted: false` in `manifest.json`
3. Accepted albedo PNGs move to `assets/accepted/` via helper script
4. Derive normal/roughness/AO maps from accepted albedos (Materialize or similar)
5. All four map types imported into Godot with correct settings (§9.1)
This keeps generation non-destructive and separates candidates from production assets.
## Phase Integration
Pipeline is addendum Phase 27 and can run in parallel with active development phases. Texture generation is gated only on biome specs being stable (Phase 8+). Concept art can begin immediately.
**Dependencies on existing specs**:
- Canonical biome definitions (in Biome Prompt Schema above)
- World audio spec (biome boundaries and atmosphere inform concept art composition)
- Observer harness (can be extended to run visual QA on generated assets)
## Image Acquisition Pipeline
### Prerequisites
```bash
pip install gallery-dl # Flickr CC batch download
pip install requests Pillow # image validation script
brew install wget # USFS / Wikimedia direct downloads
```
### USFS Photo Library (Public Domain)
Browse https://www.fs.usda.gov/photography and search for:
- `old growth`, `Pacific Northwest`, `Olympic National Forest`, `Mount Baker-Snoqualmie`
- `Douglas fir`, `western red cedar`, `sword fern`, `nurse log`
- `fungi`, `mushroom`, `lichen`
Download manually — no bulk API. Organize into `training_data/` by biome and category per Dataset Composition above.
### Wikimedia Commons (CC BY / CC BY-SA / PD)
Use Wikimedia Commons API to batch-download by category, filtered to CC BY, CC BY-SA, and Public Domain. Full Python script `scripts/wikimedia_fetch.py` with license filtering and minimum-resolution check lives in the archived spec. Example invocation:
```bash
python scripts/wikimedia_fetch.py \
--category "Pseudotsuga menziesii" \
--licenses "CC-BY-4.0,CC-BY-SA-4.0,PD" \
--output training_data/verdant_old_growth/bark_closeup/ \
--min-resolution 512
```
### Flickr CC
`gallery-dl` handles Flickr CC with license filtering. License IDs: 4 = CC BY, 5 = CC BY-SA.
```bash
gallery-dl \
"https://www.flickr.com/search/?text=old+growth+forest+pacific+northwest&license=4,5" \
--directory training_data/verdant_old_growth/wide_canopy/ \
--filter "width >= 512 and height >= 512"
```
Repeat for each biome × subject combination. See archived spec for the full set of canonical Flickr queries.
### Unsplash / Pexels
Both have no usable bulk API for specific searches without an API key. Full `scripts/unsplash_fetch.py` Python script in archived spec — invoke per biome × subject combination from Dataset Composition.
### Dataset Validation
Before captioning, validate the full dataset with `scripts/validate_dataset.py --input training_data/` (full Python source in archived spec). Checks each image is ≥512px minimum edge, mode is RGB or RGBA, and file is not corrupt.
### Caption Generation
AI Toolkit requires a `.txt` caption file alongside each training image. Captions must include the trigger word for the target biome. Full `scripts/caption_dataset.py` (BLIP-2-based auto-captioning) in archived spec. Trigger words:
- `verdant_og_style`
- `fungal_depths_style`
- `petrifold_style`
**Review captions manually after generation.** Auto-captions will be imperfect — correct any that describe the wrong subject or miss the key material.
## Model Download
### HuggingFace Authentication
Flux.1 [schnell] is a gated model requiring HuggingFace account acceptance of the license.
```bash
pip install huggingface_hub
huggingface-cli login # paste your HF token from hf.co/settings/tokens
# Accept license at: https://huggingface.co/black-forest-labs/FLUX.1-schnell
```
### Download Models
```bash
# Inference model
huggingface-cli download black-forest-labs/FLUX.1-schnell \
flux1-schnell.safetensors \
--local-dir ~/concourse/ComfyUI/models/checkpoints/
# fp8 quantized for fast inference (optional, saves ~12GB vs full)
huggingface-cli download Comfy-Org/flux1-schnell \
flux1-schnell-fp8.safetensors \
--local-dir ~/concourse/ComfyUI/models/checkpoints/
# Text encoders (shared between schnell variants)
huggingface-cli download comfyanonymous/flux_text_encoders \
clip_l.safetensors t5xxl_fp16.safetensors \
--local-dir ~/concourse/ComfyUI/models/text_encoders/
# VAE
huggingface-cli download black-forest-labs/FLUX.1-schnell \
ae.safetensors \
--local-dir ~/concourse/ComfyUI/models/vae/
# Upscale model
huggingface-cli download ai-forever/Real-ESRGAN \
RealESRGAN_x4plus.pth \
--local-dir ~/concourse/ComfyUI/models/upscale_models/
```
### Verify Downloads
```bash
ls -lh ~/concourse/ComfyUI/models/checkpoints/
ls -lh ~/concourse/ComfyUI/models/text_encoders/
ls -lh ~/concourse/ComfyUI/models/vae/
ls -lh ~/concourse/ComfyUI/models/upscale_models/
# Verify ComfyUI sees all models
curl -s http://127.0.0.1:8189/object_info | python3 -m json.tool | grep -A5 "CheckpointLoaderSimple"
```
## Fine-Tuning Process
### Fine-Tuning Process
#### AI Toolkit Installation
```bash
cd ~/concourse
git clone https://github.com/ostris/ai-toolkit
cd ai-toolkit
python -m venv ../.venv-aitoolkit
source ../.venv-aitoolkit/bin/activate
pip install torch torchvision torchaudio # MPS backend
pip install -r requirements.txt
```
#### Training Config (`configs/pnw-oldgrowth-v1.yaml`)
```yaml
job: extension
config:
name: pnw-oldgrowth-v1
process:
- type: sd_trainer
training_folder: output/pnw-oldgrowth-v1
device: mps
trigger_word: null # trigger words are per-image in captions
network:
type: lora
linear: 16
linear_alpha: 16
save:
dtype: float16
save_every: 500
max_step_saves_to_keep: 4
datasets:
- folder_path: ../training_data/
caption_ext: txt
caption_dropout_rate: 0.05
shuffle_tokens: false
cache_latents_to_disk: true
resolution: [512, 768, 1024]
train:
batch_size: 1
steps: 2000
gradient_accumulation_steps: 4
train_unet: true
train_text_encoder: false
gradient_checkpointing: true
noise_scheduler: flowmatch
optimizer: adamw8bit
lr: 1e-4
lr_scheduler: cosine_with_restarts
lr_warmup_steps: 100
max_grad_norm: 1.0
dtype: bf16
model:
name_or_path: ~/concourse/ComfyUI/models/checkpoints/flux1-schnell.safetensors
is_flux: true
quantize: false
sample:
sampler: flowmatch
sample_every: 500
width: 1024
height: 1024
prompts:
- "verdant_og_style, seamless tileable texture of douglas fir bark, pacific northwest"
- "fungal_depths_style, seamless tileable texture of oyster mushroom clusters, pacific northwest"
- "petrifold_style, seamless tileable texture of petrified mineral wood surface, pacific northwest"
neg: ""
seed: 42
walk_seed: true
guidance_scale: 1.0
sample_steps: 4
meta:
name: pnw-oldgrowth-v1
version: "1.0"
```
Train on the full precision schnell model (`flux1-schnell.safetensors`), not the fp8 quantized checkpoint. Training on fp8 weights degrades gradient quality.
```bash
cd ~/concourse/ai-toolkit
source ../.venv-aitoolkit/bin/activate
python run.py configs/pnw-oldgrowth-v1.yaml
```
Training produces checkpoint saves every 500 steps in `output/pnw-oldgrowth-v1/`. Sample images generate at each checkpoint — use these to monitor progress.
**Checkpoint evaluation:**
| Checkpoint | What to look for |
|---|---|
| Step 500 | Trigger words beginning to activate; textures roughly on-topic |
| Step 1000 | Biome character emerging; PNW palette starting to appear |
| Step 1500 | Consistent trigger word response; correct materials and lighting |
| Step 2000 | Full convergence; all three trigger words producing distinct biome characters |
Signs of overfit: images look like copies of training photos, loss of diversity, prompt details ignored → stop earlier. Signs of underfit: trigger words produce generic results, no visible biome differentiation → train longer.
```bash
# Export final LoRA to ComfyUI
cp output/pnw-oldgrowth-v1/pnw-oldgrowth-v1_000002000.safetensors \
~/concourse/ComfyUI/models/loras/pnw-oldgrowth-v1.safetensors
```
### Serving the Model
```bash
# Verify LoRA loads in ComfyUI
curl -s http://127.0.0.1:8189/object_info | python3 -c \
"import json,sys; d=json.load(sys.stdin); print(d['LoraLoader']['input']['required']['lora_name'][0])"
# Should include: pnw-oldgrowth-v1.safetensors
```
Smoke test via `http://127.0.0.1:8189` — load Flux schnell workflow, add LoRA loader, run with prompt `verdant_og_style, seamless tileable texture of douglas fir bark, pacific northwest`, LoRA strength `0.75`, steps `4`, CFG `1.0`. Expected: PNW bark character, cool-neutral palette, visible moss/lichen detail. If output is generic, increase LoRA strength to `0.85–0.95`.
### Generation Workflow
The texture workflow: **generate → make seamless → upscale**.
**Stage 1: Generate 1024×1024 albedo** (via comfyui-mcp `txt2img`):
```json
{
"prompt": "verdant_og_style, a seamless tileable texture of douglas fir bark, deep forest green and rich brown tones, diffused Pacific Northwest canopy light, ancient primordial damp, highly detailed surface, macro photography, physically based rendering material, no horizon, no sky, flat lay perspective",
"negative_prompt": "sunlit clearing, dry, desert, tropical, bright sky, watermark, signature, text, blurry, low quality, cartoon",
"width": 1024, "height": 1024, "steps": 4, "cfg": 1.0, "seed": 42,
"model": "flux1-schnell-fp8.safetensors",
"lora": {"name": "pnw-oldgrowth-v1.safetensors", "strength": 0.75},
"batch_size": 4
}
```
**Stage 2: Make seamless** — apply via ComfyUI `SeamlessTile` node. The offset-and-blend approach shifts the image by half its dimensions, inpaints the visible seam line at low denoise (0.2–0.35), then shifts back. Two passes (horizontal + vertical) required.
**Stage 3: Upscale to 2048×2048** — use RealESRGAN_x4plus at 2× scale from 1024 (not 4× from 512, which loses generation detail).
## Related
- [[Palettes]] — per-biome color regimes
- [[Biomes]] — the three-register gradient art serves
- [[Biome Behavior]] — biome identity across systems
- [[Tone and Horror]] — tonal discipline the art must carry
- [[Pacific Drive Reference]] — peer reference for PNW + domestic-vehicle horror
- [[Rock Scatter Design]] — biome tinting applied to rocks
- [[Art]] — folder MOC
- [[Tooling — Domain MOC]] — domain MOC
- [[Constitution]] — Articles IX (Atmosphere) and XII-G (Visual Design) are the authoritative sources
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-spec-157-two-damage-paths-to-tires: layer/gdscript
Tag assignment · test- Gold
- The note calls for `layer/gdscript`: GDScript (Godot scene-tier logic) (True)
- ember
- The note has no reason to carry `layer/gdscript` (False)
- Signal
- P(true) 0.32 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Spec 157 — Two Damage Paths to Tires.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 157 — Two Damage Paths to Tires
The tire damage system has two independent damage paths that both target tire panels. Adding spawn protection at only the catastrophic-impact level (tire_damage_driver) left the normal damage path unguarded, requiring a second grace at the collision processing level.
## Discovery — Dual Damage Paths to Tires
**Context**: When the player reported losing wheels on spawn, the initial fix added a `spawn_grace_frames` counter to `tire_damage_driver.gd` that suppressed the catastrophic tire-detachment check (`_on_bus_collision`) during the initial settling period. The fix did not prevent wheel loss.
**Discovery**: Tire panels can be destroyed through two independent code paths from a single collision event:
1. **Catastrophic path**: `samba_bus.gd` emits `Events.bus_collision`, which triggers `tire_damage_driver._on_bus_collision`. If force exceeds `catastrophic_force_threshold` (15000) within `_CATASTROPHIC_PROXIMITY` (1.5 m) of a tire, the tire is instantly DETACHED via `panel.take_damage(panel.current_health)`.
2. **Normal damage path**: `samba_bus.gd` calls `_damage_system.apply_damage_at_point(max_force, max_force_position)`, which finds the nearest 1-3 panels to the impact point and applies `damage_amount = force * FORCE_TO_DAMAGE_SCALE`. If a tire is the nearest panel (e.g., the bus lands on a wheel), it receives the full scaled damage — enough to destroy an 80 HP tire from a 16000N+ settling impact.
The first fix only suppressed path 1. The spawn-settling impact was still destroying tires through path 2.
**Resolution**: A two-tier grace approach was needed:
- Tier 1 (`samba_bus.gd`, `_settling_grace_remaining`): Suppresses `apply_damage_at_point` entirely for 90 frames after spawn stabilization ends. `Events.bus_collision` still fires (for the tire driver to track) but damage application and disturbance are skipped.
- Tier 2 (`tire_damage_driver.gd`, `_spawn_grace_remaining`): Longer 300-frame safety net that suppresses catastrophic tire detachment as a fallback if Tier 1 expires before the bus has fully settled.
The two tiers are independent and redundant — tier 1 is the primary defense, tier 2 is insurance against late bounces or edge cases.
## Implications
- Any future modification to spawn behavior or collision processing must account for both damage paths.
- A grace period at the bus level (`_process_collisions`) is the correct place to suppress all spawn-time damage, because both paths originate from the same collision processing call.
- The tire-level grace alone is insufficient for spawn protection but remains useful as a safety net against the catastrophic path specifically.
## References
- `src/scripts/bus/samba_bus.gd` — `_process_collisions` (line 224-230), `_settling_grace_remaining` (line 62)
- `src/scripts/bus/tire_damage_driver.gd` — `_on_bus_collision` (line 421-425), `_spawn_grace_remaining` (line 48)
- `src/scripts/bus/bus_damage_system.gd` — `apply_damage_at_point` (line 174-183)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-spec-157-two-damage-paths-to-tires: type/code
Tag assignment · test- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.57 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Discoveries/Spec 157 — Two Damage Paths to Tires.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 157 — Two Damage Paths to Tires
The tire damage system has two independent damage paths that both target tire panels. Adding spawn protection at only the catastrophic-impact level (tire_damage_driver) left the normal damage path unguarded, requiring a second grace at the collision processing level.
## Discovery — Dual Damage Paths to Tires
**Context**: When the player reported losing wheels on spawn, the initial fix added a `spawn_grace_frames` counter to `tire_damage_driver.gd` that suppressed the catastrophic tire-detachment check (`_on_bus_collision`) during the initial settling period. The fix did not prevent wheel loss.
**Discovery**: Tire panels can be destroyed through two independent code paths from a single collision event:
1. **Catastrophic path**: `samba_bus.gd` emits `Events.bus_collision`, which triggers `tire_damage_driver._on_bus_collision`. If force exceeds `catastrophic_force_threshold` (15000) within `_CATASTROPHIC_PROXIMITY` (1.5 m) of a tire, the tire is instantly DETACHED via `panel.take_damage(panel.current_health)`.
2. **Normal damage path**: `samba_bus.gd` calls `_damage_system.apply_damage_at_point(max_force, max_force_position)`, which finds the nearest 1-3 panels to the impact point and applies `damage_amount = force * FORCE_TO_DAMAGE_SCALE`. If a tire is the nearest panel (e.g., the bus lands on a wheel), it receives the full scaled damage — enough to destroy an 80 HP tire from a 16000N+ settling impact.
The first fix only suppressed path 1. The spawn-settling impact was still destroying tires through path 2.
**Resolution**: A two-tier grace approach was needed:
- Tier 1 (`samba_bus.gd`, `_settling_grace_remaining`): Suppresses `apply_damage_at_point` entirely for 90 frames after spawn stabilization ends. `Events.bus_collision` still fires (for the tire driver to track) but damage application and disturbance are skipped.
- Tier 2 (`tire_damage_driver.gd`, `_spawn_grace_remaining`): Longer 300-frame safety net that suppresses catastrophic tire detachment as a fallback if Tier 1 expires before the bus has fully settled.
The two tiers are independent and redundant — tier 1 is the primary defense, tier 2 is insurance against late bounces or edge cases.
## Implications
- Any future modification to spawn behavior or collision processing must account for both damage paths.
- A grace period at the bus level (`_process_collisions`) is the correct place to suppress all spawn-time damage, because both paths originate from the same collision processing call.
- The tire-level grace alone is insufficient for spawn protection but remains useful as a safety net against the catastrophic path specifically.
## References
- `src/scripts/bus/samba_bus.gd` — `_process_collisions` (line 224-230), `_settling_grace_remaining` (line 62)
- `src/scripts/bus/tire_damage_driver.gd` — `_on_bus_collision` (line 421-425), `_spawn_grace_remaining` (line 48)
- `src/scripts/bus/bus_damage_system.gd` — `apply_damage_at_point` (line 174-183)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-discoveries-spec-157-two-damage-paths-to-tires: type/spec
Tag assignment · test- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.83 (yes)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Discoveries/Spec 157 — Two Damage Paths to Tires.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 157 — Two Damage Paths to Tires
The tire damage system has two independent damage paths that both target tire panels. Adding spawn protection at only the catastrophic-impact level (tire_damage_driver) left the normal damage path unguarded, requiring a second grace at the collision processing level.
## Discovery — Dual Damage Paths to Tires
**Context**: When the player reported losing wheels on spawn, the initial fix added a `spawn_grace_frames` counter to `tire_damage_driver.gd` that suppressed the catastrophic tire-detachment check (`_on_bus_collision`) during the initial settling period. The fix did not prevent wheel loss.
**Discovery**: Tire panels can be destroyed through two independent code paths from a single collision event:
1. **Catastrophic path**: `samba_bus.gd` emits `Events.bus_collision`, which triggers `tire_damage_driver._on_bus_collision`. If force exceeds `catastrophic_force_threshold` (15000) within `_CATASTROPHIC_PROXIMITY` (1.5 m) of a tire, the tire is instantly DETACHED via `panel.take_damage(panel.current_health)`.
2. **Normal damage path**: `samba_bus.gd` calls `_damage_system.apply_damage_at_point(max_force, max_force_position)`, which finds the nearest 1-3 panels to the impact point and applies `damage_amount = force * FORCE_TO_DAMAGE_SCALE`. If a tire is the nearest panel (e.g., the bus lands on a wheel), it receives the full scaled damage — enough to destroy an 80 HP tire from a 16000N+ settling impact.
The first fix only suppressed path 1. The spawn-settling impact was still destroying tires through path 2.
**Resolution**: A two-tier grace approach was needed:
- Tier 1 (`samba_bus.gd`, `_settling_grace_remaining`): Suppresses `apply_damage_at_point` entirely for 90 frames after spawn stabilization ends. `Events.bus_collision` still fires (for the tire driver to track) but damage application and disturbance are skipped.
- Tier 2 (`tire_damage_driver.gd`, `_spawn_grace_remaining`): Longer 300-frame safety net that suppresses catastrophic tire detachment as a fallback if Tier 1 expires before the bus has fully settled.
The two tiers are independent and redundant — tier 1 is the primary defense, tier 2 is insurance against late bounces or edge cases.
## Implications
- Any future modification to spawn behavior or collision processing must account for both damage paths.
- A grace period at the bus level (`_process_collisions`) is the correct place to suppress all spawn-time damage, because both paths originate from the same collision processing call.
- The tire-level grace alone is insufficient for spawn protection but remains useful as a safety net against the catastrophic path specifically.
## References
- `src/scripts/bus/samba_bus.gd` — `_process_collisions` (line 224-230), `_settling_grace_remaining` (line 62)
- `src/scripts/bus/tire_damage_driver.gd` — `_on_bus_collision` (line 421-425), `_spawn_grace_remaining` (line 48)
- `src/scripts/bus/bus_damage_system.gd` — `apply_damage_at_point` (line 174-183)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-art-pacific-drive-reference: type/reference
Tag assignment · test- Gold
- The note calls for `type/reference`: Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`) (True)
- ember
- The note has no reason to carry `type/reference` (False)
- Signal
- P(true) 0.30 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Art/Pacific Drive Reference.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Pacific Drive Reference
The full visual atmosphere and environment design reference for Pacific Drive, assembled as a peer reference for Old Growth's art direction. Pacific Drive is a masterclass in environmental storytelling through *"the twisting of the familiar"* — Creative Director Cassandra Dracott's core design pillar.
Pacific Drive is NOT a direct model for Old Growth, but a useful peer — it shares the Pacific Northwest setting, the single-vehicle horror premise, and the gradient-of-alienation structure Old Growth pursues. This note is the full zone-by-zone catalog for use in image generation, video prompts, and descriptive prose.
## Setting — The Olympic Exclusion Zone
The OEZ encompasses **3,600 square miles** of the Olympic Peninsula in Washington State. Three concentric wards radiate inward, each established during a different era of government experimentation:
- **Outer Zone** (1967)
- **Mid Zone** (1961)
- **Deep Zone** (1955, original test site)
Massive industrial expansion walls separate each ward. The deeper you travel, the longer the land has "marinated in radiation," the more the environment departs from recognizable reality.
**Art style**: stylized realism — not photorealistic but richly atmospheric. Compared to Simon Stålenhag's retro-futuristic paintings, the surrealism of *Twin Peaks*, and the "New Weird" fiction of Jeff VanderMeer's *Annihilation*. Environments "bathed in pastel tones, creating a mood that is both familiar and eerie," with "wonderfully unrealistic lighting" and "a wide array of strong colors for different biomes and different times of day."
Built in Unreal Engine 4 using a Houdini-based procedural terrain pipeline, with Virtual Textures enabling detailed landscape shaders for roads, foliage scattering, and biome blending. Each junction map exceeds **1 km in size**.
## I. The Outer Zone
*Biomes: Damp Forest and Blistering Woods · Sectors E, F, G · Established 1967*
### Weather and atmospheric conditions
The Outer Zone wears the Pacific Northwest's signature weather like a second skin. **Persistent rain** — light to moderate — spatters against windshields with rhythmic regularity. Fog settles in valleys and threads between tree trunks, reducing visibility to a few car-lengths in dense patches. Overcast skies dominate, casting diffused gray-green light through the forest canopy. Cloud cover is thick, low, and unbroken — rarely does direct sunlight penetrate. During **Electromagnetic Squalls**, localized electrical storms crackle through the air with blue-white lightning arcs.
The instability storm's first approach manifests as a **yellow-tinted wall** of atmospheric distortion closing in from the junction edges — the sky shifts to sickly amber, and anomalies spawn aggressively. The second stage follows as a **churning wall of red swirling light** with irregular wavy edges, accompanied by flashes of blue and orange lightning. Air-raid sirens wail as the red front consumes the landscape. Visibility conditions swing between eerie stillness (fog, drizzle, muted light) and violent atmospheric chaos (storms, electrical discharge, falling debris). The weather is never comfortable — even in calm moments, the air feels charged, expectant.
### Terrain and landscape
Dense **temperate rainforest** dominates — towering Douglas firs, western red cedars, and Sitka spruces form a cathedral-like canopy. The understory is lush with ferns, moss-covered logs, abundant undergrowth. Everything glistens with moisture. Ground textures range from soft rain-saturated earth to rocky outcroppings and patches of gravel.
Roads are **cracked two-lane highways** — asphalt seamed with age, edges crumbling into overgrown shoulders. Vegetation reclaims road margins aggressively; grass and ferns push through fractures. Dirt trails branch off into the forest, rutted and muddy.
- **Damp Forest biome**: quintessential PNW — deep greens, moss on every surface, constant wetness, streams cutting through shallow ravines
- **Blistering Woods biome**: same forested character but introduces more electrical anomaly presence — the air itself seems to hum
- **Sierram**: destroyed town in a valley in Junction E1, features a dilapidated highway bridge passing through its center, surrounded by expansive fields of radioactive Hot Dust, with the colossal anomaly "Cappy" dominating the skyline at roughly 15 stories tall
### Structures and architecture
Abandoned infrastructure dots the forest: empty houses with boarded windows, overgrown yards, collapsed porches. **ARDA research trailers** (Anomalous Research Department) rust among the trees — institutional beige-and-green metal boxes trailing cables to nowhere. Gas stations with shattered canopies and dry pumps mark intersections. Emergency cabins with faded signage appear along trails. Derelict vehicles — sedans, trucks, vans from the 1950s–80s — rust in ditches and on road shoulders, some half-consumed by vegetation.
**Observation towers** rise near Sierram, erected post-1973 to monitor the zone's spreading instability. **Sector Stabilizers** — large industrial machinery — are visible in the distance. Power lines sag between weathered wooden poles, cables occasionally sparking.
The structures feel abandoned mid-activity — as if everyone simply vanished, leaving behind detritus of interrupted lives and halted research. Infrastructure is aged but not yet alien; the decay reads as decades of neglect rather than supernatural corruption.
### Lighting and color palette
Dominant palette: **greens, dark browns, slate grays, forest blacks** — the natural PNW spectrum. Lighting consistently **diffused and overcast**, creating soft shadows and muted contrasts. Light filters through the canopy as dappled gray-green. In rain, surfaces reflect with a wet sheen. At dusk and dawn, warm amber tones briefly warm the edges.
**Night is oppressively dark** — without functioning headlights, visibility drops to near-zero. The car's headlights cut weak yellow cones through the murk, catching rain droplets and fog particles. Electrical anomalies introduce **vivid blue-white** accents — Spark Towers throw crackling blue arcs across dark backgrounds. **Violet lights** mark lead panel resource nodes. The stable energy of the Outer Zone glows **yellow/standard** — a warm but cautious hue.
Occasional bioluminescent elements — glowing flowers, anomalous fungi — provide small points of otherworldly color against the overwhelming green-gray.
### Anomalies and surreal elements
The Outer Zone's anomalies feel like reality developing hairline fractures.
- **Tourists** — signature horror. Beige, featureless crash-test-dummy humanoids with smooth faces, visible ball joints at elbows and knees, **dim pulsing red glow** at the chest. Feet root into the ground in a tar-like substance. Only move when unobserved — turn away and they silently close the distance, sometimes appearing in groups of up to 50. Some adopt poses: saluting, arms outstretched.
- **Abductors** — float overhead. UFO-shaped assemblages of car parts (wheels, doors, exhaust pipes, headlights) with traffic-light "eyes," projecting a **blue spotlight** in patrol mode that shifts to **angry red** upon detection. Latch onto the car magnetically and hurl it.
- **Spark Towers** — tall pylons wreathed in vibrant blue electricity, arcing bolts toward anything nearby.
- **Glittering Boulders** — bright shimmering orbs that detonate on proximity.
- **Shakers** — yellow-glowing cracks in the ground that erupt without warning.
- **Potholes** — floating chunks of asphalt and earth suspended in mid-air — gravitational distortion made visible.
- **Honeypots** — abandoned cars in suspiciously perfect condition, loaded with lit road flares, trigger ambushes.
- **Broken Bunnies** — erratic mechanical scrap-metal creatures that hop and latch onto the car — chirp and bounce ahead like corrupted wildlife.
The anomalies are unsettling but comprehensible; the world is wrong, but you can still recognize what it used to be.
### Mood and atmosphere
Developer: *"the feeling of being out there in the woods with this old car, a surreal experience."* Mood oscillates between **cozy isolation** — rain pattering on the windshield, wipers squeaking, radio playing indie folk — and **creeping dread**. Forest "groans and roars" ambientally; bushes rustle with invisible movement. Distant foghorn sounds carry through the fog.
Junction condition **"Eerie Darkness"** plunges everything into pitch black, transforming the experience into genuine survival horror. Combined with **"Something's Moving"** — where Tourists teleport more frequently and shadowy figures appear between trees — the Outer Zone becomes terrifying despite its surface beauty.
Emotional register: a place once ordinary, now haunted by something that doesn't follow the rules. Beauty — genuinely beautiful PNW forestscapes, golden light through rain, wildflowers growing through cracked asphalt — watched over by blank-faced mannequins and humming electrical ghosts.
## II. The Mid Zone
*Biomes: The Mires and The Scorch · Sectors C, D · Established 1961*
### Weather and atmospheric conditions
The Mid Zone is **darker, murkier, wetter** than the Outer Zone. In the Mires, thick mist and fog are near-constant, reducing visibility significantly and creating claustrophobic enclosure. **Corrosive Squalls** bring acid rain — visibly green-tinted precipitation that eats through metal. The rain itself becomes a weapon; droplets leave sizzling trails on the car's surface. The sky hangs lower, heavier, less differentiated — a uniform oppressive gray that rarely breaks.
In the Scorch, atmosphere shifts to a dry, heat-distorted quality — shimmer lines rise from charred ground, and the air carries a haze-like quality despite the lack of actual heat. Electromagnetic disturbances are more frequent and violent. The instability storm feels more urgent here — the yellow and red walls close faster, the environment resists your presence more actively.
### Terrain and landscape
**The Mires**: swampy waterlogged marshland — standing bodies of dark water, boggy ground that sucks at tires, partially submerged vegetation. The lush green of the Outer Zone has curdled into **sickly yellows and murky olive-greens**. Waterways thread between patches of solid ground. Roads are worse — more cracked, more submerged, more overgrown. Driving often means navigating between patches of solid terrain surrounded by black water.
- **Swamp Coral** — bioluminescent organic growths — punctuate the murk with eerie glows
- **Marsh Eggs** — alien organic material — cluster in waterlogged areas
**The Scorch**: burnt wasteland — dead trees stand as blackened skeletons against the sky, branches bare and charred. Ground is **charcoal-black**, cracked and ashen. Sparse surviving plant matter **glows like embers** — orange-red points of light scattered across a dark canvas. **Thermosap Crystals** — large glowing amber-red crystalline formations — protrude from the scorched earth, casting warm light across the blackened landscape. Terrain feels post-apocalyptic — as if flash-burned and never recovered. Groves of skeletal trees create stark silhouettes against the overcast sky.
Contrast between the two Mid Zone biomes is dramatic: one drowning, one burned, both hostile.
### Structures and architecture
Infrastructure in the Mid Zone is more **decayed and partially consumed** by the environment. Buildings in the Mires are half-submerged, lower stories underwater, rooftops barely visible above the marsh. Research facilities show more advanced degradation — walls crumbling, roofs collapsed, equipment corroded by decades of acid exposure.
The Scorch features more **industrial/research remnants** — larger facilities, heavier equipment, installations suggesting serious government investment before abandonment. Cargo containers along routes, some stacked haphazardly, others half-buried. Infrastructure reads as older than the Outer Zone — because it is. Sealed six years earlier (1961 vs 1967), the additional time shows in deeper corrosion, more structural collapse, more complete reclamation by the corrupted environment.
### Lighting and color palette
Mid Zone palette fractures into two distinct registers.
**The Mires**: dominated by **dark greens, murky browns, sickly yellows** — the color of stagnant water, rotting vegetation, corrosive chemicals. Bioluminescent Swamp Coral provides **acid-green glows** reflecting off standing water. Oppressive lighting with minimal contrast — everything uniformly dark and murky.
**The Scorch**: inverts to **blacks, deep charcoal grays, ember-orange accents** — palette of a dying fire. Thermosap Crystals cast **warm amber-red light** across blackened surfaces, creating dramatic contrast between glowing formations and surrounding darkness.
Energy signature of Mid Zone is **Unstable**, coded in **red** — significant visual escalation from Outer Zone's yellow. Overall lighting feels hostile — the beautiful diffused PNW light replaced by suffocating murk or harsh ember-glow. Shadows deeper, contrasts more extreme, comforting green of the forest gone.
### Anomalies and surreal elements
Mid Zone introduces **corrosive/organic anomalies** that feel more biologically disturbing.
- **Pickpockets** — pink translucent jellyfish-like floating creatures with bright green eyes and hanging tentacles — bioluminescent and alien, grab items from your hands or drag you through the air
- **Rotten Eggs** — yellowish acid-goop formations in various sizes — organic pustule-like blobs exploding on surfaces and sticking to the car
- **Moldy Balloons** — fly through the air spraying corrosive trails
- **Cough Boxes** — produce clouds of sickly green corrosive gas enveloping areas
- **Shaggy Scramblers** — mobile explosive threats moving toward the player
- **Belching Barnacles** — extend underground with surface protrusions
- **Spike Puddles** — dark black gooey substances on roads with shimmering purple spikes protruding upward
- **Acid-spitting nests** — launch corrosive projectiles at the car
- **Goo volcano** — shoots animal-shaped goo traps
Anomalies feel less mechanical and more organic — as if zone's corruption has begun generating its own biology. Visual language shifts from Outer Zone's "broken machinery" to something more like "diseased ecosystem."
### Mood and atmosphere
Emotional register shifts from Outer Zone's eerie-but-recognizable to **genuinely hostile and alien**. In-game character Tobias warns things here have *"been marinating in radiation for so long"* that *"things don't work the same."* Mood is **oppressive, claustrophobic, poisonous** — environment itself feels actively malevolent rather than passively strange.
In the Mires, standing water and limited visibility create a feeling of being trapped in a slowly drowning landscape. In the Scorch, charred desolation evokes post-catastrophe despair — a place where something terrible happened and the land never healed. Beauty-in-decay of Outer Zone largely absent; replaced by **grim corroded hostility** making even resource-gathering feel like desperation.
Contrast with Outer Zone is the point — the game deliberately escalates visual and emotional stakes to make the player understand that going deeper means losing the last traces of the familiar.
## III. The Deep Zone
*Biomes: Smokestacks and Red Spires · Sectors A, B, R · Established 1955*
### Weather and atmospheric conditions
Sky in the Deep Zone is **always overcast** — permanent oppressive ceiling of dark cloud that never breaks. Atmosphere denser, heavier, darker than anywhere else in the exclusion zone. **Meteorite Squalls** are the signature weather event — massive objects fall from the sky. These are not small rocks: **rectangular slabs of stone resembling double-wide trailers or building facades**, irradiated and trailing radiation plumes as they slam into the ground. Create a "concrete city of death" as they pile up around the player's route.
The instability storm here is fastest and most aggressive — the red wall of churning light closes with lethal speed. Electromagnetic disturbances are constant background noise. Air itself feels charged and wrong — visibility limited not by fog but by sheer density of atmospheric corruption. Every weather event in the Deep Zone feels apocalyptic in scale.
### Terrain and landscape
**Smokestacks biome**: industrial wasteland — prominent smokestack structures rise from ruins, heavy infrastructure dominates the landscape, terrain reshaped by decades of anomalous activity. Ground scarred, uneven, hostile. Roads barely recognizable beneath debris and geological upheaval.
**Red Spires biome**: game's most visually alien environment. **Towering red-glowing rock formations** — the eponymous spires — rise from the landscape like geological cathedrals, surfaces emanating deep crimson light. **Olympium deposits** — large gray pillars with glowing purple/magenta stones attached — protrude from the earth, creating an alien mineral garden. Ground scored with glowing cracks and anomalous scarring. Landscape departed entirely from anything recognizable as Pacific Northwest terrain — an **otherworldly hellscape** reading as alien geology transplanted onto Earth.
Biome name "Red Spires" doesn't adequately convey the visual impact: imagine Bryce Canyon formations made of glowing red stone, surrounded by purple crystal veins, under a permanently dark sky. The Deep Zone terrain represents the original 1955 test site — the place where everything went wrong first, and has had the longest to become something entirely other.
### Structures and architecture
Deep Zone contains the oldest infrastructure — **1950s-era research facilities** in advanced states of decay and anomalous corruption. Smokestacks biome features heavy industrial architecture — factories, processing plants, research installations with prominent chimney structures. **Red Meadow Research Facility** and similar ARDA installations are present but barely recognizable beneath decades of damage and anomalous transformation. Equipment and structures are partially fused with geological formations. The boundary between built environment and corrupted landscape blurs — walls merge into rock faces, machinery sprouts crystal growths, corridors collapse into geological features.
**The Well**, the final location in the deepest reaches, abandons all pretense of normal architecture — described as **"a bizarre maze of television screens"** where fragments of past conversations play, existing as pure anomalous abstraction rather than any recognizable built environment.
### Lighting and color palette
Deep Zone palette undergoes the game's most dramatic shift. Dominant color is **red** — deep crimson, rust, blood. The entire atmosphere takes on a reddish cast, so pervasive it tints the player's garage lighting when they return. Community members report *"the ambient lighting and sky of my garage has turned to red spires red."* Corrupt energy of this zone glows **magenta/purple** — sickly otherworldly color signaling maximum danger.
In Smokestacks: palette combines **industrial grays with the red shift** — rusted, irradiated industrial aesthetic. In Red Spires: red dominates everything — glowing rock formations, crimson-tinted sky, purple crystal accents. Lighting is **harsh and directional** from anomalous sources rather than natural — no sunlight penetrates the permanent overcast, so illumination comes from glowing spires, purple crystal deposits, and the player's headlights.
Color progression across the entire game — **blue-green (Outer) → murky green/ember orange (Mid) → crimson/magenta (Deep)** — is one of the most effective visual storytelling devices, making the player feel the escalation in their bones.
### Anomalies and surreal elements
Deep Zone anomalies are the most spectacular and dangerous.
- **The Blacksmith** — massive heap of glowing blocks and smooth rocks that emits a **pink/light purple glow** when activated — shakes the ground violently and sends rows of jagged stone protrusions (**Titan's Bones**) radiating outward from its position, spikes erupting from the earth in spreading lines. Glowing marks appear on the ground before the spikes emerge.
- **Can Openers** — giant saw blades that travel back and forth along **glowing red track-lines**, literally **cutting through the earth itself** — the visual of the ground being sliced open is deeply unsettling.
- **Fallen Firmament** — drops massive flat irradiated monoliths from the sky during Meteorite Squalls — alien debris crashing through whatever remains of the canopy.
- **Tour Buses** — grotesque amalgamations of fused Tourists — legs melted into a shared base, bodies arranged in the **shape of two outstretched hands** holding a larger dummy in the center, arms reaching upward, emitting a **bright red glow** and ominous low hum.
- **Boom Bunnies** — angry red-lit explosive scrap creatures bouncing wildly and detonating on contact.
- **Devil Grinders** — destroy tires.
- **Radiant Dredges** — cause punctures, indicated by **purple glow on concrete pillars**.
- **Wriggling Wrecks** — animate car husks moving with disturbing intent.
Anomalies no longer feel like glitches in reality; reality has been completely overwritten.
### Mood and atmosphere
Deep Zone achieves what community members describe as **genuine Lovecraftian wrongness** — a sense that the environment is not merely damaged or corrupted but has become something fundamentally alien that was never meant to be experienced by human consciousness. The red-dominated palette, permanent dark sky, alien geological formations, and sheer density of hostile anomalies create an atmosphere of **maximum hostility and alienation**. Players report increased heart rates and genuine fear.
Mood is not the creeping dread of the Outer Zone or the oppressive hostility of the Mid Zone — it is **existential confrontation with the incomprehensible**. Yet there remains a terrible beauty: the glowing red spires against the dark sky, the purple crystal formations catching light, the spectacular violence of Meteorite Squalls raining alien monoliths. The Deep Zone embodies the game's aesthetic thesis at its most extreme — the Pacific Northwest transformed beyond recognition into something simultaneously terrifying and awe-inspiring.
## IV. The Expansion Walls and Barriers
### The physical wall between zones
Expansion Walls are **massive concrete/industrial barriers** of enormous scale — think Hoover Dam-sized construction spanning the landscape from horizon to horizon. **Construction cranes** remain affixed to the wall's upper edges, frozen mid-build — evidence the barrier was erected hastily and never properly completed. Walls are weathered, stained with decades of rain and radiation exposure, concrete surfaces cracked and discolored.
From a distance, they read as titanic gray-brown slabs cutting across the landscape, disrupting natural topography with brutal industrial geometry. They represent the most visible evidence of government intervention — monuments to containment built by an organization (ARDA) that ultimately failed.
### The storm drain crossing
Passage between the Outer and Mid Zones is through a **storm drain opening** — a large pipe entrance lit by a **single red light**. Interior is **extremely dark** — headlights essential and barely adequate. Inside, architecture shifts to what resembles a **railway station or dockyard**: cargo containers stacked in corridors, industrial pipes carrying coolant along walls, metal catwalks and grating. Pockets of radiation glow in corners. Path winds through darkened tunnels with minimal lighting — bare industrial bulbs, emergency strips. Atmosphere claustrophobic, industrial, threatening. Emerging on the Mid Zone side feels like crossing a threshold from one reality into a worse one.
### The Old Wall crossing into the Deep Zone
Deep Zone crossing involves a **circuit-connection mechanic** — player drives between checkpoint gates that glow **green** when active and shift to **orange** as they expire. **LIM Shield** creates a visible energy barrier around the car during this sequence — shimmering translucent forcefield enveloping the vehicle. Visual effect combines industrial decay (oldest barrier, most corroded) with active energy effects (shield, checkpoint gates, electromagnetic interference). Passage feels more desperate and unstable than the Outer-to-Mid crossing — infrastructure older, more damaged, anomalous activity palpable even in the transitional space.
## V. The World Outside the Exclusion Zone
### The broader Pacific Northwest context
Game set in **1998** — world outside the OEZ is late-'90s Pacific Northwest America. OEZ established in **1955** after catastrophic government experiment; outside world has lived alongside this sealed-off anomalous territory for over four decades. Setting implies **small Pacific Northwest towns** — logging communities, coastal villages, the kind of places where rumors about what's inside the zone circulate as local folklore.
1998 temporal setting allows a specific technological aesthetic: no smartphones, CRT monitors, analog equipment, cassette tapes and early CD players, chunky plastic electronics. World outside presumably features normal beauty of the Olympic Peninsula — **temperate rainforests, rugged Pacific coastline, snow-capped Olympic Mountains, small towns with main streets and lumber mills** — all existing in unsettling proximity to a 3,600-square-mile exclusion zone full of impossible phenomena.
### The player's garage and base camp
Home base is an **abandoned auto shop** — last safe haven inside the zone. Features a **large garage bay** with the station wagon parked inside, surrounded by crafting stations, workbenches covered in tools and salvaged components. A **jukebox** provides music. A **route-planning map** dominates one wall.
Aesthetic: *"mechanic's workshop meets survivalist bunker"* — organized chaos, with shelves of parts, cables hanging from the ceiling, improvised lighting, warm glow of work lamps. Garage exists in a pocket of stability within the zone — outside its walls, the anomalous environment presses in. Contrast between the garage's domestic, warm-lit, workshop comfort and the hostile world outside its doors is a critical emotional anchor.
Garage's ambient lighting and sky actually shift to match the deepest zone the player has accessed — turning red when the Deep Zone's Red Spires have been visited, visually contaminating even this safe space.
### The retro-tech UI aesthetic
Interface design reinforces 1998 temporal setting. Player's **AR headset** is designed as a hacked-together PDA — UI inspired by **Windows 3.1** aesthetics with pixelated fonts, thick angular lines, and "home office" OS-like icons. Menus resemble **tarnished scraps of paper** — physical documents from a time before the exclusion zone. Icon system uses "chunky and readable" designs based on household objects — rubber ducks, plastic cups — rather than abstract sci-fi symbols.
Retro-tech aesthetic extends to in-game equipment: analog gauges, physical switches, cassette-based recording devices. Grounds the experience in a specific nostalgic technological moment contrasting sharply with the impossible phenomena the technology is being used to navigate.
## VI. The Station Wagon — A Visual Character Study
### Base aesthetic and design lineage
Car is a **fictional 1983 "Radius Motors" station wagon** — a composite of American "woodie" wagons from the late '70s and '80s. Primary design inspirations: **1989 Buick LeSabre Estate**, **Volvo 700-series wagon**, **1979 Ford Country Squire**, **Mercury Colony Park**. Interior resembles a **1980s Mercury Zephyr**. License plate reads **"MELIC HT"** (an anagram of "LIM TECH") on a Washington 1947 plate.
Starting appearance: **beat-up woodie wagon** — faux wood-grain paneling along the sides, chrome bumpers with surface pitting, rectangular headlights behind cracked lenses, boxy silhouette that screams family road trips and suburban driveways. Car begins as a mishmash of Crude and Steel parts — improvised, barely functional, held together by determination and anomalous energy.
### The car as living anomaly
Wagon is a **"Remnant"** — living anomaly bonded to the driver. **Strange pulsing veins** run across certain components, most visible under the hood when the engine is removed and in the **"trunk gunk"** — organic vein-like substance holding the small gas can in place. Veins mirror organic structures found on anomalous formations throughout the zone.
Car develops **"quirks"** — emergent if-then behaviors from prolonged zone exposure: headlights dimming when the steering wheel turns, doors flying open during acceleration, the hood popping up to block the driver's view, the horn blaring when the trunk closes. Quirks make the car feel alive — an unpredictable partner rather than a mere vehicle.
### Visual progression and damage aesthetics
Car's visual journey follows what developers called the **"Doc Brown in a rush"** principle — the aesthetic of improvised science, of equipment that is functional but hastily assembled. Progression moves from **Crude** (rough, scavenged, barely held together) through **Steel** (industrial gray, functional) to specialized variants:
- **Insulated** (electrical resistance — rubberized, insulated surfaces)
- **Lead-Plated** (radiation resistance — heavy, dull-gray plating)
- **Anticorrosive** (acid resistance)
- **Armored** (impact protection — visible reinforcement)
- **Carbon Fiber** (lightweight, modern)
- **Olympium** (endgame material — **purple-tinged**, incorporating the zone's rarest mineral)
Damage is individual and granular — panels dent and crack independently, tires pop and deflate (forcing driving on bare rims), windshield spiders with cracks, doors can be torn away entirely, and the hood can be destroyed. Damage state of the car at any moment tells its own visual story of what the player has survived.
### Dashboard and interior atmosphere
Interior experienced exclusively in **first person** — player sees the world through windshield and side windows, creating intimate claustrophobic immersion. Dashboard features **functional analog gauges** glowing amber — fuel, battery, health, shield condition. Car requires physical interaction: **turning the ignition key, shifting gears, cranking windows, toggling headlights and wipers**.
Cosmetic customization allows fuzzy dice hanging from the rearview, dashboard bobbleheads, 8-ball shifter knobs, pirate ship hood ornaments, antenna toppers (including a buttered toast slice). Radio cycles through stations playing rock, country, indie folk, synthwave, ethereal vocals — soundtrack providing emotional counterpoint to visual chaos outside.
Overall interior aesthetic: **"beloved beater car"** — personal, lived-in, comforting in its familiarity, even as the windshield frames impossible horrors.
### The car in its environment
Central visual tension of Pacific Drive: **contrast between the battered domestic station wagon and the hostile alien landscape it traverses**. A wood-paneled family wagon bristling with improvised sci-fi equipment — lightning rods, LIM pulse emitters, ion shields, radar antennae, side racks — careening through acid-scarred marshlands and crimson alien spire formations.
Headlights cut weak yellow cones through dense fog, catching rain droplets and illuminating Tourists standing motionless on the road ahead. Narrow first-person view amplifies disorientation during extreme moments: being launched airborne by Airstrip anomalies, being dragged sideways by Abductors, careening through acid rain with wipers going haywire, racing toward a **massive pillar of orange-white exit light** erupting from the ground while the red storm wall churns behind.
Available paint jobs reinforce the aesthetic range: **Aurora Frost** (shimmering green-blue Northern Lights gradient), **Rust & Gold** (weathered metallic), **Neon Dreams** (black base with glowing neon stripes that pulse in dim light). The car is simultaneously the most mundane and most extraordinary object in the game world.
## VII. Prompt-Ready Visual Descriptions
### Image generation reference passages
**The cozy-dread drive**: A battered 1980s wood-paneled station wagon with mismatched steel body panels and improvised sci-fi equipment on its roof rack — lightning rods, antenna arrays, glowing sensors — drives down a rain-slicked two-lane forest highway at dusk. Headlights cut weak amber cones through dense Pacific Northwest fog. Towering Douglas firs line both sides, their trunks disappearing into mist. Rain spatters the cracked windshield. Wipers mid-sweep. The road ahead is empty but feels watched. Muted teal-green color grading. Stylized realism. Pastel atmospheric tones. The mood of Twin Peaks meets Stalker.
**Tourist encounter**: A beaten station wagon's headlights illuminate a semicircle of featureless beige crash-test-dummy humanoid figures standing motionless on an overgrown forest road. Their smooth faces are blank. Ball joints visible at elbows and knees. A dim pulsing red glow emanates from each chest. Their feet sink into tar-like black substance fusing them to the asphalt. Dense fog behind them. Dead trees. One figure's arm is raised in a frozen salute. The car's paint is scratched and dented. Atmosphere of dread and wrongness. Night scene, headlight illumination only.
**The storm escape**: A station wagon races at maximum speed down a crumbling forest road toward a massive pillar of blinding orange-white light erupting from the ground into the sky. Behind the car, a wall of churning red swirling energy consumes the landscape — trees being torn apart, debris spinning in the air, the ground glowing red. Yellow-tinted radiation zone transitions to hellish crimson. Blue and orange lightning flashes within the red wall. Air-raid siren mood. Desperate velocity. The car's rear bumper sparks against road debris.
**The Mires**: A station wagon navigates a waterlogged marshland road — standing bodies of dark water on both sides, partially submerged dead trees, bioluminescent swamp coral glowing acid-green beneath the surface. Thick fog limits visibility. The sky is a uniform oppressive gray. Sickly yellow-green color palette. Jellyfish-like pink anomalies float in the middle distance, translucent and bioluminescent. The car's paint is corroded, panels pocked with acid damage. The mood is suffocating, poisonous, claustrophobic.
**The Scorch**: A lone station wagon on a road through a charred wasteland — blackened skeletal trees against a dark overcast sky, ground cracked and ashen. Scattered among the darkness, surviving plant matter glows like dying embers — orange-red points of light. Large amber-red crystalline formations (Thermosap Crystals) protrude from the scorched earth, casting warm light across the blackened landscape. The palette is charcoal, black, and ember-orange. Post-apocalyptic beauty. The car's headlights reveal the devastation ahead.
**Red Spires vista**: A battered station wagon on a winding road through an alien landscape — towering red-glowing rock spire formations rise against a permanently dark overcast sky. Purple-magenta crystal veins grow from gray stone pillars. The ground is scored with glowing pink cracks radiating outward from a distant anomalous formation. The entire atmosphere is tinted crimson. The car, with its wood paneling and improvised equipment, looks impossibly mundane against this otherworldly hellscape. The mood is awe and existential dread in equal measure.
**The Expansion Wall**: A massive concrete barrier stretches from horizon to horizon across a forested Pacific Northwest landscape. Construction cranes remain affixed to its upper edge, frozen mid-build. The concrete is weathered, stained with decades of rain and anomalous exposure. At its base, a storm drain opening glows with a single red light. A station wagon approaches, headlights on, dwarfed by the wall's scale. Dense forest presses against both sides of the barrier. The mood is governmental brutalism versus wild nature — containment architecture on a titanic scale.
**The Blacksmith eruption**: A massive formation of glowing blocks and smooth rocks emits intense pink-purple light from its core. Jagged stone spikes erupt from the earth in radiating lines, spreading outward in all directions. Glowing marks on the ground precede each spike's emergence. A station wagon swerves desperately between the emerging formations. Debris and dust fill the air. The Red Spires biome's crimson rock formations loom in the background. The entire scene is chaos rendered in reds, pinks, purples, and stone grays. Deep Zone atmosphere — alien, hostile, spectacular.
**Dashboard intimacy in rain**: First-person interior view of a 1980s station wagon dashboard — amber-glowing analog gauges, a fuzzy dice hanging from the rearview mirror, a small bobblehead on the dash. Rain hammers the cracked windshield — wipers smear water across fracture lines. Through the glass: a pair of floating Abductor drones with green scanning light-cones hover above a dark forest road. The radio dial glows softly. The car's battery gauge is low. Outside, the world is hostile and alien. Inside, it's almost cozy. The contrast is everything.
### Video generation reference passages
**Slow approach through fog (5–10 seconds)**: Camera follows behind a station wagon driving slowly through dense Pacific Northwest fog on a narrow forest road. Headlights create amber cones in the mist. Trees materialize and pass. Rain beads on surfaces. The car's brake lights glow red as it slows. Something stands in the road ahead — a humanoid silhouette, motionless. The car stops. The figure doesn't move. Atmospheric sound: rain, engine idle, distant foghorn.
**Storm chase sequence (10–15 seconds)**: Camera mounted on car's rear, facing backward. A churning wall of red energy advances, consuming forest behind the car. Blue-orange lightning flashes within the wall. Trees are torn apart at the boundary. The car accelerates — road debris scatters, tires squeal on wet asphalt. Camera pans forward to reveal a pillar of white-orange light ahead — the exit gateway. The car races toward it. The red wall gains. Impact with the light column — white flash to black.
**Zone transition montage (15–20 seconds)**: Series of dissolving landscape shots showing the progression: lush green PNW forest with rain → murky swamp with bioluminescent green glows → charred blackened landscape with ember-orange points → towering red rock spires under dark sky with purple crystal formations. Each transition increases the color saturation toward red. A station wagon appears in each shot, growing progressively more battered and modified. The music shifts from ambient folk to dissonant electronic drone.
## The Thread Connecting Beauty to Horror
Pacific Drive's visual achievement is not any single element but the **gradient between them**. The game begins in a place you might actually visit — the rainy forests of Washington's Olympic Peninsula, where moss covers everything and the trees are older than nations. It ends in a place that could not exist — crimson spire formations under a permanently dark sky, where the ground erupts in radiating lines of stone and rectangular slabs of rock fall from the clouds like alien rain.
Between those extremes, the game maps a continuous visual degradation: green becomes sickly yellow, rain becomes acid, forests become marshes become charred wastelands become alien geology. And through all of it drives a wood-paneled 1980s station wagon held together with tape, improvised science, and what might be the car's own living will to survive.
Developers drew from Stålenhag's kitchen-sink sci-fi, VanderMeer's New Weird, and the genuine atmosphere of the Pacific Northwest's "very, very old parts of nature" to create a visual vocabulary that is unique in games — familiar enough to feel like a place you've been, strange enough to feel like a place that shouldn't be. That tension between recognition and alienation, between the cozy glow of a car radio and the blank stare of a crash-test mannequin in your headlights, is what makes Pacific Drive's visual identity so potent for creative reference.
## Why This Matters for Old Growth
- **Shared setting register**: Old Growth's PNW (a fictional ancient forest) and Pacific Drive's OEZ (a fictional Olympic Peninsula) share moss, rain, dense conifers, and the weight of a place older than human presence.
- **Shared premise**: a single vehicle, a hostile world, and an inability to defeat the environment through escalating capability.
- **Shared gradient**: Pacific Drive escalates from rainy forest → murky marsh → alien spires; Old Growth escalates from [[Biomes|Verdant → Fungal Depths → Petrifold]]. Both use the gradient itself as the horror.
- **Shared domestic anchor**: both have a garage — Pacific Drive's "abandoned auto shop" as safe haven vs. Old Growth's garage (see [[Narrative Direction]]).
- **Key difference**: Pacific Drive's anomalies are **mechanical/biomechanical** (UFOs, crash-test-dummies, saw blades). Old Growth's anomalies are **biological/geological** (mycelium, spores, petrification).
- **Key difference**: Pacific Drive's OEZ has a **government narrative** (ARDA, test site, history). Old Growth's forest has **no history** — no author, no cause, no intent. The forest is older than narrative.
- **Key difference**: Pacific Drive's car develops **upgrades and quirks** (progression with personality). Old Growth's Samba carries **commitments as tradeoffs** (Article IV Rule 8) — different configuration, not greater capability.
## Related
- [[Visual Direction]] — Old Growth's canonical art direction
- [[Palettes]] — per-biome palette decisions
- [[Biomes]] — the gradient that drives palette shifts
- [[Narrative Direction]] — the garage/vehicle/world triangle
- [[Tone and Horror]] — the tonal discipline
- [[Rock Scatter Design]] — concrete art decision for biome identity
- [[Art]] — folder MOC
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-dashboard-telemetry-and-model-routing-may-22-2026: domain/tooling
Tag assignment · test- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.08 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Dashboard Telemetry and Model Routing — May 22 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Dashboard Telemetry and Model Routing — May 22 2026
This session added model routing visibility to the persona dashboard and closed a critical telemetry gap. The work spanned three layers: a new backend endpoint, dashboard card rendering, and a telemetry wiring fix in the Letta bridge. A significant discovery emerged when the dashboard surfaced GTM's 100% escalation rate.
## Work completed
### GET /health/model-routing endpoint
New JSON endpoint at runtime.py that reads the weekly JSONL telemetry files and classifies each LLM call into a routing tier using a `_MODEL_TIERS` mapping dict. Returns per-persona: total calls and cost, breakdown by tier (cheap/foundational/frontier), breakdown by model, escalation rate (foundational+frontier / total), and a color-coded status indicator (green <=10%, yellow 10-15%, red >15%). Supports `?window=daily|weekly|monthly` query parameter. The endpoint is polled by the dashboard every 10 seconds alongside the existing 15 health endpoints.
### Dashboard Model Routing card
New card rendered in the existing health dashboard at `:9100/dashboard` and `:9101/dashboard`. Shows per-persona escalation rate with status indicator, tier distribution with call counts and percentages, and per-model sub-breakdown with cost. Sits alongside the existing cost card (total spend by model and persona), routing card (inter-agent dispatch stats), API calls card (Prometheus counters), and audit log card.
### Letta bridge telemetry
Before this session, `record_llm_call()` in telemetry.py was only called from `llm.py`, which is orphaned — the runtime uses Letta for all LLM calls, not the direct dispatch in `llm.py`. This meant the weekly JSONL files at `logs/personas/{persona}/weekly/{iso-week}.jsonl` were never populated, and the new `/health/model-routing` endpoint returned empty data.
Fixed by adding a lazy import of `record_llm_call` in `letta_bridge.py:send_to_letta()`. After each successful Letta API call, the function extracts usage data (prompt tokens, completion tokens) from the response, resolves the agent model from the Letta agent config (cached in a module-level dict), estimates cost using a per-model pricing map, and calls `record_llm_call()`. The OTel span emission fails silently if the collector is unavailable — the weekly JSONL write and Prometheus counters are the primary telemetry path.
A `_resolve_agent_model()` helper queries the Letta agent's `model` field on first call for each agent_id and caches the result. An `_estimate_cost()` function maps model names to per-million-token rates (input, output) from a dict keyed by short model name.
### model tier mapping expansion
The `_MODEL_TIERS` dict in runtime.py was expanded to cover both bare model names (`openai/gpt-4o-mini`) and openrouter-prefixed variants (`openrouter/openai/gpt-4o-mini`) since Letta returns the full handle. Also added `openrouter/anthropic/claude-sonnet-4` mapping to `foundational` tier.
## Discovery: GTM on Sonnet for 12 days
When the dashboard first showed the Model Routing card, the GTM persona displayed a 100% escalation rate — every call was routed through `openrouter/anthropic/claude-sonnet-4` (foundational tier) instead of a cheap-tier model. Investigation revealed:
- The GTM Letta agent was created on 2026-05-10 with `claude-sonnet-4` as its default model
- The cheap-tier-default routing strategy was ratified in `Autonomous Company LLM Provider Selection` on 2026-05-08 — two days before the agent was created
- The routing layer that would enforce model selection (backlog item 28) was never implemented
- The dashboard that would surface escalation rates did not exist until this session
- The telemetry that would show the cost discrepancy was not wired until this session
The GTM agent's model was updated to `openrouter/openai/gpt-4o-mini` via the Letta API during this session. The 3 historic Sonnet calls remain in the weekly JSONL and will roll off as new cheap-tier calls accumulate. At $0.15/M input vs $3.00/M input, the GTM was operating at 20x the expected cost for its entire lifetime.
Root cause: the design was ratified but never enforced. Three separate gaps — no routing layer, no visibility dashboard, no telemetry — all had to exist simultaneously for the misconfiguration to persist undetected for 12 days. Closing any one of them would have caught it.
### Backlog items enriched
Items 25 (Intra-Session Persona Cost Telemetry), 28 (Cheap-Tier Routing Layer Spec), and 29 (Foundational-Escalation Tag Protocol) were enriched with concrete implementation context from this session, including file paths to modify, the `MODEL_DEFAULT` discrepancy in `llm.py`, the discovery that `llm.py` is orphaned, and the GTM-on-Sonnet lesson.
## References
- `personas/runtime.py` — `/health/model-routing` endpoint, `_MODEL_TIERS`, dashboard Model Routing card
- `personas/letta_bridge.py` — `send_to_letta()` now calls `record_llm_call()`, `_resolve_agent_model()`, `_estimate_cost()`
- `personas/telemetry.py` — `record_llm_call()`, `_record_weekly_summary()`
- `personas/llm.py` — orphaned routing layer with `MODEL_DEFAULT` discrepancy
- `docs/vault/Reference/Autonomous Company/Backlog.md` — items 25, 28, 29 enriched
- `docs/vault/Reference/Autonomous Company/Autonomous Company Architecture.md` — status table updated
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-dashboard-telemetry-and-model-routing-may-22-2026: type/code
Tag assignment · test- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.69 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Dashboard Telemetry and Model Routing — May 22 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Dashboard Telemetry and Model Routing — May 22 2026
This session added model routing visibility to the persona dashboard and closed a critical telemetry gap. The work spanned three layers: a new backend endpoint, dashboard card rendering, and a telemetry wiring fix in the Letta bridge. A significant discovery emerged when the dashboard surfaced GTM's 100% escalation rate.
## Work completed
### GET /health/model-routing endpoint
New JSON endpoint at runtime.py that reads the weekly JSONL telemetry files and classifies each LLM call into a routing tier using a `_MODEL_TIERS` mapping dict. Returns per-persona: total calls and cost, breakdown by tier (cheap/foundational/frontier), breakdown by model, escalation rate (foundational+frontier / total), and a color-coded status indicator (green <=10%, yellow 10-15%, red >15%). Supports `?window=daily|weekly|monthly` query parameter. The endpoint is polled by the dashboard every 10 seconds alongside the existing 15 health endpoints.
### Dashboard Model Routing card
New card rendered in the existing health dashboard at `:9100/dashboard` and `:9101/dashboard`. Shows per-persona escalation rate with status indicator, tier distribution with call counts and percentages, and per-model sub-breakdown with cost. Sits alongside the existing cost card (total spend by model and persona), routing card (inter-agent dispatch stats), API calls card (Prometheus counters), and audit log card.
### Letta bridge telemetry
Before this session, `record_llm_call()` in telemetry.py was only called from `llm.py`, which is orphaned — the runtime uses Letta for all LLM calls, not the direct dispatch in `llm.py`. This meant the weekly JSONL files at `logs/personas/{persona}/weekly/{iso-week}.jsonl` were never populated, and the new `/health/model-routing` endpoint returned empty data.
Fixed by adding a lazy import of `record_llm_call` in `letta_bridge.py:send_to_letta()`. After each successful Letta API call, the function extracts usage data (prompt tokens, completion tokens) from the response, resolves the agent model from the Letta agent config (cached in a module-level dict), estimates cost using a per-model pricing map, and calls `record_llm_call()`. The OTel span emission fails silently if the collector is unavailable — the weekly JSONL write and Prometheus counters are the primary telemetry path.
A `_resolve_agent_model()` helper queries the Letta agent's `model` field on first call for each agent_id and caches the result. An `_estimate_cost()` function maps model names to per-million-token rates (input, output) from a dict keyed by short model name.
### model tier mapping expansion
The `_MODEL_TIERS` dict in runtime.py was expanded to cover both bare model names (`openai/gpt-4o-mini`) and openrouter-prefixed variants (`openrouter/openai/gpt-4o-mini`) since Letta returns the full handle. Also added `openrouter/anthropic/claude-sonnet-4` mapping to `foundational` tier.
## Discovery: GTM on Sonnet for 12 days
When the dashboard first showed the Model Routing card, the GTM persona displayed a 100% escalation rate — every call was routed through `openrouter/anthropic/claude-sonnet-4` (foundational tier) instead of a cheap-tier model. Investigation revealed:
- The GTM Letta agent was created on 2026-05-10 with `claude-sonnet-4` as its default model
- The cheap-tier-default routing strategy was ratified in `Autonomous Company LLM Provider Selection` on 2026-05-08 — two days before the agent was created
- The routing layer that would enforce model selection (backlog item 28) was never implemented
- The dashboard that would surface escalation rates did not exist until this session
- The telemetry that would show the cost discrepancy was not wired until this session
The GTM agent's model was updated to `openrouter/openai/gpt-4o-mini` via the Letta API during this session. The 3 historic Sonnet calls remain in the weekly JSONL and will roll off as new cheap-tier calls accumulate. At $0.15/M input vs $3.00/M input, the GTM was operating at 20x the expected cost for its entire lifetime.
Root cause: the design was ratified but never enforced. Three separate gaps — no routing layer, no visibility dashboard, no telemetry — all had to exist simultaneously for the misconfiguration to persist undetected for 12 days. Closing any one of them would have caught it.
### Backlog items enriched
Items 25 (Intra-Session Persona Cost Telemetry), 28 (Cheap-Tier Routing Layer Spec), and 29 (Foundational-Escalation Tag Protocol) were enriched with concrete implementation context from this session, including file paths to modify, the `MODEL_DEFAULT` discrepancy in `llm.py`, the discovery that `llm.py` is orphaned, and the GTM-on-Sonnet lesson.
## References
- `personas/runtime.py` — `/health/model-routing` endpoint, `_MODEL_TIERS`, dashboard Model Routing card
- `personas/letta_bridge.py` — `send_to_letta()` now calls `record_llm_call()`, `_resolve_agent_model()`, `_estimate_cost()`
- `personas/telemetry.py` — `record_llm_call()`, `_record_weekly_summary()`
- `personas/llm.py` — orphaned routing layer with `MODEL_DEFAULT` discrepancy
- `docs/vault/Reference/Autonomous Company/Backlog.md` — items 25, 28, 29 enriched
- `docs/vault/Reference/Autonomous Company/Autonomous Company Architecture.md` — status table updated
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-dashboard-telemetry-and-model-routing-may-22-2026: type/discovery
Tag assignment · test- Gold
- The note has no reason to carry `type/discovery` (False)
- ember
- The note calls for `type/discovery`: Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.) (True)
- Signal
- P(true) 0.55 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Dashboard Telemetry and Model Routing — May 22 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Dashboard Telemetry and Model Routing — May 22 2026
This session added model routing visibility to the persona dashboard and closed a critical telemetry gap. The work spanned three layers: a new backend endpoint, dashboard card rendering, and a telemetry wiring fix in the Letta bridge. A significant discovery emerged when the dashboard surfaced GTM's 100% escalation rate.
## Work completed
### GET /health/model-routing endpoint
New JSON endpoint at runtime.py that reads the weekly JSONL telemetry files and classifies each LLM call into a routing tier using a `_MODEL_TIERS` mapping dict. Returns per-persona: total calls and cost, breakdown by tier (cheap/foundational/frontier), breakdown by model, escalation rate (foundational+frontier / total), and a color-coded status indicator (green <=10%, yellow 10-15%, red >15%). Supports `?window=daily|weekly|monthly` query parameter. The endpoint is polled by the dashboard every 10 seconds alongside the existing 15 health endpoints.
### Dashboard Model Routing card
New card rendered in the existing health dashboard at `:9100/dashboard` and `:9101/dashboard`. Shows per-persona escalation rate with status indicator, tier distribution with call counts and percentages, and per-model sub-breakdown with cost. Sits alongside the existing cost card (total spend by model and persona), routing card (inter-agent dispatch stats), API calls card (Prometheus counters), and audit log card.
### Letta bridge telemetry
Before this session, `record_llm_call()` in telemetry.py was only called from `llm.py`, which is orphaned — the runtime uses Letta for all LLM calls, not the direct dispatch in `llm.py`. This meant the weekly JSONL files at `logs/personas/{persona}/weekly/{iso-week}.jsonl` were never populated, and the new `/health/model-routing` endpoint returned empty data.
Fixed by adding a lazy import of `record_llm_call` in `letta_bridge.py:send_to_letta()`. After each successful Letta API call, the function extracts usage data (prompt tokens, completion tokens) from the response, resolves the agent model from the Letta agent config (cached in a module-level dict), estimates cost using a per-model pricing map, and calls `record_llm_call()`. The OTel span emission fails silently if the collector is unavailable — the weekly JSONL write and Prometheus counters are the primary telemetry path.
A `_resolve_agent_model()` helper queries the Letta agent's `model` field on first call for each agent_id and caches the result. An `_estimate_cost()` function maps model names to per-million-token rates (input, output) from a dict keyed by short model name.
### model tier mapping expansion
The `_MODEL_TIERS` dict in runtime.py was expanded to cover both bare model names (`openai/gpt-4o-mini`) and openrouter-prefixed variants (`openrouter/openai/gpt-4o-mini`) since Letta returns the full handle. Also added `openrouter/anthropic/claude-sonnet-4` mapping to `foundational` tier.
## Discovery: GTM on Sonnet for 12 days
When the dashboard first showed the Model Routing card, the GTM persona displayed a 100% escalation rate — every call was routed through `openrouter/anthropic/claude-sonnet-4` (foundational tier) instead of a cheap-tier model. Investigation revealed:
- The GTM Letta agent was created on 2026-05-10 with `claude-sonnet-4` as its default model
- The cheap-tier-default routing strategy was ratified in `Autonomous Company LLM Provider Selection` on 2026-05-08 — two days before the agent was created
- The routing layer that would enforce model selection (backlog item 28) was never implemented
- The dashboard that would surface escalation rates did not exist until this session
- The telemetry that would show the cost discrepancy was not wired until this session
The GTM agent's model was updated to `openrouter/openai/gpt-4o-mini` via the Letta API during this session. The 3 historic Sonnet calls remain in the weekly JSONL and will roll off as new cheap-tier calls accumulate. At $0.15/M input vs $3.00/M input, the GTM was operating at 20x the expected cost for its entire lifetime.
Root cause: the design was ratified but never enforced. Three separate gaps — no routing layer, no visibility dashboard, no telemetry — all had to exist simultaneously for the misconfiguration to persist undetected for 12 days. Closing any one of them would have caught it.
### Backlog items enriched
Items 25 (Intra-Session Persona Cost Telemetry), 28 (Cheap-Tier Routing Layer Spec), and 29 (Foundational-Escalation Tag Protocol) were enriched with concrete implementation context from this session, including file paths to modify, the `MODEL_DEFAULT` discrepancy in `llm.py`, the discovery that `llm.py` is orphaned, and the GTM-on-Sonnet lesson.
## References
- `personas/runtime.py` — `/health/model-routing` endpoint, `_MODEL_TIERS`, dashboard Model Routing card
- `personas/letta_bridge.py` — `send_to_letta()` now calls `record_llm_call()`, `_resolve_agent_model()`, `_estimate_cost()`
- `personas/telemetry.py` — `record_llm_call()`, `_record_weekly_summary()`
- `personas/llm.py` — orphaned routing layer with `MODEL_DEFAULT` discrepancy
- `docs/vault/Reference/Autonomous Company/Backlog.md` — items 25, 28, 29 enriched
- `docs/vault/Reference/Autonomous Company/Autonomous Company Architecture.md` — status table updated
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-authoring-session-083-096-apr-23-2026: domain/art
Tag assignment · test- Gold
- The note calls for `domain/art`: Visual direction, palettes, references, asset pipeline (True)
- ember
- The note has no reason to carry `domain/art` (False)
- Signal
- P(true) 0.05 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec Authoring Session — 083-096 Apr 23 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec Authoring Session — 083-096 Apr 23 2026
This session authored 13 new feature specs for Old Growth (083–087, 089–096), corrected a branch targeting error, and updated the vault with corresponding spec stubs.
## Work Completed
### Spec Creation (088-vault-audit-agent branch)
Fourteen spec directories were created (083–097); all except 088 and 097 (already authored) received full content in this session. The following specs were authored from blank placeholders to complete spec.md files:
| Spec | Title | Lines |
|---|---|---|
| 083 | Atmospheric Splash Screen | 172 |
| 084 | First-Run Experience | 140 |
| 085 | Game Controller Support | 184 |
| 086 | Performance and Stability Testing | 167 |
| 087 | Main Menu Polish | 150 |
| 089 | Crafting Panel and Car Maintenance Abilities | 132 |
| 090 | Car Maintenance System | 169 |
| 091 | Player Abilities System | 148 |
| 092 | Garage Map and Trip Planner | 148 |
| 093 | Player Clothing and Equipment | 149 |
| 094 | Additional Biomes — Meadow and Swamp | 186 |
| 095 | Train Tracks and Abandoned Railways | 132 |
| 096 | Waterways — Lakes and Rivers | 132 |
All specs passed zero-placeholder-hit verification before commit.
### Branch Correction
Spec creation commits initially landed on `main` (two commits: `0c2aaf8`, `f5f7496`). Corrected via cherry-pick onto `088-vault-audit-agent` followed by `git reset --hard origin/main` on main. Both commits are now exclusively on `088-vault-audit-agent` at `80682f6` and `65547b4`.
### Vault Updates
Thirteen spec stubs added to `docs/vault/Specs/` (083–087, 089–096). This session note added. Specs MOC updated.
## Key Design Decisions Captured in Specs
- **083 Splash**: `splash_completed` signal lives on the script, not on `Events` — transient scene, single consumer.
- **084 First-Run**: Fixed seed in a Resource; flag set only on successful extraction.
- **085 Controller**: Rumble is a horror signal tied to disturbance thresholds, not convenience feedback.
- **090 Car Maintenance**: Battery at 0% is the only state that blocks run start; all other systems degrade gracefully.
- **091 Abilities**: Every ability cost is unconditional and structurally separate from its benefit.
- **092 Map**: Shows the *previous* run's world, not the upcoming one — intentional horror design.
- **094 Swamp**: Disturbance "unreliability" is a display offset, not a change to the underlying meter value.
- **096 Waterways**: River ambient audio contributes +0.02/s passive disturbance within 20m.
## References
- `specs/083-atmospheric-splash-screen/spec.md`
- `specs/084-first-run-experience/spec.md`
- `specs/085-game-controller-support/spec.md`
- `specs/086-performance-stability-testing/spec.md`
- `specs/087-main-menu-polish/spec.md`
- `specs/089-crafting-panel/spec.md`
- `specs/090-car-maintenance/spec.md`
- `specs/091-player-abilities/spec.md`
- `specs/092-garage-trip-planner/spec.md`
- `specs/093-player-equipment/spec.md`
- `specs/094-meadow-swamp-biomes/spec.md`
- `specs/095-train-tracks/spec.md`
- `specs/096-water-ways/spec.md`
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-authoring-session-083-096-apr-23-2026: domain/governance
Tag assignment · test- Gold
- The note calls for `domain/governance`: Constitution, phased spec, policies (True)
- ember
- The note has no reason to carry `domain/governance` (False)
- Signal
- P(true) 0.04 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec Authoring Session — 083-096 Apr 23 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec Authoring Session — 083-096 Apr 23 2026
This session authored 13 new feature specs for Old Growth (083–087, 089–096), corrected a branch targeting error, and updated the vault with corresponding spec stubs.
## Work Completed
### Spec Creation (088-vault-audit-agent branch)
Fourteen spec directories were created (083–097); all except 088 and 097 (already authored) received full content in this session. The following specs were authored from blank placeholders to complete spec.md files:
| Spec | Title | Lines |
|---|---|---|
| 083 | Atmospheric Splash Screen | 172 |
| 084 | First-Run Experience | 140 |
| 085 | Game Controller Support | 184 |
| 086 | Performance and Stability Testing | 167 |
| 087 | Main Menu Polish | 150 |
| 089 | Crafting Panel and Car Maintenance Abilities | 132 |
| 090 | Car Maintenance System | 169 |
| 091 | Player Abilities System | 148 |
| 092 | Garage Map and Trip Planner | 148 |
| 093 | Player Clothing and Equipment | 149 |
| 094 | Additional Biomes — Meadow and Swamp | 186 |
| 095 | Train Tracks and Abandoned Railways | 132 |
| 096 | Waterways — Lakes and Rivers | 132 |
All specs passed zero-placeholder-hit verification before commit.
### Branch Correction
Spec creation commits initially landed on `main` (two commits: `0c2aaf8`, `f5f7496`). Corrected via cherry-pick onto `088-vault-audit-agent` followed by `git reset --hard origin/main` on main. Both commits are now exclusively on `088-vault-audit-agent` at `80682f6` and `65547b4`.
### Vault Updates
Thirteen spec stubs added to `docs/vault/Specs/` (083–087, 089–096). This session note added. Specs MOC updated.
## Key Design Decisions Captured in Specs
- **083 Splash**: `splash_completed` signal lives on the script, not on `Events` — transient scene, single consumer.
- **084 First-Run**: Fixed seed in a Resource; flag set only on successful extraction.
- **085 Controller**: Rumble is a horror signal tied to disturbance thresholds, not convenience feedback.
- **090 Car Maintenance**: Battery at 0% is the only state that blocks run start; all other systems degrade gracefully.
- **091 Abilities**: Every ability cost is unconditional and structurally separate from its benefit.
- **092 Map**: Shows the *previous* run's world, not the upcoming one — intentional horror design.
- **094 Swamp**: Disturbance "unreliability" is a display offset, not a change to the underlying meter value.
- **096 Waterways**: River ambient audio contributes +0.02/s passive disturbance within 20m.
## References
- `specs/083-atmospheric-splash-screen/spec.md`
- `specs/084-first-run-experience/spec.md`
- `specs/085-game-controller-support/spec.md`
- `specs/086-performance-stability-testing/spec.md`
- `specs/087-main-menu-polish/spec.md`
- `specs/089-crafting-panel/spec.md`
- `specs/090-car-maintenance/spec.md`
- `specs/091-player-abilities/spec.md`
- `specs/092-garage-trip-planner/spec.md`
- `specs/093-player-equipment/spec.md`
- `specs/094-meadow-swamp-biomes/spec.md`
- `specs/095-train-tracks/spec.md`
- `specs/096-water-ways/spec.md`
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-authoring-session-083-096-apr-23-2026: domain/narrative
Tag assignment · test- Gold
- The note calls for `domain/narrative`: Tone, horror register, forest transmissions narrative, Gary/KTLK (True)
- ember
- The note has no reason to carry `domain/narrative` (False)
- Signal
- P(true) 0.09 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec Authoring Session — 083-096 Apr 23 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec Authoring Session — 083-096 Apr 23 2026
This session authored 13 new feature specs for Old Growth (083–087, 089–096), corrected a branch targeting error, and updated the vault with corresponding spec stubs.
## Work Completed
### Spec Creation (088-vault-audit-agent branch)
Fourteen spec directories were created (083–097); all except 088 and 097 (already authored) received full content in this session. The following specs were authored from blank placeholders to complete spec.md files:
| Spec | Title | Lines |
|---|---|---|
| 083 | Atmospheric Splash Screen | 172 |
| 084 | First-Run Experience | 140 |
| 085 | Game Controller Support | 184 |
| 086 | Performance and Stability Testing | 167 |
| 087 | Main Menu Polish | 150 |
| 089 | Crafting Panel and Car Maintenance Abilities | 132 |
| 090 | Car Maintenance System | 169 |
| 091 | Player Abilities System | 148 |
| 092 | Garage Map and Trip Planner | 148 |
| 093 | Player Clothing and Equipment | 149 |
| 094 | Additional Biomes — Meadow and Swamp | 186 |
| 095 | Train Tracks and Abandoned Railways | 132 |
| 096 | Waterways — Lakes and Rivers | 132 |
All specs passed zero-placeholder-hit verification before commit.
### Branch Correction
Spec creation commits initially landed on `main` (two commits: `0c2aaf8`, `f5f7496`). Corrected via cherry-pick onto `088-vault-audit-agent` followed by `git reset --hard origin/main` on main. Both commits are now exclusively on `088-vault-audit-agent` at `80682f6` and `65547b4`.
### Vault Updates
Thirteen spec stubs added to `docs/vault/Specs/` (083–087, 089–096). This session note added. Specs MOC updated.
## Key Design Decisions Captured in Specs
- **083 Splash**: `splash_completed` signal lives on the script, not on `Events` — transient scene, single consumer.
- **084 First-Run**: Fixed seed in a Resource; flag set only on successful extraction.
- **085 Controller**: Rumble is a horror signal tied to disturbance thresholds, not convenience feedback.
- **090 Car Maintenance**: Battery at 0% is the only state that blocks run start; all other systems degrade gracefully.
- **091 Abilities**: Every ability cost is unconditional and structurally separate from its benefit.
- **092 Map**: Shows the *previous* run's world, not the upcoming one — intentional horror design.
- **094 Swamp**: Disturbance "unreliability" is a display offset, not a change to the underlying meter value.
- **096 Waterways**: River ambient audio contributes +0.02/s passive disturbance within 20m.
## References
- `specs/083-atmospheric-splash-screen/spec.md`
- `specs/084-first-run-experience/spec.md`
- `specs/085-game-controller-support/spec.md`
- `specs/086-performance-stability-testing/spec.md`
- `specs/087-main-menu-polish/spec.md`
- `specs/089-crafting-panel/spec.md`
- `specs/090-car-maintenance/spec.md`
- `specs/091-player-abilities/spec.md`
- `specs/092-garage-trip-planner/spec.md`
- `specs/093-player-equipment/spec.md`
- `specs/094-meadow-swamp-biomes/spec.md`
- `specs/095-train-tracks/spec.md`
- `specs/096-water-ways/spec.md`
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-authoring-session-083-096-apr-23-2026: domain/player
Tag assignment · test- Gold
- The note calls for `domain/player`: Player mode, input, controls, camera, exit/entry (True)
- ember
- The note has no reason to carry `domain/player` (False)
- Signal
- P(true) 0.41 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spec Authoring Session — 083-096 Apr 23 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec Authoring Session — 083-096 Apr 23 2026
This session authored 13 new feature specs for Old Growth (083–087, 089–096), corrected a branch targeting error, and updated the vault with corresponding spec stubs.
## Work Completed
### Spec Creation (088-vault-audit-agent branch)
Fourteen spec directories were created (083–097); all except 088 and 097 (already authored) received full content in this session. The following specs were authored from blank placeholders to complete spec.md files:
| Spec | Title | Lines |
|---|---|---|
| 083 | Atmospheric Splash Screen | 172 |
| 084 | First-Run Experience | 140 |
| 085 | Game Controller Support | 184 |
| 086 | Performance and Stability Testing | 167 |
| 087 | Main Menu Polish | 150 |
| 089 | Crafting Panel and Car Maintenance Abilities | 132 |
| 090 | Car Maintenance System | 169 |
| 091 | Player Abilities System | 148 |
| 092 | Garage Map and Trip Planner | 148 |
| 093 | Player Clothing and Equipment | 149 |
| 094 | Additional Biomes — Meadow and Swamp | 186 |
| 095 | Train Tracks and Abandoned Railways | 132 |
| 096 | Waterways — Lakes and Rivers | 132 |
All specs passed zero-placeholder-hit verification before commit.
### Branch Correction
Spec creation commits initially landed on `main` (two commits: `0c2aaf8`, `f5f7496`). Corrected via cherry-pick onto `088-vault-audit-agent` followed by `git reset --hard origin/main` on main. Both commits are now exclusively on `088-vault-audit-agent` at `80682f6` and `65547b4`.
### Vault Updates
Thirteen spec stubs added to `docs/vault/Specs/` (083–087, 089–096). This session note added. Specs MOC updated.
## Key Design Decisions Captured in Specs
- **083 Splash**: `splash_completed` signal lives on the script, not on `Events` — transient scene, single consumer.
- **084 First-Run**: Fixed seed in a Resource; flag set only on successful extraction.
- **085 Controller**: Rumble is a horror signal tied to disturbance thresholds, not convenience feedback.
- **090 Car Maintenance**: Battery at 0% is the only state that blocks run start; all other systems degrade gracefully.
- **091 Abilities**: Every ability cost is unconditional and structurally separate from its benefit.
- **092 Map**: Shows the *previous* run's world, not the upcoming one — intentional horror design.
- **094 Swamp**: Disturbance "unreliability" is a display offset, not a change to the underlying meter value.
- **096 Waterways**: River ambient audio contributes +0.02/s passive disturbance within 20m.
## References
- `specs/083-atmospheric-splash-screen/spec.md`
- `specs/084-first-run-experience/spec.md`
- `specs/085-game-controller-support/spec.md`
- `specs/086-performance-stability-testing/spec.md`
- `specs/087-main-menu-polish/spec.md`
- `specs/089-crafting-panel/spec.md`
- `specs/090-car-maintenance/spec.md`
- `specs/091-player-abilities/spec.md`
- `specs/092-garage-trip-planner/spec.md`
- `specs/093-player-equipment/spec.md`
- `specs/094-meadow-swamp-biomes/spec.md`
- `specs/095-train-tracks/spec.md`
- `specs/096-water-ways/spec.md`
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-authoring-session-083-096-apr-23-2026: domain/tooling
Tag assignment · test- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.09 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec Authoring Session — 083-096 Apr 23 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec Authoring Session — 083-096 Apr 23 2026
This session authored 13 new feature specs for Old Growth (083–087, 089–096), corrected a branch targeting error, and updated the vault with corresponding spec stubs.
## Work Completed
### Spec Creation (088-vault-audit-agent branch)
Fourteen spec directories were created (083–097); all except 088 and 097 (already authored) received full content in this session. The following specs were authored from blank placeholders to complete spec.md files:
| Spec | Title | Lines |
|---|---|---|
| 083 | Atmospheric Splash Screen | 172 |
| 084 | First-Run Experience | 140 |
| 085 | Game Controller Support | 184 |
| 086 | Performance and Stability Testing | 167 |
| 087 | Main Menu Polish | 150 |
| 089 | Crafting Panel and Car Maintenance Abilities | 132 |
| 090 | Car Maintenance System | 169 |
| 091 | Player Abilities System | 148 |
| 092 | Garage Map and Trip Planner | 148 |
| 093 | Player Clothing and Equipment | 149 |
| 094 | Additional Biomes — Meadow and Swamp | 186 |
| 095 | Train Tracks and Abandoned Railways | 132 |
| 096 | Waterways — Lakes and Rivers | 132 |
All specs passed zero-placeholder-hit verification before commit.
### Branch Correction
Spec creation commits initially landed on `main` (two commits: `0c2aaf8`, `f5f7496`). Corrected via cherry-pick onto `088-vault-audit-agent` followed by `git reset --hard origin/main` on main. Both commits are now exclusively on `088-vault-audit-agent` at `80682f6` and `65547b4`.
### Vault Updates
Thirteen spec stubs added to `docs/vault/Specs/` (083–087, 089–096). This session note added. Specs MOC updated.
## Key Design Decisions Captured in Specs
- **083 Splash**: `splash_completed` signal lives on the script, not on `Events` — transient scene, single consumer.
- **084 First-Run**: Fixed seed in a Resource; flag set only on successful extraction.
- **085 Controller**: Rumble is a horror signal tied to disturbance thresholds, not convenience feedback.
- **090 Car Maintenance**: Battery at 0% is the only state that blocks run start; all other systems degrade gracefully.
- **091 Abilities**: Every ability cost is unconditional and structurally separate from its benefit.
- **092 Map**: Shows the *previous* run's world, not the upcoming one — intentional horror design.
- **094 Swamp**: Disturbance "unreliability" is a display offset, not a change to the underlying meter value.
- **096 Waterways**: River ambient audio contributes +0.02/s passive disturbance within 20m.
## References
- `specs/083-atmospheric-splash-screen/spec.md`
- `specs/084-first-run-experience/spec.md`
- `specs/085-game-controller-support/spec.md`
- `specs/086-performance-stability-testing/spec.md`
- `specs/087-main-menu-polish/spec.md`
- `specs/089-crafting-panel/spec.md`
- `specs/090-car-maintenance/spec.md`
- `specs/091-player-abilities/spec.md`
- `specs/092-garage-trip-planner/spec.md`
- `specs/093-player-equipment/spec.md`
- `specs/094-meadow-swamp-biomes/spec.md`
- `specs/095-train-tracks/spec.md`
- `specs/096-water-ways/spec.md`
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-authoring-session-083-096-apr-23-2026: domain/vehicle
Tag assignment · test- Gold
- The note calls for `domain/vehicle`: The Samba: vehicle physics, dashboard, damage, panels (True)
- ember
- The note has no reason to carry `domain/vehicle` (False)
- Signal
- P(true) 0.28 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spec Authoring Session — 083-096 Apr 23 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec Authoring Session — 083-096 Apr 23 2026
This session authored 13 new feature specs for Old Growth (083–087, 089–096), corrected a branch targeting error, and updated the vault with corresponding spec stubs.
## Work Completed
### Spec Creation (088-vault-audit-agent branch)
Fourteen spec directories were created (083–097); all except 088 and 097 (already authored) received full content in this session. The following specs were authored from blank placeholders to complete spec.md files:
| Spec | Title | Lines |
|---|---|---|
| 083 | Atmospheric Splash Screen | 172 |
| 084 | First-Run Experience | 140 |
| 085 | Game Controller Support | 184 |
| 086 | Performance and Stability Testing | 167 |
| 087 | Main Menu Polish | 150 |
| 089 | Crafting Panel and Car Maintenance Abilities | 132 |
| 090 | Car Maintenance System | 169 |
| 091 | Player Abilities System | 148 |
| 092 | Garage Map and Trip Planner | 148 |
| 093 | Player Clothing and Equipment | 149 |
| 094 | Additional Biomes — Meadow and Swamp | 186 |
| 095 | Train Tracks and Abandoned Railways | 132 |
| 096 | Waterways — Lakes and Rivers | 132 |
All specs passed zero-placeholder-hit verification before commit.
### Branch Correction
Spec creation commits initially landed on `main` (two commits: `0c2aaf8`, `f5f7496`). Corrected via cherry-pick onto `088-vault-audit-agent` followed by `git reset --hard origin/main` on main. Both commits are now exclusively on `088-vault-audit-agent` at `80682f6` and `65547b4`.
### Vault Updates
Thirteen spec stubs added to `docs/vault/Specs/` (083–087, 089–096). This session note added. Specs MOC updated.
## Key Design Decisions Captured in Specs
- **083 Splash**: `splash_completed` signal lives on the script, not on `Events` — transient scene, single consumer.
- **084 First-Run**: Fixed seed in a Resource; flag set only on successful extraction.
- **085 Controller**: Rumble is a horror signal tied to disturbance thresholds, not convenience feedback.
- **090 Car Maintenance**: Battery at 0% is the only state that blocks run start; all other systems degrade gracefully.
- **091 Abilities**: Every ability cost is unconditional and structurally separate from its benefit.
- **092 Map**: Shows the *previous* run's world, not the upcoming one — intentional horror design.
- **094 Swamp**: Disturbance "unreliability" is a display offset, not a change to the underlying meter value.
- **096 Waterways**: River ambient audio contributes +0.02/s passive disturbance within 20m.
## References
- `specs/083-atmospheric-splash-screen/spec.md`
- `specs/084-first-run-experience/spec.md`
- `specs/085-game-controller-support/spec.md`
- `specs/086-performance-stability-testing/spec.md`
- `specs/087-main-menu-polish/spec.md`
- `specs/089-crafting-panel/spec.md`
- `specs/090-car-maintenance/spec.md`
- `specs/091-player-abilities/spec.md`
- `specs/092-garage-trip-planner/spec.md`
- `specs/093-player-equipment/spec.md`
- `specs/094-meadow-swamp-biomes/spec.md`
- `specs/095-train-tracks/spec.md`
- `specs/096-water-ways/spec.md`
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-forest-transmissions: type/design
Tag assignment · dev- Gold
- The note has no reason to carry `type/design` (False)
- ember
- The note calls for `type/design`: Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`) (True)
- Signal
- P(true) 0.86 (yes)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Systems/Audio — Forest Transmissions.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — Forest Transmissions
The audio content that comes through the Samba's radio speakers *after* the KTLK signal fails. The forest has been using the frequency for longer than the station has existed. It continues to do so. What comes through is not static.
## Overview
Forest transmissions are the audio layer that replaces the radio when the human-broadcast signal is lost — through deep Petrifold geography, Cascade+ disturbance, or certain anomaly proximity. They are diegetic but their source is ambiguous: *the forest transmitting* is the narrative register, even if the technical implementation is generative or composed.
## Design Intent
This system implements one of the most charged moments in the World Bible (Section 17):
> The radio plays until it cannot. A distant signal, a voice, content that degrades in correlation with how deep the player is and how much the process has been fed. When the signal finally fails, the speakers do not go silent. The forest has been using the frequency for longer than the station has existed, and it continues to do so. What comes through after the signal dies is not static.
Forest transmissions are the forest *at the frequency the radio uses*. They are not intended to be parseable. They are intended to be felt.
## Key Decisions
### Content register
What comes through after KTLK dies includes (but is not limited to):
- **Repeating fragments** — snatches of words, not a voice — that almost form sentences
- **Mycelium signal artifacts** — the same acoustic register as the Mycelium Network anomaly's "conversation the forest is having with itself"
- **Traces of previous occupants** — fragments that *almost* resemble broadcasts from decades past. Never literally; always suggestively. The wall map in the garage was updated by multiple hands; the radio was heard by those hands too.
- **Petrifold audio** — extended dry cracking, amplified and processed through radio texture
- **Deep Silence** — not true silence, but a space the radio is trying and failing to fill with nothing
### Never literal
Forest transmissions must never include a clear narrative voice giving information. They must not explain the forest. They must not describe what is happening. They must only suggest — at the edge of pareidolia. The moment a forest transmission becomes parseable as a coherent message, the horror collapses into something manageable.
Per [[Tone and Horror]]: *the moment the forest is given a motive, a personality, a legible anger, the horror collapses into something manageable*.
### Not music
Forest transmissions are not a musical layer. They sit in the same frequency space as the radio (which is broadcast voice + music) but are not structured musically. They do not have rhythm, melody, or progression in a conventional sense.
### Triggered by signal failure, not state
Forest transmissions are not directly triggered by the [[Disturbance System]] — they are triggered by *radio signal failure*, which itself is driven by disturbance + biome + geography. This preserves diegesis: the forest does not care about the disturbance meter. It cares about the frequency being open.
## Implementation
_The generation approach — composed audio assets vs. procedural audio assembly vs. hybrid — remains an open question. This is tracked in [[Open Questions]] and is a decision to be made when this system is implemented._
> [!question] UNDOCUMENTED
> The source spec does not establish whether forest transmissions are:
> - Pre-composed audio assets assembled dynamically
> - Procedurally generated from mycelium/Petrifold samples
> - Hybrid: a composed "shape" with procedural detail
> This is a decision to be made during implementation of this system.
## Test Coverage
_To be populated once this system is implemented._
## Related
- [[Audio — Car Radio]] — the system this replaces when signal fails
- [[Audio — Radio Stations]] — the KTLK content that precedes signal loss
- [[Audio — Overview]] — top-level audio architecture
- [[Audio — World Audio]] — the ambient world audio that forest transmissions sit alongside
- [[Narrative Direction]] — the narrative register that forest transmissions serve
- [[Tone and Horror]] — the tonal discipline that governs forest transmission content
- [[Audio — Domain MOC]] — domain MOC
- [[Diegesis Thread]] — forest transmissions preserve diegesis through signal failure
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-forest-transmissions: type/spec
Tag assignment · dev- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.60 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Audio — Forest Transmissions.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — Forest Transmissions
The audio content that comes through the Samba's radio speakers *after* the KTLK signal fails. The forest has been using the frequency for longer than the station has existed. It continues to do so. What comes through is not static.
## Overview
Forest transmissions are the audio layer that replaces the radio when the human-broadcast signal is lost — through deep Petrifold geography, Cascade+ disturbance, or certain anomaly proximity. They are diegetic but their source is ambiguous: *the forest transmitting* is the narrative register, even if the technical implementation is generative or composed.
## Design Intent
This system implements one of the most charged moments in the World Bible (Section 17):
> The radio plays until it cannot. A distant signal, a voice, content that degrades in correlation with how deep the player is and how much the process has been fed. When the signal finally fails, the speakers do not go silent. The forest has been using the frequency for longer than the station has existed, and it continues to do so. What comes through after the signal dies is not static.
Forest transmissions are the forest *at the frequency the radio uses*. They are not intended to be parseable. They are intended to be felt.
## Key Decisions
### Content register
What comes through after KTLK dies includes (but is not limited to):
- **Repeating fragments** — snatches of words, not a voice — that almost form sentences
- **Mycelium signal artifacts** — the same acoustic register as the Mycelium Network anomaly's "conversation the forest is having with itself"
- **Traces of previous occupants** — fragments that *almost* resemble broadcasts from decades past. Never literally; always suggestively. The wall map in the garage was updated by multiple hands; the radio was heard by those hands too.
- **Petrifold audio** — extended dry cracking, amplified and processed through radio texture
- **Deep Silence** — not true silence, but a space the radio is trying and failing to fill with nothing
### Never literal
Forest transmissions must never include a clear narrative voice giving information. They must not explain the forest. They must not describe what is happening. They must only suggest — at the edge of pareidolia. The moment a forest transmission becomes parseable as a coherent message, the horror collapses into something manageable.
Per [[Tone and Horror]]: *the moment the forest is given a motive, a personality, a legible anger, the horror collapses into something manageable*.
### Not music
Forest transmissions are not a musical layer. They sit in the same frequency space as the radio (which is broadcast voice + music) but are not structured musically. They do not have rhythm, melody, or progression in a conventional sense.
### Triggered by signal failure, not state
Forest transmissions are not directly triggered by the [[Disturbance System]] — they are triggered by *radio signal failure*, which itself is driven by disturbance + biome + geography. This preserves diegesis: the forest does not care about the disturbance meter. It cares about the frequency being open.
## Implementation
_The generation approach — composed audio assets vs. procedural audio assembly vs. hybrid — remains an open question. This is tracked in [[Open Questions]] and is a decision to be made when this system is implemented._
> [!question] UNDOCUMENTED
> The source spec does not establish whether forest transmissions are:
> - Pre-composed audio assets assembled dynamically
> - Procedurally generated from mycelium/Petrifold samples
> - Hybrid: a composed "shape" with procedural detail
> This is a decision to be made during implementation of this system.
## Test Coverage
_To be populated once this system is implemented._
## Related
- [[Audio — Car Radio]] — the system this replaces when signal fails
- [[Audio — Radio Stations]] — the KTLK content that precedes signal loss
- [[Audio — Overview]] — top-level audio architecture
- [[Audio — World Audio]] — the ambient world audio that forest transmissions sit alongside
- [[Narrative Direction]] — the narrative register that forest transmissions serve
- [[Tone and Horror]] — the tonal discipline that governs forest transmission content
- [[Audio — Domain MOC]] — domain MOC
- [[Diegesis Thread]] — forest transmissions preserve diegesis through signal failure
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-audio-forest-transmissions: type/system
Tag assignment · dev- Gold
- The note calls for `type/system`: A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`) (True)
- ember
- The note has no reason to carry `type/system` (False)
- Signal
- P(true) 0.33 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/Audio — Forest Transmissions.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Audio — Forest Transmissions
The audio content that comes through the Samba's radio speakers *after* the KTLK signal fails. The forest has been using the frequency for longer than the station has existed. It continues to do so. What comes through is not static.
## Overview
Forest transmissions are the audio layer that replaces the radio when the human-broadcast signal is lost — through deep Petrifold geography, Cascade+ disturbance, or certain anomaly proximity. They are diegetic but their source is ambiguous: *the forest transmitting* is the narrative register, even if the technical implementation is generative or composed.
## Design Intent
This system implements one of the most charged moments in the World Bible (Section 17):
> The radio plays until it cannot. A distant signal, a voice, content that degrades in correlation with how deep the player is and how much the process has been fed. When the signal finally fails, the speakers do not go silent. The forest has been using the frequency for longer than the station has existed, and it continues to do so. What comes through after the signal dies is not static.
Forest transmissions are the forest *at the frequency the radio uses*. They are not intended to be parseable. They are intended to be felt.
## Key Decisions
### Content register
What comes through after KTLK dies includes (but is not limited to):
- **Repeating fragments** — snatches of words, not a voice — that almost form sentences
- **Mycelium signal artifacts** — the same acoustic register as the Mycelium Network anomaly's "conversation the forest is having with itself"
- **Traces of previous occupants** — fragments that *almost* resemble broadcasts from decades past. Never literally; always suggestively. The wall map in the garage was updated by multiple hands; the radio was heard by those hands too.
- **Petrifold audio** — extended dry cracking, amplified and processed through radio texture
- **Deep Silence** — not true silence, but a space the radio is trying and failing to fill with nothing
### Never literal
Forest transmissions must never include a clear narrative voice giving information. They must not explain the forest. They must not describe what is happening. They must only suggest — at the edge of pareidolia. The moment a forest transmission becomes parseable as a coherent message, the horror collapses into something manageable.
Per [[Tone and Horror]]: *the moment the forest is given a motive, a personality, a legible anger, the horror collapses into something manageable*.
### Not music
Forest transmissions are not a musical layer. They sit in the same frequency space as the radio (which is broadcast voice + music) but are not structured musically. They do not have rhythm, melody, or progression in a conventional sense.
### Triggered by signal failure, not state
Forest transmissions are not directly triggered by the [[Disturbance System]] — they are triggered by *radio signal failure*, which itself is driven by disturbance + biome + geography. This preserves diegesis: the forest does not care about the disturbance meter. It cares about the frequency being open.
## Implementation
_The generation approach — composed audio assets vs. procedural audio assembly vs. hybrid — remains an open question. This is tracked in [[Open Questions]] and is a decision to be made when this system is implemented._
> [!question] UNDOCUMENTED
> The source spec does not establish whether forest transmissions are:
> - Pre-composed audio assets assembled dynamically
> - Procedurally generated from mycelium/Petrifold samples
> - Hybrid: a composed "shape" with procedural detail
> This is a decision to be made during implementation of this system.
## Test Coverage
_To be populated once this system is implemented._
## Related
- [[Audio — Car Radio]] — the system this replaces when signal fails
- [[Audio — Radio Stations]] — the KTLK content that precedes signal loss
- [[Audio — Overview]] — top-level audio architecture
- [[Audio — World Audio]] — the ambient world audio that forest transmissions sit alongside
- [[Narrative Direction]] — the narrative register that forest transmissions serve
- [[Tone and Horror]] — the tonal discipline that governs forest transmission content
- [[Audio — Domain MOC]] — domain MOC
- [[Diegesis Thread]] — forest transmissions preserve diegesis through signal failure
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-143-implementation-session-may-15-2026: domain/tooling
Tag assignment · test- Gold
- The note calls for `domain/tooling`: Build, CI, observer, TTS pipeline, dev-time tools (True)
- ember
- The note has no reason to carry `domain/tooling` (False)
- Signal
- P(true) 0.05 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec 143 Implementation Session — May 15 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 143 Implementation Session — May 15 2026
Full implementation session for [[143 Inter Agent Extensions]] on branch `001-inter-agent-extensions`. The spec extends spec 142's persona inter-agent messaging with four independently shippable capabilities: broadcast messaging via tags, orphaned message janitor, unified health monitoring dashboard, and persistent echo thread state.
## Phases
**Phase 1 — Setup (T001-T003)**: Added `tags` field to `personas/letta_agents.json` (coordinator + gtm entries); added `get_all_states()` to `personas/circuit_breaker.py`; added `get_all_status()` to `personas/health_state.py`.
**Phase 2 — Foundational (T004-T009)**: Implemented `resolve_by_tags()` and `broadcast()` in `personas/inter_agent.py`; wired `health_state` parameter into `send()` for heartbeat recording; implemented thread persistence primitives (load/save/dirty-flag flusher) in `personas/slack_echo.py`.
**Phase 3 — US1 Broadcast (T010-T014)**: Wired broadcast routing into `personas/coordinator.py::coordinator_response_hook` with `task_type: "broadcast"` support; created `test_broadcast.py` with 9 tests covering OR/AND resolution, parallel dispatch, partial failure, and zero-match edge case.
**Phase 4 — US2 Orphan Janitor (T015-T018)**: Added `GET /health/orphans` and `POST /health/orphans/resolve` routes with per-correlation_id race-condition detection; implemented 60-second background janitor daemon; created `test_orphan_janitor.py` with 7 tests.
**Phase 5 — US3 Health Dashboard (T019-T025)**: Extended `GET /health` with persona liveness; added `/health/personas`, `/health/circuits`, `/health/threads`, `/health/audit-stats` routes; created `test_health_routes.py` with 4 tests.
**Phase 6 — US4 Persistent Threads (T026-T030)**: Wired `load_persisted_threads()` and `_evict_stale_threads()` into `runtime.py::_init_slack_echo()`; created `test_thread_persistence.py` with 9 tests.
**Phase 7 — Polish (T031-T035)**: Updated operations reference with new health endpoints; moved spec 143 to Done in `Specs.md`; set frontmatter `spec_status: done`.
## Key Decisions and Discoveries
**Dict identity constraint (Discovery)**: Module-level dicts in `slack_echo.py` must use `.clear()` + `.update()` instead of `= {}` reassignment. Consumers that import via `from X import` hold references to the original dict object; reassignment silently breaks them, causing cascading test failures.
**Orphan race resolution (Decision)**: Two-layer protection against duplicate terminal rows — per-correlation_id scan view at detection time, plus per-orphan re-scan before resolution write, plus post-resolution idempotency.
## Test Results
- 224 passed, 15 skipped, 0 failures
- 4 new test files (29 new tests)
- All 195 spec 142 tests preserved (no regressions)
## References
- `personas/letta_agents.json` — tags schema extension
- `personas/inter_agent.py` — `resolve_by_tags()`, `broadcast()`, health_state in `send()`
- `personas/slack_echo.py` — `load_persisted_threads()`, `save_threads()`, dirty-flag flusher
- `personas/runtime.py` — 7 health routes, orphan janitor, thread persistence wiring
- `personas/circuit_breaker.py` — `get_all_states()`
- `personas/health_state.py` — `get_all_status()`
- `personas/coordinator.py` — broadcast routing
- `docs/vault/Specs/143 Inter Agent Extensions/` — full spec artifact set
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-143-implementation-session-may-15-2026: status/draft
Tag assignment · test- Gold
- The note calls for `status/draft`: Authoring in progress, not yet verified (True)
- ember
- The note has no reason to carry `status/draft` (False)
- Signal
- P(true) 0.04 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Sessions/Spec 143 Implementation Session — May 15 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 143 Implementation Session — May 15 2026
Full implementation session for [[143 Inter Agent Extensions]] on branch `001-inter-agent-extensions`. The spec extends spec 142's persona inter-agent messaging with four independently shippable capabilities: broadcast messaging via tags, orphaned message janitor, unified health monitoring dashboard, and persistent echo thread state.
## Phases
**Phase 1 — Setup (T001-T003)**: Added `tags` field to `personas/letta_agents.json` (coordinator + gtm entries); added `get_all_states()` to `personas/circuit_breaker.py`; added `get_all_status()` to `personas/health_state.py`.
**Phase 2 — Foundational (T004-T009)**: Implemented `resolve_by_tags()` and `broadcast()` in `personas/inter_agent.py`; wired `health_state` parameter into `send()` for heartbeat recording; implemented thread persistence primitives (load/save/dirty-flag flusher) in `personas/slack_echo.py`.
**Phase 3 — US1 Broadcast (T010-T014)**: Wired broadcast routing into `personas/coordinator.py::coordinator_response_hook` with `task_type: "broadcast"` support; created `test_broadcast.py` with 9 tests covering OR/AND resolution, parallel dispatch, partial failure, and zero-match edge case.
**Phase 4 — US2 Orphan Janitor (T015-T018)**: Added `GET /health/orphans` and `POST /health/orphans/resolve` routes with per-correlation_id race-condition detection; implemented 60-second background janitor daemon; created `test_orphan_janitor.py` with 7 tests.
**Phase 5 — US3 Health Dashboard (T019-T025)**: Extended `GET /health` with persona liveness; added `/health/personas`, `/health/circuits`, `/health/threads`, `/health/audit-stats` routes; created `test_health_routes.py` with 4 tests.
**Phase 6 — US4 Persistent Threads (T026-T030)**: Wired `load_persisted_threads()` and `_evict_stale_threads()` into `runtime.py::_init_slack_echo()`; created `test_thread_persistence.py` with 9 tests.
**Phase 7 — Polish (T031-T035)**: Updated operations reference with new health endpoints; moved spec 143 to Done in `Specs.md`; set frontmatter `spec_status: done`.
## Key Decisions and Discoveries
**Dict identity constraint (Discovery)**: Module-level dicts in `slack_echo.py` must use `.clear()` + `.update()` instead of `= {}` reassignment. Consumers that import via `from X import` hold references to the original dict object; reassignment silently breaks them, causing cascading test failures.
**Orphan race resolution (Decision)**: Two-layer protection against duplicate terminal rows — per-correlation_id scan view at detection time, plus per-orphan re-scan before resolution write, plus post-resolution idempotency.
## Test Results
- 224 passed, 15 skipped, 0 failures
- 4 new test files (29 new tests)
- All 195 spec 142 tests preserved (no regressions)
## References
- `personas/letta_agents.json` — tags schema extension
- `personas/inter_agent.py` — `resolve_by_tags()`, `broadcast()`, health_state in `send()`
- `personas/slack_echo.py` — `load_persisted_threads()`, `save_threads()`, dirty-flag flusher
- `personas/runtime.py` — 7 health routes, orphan janitor, thread persistence wiring
- `personas/circuit_breaker.py` — `get_all_states()`
- `personas/health_state.py` — `get_all_status()`
- `personas/coordinator.py` — broadcast routing
- `docs/vault/Specs/143 Inter Agent Extensions/` — full spec artifact set
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-143-implementation-session-may-15-2026: type/session-log
Tag assignment · test- Gold
- The note calls for `type/session-log`: Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.) (True)
- ember
- The note has no reason to carry `type/session-log` (False)
- Signal
- P(true) 0.40 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spec 143 Implementation Session — May 15 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 143 Implementation Session — May 15 2026
Full implementation session for [[143 Inter Agent Extensions]] on branch `001-inter-agent-extensions`. The spec extends spec 142's persona inter-agent messaging with four independently shippable capabilities: broadcast messaging via tags, orphaned message janitor, unified health monitoring dashboard, and persistent echo thread state.
## Phases
**Phase 1 — Setup (T001-T003)**: Added `tags` field to `personas/letta_agents.json` (coordinator + gtm entries); added `get_all_states()` to `personas/circuit_breaker.py`; added `get_all_status()` to `personas/health_state.py`.
**Phase 2 — Foundational (T004-T009)**: Implemented `resolve_by_tags()` and `broadcast()` in `personas/inter_agent.py`; wired `health_state` parameter into `send()` for heartbeat recording; implemented thread persistence primitives (load/save/dirty-flag flusher) in `personas/slack_echo.py`.
**Phase 3 — US1 Broadcast (T010-T014)**: Wired broadcast routing into `personas/coordinator.py::coordinator_response_hook` with `task_type: "broadcast"` support; created `test_broadcast.py` with 9 tests covering OR/AND resolution, parallel dispatch, partial failure, and zero-match edge case.
**Phase 4 — US2 Orphan Janitor (T015-T018)**: Added `GET /health/orphans` and `POST /health/orphans/resolve` routes with per-correlation_id race-condition detection; implemented 60-second background janitor daemon; created `test_orphan_janitor.py` with 7 tests.
**Phase 5 — US3 Health Dashboard (T019-T025)**: Extended `GET /health` with persona liveness; added `/health/personas`, `/health/circuits`, `/health/threads`, `/health/audit-stats` routes; created `test_health_routes.py` with 4 tests.
**Phase 6 — US4 Persistent Threads (T026-T030)**: Wired `load_persisted_threads()` and `_evict_stale_threads()` into `runtime.py::_init_slack_echo()`; created `test_thread_persistence.py` with 9 tests.
**Phase 7 — Polish (T031-T035)**: Updated operations reference with new health endpoints; moved spec 143 to Done in `Specs.md`; set frontmatter `spec_status: done`.
## Key Decisions and Discoveries
**Dict identity constraint (Discovery)**: Module-level dicts in `slack_echo.py` must use `.clear()` + `.update()` instead of `= {}` reassignment. Consumers that import via `from X import` hold references to the original dict object; reassignment silently breaks them, causing cascading test failures.
**Orphan race resolution (Decision)**: Two-layer protection against duplicate terminal rows — per-correlation_id scan view at detection time, plus per-orphan re-scan before resolution write, plus post-resolution idempotency.
## Test Results
- 224 passed, 15 skipped, 0 failures
- 4 new test files (29 new tests)
- All 195 spec 142 tests preserved (no regressions)
## References
- `personas/letta_agents.json` — tags schema extension
- `personas/inter_agent.py` — `resolve_by_tags()`, `broadcast()`, health_state in `send()`
- `personas/slack_echo.py` — `load_persisted_threads()`, `save_threads()`, dirty-flag flusher
- `personas/runtime.py` — 7 health routes, orphan janitor, thread persistence wiring
- `personas/circuit_breaker.py` — `get_all_states()`
- `personas/health_state.py` — `get_all_status()`
- `personas/coordinator.py` — broadcast routing
- `docs/vault/Specs/143 Inter Agent Extensions/` — full spec artifact set
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-sessions-spec-143-implementation-session-may-15-2026: type/spec
Tag assignment · test- Gold
- The note has no reason to carry `type/spec` (False)
- ember
- The note calls for `type/spec`: Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`) (True)
- Signal
- P(true) 0.64 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Sessions/Spec 143 Implementation Session — May 15 2026.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
Spec 143 Implementation Session — May 15 2026
Full implementation session for [[143 Inter Agent Extensions]] on branch `001-inter-agent-extensions`. The spec extends spec 142's persona inter-agent messaging with four independently shippable capabilities: broadcast messaging via tags, orphaned message janitor, unified health monitoring dashboard, and persistent echo thread state.
## Phases
**Phase 1 — Setup (T001-T003)**: Added `tags` field to `personas/letta_agents.json` (coordinator + gtm entries); added `get_all_states()` to `personas/circuit_breaker.py`; added `get_all_status()` to `personas/health_state.py`.
**Phase 2 — Foundational (T004-T009)**: Implemented `resolve_by_tags()` and `broadcast()` in `personas/inter_agent.py`; wired `health_state` parameter into `send()` for heartbeat recording; implemented thread persistence primitives (load/save/dirty-flag flusher) in `personas/slack_echo.py`.
**Phase 3 — US1 Broadcast (T010-T014)**: Wired broadcast routing into `personas/coordinator.py::coordinator_response_hook` with `task_type: "broadcast"` support; created `test_broadcast.py` with 9 tests covering OR/AND resolution, parallel dispatch, partial failure, and zero-match edge case.
**Phase 4 — US2 Orphan Janitor (T015-T018)**: Added `GET /health/orphans` and `POST /health/orphans/resolve` routes with per-correlation_id race-condition detection; implemented 60-second background janitor daemon; created `test_orphan_janitor.py` with 7 tests.
**Phase 5 — US3 Health Dashboard (T019-T025)**: Extended `GET /health` with persona liveness; added `/health/personas`, `/health/circuits`, `/health/threads`, `/health/audit-stats` routes; created `test_health_routes.py` with 4 tests.
**Phase 6 — US4 Persistent Threads (T026-T030)**: Wired `load_persisted_threads()` and `_evict_stale_threads()` into `runtime.py::_init_slack_echo()`; created `test_thread_persistence.py` with 9 tests.
**Phase 7 — Polish (T031-T035)**: Updated operations reference with new health endpoints; moved spec 143 to Done in `Specs.md`; set frontmatter `spec_status: done`.
## Key Decisions and Discoveries
**Dict identity constraint (Discovery)**: Module-level dicts in `slack_echo.py` must use `.clear()` + `.update()` instead of `= {}` reassignment. Consumers that import via `from X import` hold references to the original dict object; reassignment silently breaks them, causing cascading test failures.
**Orphan race resolution (Decision)**: Two-layer protection against duplicate terminal rows — per-correlation_id scan view at detection time, plus per-orphan re-scan before resolution write, plus post-resolution idempotency.
## Test Results
- 224 passed, 15 skipped, 0 failures
- 4 new test files (29 new tests)
- All 195 spec 142 tests preserved (no regressions)
## References
- `personas/letta_agents.json` — tags schema extension
- `personas/inter_agent.py` — `resolve_by_tags()`, `broadcast()`, health_state in `send()`
- `personas/slack_echo.py` — `load_persisted_threads()`, `save_threads()`, dirty-flag flusher
- `personas/runtime.py` — 7 health routes, orphan janitor, thread persistence wiring
- `personas/circuit_breaker.py` — `get_all_states()`
- `personas/health_state.py` — `get_all_status()`
- `personas/coordinator.py` — broadcast routing
- `docs/vault/Specs/143 Inter Agent Extensions/` — full spec artifact set
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-world-procedural-generation: type/code
Tag assignment · dev- Gold
- The note has no reason to carry `type/code` (False)
- ember
- The note calls for `type/code`: Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`) (True)
- Signal
- P(true) 0.59 (unsure)
- Kit outcome
- deferred: treated as unsure
- Why this gold label
- Mined from Systems/World — Procedural Generation.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
World — Procedural Generation
The system that produces Old Growth's ephemeral worlds from seeds. Every run generates a new forest — biomes, terrain, roads, vegetation, structures, anomalies, weather, audio zones — all reproducibly derived from a single integer seed. The same seed produces the same world. World state is never persisted; only the garage and the Samba survive between runs.
## Overview
World generation runs as a pipeline of deterministic steps. Each step takes the seed (and any prior step outputs) as inputs and produces outputs consumed by downstream steps. The pipeline is pure: given the same seed and the same game version, it produces bit-identical output. Article IV Rule 3 establishes this as non-negotiable.
## Design Intent
Per Article IV:
- Rule 3 (Seed Determinism): same seed + same game version = identical world
- Rule 5: global `randf()` / `randi()` prohibited — every random call uses the run-seeded RNG
- Rule 6 (Ephemeral Worlds): world state is per-run; only garage and vehicle persist
- Rule 7: persistent data is strictly limited — vehicle components, gear, garage state
Procedural generation is not a performance trick or a content-volume strategy. It is a **design commitment**: every run is genuinely new, the player cannot memorize a specific world, and the forest's indifference to the player extends to the forest's very layout.
## Key Decisions
### The pipeline
The pipeline is orchestrated by `world_generator.gd`'s top-level 10 steps, with step 9 (`_integrate_existing_systems`) itself running four substeps. Seeds for each generator are derived from `GameState.current_seed` + a per-generator offset so seed determinism holds across the entire pipeline.
**Top-level steps** (tracked by `_log_step_time` diagnostics, confirmed against source 2026-04-21):
1. **Create Terrain3D regions** — blank terrain regions sized to the zone (GDScript)
2. **Generate heightmap** — deterministic heightmap via Rust TerrainComputeNode (Rust-backed)
3. **Generate biomes** — Voronoi biome regions with blend transitions via Rust BiomeGeneratorNode (Rust-backed)
4. **Place exits and center hub** — exit gate placement via Rust ExitPointManagerNode; entry is world center hub (Rust-backed)
- **Step 4.5** (diag ID 45): **Smooth exit corridors** — terrain smoothing before roads bake Y values (Rust-backed)
5. **Generate road network** — hub-and-spoke topology via Rust RoadGeneratorNode; curves extracted for downstream systems (Rust-backed)
6. **Raise mountain boundaries** — impassable ridge-line boundaries around the zone (GDScript)
- **Step 6.25** (diag ID 625): **Apply terrain roughness** — noise-based surface variation, params passed to Rust
- **Step 6.5** (diag ID 65): **Validate playability** — terrain passability constraint check
7. **Flatten terrain under roads** — RoadMeshGenerator.flatten_terrain + flatten_hub around center spoke convergence (GDScript using Rust height snapshots)
- **Step 7.5** (diag ID 75): **Build road meshes** — post-flatten, terrain-sampled Y; GDScript mesh extrusion from Rust curves (RoadMeshGenerator.generate_meshes)
8. **Paint biome textures** — per-biome terrain texture painting (GDScript — TerrainTexturePainter)
9. **Integrate existing systems** — four substeps (see below) (GDScript orchestration)
10. **Spawn vehicle and initialize systems** — vehicle spawn at center hub, engine/radio/sensor/wiper/damage initialization (GDScript)
11. **Load radio channels and finalize world** — awaits radio loading, recalculates height range, updates terrain maps, bakes navmesh, emits Events.world_generation_completed (GDScript)
**Step 9 substeps** (`_integrate_existing_systems`, tracked by `_diag_start` / `_diag_end` IDs 91–94):
- **91 — Fog setup**: fog system configured, valley fog placed along road curves
- **92 — Vegetation scatter + tree collision**: trees, ferns, logs placed via Poisson-disc rejection with biome-aware density (spec 070 — biome blending Rust, VegetationScatterNode); tree collision manager initialized with the placed positions
- **93 — Weather zones**: regional weather patches generated along the main road curve; vehicle biome-lookup connected so `Events.biome_zone_changed` fires from frame 1
- **94 — Road structure scatter**: 15–20 structures (ranger stations, research trailers, gas stations, cabins, fire lookouts) placed along road network with spawn exclusion around the player start
**Note on Rust/GDScript boundary**: The boundary is not a clean "first N steps are Rust." Rust is involved in steps 2, 3, 4, 4.5, 5, and substep 92 (vegetation candidates). Steps 1, 6, 8, 10, 11 and substeps 91, 93, 94 are GDScript. Steps 6.25 and 7.5 are mixed.
**Not yet a distinct pipeline step** (per Phase 4 audit of the current `world_generator.gd`):
- **Rock scatter** — [[Rock Scatter Design]] is specified but the dedicated scatter step is not yet integrated into the top-level pipeline; rocks may be a future step 95 under `_integrate_existing_systems` or a separate top-level step.
- **Anomaly scatter** — anomaly placement is not yet a top-level pipeline step; anomalies are placed via individual anomaly systems (see [[Anomalies]] MOC and spec 022).
- **Audio zone placement** — biome audio beds + proximity audio are set up by the relevant systems at runtime (weather zone system, biome generator); there is not a distinct audio-scatter step in the generator.
### Constraint satisfaction vs. heuristic
Per Article XII-E: hard generation requirements (road connectivity, minimum biome separation, navigation passability, entry/exit placement) are **post-generation validation constraints**, not generation heuristics. If generation produces an invalid world, it retries with a modified seed or parameters — it does not silently ship a broken world.
### Grammar-based where applicable
Road networks, structure placement, and biome distribution use production grammars. Grammars are inspectable and adjustable without rewriting core logic — a new biome variant is a new grammar rule, not a code change.
### Noise functions
Terrain, density maps, and distribution fields use the approved `fastnoise-lite` crate in rust-core (Appendix A). Noise parameters (frequency, octaves, lacunarity, gain) are `@export` variables exposed for tuning without recompilation.
### Emergent complexity from simple rules
Generation algorithms are structured so complexity emerges from rule interactions, not from increasingly complex individual rules. A road network that produces interesting layouts through simple hierarchical expansion rules (primary → secondary → tertiary) is preferred over a monolithic algorithm that encodes layout complexity directly.
### Rust-core for performance-critical steps
Per Article III Rule 17: performance-critical logic lives in `rust-core` (engine-agnostic). The gdext-wrapper bridges Rust outputs to Godot types. Steps migrated to Rust include biome blending (spec 070), road generation (spec 053), and vegetation scatter-time collision rejection.
### Determinism across the Rust/GDScript boundary
Deterministic RNG is seeded both in GDScript (`RandomNumberGenerator`) and in Rust (`SmallRng` / `fastnoise-lite` seeds). All calls into Rust receive their seed as an input. No Rust path consumes OS entropy.
### Baseline validation
Every debug build writes `user://logs/veg_counts.json` with tree/fern/log counts per seed. SC-001 from spec 070: tree count within ±5% of baseline for the canonical test seed (1250827250: trees=1452, ferns=5300, logs=3061 — post-spec-103 per-tree-clearance baseline, recorded 2026-04-26; see AGENTS.md).
## Implementation
Primary entry points:
- `src/scripts/generation/world_generator.gd` — pipeline orchestrator
- `src/scripts/generation/biome_generator.gd` — biome map
- `src/scripts/generation/road_network_generator.gd`, `road_mesh_generator.gd`, `road_path_utils.gd` — road network (spec 053)
- `src/scripts/generation/vegetation_scatter.gd` — vegetation placement (spec 070)
- `rust/rust-core/src/` — biome blending, road spline math, vegetation rejection
Generation diagnostics are written per-run to `user://logs/generation_YYYY-MM-DD_HH-MM-SS.json` with step timings and final metrics.
## Test Coverage
Primary test suites:
- `src/tests/unit/test_world_generator.gd`, `test_world_generator_cli.gd` — generation orchestrator and CLI
- `src/tests/unit/test_biome_generator.gd`, `test_biome_transitions.gd` — biome placement + transitions
- `src/tests/unit/test_road_network_generator.gd`, `test_road_mesh_generator.gd`, `test_road_diagnostic_report.gd`, `test_road_spine_overlay.gd` — road generation pipeline
- `src/tests/unit/test_terrain_generator.gd`, `test_terrain_setup.gd`, `test_terrain_roughness.gd` — terrain
- `src/tests/unit/test_vegetation_scatter.gd`, `test_tree_collision_manager.gd` — vegetation
- `src/tests/unit/test_road_structures.gd` — structures
- `src/tests/unit/test_exit_point_manager.gd` — exit gate placement
- `src/tests/unit/test_fog_system.gd` — fog system
- `src/tests/unit/test_world_preview.gd` — world preview harness
## Autoload Architecture — Delegation to Rust
`GameState` and `Disturbance` follow a thin-shell delegation pattern: GDScript owns all state backing variables and exposes `@export` surfaces, while the Rust layer (`GameStateNode`, `DisturbanceNode`) provides change-detection callbacks and arithmetic rather than state ownership. The `GameStateNode` bridge methods are validators and change-detection callbacks only — all mutations happen in GDScript against its own backing variables. `Disturbance` goes further: the arithmetic for `add()` and threshold-crossing detection runs entirely in `rust-core/src/disturbance.rs`, including clamping. A critical Godot 4.6+ constraint for all three architectural autoloads (`Events`, `GameState`, `Disturbance`): `class_name` must never appear on an autoload script, as the engine registers autoloads under their configured singleton name and a matching `class_name` produces duplicate registration conflicts that manifest as subtle runtime failures.
[[Autoload Delegation Pattern]]
## Weather Biome Restrictions and Fog Constraints
The weather zone system enforces per-biome weather restrictions during zone generation: Verdant supports all four weather states (CLEAR, LIGHT_RAIN, HEAVY_RAIN, FOG_BANK), Fungal biome permits only CLEAR and FOG_BANK, and Petrifold permits only CLEAR. Rain cannot appear inside Fungal or Petrifold zones regardless of RNG output — the generator filters against `BIOME_ALLOWED_WEATHER` constants before committing zone state. A non-obvious constraint limits weather-driven fog scaling: Godot's volumetric fog fills the entire scene uniformly, including bus interiors, making the diegetic dashboard invisible under any weather-driven density increase. All weather-intensity fog responses are therefore applied exclusively to standard (height) fog density; the volumetric fog export properties in `fog_system.gd` are reserved and inactive pending Godot support for interior fog masking.
[[Environment — Weather and Generation Order]]
## Street Light Time-of-Day Initialization
Street lights on power poles (`power_pole.gd`) initialize with `light.visible = false` so they do not emit during the first frame of world generation before `TimeOfDaySystem` has run its initial sync. Without this guard, lights were active from frame 0 and produced visible strobing artefacts during early scene initialization. The correct state machine — off at init and during DAY, on at DUSK and NIGHT — is pinned by regression tests in `src/tests/unit/test_road_structures.gd`.
[[Environment — Weather and Generation Order]]
## Road Artifact Remediation (Spec 114)
Implemented 2026-05-02. All seven outstanding FRs resolved:
### Generation Warning Field (FR-013)
`RoadNetwork` carries a `warnings: Vec<String>` field (Rust). The gdext-wrapper exposes it as `PackedStringArray`. When A* expansion limit is hit (steep terrain fallback), `"steep_terrain_fallback:astar"` is pushed. `road_network_generator.gd` iterates warnings and calls `Debug.warn("RoadGenerator", …)` per entry. The `road_diagnostic_<seed>.json` now includes a `"generation_warnings"` array.
### Sibling Branch Angular Separation (FR-002)
`generator.rs` enforces ≥ 25° pairwise separation between sibling branches off the same parent node — for both CountyRoad (perpendicular + jitter) and LoggingRoad (random `0..TAU`) placements. An ephemeral `HashMap<u32, Vec<f32>>` tracks accepted angles per node; up to 10 retry attempts before skipping. LoggingRoad also rejects any heading > 150° from the parent tangent (U-turn guard).
### Leaf-Node Proximity Pruning (FR-003)
`intersection.rs` implements `prune_nearby_leaf_nodes(network, 50.0)` called after `eliminate_dead_ends`. Any pair of non-Highway leaf nodes within 50 m has the shorter incident segment removed (by `segment.length_m`). No node re-indexing is performed after removal — uses `retain` + `rebuild_node_connections` to preserve stable node IDs and avoid corrupting highway endpoint references.
### Dead-End Connector Approach Angle (FR-004)
`eliminate_dead_ends` in `intersection.rs` replaces the midpoint formula with a 25 m perpendicular offset from the target segment tangent, producing a radial approach vector. The connector segment now approaches the junction at a natural angle rather than tangentially.
### Highway Meander Terrain Guard (FR-011)
`highway_centerline()` in `generator.rs` skips a sinusoidal displacement if the displaced height exceeds the nominal centerline Y by more than 15 m (raised from the original 3 m spec value — 3 m fired on normal terrain undulation causing ECG-noise centerlines; 15 m fires only on genuine cliff faces ≥ 37.5% grade over 40 m horizontal). Clamped points push `"highway_meander_clamped:slope"` to `net.warnings`. County and logging roads are unaffected (their endpoint selection uses A* routing, not meander).
### Flatten Offset Auto-Derivation (FR-010)
`world_generator.gd` computes `effective_flatten_offset = -(road_mesh_y_offset + 0.01)` when `road_flatten_offset == 0.0` (sentinel). At default `road_mesh_y_offset = 0.02` this produces `−0.03`, ensuring the terrain flatten depth always exceeds the side-wall height and eliminating the visible road plinth/rim.
### Dirt Road Mesh Clipping at Paved Boundaries (FR-012)
`road_mesh_generator.gd` adds `_paved_clip_distance()` helper and `clip_start_m` / `clip_end_m` parameters to `_extrude_road_mesh()`. When a non-paved segment's endpoint connects to a paved segment, the dirt mesh is clipped by `paved_half_width + 0.5 m` so dirt surfaces do not intrude into paved footprints.
### Performance Gate (SC-006)
Step 5 across 20 observed seeds: max 10,811 ms, well under the 11,046 ms cap (10% above 10,042 ms baseline).
## Related
- [[The Crater]] — the origin event; the pipeline generates worlds inside the crater basin; step 6 raises the crater rim
- [[The Temporal Fracture]] — each seed accesses a different temporal configuration of the structure
- [[World Model]] — the design the pipeline realizes
- [[Biomes]] — what the pipeline places
- [[Ephemerality Thread]] — the run-reset rule
- [[Biome Behavior]] — biome identity across systems
- [[Rock Scatter Design]] — the rock-scatter step specification
- [[Rust — Core]] — the engine-agnostic core
- [[Source Tree]] — where the generation code lives
- [[Systems]] — folder MOC
- [[World — Domain MOC]] — domain MOC
- [[Constitution]] — Article IV (seed determinism), Article XII-E (procedural), Article III Rule 17 (rust-core)
## Agent Notes (backlinks)
- [[Decisions/Rust-GDScript Split Architecture]] — Rust/GDScript split decision, ClassDB delegation pattern, editor restart constraint
- [[Autoload Delegation Pattern]] — Thin GDScript shell over Rust nodes, no class_name constraint
- [[Environment — Weather and Generation Order]] — 11-step pipeline order, Rust handoff points, SC-001 baseline, diagnostic JSON
- [[Road System — Discoveries]] — Road Rust topology layer, GDScript mesh layer, debug visualization
- [[114 Road Artifact Remediation]] — 13 road artifact categories (graph hygiene, mesh quality, multi-type integration); RCA confirmed road mesh is GDScript, branch sampling is unconstrained, no per-segment AABB culling exists
- [[Specs/114 Road Artifact Remediation/114 Road Artifact Remediation - research|114 Road Artifact Remediation — Research]] — Codebase research for spec 114: FR-002/003/004/010/011/012/013 findings, R=50m leaf-node separation, SC-006 performance baseline
- [[115 Terrain Region Boundary Rendering]] — Terrain3D region boundary rendering artifacts deferred from spec 114 (transverse gaps, chunk seam appearance split)
- Environment — Weather and Generation Order — Weather/time/fog coupling, volumetric fog bus-interior constraint
- [[Decisions/NoiseHelper Utility Class Design]] — NoiseHelper utility class decision (RefCounted pattern, seed-parameterised noise)
- Road System — Discoveries — Road shoulder terracing root cause analysis (directional bias in branch sampling)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraintstag_assignment-systems-world-procedural-generation: type/system
Tag assignment · dev- Gold
- The note calls for `type/system`: A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`) (True)
- ember
- The note has no reason to carry `type/system` (False)
- Signal
- P(true) 0.15 (no)
- Kit outcome
- acted on a wrong answer
- Why this gold label
- Mined from Systems/World — Procedural Generation.md at 692f4bb58b7f62105aeb73fba8dfee12bfdecd53; gold is the note's own vocabulary tags.
World — Procedural Generation
The system that produces Old Growth's ephemeral worlds from seeds. Every run generates a new forest — biomes, terrain, roads, vegetation, structures, anomalies, weather, audio zones — all reproducibly derived from a single integer seed. The same seed produces the same world. World state is never persisted; only the garage and the Samba survive between runs.
## Overview
World generation runs as a pipeline of deterministic steps. Each step takes the seed (and any prior step outputs) as inputs and produces outputs consumed by downstream steps. The pipeline is pure: given the same seed and the same game version, it produces bit-identical output. Article IV Rule 3 establishes this as non-negotiable.
## Design Intent
Per Article IV:
- Rule 3 (Seed Determinism): same seed + same game version = identical world
- Rule 5: global `randf()` / `randi()` prohibited — every random call uses the run-seeded RNG
- Rule 6 (Ephemeral Worlds): world state is per-run; only garage and vehicle persist
- Rule 7: persistent data is strictly limited — vehicle components, gear, garage state
Procedural generation is not a performance trick or a content-volume strategy. It is a **design commitment**: every run is genuinely new, the player cannot memorize a specific world, and the forest's indifference to the player extends to the forest's very layout.
## Key Decisions
### The pipeline
The pipeline is orchestrated by `world_generator.gd`'s top-level 10 steps, with step 9 (`_integrate_existing_systems`) itself running four substeps. Seeds for each generator are derived from `GameState.current_seed` + a per-generator offset so seed determinism holds across the entire pipeline.
**Top-level steps** (tracked by `_log_step_time` diagnostics, confirmed against source 2026-04-21):
1. **Create Terrain3D regions** — blank terrain regions sized to the zone (GDScript)
2. **Generate heightmap** — deterministic heightmap via Rust TerrainComputeNode (Rust-backed)
3. **Generate biomes** — Voronoi biome regions with blend transitions via Rust BiomeGeneratorNode (Rust-backed)
4. **Place exits and center hub** — exit gate placement via Rust ExitPointManagerNode; entry is world center hub (Rust-backed)
- **Step 4.5** (diag ID 45): **Smooth exit corridors** — terrain smoothing before roads bake Y values (Rust-backed)
5. **Generate road network** — hub-and-spoke topology via Rust RoadGeneratorNode; curves extracted for downstream systems (Rust-backed)
6. **Raise mountain boundaries** — impassable ridge-line boundaries around the zone (GDScript)
- **Step 6.25** (diag ID 625): **Apply terrain roughness** — noise-based surface variation, params passed to Rust
- **Step 6.5** (diag ID 65): **Validate playability** — terrain passability constraint check
7. **Flatten terrain under roads** — RoadMeshGenerator.flatten_terrain + flatten_hub around center spoke convergence (GDScript using Rust height snapshots)
- **Step 7.5** (diag ID 75): **Build road meshes** — post-flatten, terrain-sampled Y; GDScript mesh extrusion from Rust curves (RoadMeshGenerator.generate_meshes)
8. **Paint biome textures** — per-biome terrain texture painting (GDScript — TerrainTexturePainter)
9. **Integrate existing systems** — four substeps (see below) (GDScript orchestration)
10. **Spawn vehicle and initialize systems** — vehicle spawn at center hub, engine/radio/sensor/wiper/damage initialization (GDScript)
11. **Load radio channels and finalize world** — awaits radio loading, recalculates height range, updates terrain maps, bakes navmesh, emits Events.world_generation_completed (GDScript)
**Step 9 substeps** (`_integrate_existing_systems`, tracked by `_diag_start` / `_diag_end` IDs 91–94):
- **91 — Fog setup**: fog system configured, valley fog placed along road curves
- **92 — Vegetation scatter + tree collision**: trees, ferns, logs placed via Poisson-disc rejection with biome-aware density (spec 070 — biome blending Rust, VegetationScatterNode); tree collision manager initialized with the placed positions
- **93 — Weather zones**: regional weather patches generated along the main road curve; vehicle biome-lookup connected so `Events.biome_zone_changed` fires from frame 1
- **94 — Road structure scatter**: 15–20 structures (ranger stations, research trailers, gas stations, cabins, fire lookouts) placed along road network with spawn exclusion around the player start
**Note on Rust/GDScript boundary**: The boundary is not a clean "first N steps are Rust." Rust is involved in steps 2, 3, 4, 4.5, 5, and substep 92 (vegetation candidates). Steps 1, 6, 8, 10, 11 and substeps 91, 93, 94 are GDScript. Steps 6.25 and 7.5 are mixed.
**Not yet a distinct pipeline step** (per Phase 4 audit of the current `world_generator.gd`):
- **Rock scatter** — [[Rock Scatter Design]] is specified but the dedicated scatter step is not yet integrated into the top-level pipeline; rocks may be a future step 95 under `_integrate_existing_systems` or a separate top-level step.
- **Anomaly scatter** — anomaly placement is not yet a top-level pipeline step; anomalies are placed via individual anomaly systems (see [[Anomalies]] MOC and spec 022).
- **Audio zone placement** — biome audio beds + proximity audio are set up by the relevant systems at runtime (weather zone system, biome generator); there is not a distinct audio-scatter step in the generator.
### Constraint satisfaction vs. heuristic
Per Article XII-E: hard generation requirements (road connectivity, minimum biome separation, navigation passability, entry/exit placement) are **post-generation validation constraints**, not generation heuristics. If generation produces an invalid world, it retries with a modified seed or parameters — it does not silently ship a broken world.
### Grammar-based where applicable
Road networks, structure placement, and biome distribution use production grammars. Grammars are inspectable and adjustable without rewriting core logic — a new biome variant is a new grammar rule, not a code change.
### Noise functions
Terrain, density maps, and distribution fields use the approved `fastnoise-lite` crate in rust-core (Appendix A). Noise parameters (frequency, octaves, lacunarity, gain) are `@export` variables exposed for tuning without recompilation.
### Emergent complexity from simple rules
Generation algorithms are structured so complexity emerges from rule interactions, not from increasingly complex individual rules. A road network that produces interesting layouts through simple hierarchical expansion rules (primary → secondary → tertiary) is preferred over a monolithic algorithm that encodes layout complexity directly.
### Rust-core for performance-critical steps
Per Article III Rule 17: performance-critical logic lives in `rust-core` (engine-agnostic). The gdext-wrapper bridges Rust outputs to Godot types. Steps migrated to Rust include biome blending (spec 070), road generation (spec 053), and vegetation scatter-time collision rejection.
### Determinism across the Rust/GDScript boundary
Deterministic RNG is seeded both in GDScript (`RandomNumberGenerator`) and in Rust (`SmallRng` / `fastnoise-lite` seeds). All calls into Rust receive their seed as an input. No Rust path consumes OS entropy.
### Baseline validation
Every debug build writes `user://logs/veg_counts.json` with tree/fern/log counts per seed. SC-001 from spec 070: tree count within ±5% of baseline for the canonical test seed (1250827250: trees=1452, ferns=5300, logs=3061 — post-spec-103 per-tree-clearance baseline, recorded 2026-04-26; see AGENTS.md).
## Implementation
Primary entry points:
- `src/scripts/generation/world_generator.gd` — pipeline orchestrator
- `src/scripts/generation/biome_generator.gd` — biome map
- `src/scripts/generation/road_network_generator.gd`, `road_mesh_generator.gd`, `road_path_utils.gd` — road network (spec 053)
- `src/scripts/generation/vegetation_scatter.gd` — vegetation placement (spec 070)
- `rust/rust-core/src/` — biome blending, road spline math, vegetation rejection
Generation diagnostics are written per-run to `user://logs/generation_YYYY-MM-DD_HH-MM-SS.json` with step timings and final metrics.
## Test Coverage
Primary test suites:
- `src/tests/unit/test_world_generator.gd`, `test_world_generator_cli.gd` — generation orchestrator and CLI
- `src/tests/unit/test_biome_generator.gd`, `test_biome_transitions.gd` — biome placement + transitions
- `src/tests/unit/test_road_network_generator.gd`, `test_road_mesh_generator.gd`, `test_road_diagnostic_report.gd`, `test_road_spine_overlay.gd` — road generation pipeline
- `src/tests/unit/test_terrain_generator.gd`, `test_terrain_setup.gd`, `test_terrain_roughness.gd` — terrain
- `src/tests/unit/test_vegetation_scatter.gd`, `test_tree_collision_manager.gd` — vegetation
- `src/tests/unit/test_road_structures.gd` — structures
- `src/tests/unit/test_exit_point_manager.gd` — exit gate placement
- `src/tests/unit/test_fog_system.gd` — fog system
- `src/tests/unit/test_world_preview.gd` — world preview harness
## Autoload Architecture — Delegation to Rust
`GameState` and `Disturbance` follow a thin-shell delegation pattern: GDScript owns all state backing variables and exposes `@export` surfaces, while the Rust layer (`GameStateNode`, `DisturbanceNode`) provides change-detection callbacks and arithmetic rather than state ownership. The `GameStateNode` bridge methods are validators and change-detection callbacks only — all mutations happen in GDScript against its own backing variables. `Disturbance` goes further: the arithmetic for `add()` and threshold-crossing detection runs entirely in `rust-core/src/disturbance.rs`, including clamping. A critical Godot 4.6+ constraint for all three architectural autoloads (`Events`, `GameState`, `Disturbance`): `class_name` must never appear on an autoload script, as the engine registers autoloads under their configured singleton name and a matching `class_name` produces duplicate registration conflicts that manifest as subtle runtime failures.
[[Autoload Delegation Pattern]]
## Weather Biome Restrictions and Fog Constraints
The weather zone system enforces per-biome weather restrictions during zone generation: Verdant supports all four weather states (CLEAR, LIGHT_RAIN, HEAVY_RAIN, FOG_BANK), Fungal biome permits only CLEAR and FOG_BANK, and Petrifold permits only CLEAR. Rain cannot appear inside Fungal or Petrifold zones regardless of RNG output — the generator filters against `BIOME_ALLOWED_WEATHER` constants before committing zone state. A non-obvious constraint limits weather-driven fog scaling: Godot's volumetric fog fills the entire scene uniformly, including bus interiors, making the diegetic dashboard invisible under any weather-driven density increase. All weather-intensity fog responses are therefore applied exclusively to standard (height) fog density; the volumetric fog export properties in `fog_system.gd` are reserved and inactive pending Godot support for interior fog masking.
[[Environment — Weather and Generation Order]]
## Street Light Time-of-Day Initialization
Street lights on power poles (`power_pole.gd`) initialize with `light.visible = false` so they do not emit during the first frame of world generation before `TimeOfDaySystem` has run its initial sync. Without this guard, lights were active from frame 0 and produced visible strobing artefacts during early scene initialization. The correct state machine — off at init and during DAY, on at DUSK and NIGHT — is pinned by regression tests in `src/tests/unit/test_road_structures.gd`.
[[Environment — Weather and Generation Order]]
## Road Artifact Remediation (Spec 114)
Implemented 2026-05-02. All seven outstanding FRs resolved:
### Generation Warning Field (FR-013)
`RoadNetwork` carries a `warnings: Vec<String>` field (Rust). The gdext-wrapper exposes it as `PackedStringArray`. When A* expansion limit is hit (steep terrain fallback), `"steep_terrain_fallback:astar"` is pushed. `road_network_generator.gd` iterates warnings and calls `Debug.warn("RoadGenerator", …)` per entry. The `road_diagnostic_<seed>.json` now includes a `"generation_warnings"` array.
### Sibling Branch Angular Separation (FR-002)
`generator.rs` enforces ≥ 25° pairwise separation between sibling branches off the same parent node — for both CountyRoad (perpendicular + jitter) and LoggingRoad (random `0..TAU`) placements. An ephemeral `HashMap<u32, Vec<f32>>` tracks accepted angles per node; up to 10 retry attempts before skipping. LoggingRoad also rejects any heading > 150° from the parent tangent (U-turn guard).
### Leaf-Node Proximity Pruning (FR-003)
`intersection.rs` implements `prune_nearby_leaf_nodes(network, 50.0)` called after `eliminate_dead_ends`. Any pair of non-Highway leaf nodes within 50 m has the shorter incident segment removed (by `segment.length_m`). No node re-indexing is performed after removal — uses `retain` + `rebuild_node_connections` to preserve stable node IDs and avoid corrupting highway endpoint references.
### Dead-End Connector Approach Angle (FR-004)
`eliminate_dead_ends` in `intersection.rs` replaces the midpoint formula with a 25 m perpendicular offset from the target segment tangent, producing a radial approach vector. The connector segment now approaches the junction at a natural angle rather than tangentially.
### Highway Meander Terrain Guard (FR-011)
`highway_centerline()` in `generator.rs` skips a sinusoidal displacement if the displaced height exceeds the nominal centerline Y by more than 15 m (raised from the original 3 m spec value — 3 m fired on normal terrain undulation causing ECG-noise centerlines; 15 m fires only on genuine cliff faces ≥ 37.5% grade over 40 m horizontal). Clamped points push `"highway_meander_clamped:slope"` to `net.warnings`. County and logging roads are unaffected (their endpoint selection uses A* routing, not meander).
### Flatten Offset Auto-Derivation (FR-010)
`world_generator.gd` computes `effective_flatten_offset = -(road_mesh_y_offset + 0.01)` when `road_flatten_offset == 0.0` (sentinel). At default `road_mesh_y_offset = 0.02` this produces `−0.03`, ensuring the terrain flatten depth always exceeds the side-wall height and eliminating the visible road plinth/rim.
### Dirt Road Mesh Clipping at Paved Boundaries (FR-012)
`road_mesh_generator.gd` adds `_paved_clip_distance()` helper and `clip_start_m` / `clip_end_m` parameters to `_extrude_road_mesh()`. When a non-paved segment's endpoint connects to a paved segment, the dirt mesh is clipped by `paved_half_width + 0.5 m` so dirt surfaces do not intrude into paved footprints.
### Performance Gate (SC-006)
Step 5 across 20 observed seeds: max 10,811 ms, well under the 11,046 ms cap (10% above 10,042 ms baseline).
## Related
- [[The Crater]] — the origin event; the pipeline generates worlds inside the crater basin; step 6 raises the crater rim
- [[The Temporal Fracture]] — each seed accesses a different temporal configuration of the structure
- [[World Model]] — the design the pipeline realizes
- [[Biomes]] — what the pipeline places
- [[Ephemerality Thread]] — the run-reset rule
- [[Biome Behavior]] — biome identity across systems
- [[Rock Scatter Design]] — the rock-scatter step specification
- [[Rust — Core]] — the engine-agnostic core
- [[Source Tree]] — where the generation code lives
- [[Systems]] — folder MOC
- [[World — Domain MOC]] — domain MOC
- [[Constitution]] — Article IV (seed determinism), Article XII-E (procedural), Article III Rule 17 (rust-core)
## Agent Notes (backlinks)
- [[Decisions/Rust-GDScript Split Architecture]] — Rust/GDScript split decision, ClassDB delegation pattern, editor restart constraint
- [[Autoload Delegation Pattern]] — Thin GDScript shell over Rust nodes, no class_name constraint
- [[Environment — Weather and Generation Order]] — 11-step pipeline order, Rust handoff points, SC-001 baseline, diagnostic JSON
- [[Road System — Discoveries]] — Road Rust topology layer, GDScript mesh layer, debug visualization
- [[114 Road Artifact Remediation]] — 13 road artifact categories (graph hygiene, mesh quality, multi-type integration); RCA confirmed road mesh is GDScript, branch sampling is unconstrained, no per-segment AABB culling exists
- [[Specs/114 Road Artifact Remediation/114 Road Artifact Remediation - research|114 Road Artifact Remediation — Research]] — Codebase research for spec 114: FR-002/003/004/010/011/012/013 findings, R=50m leaf-node separation, SC-006 performance baseline
- [[115 Terrain Region Boundary Rendering]] — Terrain3D region boundary rendering artifacts deferred from spec 114 (transverse gaps, chunk seam appearance split)
- Environment — Weather and Generation Order — Weather/time/fog coupling, volumetric fog bus-interior constraint
- [[Decisions/NoiseHelper Utility Class Design]] — NoiseHelper utility class decision (RefCounted pattern, seed-parameterised noise)
- Road System — Discoveries — Road shoulder terracing root cause analysis (directional bias in branch sampling)
Controlled tag vocabulary (as read):
type/*:
- `type/code` — Code architecture, autoloads, conventions, tooling. Carries `code-refs:`. (e.g. `Code/Autoloads — Disturbance.md`)
- `type/decision` — Architectural or design decision made by an agent during a session. Written to `docs/vault/Decisions/`. (Agent notes only — spec 080.)
- `type/design` — Design intent, player-facing concepts, world lore. (e.g. `Design/World Model.md`)
- `type/discovery` — Non-obvious constraint, gap, or conflict found by an agent during a session. Written to `docs/vault/Discoveries/`. (Agent notes only — spec 080.)
- `type/moc` — Map of Content at any of three tiers (folder / domain / concept). (e.g. `Audio.md`)
- `type/principle` — Governance and constitutional notes. Non-negotiable rules. (e.g. `Governance/Constitution.md`)
- `type/reference` — Glossary, decision log, open questions, epics. (e.g. `Reference/Glossary.md`)
- `type/session-log` — Session activity log; permanent audit trail. Written to `docs/vault/Sessions/`. Append-only, never pruned. (Agent notes only — spec 080.)
- `type/spec` — Spec document at `docs/vault/Specs/NNN Title/spec.md`. Carries `spec-refs:`. (e.g. `Specs/047 Car Radio/spec.md`)
- `type/system` — A bounded subsystem with implementation. Carries `code-refs`, `spec-refs`, `implements:` frontmatter. (e.g. `Systems/Audio — Car Radio.md`)
- `type/vision` — Strategic / vision-layer decision. Written to `docs/vault/Vision/`. Human-authored or agent-proposed with human ratification before promotion to `status: reviewed`. (Per triage #3 — spec 133 autonomous-company redesign.)
domain/*:
- `domain/anomalies` — Anomaly taxonomy, individual anomaly systems, spawn, behavior
- `domain/art` — Visual direction, palettes, references, asset pipeline
- `domain/audio` — Audio systems: world ambient, radio, transmissions, buses, pipeline
- `domain/autonomous-company` — Autonomous company v0.1 experiment: Slack infra, persona middleware, Letta integration, restart recovery
- `domain/build` — Build system, packaging, export, release automation
- `domain/governance` — Constitution, phased spec, policies
- `domain/infrastructure` — Infrastructure provisioning: CI/CD, hosting, deployment tooling, secrets management
- `domain/narrative` — Tone, horror register, forest transmissions narrative, Gary/KTLK
- `domain/player` — Player mode, input, controls, camera, exit/entry
- `domain/systems` — Cross-cutting system architecture and integration work
- `domain/tooling` — Build, CI, observer, TTS pipeline, dev-time tools
- `domain/vehicle` — The Samba: vehicle physics, dashboard, damage, panels
- `domain/world` — World model, biomes, terrain, procedural generation, weather
status/*:
- `status/canonical` — Fully authoritative; promoted by humans only. Highest trust. (Agent notes only — spec 080.)
- `status/draft` — Authoring in progress, not yet verified
- `status/reviewed` — Agent-created note verified against codebase within its creating session. Eligible for auto-injection in subsequent task dispatches. (Agent notes only — spec 080.)
- `status/stale` — Known out of date; cannot update immediately. Pairs with `> [!warning] STALE — needs update` callout.
- `status/superseded` — Replaced by another note. Terminal state. Pairs with `> [!warning] SUPERSEDED` callout.
- `status/wip` — Stable but under active revision
layer/*:
- `layer/content` — Content assets (audio, models, textures, audio provenance)
- `layer/gdext` — gdext wrapper layer (`rust/gdext-wrapper/`)
- `layer/gdscript` — GDScript (Godot scene-tier logic)
- `layer/infra` — Build, CI, deployment infrastructure
- `layer/python` — Python development tooling (`tools/`)
- `layer/rust-core` — Engine-agnostic Rust core (`rust/rust-core/`)
concept/*:
- `concept/biome` — Biome-reactive behavior across systems
- `concept/contamination` — Across-runs panel and garage degradation; the slow-horror mechanic that doesn't reset with the world
- `concept/diegetic` — Diegetic audio / UI / world-sourced design
- `concept/disturbance` — The disturbance mechanic thread (Article X)
- `concept/ephemeral-world` — Run-reset, transient worlds (Article IV Rules 6–7)
- `concept/governance` — Constitutional and governance concepts
- `concept/horror` — Horror register and dread design
- `concept/procedural` — Procedural generation, seed determinism
- `concept/rendering` — Visual rendering quality, shader correctness, Terrain3D integration, mesh fidelity, LOD, Z-fighting
- `concept/terrain` — Terrain generation, heightmap, Terrain3D regions, roughness, playability constraints