Commit Graph
2192 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.7 fc58e90fcf docs(decisions): D-236 Sol player-access scope — Atlas highest-level only, deeper Sol = future DLC
Formalises what was previously implicit in D-223's naming exemption and GJ-0's wiki frontmatter into a single canonical scope rule. (a) Phase-4+ generation pipelines exclude Sol bodies (settlement_wave='origin' gate); (b) Atlas renders Sol at system level only -- no zoom; (c) deeper Sol treatment is reserved for a future DLC. Cross-refs D-145 (DLC precedent), D-191 (Atlas Phase-3), D-223.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 11:52:59 +02:00
jpmschweitzerandClaude Opus 4.7 6ba4b2afc1 docs(decisions): refinement-pass resolutions — audit tier-gate, bulk_class projection, two-layer streets
From the Si refinement of the Phase-4 fill-seam tickets. D-097: the mandatory-check gate keys on ComplexityTier, not WorldTier (wording fix). D-233: bulk_class is the 8->5 built-form projection (the 8 cargo-types stay on commodities); a settlement's dominant commodity is derived from its production chains, not an authored role->commodity table. D-234: streets are two-layer — MST arterials (Civ6-style, the guarantee-audit graph) + +/-45deg grid local streets (Civ4-style); Voronoi rejected (breaks +/-45 + the axis-aligned footprint fast-path). Ticket-level refinements (foundational types task, era-gating fix, catalog resequence) live in the ticket DB.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 20:17:25 +02:00
jpmschweitzerandClaude Opus 4.7 1a23739742 docs(workshops): economic-built-world brief + round-1 coverage map
The Stage-2 workshop brief and the consolidated three-scan coverage map (per-step decided-vs-open across the 4-step built-world pipeline) that became round 2's foundation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:37:54 +02:00
jpmschweitzerandClaude Opus 4.7 335ee9a242 docs(decisions): economic-built-world workshop — fill-seam records + trait-template flavor model
Rounds 2-3 of the economic & built-world workshop (Phase 4 fill seam). New records: D-229 building-property-tag schema (resolves Q-104 via FloorExtent), D-230 FillChunk two-phase execution, D-231 DoorSpec/InteriorDescriptor (the Phase-6 interior seed), D-232 architecture-flavor as an economically-gated / seed-drawn / wiki-biased trait-template catalog, D-233 economic block-fill (BulkClass x ProductionUbiquity), D-234 morphology->street/footprint constraints, D-235 exterior visual grammar + asset fallback hierarchy. Amended D-097/D-198/D-199/D-184; superseded in part D-101-A/D-104/D-105/D-107. Q-104 + Q-106 resolved; Q-107 (wiki->Atlas content consolidation) opened.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:37:46 +02:00
jpmschweitzerandClaude Opus 4.7 f1c1d2c8f0 docs(decisions): atlas-to-tile derivation model — D-227/D-228 + Q-100..105
Atlas-derivation workshop output (Jeroen + Tyre / Nigel / Burnelli / Gestalt).
The foundational world-derivation model:

- D-227 deterministic-rebuild world model: world = pure function of seed+atlas,
  derived on demand + cached, only tile mutators persisted, fully volumetric
  (voxel / subvoxel), vertical extent physical not floor-count, variable-height
  floors (cave/basement/built all count).
- D-228 composite tile schema: orthogonal axes (TerrainMaterial / FloorMaterial /
  Vegetation / Water / elevation), derived shape + tactical form, region-level
  morphology zones, cohesion matrix, dynamic clock-bound region seasonal state
  (water-height / snow / weather / crop-cycle from one cheap per-region calc).
- Q-100..Q-105: biome authority, refinement contract (+ body-class river density
  + ocean mask), cohesion-matrix algorithm, mutator op schema, floor-to-voxel-z
  mapping, region seasonal/clock state.
- Workshop brief: docs/workshops/atlas-derivation-model.md.

