Files
settled-reach/CLAUDE.md
T
jpmschweitzerandClaude Fable 5 18c731a5de docs(meta): D-255 body-map-viewer stepped render architecture + deprecation sweep
D-255 filed (server-determines-content / client map-art function / stepped
six-level ladder Global+Region→Chunk / tagged-envelope wire discharging D-225 /
three-tier cache with distinct staleness-storage axes / cache-accelerated-pure-
function seed-chaining / lakes from settled hydrology, endorheic cue =
outflow-course presence). Twelve in-place amendments: D-166 corollary repoint,
D-192 persistent-cache boundary, D-225 deferral discharged, D-226 consolidated
(ceiling re-scope, select_rung→step-index, stepped transport, item-(d)
per-request + retention cap + chunk-floor partial restore, courses gloss +
cliff sparse-list), D-227 four-part (TTL-split, version tag, seed-chaining,
lakes), D-243 gridunit vocabulary. Clerk-audit fixes: atlas-zoom-ladder-t1143
superseded banner, CLAUDE.md Phase-4 cascade row updated to the stepped model,
Q-093 prior-art cross-ref. river-courses-t1170 carrier gloss. governance
README index regenerated (109 decisions through D-255); validate clean, sync
1344 refs 0 broken. Ticket chain T-1181–T-1185 + reconciliations in changelog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 10:58:02 +02:00

16 KiB
Raw Blame History

The Settled Reach

A top-down life-sim — asymmetric information, occlusion-based perception, single-character perspective, Rimworld-style storyteller. Systems interactions like The Sims, combat and visuals like single-character Rimworld, world generation inheriting patterns from Dwarf Fortress and NMS, economy inspired by X4/EVE. Many systems interactable and ready for player participation, but capable of being ignored — the world is alive for any given run. Set in an original science fiction universe. Godot 4 client + Rust/bevy_ecs simulation server via subprocess/IPC.

Official Title: The Settled Reach Repository name: settled-reach Version source of truth: project.yaml (root version field, scheme: 0.{phase}.{n})

Project Structure

client/               # Godot 4 client
server/               # Rust/bevy_ecs simulation server
tooling/              # Build tools, scripts, asset pipelines
tests/                # Integration and end-to-end tests
docs/                 # Architecture, design, briefings, workshops
db/                   # Schema + seed data (connectors moved to tooling/db/)
.claude/              # Agents, skills, rules
governance/           # Decision records — decisions/ questions/ rejected/ per domain (pql DQR tree)

Full annotated tree: .claude/rules/project-structure.md

DevOps

See docs/DEVOPS.md for build, test, lint, and CI procedures. All development operations go through the top-level Makefile — run make for a summary of targets.

Asset pipeline

server/data/systems.db is a read-only canonical snapshot produced by a single generator (import_economics, which runs generate_brands internally) — never edit it directly. To regenerate: make regen-db. Full rules in .claude/rules/asset-pipeline.md.

Development Cascade — First Things First

Development follows a strict cascade. Each phase has a concrete deliverable. Do NOT discuss, design, or implement detail from a later phase while an earlier phase is incomplete. If you encounter references to later-phase detail (room grammar, NPC bundles, heritage tokens, etc.) in documents or decisions, either ignore them silently and stay at the correct level, or flag that the reference is dragging attention to the wrong scope level and suggest it be rephrased or moved.

Phase Focus Deliverable
1 Wiki content complete — all planets, moons, stations, heightmaps, artwork Implant-ready Godot map of the Reach with click-throughs + wiki/GTTR popups
2 Economics layer — supply/demand, transport, political/social pressure, corporations, supply chains Economics spreadsheets/graphs with runtime-tweakable simulation
3 Planetary/moon maps & station layouts — cities, rivers, mountains, roads, biomes, rail Atlas of the Reach (implant app)
4 World generation (tile/chunk/block) — deterministic multilayer seed→tile cascade; each layer above the tile fill viewed as a map in the implant Atlas (no in-world rendering; tile/voxel output is harness-verified, never Atlas-mapped; chunk (64 m) is the deepest Atlas rung — D-255, narrowing the D-226 amendment 2026-07-13) Deterministic walkable-world data + per-layer Atlas maps + asset catalog + the stepped Atlas ladder (D-255: six levels — the Global body-surface opener (rung 0, variable extent, always kept) + five fixed rungs Region→District→Quarter→Block→Chunk; every step a server-derived data canvas at its native gridunit spacing, drawn texel-exact by the client map-art function — heightmap PNGs remain invisible derivation input, never displayed)
5 Player control & in-world rendering — character, walls/stairs/doors, lighting, drawn on generated tiles (no test map) Player viewport with final-version assets on the generated world
6 Detail coloring — room-level NPC population, cultural room grammar Only when the world is walkable

v0.2 target is dropped. No scoping negotiations. Build the base systems fully.

Generation before player (D-166, amended 2026-05-22 and 2026-06-12). The world is built outside-in: geo layer → economic layer drawn onto the world (cities, buildings, streets, blocks, inter-settlement roads/rail) → building templates → door boundary contracts to building insides → only then the background NPC layer + interiors (Phase 6) → only then gameplay/scenarios ("making it a game again", parked in epic T-1036). Player control and visual rendering (Phase 5) ramp in parallel once outside generation produces walkable exteriors (gate held by T-962) — not before. There is no hand-made test map — test layers are produced by the generator itself. Progress is viewed through the implant Atlas (per-layer maps), which already exists (Phase 3). The current in-world rendering code is left as-is until Phase 5 — do not build on it or rip it out before then. Guiding statement: before this is a game, it is a Reach a character can travel through. Phase-independent maintenance/test debt lives under initiative T-1037, not a phase epic.

Spatial scale ladder (D-243)

The generation cascade is a nested, absolute-metre containment ladder with exactly one elastic seam — the jump to planetary scale. Every level below the seam is a fixed integer multiple of the one below, identical on every body; only the planetary count floats per body. Full detail + rationale: D-243.

Level Side = Role
voxel 1 m the tile
chunk 64 m 64 voxels stream / derive unit
block 128 m 2 chunks generator planning unit
quarter 512 m 4 blocks settlement footprint cell
district 2,048 m 4 quarters urban division and local-climate cell
region ~205 km 100 districts top hard block — climate/weather/season lockdown + planetary grid
— elastic seam —
planet per body round(2πR / 204.8 km) regions the only per-body-floating quantity
  • Region is the largest hard block: the unit where weather, season, and the temperature baseline are resolved (the Q-105 cheap-dynamism source — computed once, inherited by every district/tile inside). It is also the sane planetary grid (~195×98 regions on an Earth-sized body).
  • Climate is edge-fuzzed — the region/district is where climate is computed, never where it steps. Sampled values are a warp-perturbed blend across neighbours, so the grid is invisible (climate does not change on a line). Terrain detail finer than the heightmap (~4078 km/px) is invented deterministically (interpolation + domain warp + detail-scatter, D-227), never stored.
  • Vocabulary (locked): "region" = the ~205 km metric cell only. The old 1 km "RegionProfile" is dropped — its terrain/climate carrier role lives on the district. Province (D-201 tier-4, 50500 km watershed/political) is an irregular lore-bearing overlay painted across regions — not a containment rung; keep it distinct from "region". sector is reserved (perception::VisibilitySector); do not reuse it for scale.

Work Modes

Kanban mode (default)

Work flows continuously through the current phase. /whats-next selects the next epic-sized batch from the dependency graph (pql ticket list --under <active-phase-epic> --unblocked), refines ticket context via parallel Si agents, then activates a worktree. /pr-process (from worktree) and /pr-review (from main) handle the review cycle through Gitea PR comments. Phase gating is the cascade hierarchy (initiative T-745 → six phase epics), not labels or a milestone entity — a ticket is in a phase by being parented under that phase epic. The active phase is the in_progress phase epic (sequential per D-166; currently T-750, Phase 4).