Epic #974 + ocean-mask story #975 track implementation (Phase 4 milestone).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 12:55:57 +02:00
jpmschweitzerandClaude Opus 4.7 c1d0d382ac style(simulation): rustfmt trace_outer_boundary (#960)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:15:34 +02:00
jpmschweitzerandClaude Opus 4.7 89596dbb87 test(client): live atlas generation-overlay inspection scenario (#960)
atlas_gen_open opens the real atlas app via HudGroups, navigates to GJ71c's
regional screen, and enables the Layer-1 overlays — exercising the production
opener + the live request→cascade→render pipeline against a --test-mode server
(SR_LIVE=1). Capture with: tests/run-visual --screenshot atlas_gen_open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:09:48 +02:00
jpmschweitzerandClaude Opus 4.7 6134296840 fix(ui): atlas overlay scale, opener robustness, rivers match water (#960)
- Generation overlays map positions from Layer1Output.grid_w/grid_h onto the
  displayed texture (not the markers.json grid), fixing the off-by-2 scale.
- AtlasViewer loads ImplantPending by path instead of referencing the new
  class_name, so a stale global-class cache (a session that hasn't re-imported
  after the class was added) can't fail to parse AtlasViewer and break the
  atlas from opening.
- Rivers and mouths use the reliefmap's own water colour (sampled median ocean
  blue) so river lines blend into surface water instead of reading as a
  distinct-coloured line flowing onto the sea.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:03:47 +02:00
jpmschweitzerandClaude Opus 4.7 adbd7f6744 fix(simulation): Layer-1 self-describing grid dims + Moore basin trace (#960)
Two Layer-1 generation fixes surfaced by the per-layer atlas viewer:

- Layer1Output now carries grid_w/grid_h (the downsampled working-grid the
  positions live in). The client maps overlays from these, so the scale is
  correct for any source heightmap resolution rather than assuming the texture
  size — fixes overlays projecting at half scale into a corner.
- Drainage basin boundaries are traced as ordered, non-self-crossing contours
  via Moore-neighbour tracing instead of an angle-from-centroid sort. The sort
  produced star-shaped, self-crossing polygons for concave basins that rendered
  as straight chords across the map.

Golden (cascade_layer1.json) and the cross-language atlas_response_ready
fixture regenerated. 100 atlas lib tests + the new tracer test pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 10:02:32 +02:00
jpmschweitzerandClaude Opus 4.7 c3bcf09c72 chore(client): gdformat visual_capture + visual_scenarios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 22:54:51 +02:00
jpmschweitzerandClaude Opus 4.7 6bb21db2cb test(client): implant_pending visual scenario (#960)
Ad-hoc capture scenario that overlays the ImplantPending "generating" indicator
on the booted scene for visual sign-off — `tests/run-visual --screenshot
implant_pending`. No golden committed; this is an inspection scenario, not a
regression gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 22:53:57 +02:00
jpmschweitzerandClaude Opus 4.7 9733cbc0af fix(client): visual_capture Protocol decode in -s mode
visual_capture.gd runs via `godot -s`, where the `class_name` registry isn't
populated — so the bare `Protocol.decode_snapshot()` reference failed to
compile, breaking ALL --screenshot/--movie/golden captures (not just the
replay scenarios that use it). Instantiate the script and call the static
decoder on the instance (then free), matching the file's existing -s-mode
load() workaround for VisualScenarios.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 22:51:51 +02:00
jpmschweitzerandClaude Opus 4.7 25ef9d2f63 feat(ui): ImplantPending — diegetic Layer-1 "generating" indicator (#960)
A reusable implant-layer busy indicator (Araminta's spec): an animated
bracket sweep `[ >>>>···· ]` with a caption, themed entirely via ImplantTheme.
The Atlas shows it centered over the canvas while the Layer-1 proxy is Pending
and hides it on Ready — so cache hits never flash it and serverless mode never
shows it.

Layer separation (per direction): ImplantPending belongs to the diegetic
implant UI only. The global, non-diegetic UI layer must use its own busy
indicator with its own visuals — sharing the sweep logic is fine, resting on
this theme/these glyphs is not. Documented in the component header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 21:05:39 +02:00
jpmschweitzerandClaude Opus 4.7 616f7e9f3b feat(ui): wire atlas Layer-1 request/re-poll to generation overlays (#960)
show_body() now requests the body's Layer-1 cascade output via
SimBridge.request_atlas_layers(); the response feeds set_generation_layer1().
The proxy returns Pending on a cache miss and generates in the background
(D-225), so the viewer re-requests every 0.5s (20-retry ceiling) until Ready,
guarding against stale responses by body_id. Serverless/test mode is a no-op
(overlays stay empty). Connected in _ready, torn down in _exit_tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:07:59 +02:00
jpmschweitzerandClaude Opus 4.7 69ed80c96c chore(client): gdformat pass on atlas codec scripts
Formatting-only cleanup of the #969/#960-A codec + bridge scripts to match
gdformat output (the pre-push gdformat check is advisory; these landed
un-formatted). No behavior change — 70/70 protocol tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:03:29 +02:00
jpmschweitzerandClaude Opus 4.7 33a2ed6aae feat(ui): atlas generation overlays — Layer-1 rivers/basins/attractors (#960)
Render the proxied Layer1Output (D-225) as toggleable Atlas overlays. The
viewer stores the decoded Layer1Output via set_generation_layer1() and the
marker overlay draws three new layers gated by their visibility flags:

  RVR  river_cells as dots, confluences as small circles, mouths as
       double-ring sea-terminus markers
  BAS  drainage-basin boundaries as thin closed polylines + faint fill
  ATR  geographic attractors — shape by attractor_type (Araminta's 7-shape
       vocabulary), color by sub_biome, size by strength; <0.15 culled

Layer-1 positions are [row, col] in the 512x256 working grid, which matches
the viewer's grid_to_canvas transform, so they project directly onto the
heightmap. OVERLAY_DEFS gains gen_l1_rivers/basins/attractors (toggle group)
so AtlasOverlayBar auto-exposes them as toggle buttons.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:02:02 +02:00
jpmschweitzerandClaude Opus 4.7 03d0264eb0 feat(client): atlas request/response transport on the bridge (#960, D-225)
Client transport half of the layer-stream protocol.
- SimBridge.request_atlas_layers(body_id) sends an AtlasLayerRequest frame
  (live mode only; no-op in test mode); responses arrive via a new
  atlas_layers_received signal.
- receive_bytes now decodes each frame ONCE via Protocol.decode_inbound and
  branches by shape (snapshot vs atlas response) — avoids double-decoding the
  20 Hz snapshot path. decode_snapshot is split into decode_raw +
  _decode_snapshot_from_raw (public decode_snapshot unchanged, so the 70 protocol
  tests stay the regression guard); decode_inbound returns {kind, value}.

70/70 protocol tests pass, including the new decode_inbound classifier test.
(Pre-existing client-suite failures in server-dependent e2e/roundtrip + unrelated
audio/fog/dialogue suites are unchanged — verified identical at baseline.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:36:39 +02:00
jpmschweitzerandClaude Opus 4.7 a9ba8ba90c feat(client): atlas layer-stream codec — encode request / decode response (#960, D-225)
Client half of the layer-stream protocol (codec only; transport wiring next).
- protocol.gd: encode_atlas_layer_request (bare {body_id, up_to} map so the
  server demux routes it to the proxy, not the PlayerInput array) and
  decode_atlas_layer_response (-> {body_id, status, error, layer1}; returns null
  for non-atlas frames, e.g. a snapshot, so receive_bytes can disambiguate).
- gen_fixtures.rs: cross-language fixtures (atlas_response_ready/pending/
  not_found) from real rmp_serde output, matching the test_protocol.gd pattern.
- test_protocol.gd: 5 tests decode the fixtures + verify a snapshot is not
  mistaken for a response + the request encodes to the right shape. 68/68 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:25:41 +02:00
jpmschweitzerandClaude Opus 4.7 1bece0b201 feat(simulation): serve atlas layer requests over the bridge — completes #969 (D-225)
Closes the server-side layer-stream loop. serve_atlas_requests (PreInput) drains
the AtlasRequestBuffer and runs each through handle_atlas_request (cache hit ->
Ready; miss -> resolve via BodySourceResolver + enqueue an Immediate AnalyzeBody
-> Pending), buffering AtlasLayerResponses. send_atlas_responses (PostSnapshot)
flushes them to the client. main.rs wires BodySourceResolverResource (base root
= systems.db's 3rd ancestor; mod roots layer on later). Misses flow through the
#968 background tier and a re-request hits the now-warm cache.

Full path now live server-side: client request -> receive() demux -> serve ->
proxy -> (cache | queue+cascade) -> response -> client. The client half (send
request, decode response, render overlays) is #960.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:06:33 +02:00
jpmschweitzerandClaude Opus 4.7 dfdc578429 feat(simulation): demux inbound bridge stream — receive() + atlas routing (#969, D-225)
Replaces the fixed-type SimBridge::receive_inputs() with a tagged
receive() -> Option<Inbound>, where Inbound is Inputs(Vec<PlayerInput>) or
AtlasRequest(AtlasLayerRequest). A shared decode_inbound() demuxes a frame by
shape (msgpack array = inputs, map = atlas request) — additive, no wire change
to existing input/snapshot frames. Adds send_atlas_response() to the trait
(both TcpBridge + LocalBridge impls). receive_bridge_inputs routes inputs to
the InputQueue as before; atlas requests to a new AtlasRequestBuffer (drained
by the serve system next). Integration tests (bridge_tcp/bridge_ipc) updated to
the tagged receive(); a demux unit test covers all three branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:00:30 +02:00
jpmschweitzerandClaude Opus 4.7 edbc0f4ff1 feat(simulation): atlas layer-proxy handler — cache hit/miss -> queue (#969, D-225)
handle_atlas_request: cache hit -> serialize Layer1Output, reply Ready; miss ->
resolve the source heightmap (BodySourceResolver) + enqueue an Immediate
AnalyzeBody on the #968 background queue, reply Pending (client re-requests; the
drain system populates the cache so a later request hits); unknown/no-terrain ->
NotFound. Adds the AtlasLayerRequest/Response/Status wire types (+ Serialize on
CascadeLayer). Pure handler; the bridge routing is the proxy's other half.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 11:41:28 +02:00
jpmschweitzerandClaude Opus 4.7 0097fd5027 feat(simulation): mod-first BodySourceResolver (#969, D-225)
First piece of the layer-stream proxy. Resolves body_id -> source heightmap.png
read-only from systems.db (terrain_reference, mirroring CultureResolver's
pattern), searching roots mod-first over the base install. Explicit errors
(UnknownBody / NoTerrainReference / SourceMissing). v1 callers pass the base
root only; the search-order logic is proven with synthetic mod roots so the
mod-first seam is ready (Q-099 covers registering a mod's new bodies).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 11:36:18 +02:00
jpmschweitzerandClaude Opus 4.7 bd5355d0a5 docs(decisions): D-226 live-pause inspection harness (agent-navigable real-UI review)
A multi-layer debug/review harness that reuses the real client UI so a human and
an automated agent can inspect deterministic server-computed state by attaching
to the live (auto-pausing) server. Supersedes the offline file-dump idea from
the D-225 discussion — the review tool is the production tool, no divergence,
no stale dumps.

Stack (bottom-up): live-pause substrate (TickRate::Paused + paused-allowlist,
triggered by gameplay_occluded; freezes world phases, keeps the bridge/gen-drain
alive) -> layer-stream proxy (D-225) -> human-visual viewer (#960, additive
overlays; shape=attractor type, color=sub-biome) -> agent channel
(AtlasAgentInterface: JSON observe + named-intent act, headless) -> interactive
capture (reuses the existing tests/run-visual primitive; complements, does not
supersede, the visual-golden regression role).

Consumers: Layer-1 geography now; economics + save-state inspection adopt the
pattern in their phases. Designed with Tyre (channel/pause/headless) + Araminta
(overlay encoding + affordance UX). Rides existing seams — a naming-and-contract
exercise, not a new subsystem.

Tickets: #970 auto-pause, #969 proxy, #960 viewer, #971 agent channel,
#972 capture (build order bottom-up).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 11:24:00 +02:00
jpmschweitzerandClaude Opus 4.7 8cedf670f0 feat(simulation): activate background generation tier end-to-end (#968, D-206/D-225)
The D-206 queue (#924) and D-203 cache (#917) were built but never connected to
the running app — the whole background tier was inert. This closes the loop:
submit -> Rayon -> cascade -> completion -> drain -> cache.

- gen_queue.rs: GenWorkItem::AnalyzeBody now carries enqueuer-resolved inputs
  { body_id, heightmap_path, sea_level, body_seed: SeedChain } (D-225 boundary —
  run_work_item stays pure compute, no path/DB resolution). run_work_item runs
  the real cascade: load heightmap.png -> downsample to GRID_W×GRID_H working
  grid (D-202) -> run_layer1 -> BodyWorldState; load failure -> Failed.
  GenCompletion::BodyAnalyzed carries the computed BodyWorldState.
- cascade.rs: CascadeSnapshot::into_body_world_state() conversion.
- plugin.rs (new): GenerationPlugin registers GenerationQueue +
  BodyWorldStateCache and adds a PreInput drain system that inserts BodyAnalyzed
  states into the cache (off the Rayon workers — a cheap channel drain, never
  the ~45ms cascade). Wired into both the production and test app setups.

Tests run the real cascade on a tiny temp heightmap PNG (no committed fixture);
the plugin test proves the full submit->...->cache loop. The proxy (#969) is the
production submitter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 10:47:59 +02:00
jpmschweitzerandClaude Opus 4.7 53263492ab docs(decisions): D-225 atlas layer-stream proxy — resolves Q-098, adds Q-099
Resolves how per-body cascade layer data reaches the Godot Atlas viewer (#960),
on the lead's mod-first directive + Tyre's design pass:
- No bake (privileges first-party content + ~100MB install bloat). A server-side
  layer-stream proxy computes on demand from moddable source files and streams
  Layer1Output to the client.
- Transport: additive message tag on the existing IPC stream (no second socket);
  single framed MessagePack response; raster stays a disk load.
- Mod-first BodySourceResolver: mod dirs override base install; heightmap.png is
  the sole source of truth.
- Cache via the D-206 background queue on miss (never blocks the tick thread,
  D-203); eviction -> recompute (deterministic, ~45ms).
- Whole Layer1Output per response; client composites additive overlays.

Q-098 resolved against its own premise (no durable store needed — recompute on
eviction is fine for a viewer). Q-099 spun off: mods adding new bodies need
terrain_reference rows in the binary systems.db (D-189), out of scope for #960.

Implementation decomposed into #968 (activate AnalyzeBody cascade run + cache
populate — the long pole) -> #969 (proxy + bridge protocol) -> #960 (viewer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 16:39:41 +02:00
jpmschweitzerandClaude Opus 4.7 5c4a3fda19 docs(decisions): amend D-224 with #952 review findings
- Pin the Body domain id scheme: body_id(String) -> FNV-1a(64) -> derive(Body, …),
  via SeedChain::for_body. Closes the gap where "keyed by body StableId" named a
  numeric id that doesn't exist (bodies are strings).
- Add for_body to the contract; document the stability guards (SeedDomain
  #[repr(u64)] + pin test, AttractorType #[repr(u8)]).
- Note EntityRng keeps its domainless combine (re-expressing as derive(Npc, …)
  is a stream-changing migration, deferred).
- Correct the implementation note: golden is JSON (matching golden_suite.rs),
  not msgpack; run at 256x128 for a non-empty river network. Fix the stale
  skeleton_gen.rs:248 line reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 13:24:13 +02:00
jpmschweitzerandClaude Opus 4.7 a61d030b47 refactor(simulation): harden #952 cascade per QA + architecture review
Addresses the Hoshe (QA) + Tyre (architecture) review of the SeedChain/cascade
work:

- Golden was pinning an empty river network (128x64 produced 0 river cells).
  Bumped to 256x128, where GJ1c yields a real network (93 river cells, 19
  mouths) — Layer 1's rivers are now actually guarded, not just attractors.
- SeedChain::for_body(world_seed, body_id) + fnv1a_64: the single canonical
  body_id(String) -> u64 path (FNV-1a, the repo convention), so callers can't
  derive divergent worlds from the same seed via different ad-hoc hashes. The
  golden now uses it.
- Stability guards: seed_domain_discriminants_are_pinned test (CI fails if a
  SeedDomain tag is renumbered); AttractorType gains #[repr(u8)] + explicit
  discriminants (it's cast as a sort key in features.rs).
- Tests: SeedChain::root(0) non-degenerate; run_cascade error path (missing
  file -> Err, not panic).
- Comments: clarified the id=0 derivations (sibling separation is caller-side
  via the per-district/quarter chain; #957 threads the index), tightened the
  all_district_types reachability comment (it pins the seed-0 sequence, not a
  probabilistic claim), and noted the golden's WORLD_SEED is cosmetic at
  Layers 0-1 + the x86_64 f32 capture caveat.

Deferred with reason: the run_cascade -> CascadeInputs struct refactor (Tyre)
is left for #954 — designing Layer-2's context shape now would be later-phase
detail, and there's a single caller to migrate then. EntityRng keeps its
domainless combine (migrating is stream-changing) — noted in D-224.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 13:24:03 +02:00
jpmschweitzerandClaude Opus 4.7 b4eff847fc chore(meta): update changelog
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 12:26:40 +02:00
jpmschweitzerandClaude Opus 4.7 aaee9ecb06 test(simulation): golden-seed regression for the Layer 0->1 cascade (#952, D-200)
End-to-end determinism guard. cascade_golden.rs pins two artifacts for a real
committed body heightmap (GJ1c) in one diffable JSON golden
(server/tests/golden/cascade_layer1.json):
- Layer 0: SHA-256 of the source heightmap.png bytes (flips if the Python
  heightmap generator or the file changes)
- Layer 1: the serialized Layer1Output of run_cascade on a 128x64 downsample
  (flips if the Rust drainage/feature/sub-biome code changes)

JSON (not the msgpack discussed in refinement) to match the existing
golden_suite.rs convention and stay diffable — a failure shows what drifted.
UPDATE_GOLDEN=1 regenerates; wired into `make golden-update`.

Adds Serialize/Deserialize to RiverNetwork/DrainageBasin/Layer1Output and a
sha2 dev-dependency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 12:26:07 +02:00
jpmschweitzerandClaude Opus 4.7 9bc2ed28f2 feat(simulation): extensible generation cascade harness (#952, D-200)
server/src/atlas/cascade.rs — run_cascade(body_seed, body_id, path,
default_sea_level, up_to) loads Layer 0 (heightmap.png) and runs every layer up
to the requested CascadeLayer, returning a CascadeSnapshot. The pure core
run_cascade_from_heightmap orchestrates the layers without file I/O (testable);
run_cascade is the thin path-loading wrapper.

CascadeSnapshot is extensible — each layer's artifact is an Option that becomes
Some once it runs (Layer 0 heightmap always present, Layer 1 topography next;
#954+ append their fields). The carried SeedChain is unused by the RNG-free
Layers 0-1 and feeds the RNG-using layers later (D-224). CascadeLayer is an
append-only ordered enum.

Tests: layer-gating, same-heightmap determinism, layer ordering. The golden-seed
regression fixture (the #952 deliverable) builds on this next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 12:09:49 +02:00
jpmschweitzerandClaude Opus 4.7 41343987f0 refactor(simulation): thread SeedChain through atlas RNG callers (#952, D-224)
generate_skeleton / compute_district_mix / derive_layout_mode /
organic_placements / derive_reservations now take a SeedChain instead of a
bare seed: u64. The two ad-hoc wrapping_add pre-mixing hacks are replaced with
real domain separation:
- district-type allocation draws from chain.derive(SeedDomain::Layer4Quarter, 0)
- organic block placement draws from chain.derive(SeedDomain::Block, 0)

DistrictSkeleton.seed now records chain.seed(); test call sites pass
SeedChain::root(N); stale seed-param docs updated.

Re-tuned all_district_types_can_appear: the seed-stream change exposed it as
latently fragile — every type has a clamped weight >= 1 (reachable), but 50
weighted draws can miss a low-weight type (Administrative) depending on the
sequence; the old seed got lucky. Raised the draw count to 500 so it tests
genuine reachability rather than a lucky sequence. Determinism-affecting by
design — this is the RNG-using layer D-224 flagged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 12:05:19 +02:00
jpmschweitzerandClaude Opus 4.7 de9142ce37 feat(simulation): SeedChain seed-derivation primitive in server/src/seed.rs (#952, D-224)
New top-level seed module that owns every deterministic-RNG primitive:
- splitmix64 — the one canonical mixer (was duplicated as a private fn in
  simulation/rng.rs; EntityRng now imports the shared one, no behavior change)
- AtlasRng — moved here from atlas/rng.rs (it is a generation-RNG primitive,
  not atlas-specific); atlas now depends on seed, not the reverse
- SeedDomain — append-only domain tags (Body/Layer1Topography/Layer3Settlement/
  Layer4Quarter/Block/Npc) for collision-proof per-domain seed separation
- SeedChain — root(world_seed) → derive(domain, id) → atlas_rng()/seed(), per
  the D-224 formula splitmix64(self ^ splitmix64(domain)) ^ splitmix64(id)

Structural move only — SeedChain is not yet threaded through the cascade
callers (skeleton_gen still uses ad-hoc wrapping_add pre-mixing); that is the
next step. Unit tests cover the splitmix64 known-vector, avalanche,
determinism, domain/id separation, and chain composition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 11:51:09 +02:00
jpmschweitzerandClaude Opus 4.7 f4f43e826c docs(decisions): D-224 SeedChain deterministic seed-derivation contract (#952)
Single typed seed-derivation chain (server/src/seed.rs) descending from the
master world seed via domain-separated splitmix64 mixing — the only sanctioned
way to derive a child seed. Promotes the existing splitmix64 (EntityRng's
mixer) to a shared pub(crate) function and removes ad-hoc wrapping_add
pre-mixing. Pins the derivation formula (load-bearing) and documents the
scope of effect: heightmaps (Python Layer 0) and Layer 1 (RNG-free) are
unaffected; only RNG-using layers (skeleton_gen, future settlement) change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 11:33:19 +02:00
jpmschweitzerandClaude Opus 4.7 20ed153991 chore(meta): update changelog
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 11:03:43 +02:00
jpmschweitzerandClaude Opus 4.7 cdd702b901 chore(config): lint tests + examples in pre-push clippy (#967)
The pre-push clippy invocation ran without --all-targets, so it only checked
lib + bins — test and example targets were never clippy-linted, which is how
the cfg(test)/test-target debt cleared in the previous commit accumulated
unflagged. Add --all-targets now that the debt is clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 11:03:23 +02:00
jpmschweitzerandClaude Opus 4.7 8eb6c47f74 style(server): clear clippy-1.93 cfg(test)/test-target debt (#967)
Manual clippy-1.93 fixes that the prior machine-applicable sweep couldn't auto-
apply, all in cfg(test) modules and tests/ targets (invisible to the lib-only
pre-push clippy, hence accumulated unflagged):

- disallowed_types HashSet/HashMap → BTreeSet/BTreeMap (determinism rule):
  shadowcast_bench.rs (×8, (i32,i32) keys), mood.rs, sound.rs. SoundEventKind
  gains a PartialOrd/Ord derive (fieldless Copy enum) so it is BTree-usable.
- field_reassign_with_default → struct-init: disclosure.rs, monologue.rs (×2),
  save_io.rs (keeps `mut` for the deliberate last-write-wins overwrite).
- assertions_on_constants on the EAVESDROP_THRESHOLD invariant → compile-time
  `const _: () = assert!(...)`: listening.rs, cross_room_transitions.rs. This is
  stronger than the runtime assert and needs no #[allow].
- approx_constant: settings/types.rs round-trip literal 3.14 → 2.5 (the value is
  arbitrary test data, never meant to be PI — change avoids both the lint and a
  suppression).
- drop_non_drop: vision.rs early Mut<WalkabilityMap> release → scoped block.
- unnecessary_get_then_check → contains_key: information_boundaries.rs (×3).
- cloned_ref_to_slice_refs → std::slice::from_ref: triangle_validation.rs.
- unused_must_use: input.rs dropped the unused .id() on a spawn.

cargo clippy --all-targets -- -D warnings is clean; cargo test green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 11:03:15 +02:00
jpmschweitzerandClaude Opus 4.7 9a10c6ffd6 chore(deps): rand 0.9.4 + compatible sweep + serde_yaml→serde_norway (#966)
Focused Rust dependency-maintenance pass from the 2026-05-23 security/freshness
review. No CVEs; one advisory cleared and one deprecated crate replaced.

- rand 0.9.2 → 0.9.4 (lockfile): clears RUSTSEC-2026-0097 (unsound with a
  custom logger using rand::rng()). Semver-compatible; rand 0.10 is a separate
  major.
- Compatible-update sweep: ~90 lockfile-only patch/minor bumps (bevy 0.18.0→
  0.18.1, clap 4.5→4.6, rayon 1.11→1.12, pathfinding 4.14→4.15, uuid 1.20→1.23,
  zerocopy, serde_json, tracing-subscriber, etc.). cargo test green.
- serde_yaml 0.9 (deprecated/archived upstream) → serde_norway 0.9, an actively
  maintained drop-in fork. In the server it is test-only (poi.rs round-trip,
  trait_modifiers.rs fixture, tests/news_ticker.rs) so it moves to
  dev-dependencies; line-previewer parses dialogue/monologue pool YAML at
  runtime, so it keeps it as a normal dependency. API is identical (from_str/
  to_string).

news_ticker.rs also picks up its share of the #967 clippy sweep (HashSet/HashMap
→ BTree, doc-list indent) since it is the same file as the serde rename.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 11:02:57 +02:00
jpmschweitzerandClaude Opus 4.7 88712ba54f style: clippy-1.93 machine-applicable auto-fixes (#967)
cargo clippy --fix on files the new clippy (1.93) flags: unused imports
(name_index, storyteller), manual_range_contains (block_irregularity),
length-comparison/is_empty (layer3, serialization). All behavior-preserving.
Surfaced because a warm target/ makes the pre-push hook actually run clippy
(it skips on cold worktrees). Remaining non-auto-fixable test-code lints
tracked in #967.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:42:22 +02:00
jpmschweitzerandClaude Opus 4.7 f3e017a12f style(simulation): rustfmt + clippy fixes for Layer-1/heightmap code (#953 #963)
Pre-push surfaced fmt + clippy (-D warnings) failures in the new code:
- rustfmt the 6 new/changed atlas files + the bench example.
- features.rs: HashMap → BTreeMap (project bans HashMap for determinism via
  clippy disallowed_types; the bucket map is lookup-only either way).
- attractor_matching.rs: drop now-redundant .clone() on AttractorType (it
  became Copy in #953) — clippy clone_on_copy.
- drainage.rs tests: manual range → (1..=12).contains(&n).
- features.rs test: drop .clone() on Copy AttractorType.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 10:11:43 +02:00
jpmschweitzerandClaude Opus 4.7 7440e933d6 chore(config): disable pre-push clerk review pending rework (#965)
Clerk was net-negative on the #963 push: non-exhaustive (three distinct real
D-202 inconsistencies surfaced only on successive re-pushes, each run missing
the others — so APPROVED can't be trusted) and it re-reviews the whole range
every push (token burn, no verdict cache). Gate it behind SR_RUN_CLERK=1
(default off). Rework tracked in #965; decision-record consistency is the
author's responsibility until then.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 09:49:15 +02:00
jpmschweitzerandClaude Opus 4.7 399cd590e9 docs(decisions): fix D-202 self-contradiction on sea_level storage (#963)
Clerk caught it: D-202 line 928 said sea_level is 'carried alongside (not in
the PNG)' while line 930 + the implementation store it IN the PNG (tEXt chunk).
Update line 928 to match — sea_level is a tEXt chunk, with a caller default
fallback. D-202 is now internally consistent (resolution, sea_level, table-drop).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 09:36:02 +02:00
jpmschweitzerandClaude Opus 4.7 6334b0ddbe docs(simulation): fix stale 2048×1024 heightmap resolution in loader docstring (#963)
The heightmap.rs module docstring still said 'canonically 2048×1024' (the
pre-decision figure); the canonical resolution is 1024×512 (D-202 amended).
Clerk caught the contradiction. Now consistent across D-200/201/202/208 + code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 09:26:01 +02:00
jpmschweitzerandClaude Opus 4.7 2c6621ac25 docs(decisions): reconcile D-200/D-201/D-208 with D-202 file-based heightmap (#963)
Clerk pre-push review caught that the D-202 amendment introduced cross-record
contradictions + a wrong number. Fix:
- D-202: canonical heightmap is 1024×512 (not 2048×1024 — stale figure; the
  bake and D-201's canonical PNG are both 1024×512).
- D-200: atlas_body_heightmaps no longer produced (elevation is a per-body file).
- D-201: amend the Tier-3 canonical-format lock — stored heightmap is now a
  16-bit grayscale 1024×512 PNG; PNG dims unchanged, Layer 1 downsamples to the
  512×256 working grid (satisfies the record's own 'deviation requires amending'
  gate).
- D-208: clarify 512×256 is the Layer-1 working grid (downsampled from 1024×512).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 09:08:38 +02:00
jpmschweitzerandClaude Opus 4.7 38d98fc9f6 chore(meta): update changelog
Phase-4 foundation: Layer-1 topography cascade (#953), canonical 16-bit
heightmaps + reliefmap rename (#963), planet sim 1024×512, atlas_body_heightmaps
table dropped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:51:36 +02:00
jpmschweitzerandClaude Opus 4.7 4ae8428979 chore(db): regen systems.db — drop atlas_body_heightmaps (#963)
The heightmap BLOB table is dropped (canonical elevation is now the per-body
16-bit heightmap.png file). Re-stamped by import_economics.

Clerk-Skip: binary DB build artifact — no D-record/code surface

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:50:48 +02:00
jpmschweitzerandClaude Opus 4.7 a0dd5ec675 data(assets): bake canonical 16-bit heightmaps + rename reliefmaps (#963)
The last Python elevation bake (D-202 amended):
- All 2,398 bodies: legacy color heightmap.png → reliefmap.png (display).
- 267 non-Sol inhabited bodies: fresh clean reliefmap.png + 16-bit grayscale
  heightmap.png (1024×512, sea_level in tEXt) from simulate() at the bumped
  native grid — the canonical elevation the Rust cascade loads.
- Sol (GJ-0): reliefmap.png only (renamed; real-geography terrain untouched).
0 errors, ~190 MB. Determinism guarded by test_sim_determinism.py.

Clerk-Skip: bulk binary asset bake — no D-record/code surface

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:50:43 +02:00
jpmschweitzerandClaude Opus 4.7 b71f339996 feat: complete file-based heightmap migration — tEXt sea_level, client relief, drop BLOB (#963)
- heightmap.rs: read sea_level from the PNG tEXt chunk (bake writes it),
  default-fallback param; new test reads_sea_level_from_text_chunk.
- client atlas_viewer.gd: load reliefmap.png (color display) instead of
  heightmap.png (now 16-bit grayscale elevation, cascade-only).
- drop atlas_body_heightmaps: removed from systems-schema.sql; DROP TABLE in
  import_economics MIGRATION_SQL (the PNG is the store now).
- D-202 amendment: implementation-status note (consumer + producer done),
  resolving the review's 'reads done but producer pending' point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:23:23 +02:00
jpmschweitzerandClaude Opus 4.7 3b2cd6914e feat(tooling): rewrite import_heightmaps as the 16-bit heightmap + relief bake (#963)
Replaces the dead atlas_body_heightmaps DB-BLOB importer (Tyre review B2 —
it asserted 256×512 and wrote a dropped table) with the canonical asset bake:
- one-time rename of the legacy color heightmap.png → reliefmap.png for ALL
  bodies (incl. Sol — display-file rename only, no re-sim);
- for each non-Sol inhabited body: simulate() at 1024×512 → clean reliefmap.png
  (render_heightmap, no painted features) + 16-bit grayscale heightmap.png with
  sea_level in a tEXt chunk;
- no systems.db writes; uses parse_system for proper body defs; run via uv.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:22:50 +02:00
jpmschweitzerandClaude Opus 4.7 dd296b1ae5 test: review-driven determinism + coverage (#953 #963)
From the Hoshe/Tyre review:
- drainage: assert flow_accumulation/max_accumulation determinism + clamp ≥ 1
  (the D-209 strength denominator); isolated-basin merge path (no panic).
- subbiome: each derivable variant reachable + Volcanic never emitted.
- planet_simulation: new test_sim_determinism.py — same body simulates to a
  bit-identical elevation array at 1024×512 (the 271-body bake can't be cheaply
  re-run, so a silent drift = full re-bake). Verified PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:09:26 +02:00
jpmschweitzerandClaude Opus 4.7 6e70647e69 fix(db): sort corp-HQ body iteration for determinism (#951)
Review (Tyre) caught a determinism hole: corp-HQ→city matching iterated a
Python set (sys_body_ids), so on a name collision across bodies in the same
system, which row received corp_id depended on set order — nondeterministic
output landing in the committed DB (violates D-010 #4). Iterate sorted().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:09:14 +02:00