Pair session

Human and Claude work together interactively on a single task. No background agents, no autonomous work. Used for load-bearing architecture changes where the human needs to make judgment calls as the work progresses — not approve a finished result.

Rules:

  • No Agent spawns, no run_in_background. One thread of work.
  • Propose one change at a time. Wait for human reaction before continuing.
  • Explain what you're about to do and why before doing it.
  • After each change, verify together (compile, test, inspect) before moving to the next.
  • The human is a participant, not a reviewer. Ask questions, surface tradeoffs, flag risks in real-time.

When to use: Ticket description says "pair session", or the work touches foundational systems where a wrong call is expensive to undo (tick cycle architecture, protocol design, data model migrations, build pipeline rewrites).

Agent Instructions

Agent scope

  • All file paths are relative to the repo root (e.g. server/src/bridge/types.rs).
  • Topic branches per logical work unit (e.g. heightmap-import, city-names-table). Any agent can touch any file — personalities guide perspective, not file access.
  • Never chain git commands in a single Bash call (e.g. git add ... && git commit ...). Always run git add and git commit as separate sequential Bash calls.
  • Keep Bash commands short and simple — one operation per call. The permission gate rejects overly complex commands ("Denied — too complex for the permission system to approve cleanly"): avoid long pipelines, multiple ;/&&-chained commands, nested $() subshells, and intricate grep -oE/python -c one-liners in a single call. Prefer a dedicated tool (Read, etc.) where one fits, or split the work into granular steps / a small script file.
  • Stale git lock files: If a git command fails with index.lock: File exists, you may remove the lock file at .git/index.lock (or .git/worktrees/<name>/index.lock if in a worktree).

Planning store (pql)

Tickets live in .pql/pql.db, rebuilt from the git-tracked .pql/changelog/ (no binary-DB backup ritual). Decisions are markdown-sourced under governance/{decisions,questions,rejected}/ and synced into the same DB. Ticket ids are T-NNN (T-440 == legacy #440). The pre-commit hook auto-exports + stages the changelog.

This is a pql repo — read the pql skill (Skill(pql)) before any non-trivial planning-store operation, not just routine ticket/decision CLI. The rules here cover day-to-day pql ticket/pql decisions; the load-bearing model that bites in edge cases lives only in the skill: mutations write through to .pql/changelog/ synchronously (so never git restore/hand-edit the changelog — it is data, not a cache), pql plan export does not replicate ticket_idmap (so it can't regenerate ids you discarded), and post-checkout/post-rewrite rebuild pql.db from the changelog. Therefore moving planning across branches means re-creating via pql ticket, not changelog surgery; back up .pql/pql.db before any risky planning-store op.

Before starting work

  1. Check active work: pql ticket list --status in_progress
  2. Check your ticket: pql ticket show T-<id> --with-context
  3. Check dependencies: pql ticket show T-<id> --with-blockers
  4. Read relevant governance/**/*.md decision record(s) referenced in the ticket (pql decisions show <id>)
  5. Background context: docs/briefings/{your-name}.md, docs/discussions/

CLI tools

Use pql for tickets and decisions. Never use the sqlite3 CLI — it crashes in Claude Code (std::bad_alloc).

Tool Command Full reference
Tickets pql ticket list, show, new, status, assign, label .claude/rules/ticket-cli.md, /ticket skill
Planning pql plan whatsnext, review, status, board .claude/rules/ticket-cli.md
Decisions pql decisions show, list, claim, validate, sync .claude/rules/ticket-cli.md
Vault query pql query, search, backlinks, related, context

Phase gating is the cascade hierarchy (initiative T-745 → six phase epics), not a milestone entity: pql ticket list --under <active-phase-epic> --unblocked (active phase = the in_progress phase epic, currently T-750).

Testing preferences

  • Prefer live Gauntlet testing over mocks. For visual tests and rendering verification, use the full client/server pipeline (--test-mode + SR_LIVE=1) instead of TestHarness mocks. The Gauntlet test world produces production-identical data. Mocks can mask rendering bugs by taking different code paths.
  • Gauntlet rooms are immutable. Never modify existing rooms — new systems get new rooms. This ensures StableId determinism and fixture stability.
  • Three test tiers: (1) Live server — highest fidelity, (2) MessagePack replay via Protocol.decode_snapshot() — for unreachable rooms, (3) TestHarness mock — for UI-only tests where fog data doesn't matter.
  • make fixtures-gauntlet regenerates real server snapshot fixtures from the Gauntlet world.

Implant UI component library (D-169, D-170)

The implant UI system — all diegetic neural overlay panels — lives at client/ui/implant/. Components are Godot Control scenes styled via a shared Theme resource (default_implant.tres). Do not hand-roll implant panel layouts; compose from the library.

Components: ImplantPanel (root container), ImplantHeader (title + subtitle), ImplantSeparator (horizontal rule), ImplantDataRow (key/value row, optional color), ImplantTextBlock (RichTextLabel for wrapping text).

Theme resource: client/ui/implant/default_implant.tres — defines semantic color roles (PRIMARY_TEXT, DIM_TEXT, ACCENT_ACTIVE, ACCENT_POSITIVE, ACCENT_NEGATIVE, ACCENT_WARNING, SEPARATOR), spacing, and font sizes. Swap the entire .tres to change implant hardware appearance at runtime.

HUD visibility (D-170): client/scripts/autoloads/hud_groups.gd manages z-index layering. Modes: GAMEPLAY (z=0), INSERT (z=10), FULLSCREEN (z=20), MODAL (z=30). App paths are hierarchical: implant/map, implant/wiki/gttr, etc. Opening any implant/* app occludes gameplay; closing returns to gameplay. Key API: open_app(), close_app(), toggle_app(), is_app_active(). Emits gameplay_occluded signal so renderers can pause.

GameplayRenderer base class: client/scripts/rendering/gameplay_renderer.gd — extends Node2D. Subclasses override _gameplay_process() and _gameplay_draw(). Connected to HudGroups.gameplay_occluded to pause when the implant is fullscreen. Used by CursorRenderer, EntityRenderer, FogEntities, SoundIndicatorRenderer, WorldRenderer.

GDScript conventions

Autoload parse-order rule: Autoload scripts (client/scripts/autoloads/) compile before global class_name scripts are registered. Referencing a class_name type directly in an autoload causes a parse-time "not declared" error. Pattern:

  • Declare fields untyped: var my_field = null (comment the intended type)
  • Do not reference class_name types at the top level or in _ready() of autoloads
  • In method bodies called at runtime (e.g. apply_snapshot), use load() inline — by then the script is cached and load() returns the cached resource without reloading: var CVD := load("res://scripts/rendering/character_visual_descriptor.gd")
  • Do not cache the load() result in _ready()_ready() fires during autoload init, before the target script is in the resource cache, causing an actual file reload that breaks self-references in scripts using their own class_name

game_state.gd (character_visual_descriptor field) and sim_bridge.gd (harness field) follow this pattern.

File conventions

  • Decisions: records in governance/{decisions,questions,rejected}/<domain>.md (see governance/README.md; query via pql decisions)
  • Decision IDs: D-NNN (confirmed), Q-NNN (open questions), R-NNN (rejected)
  • Claim IDs before writing: pql decisions claim D <domain> "title" — prevents ID collisions across parallel branches
  • Diagrams: .d2 source + .png renders in docs/diagrams/{category}/. Create or update diagrams via /d2-diagram when D-records are added or modified.
  • Discussion rounds: numbered sequentially, archived to docs/discussions/ when complete
  • Briefings: one per agent, updated after decision-producing rounds
  • Tickets: managed via pql ticket/pql plan CLI or /ticket skill (T-NNN ids)