Blocking PR #129 items 1, 2 (plus polish 16 and 17):
- D-191 §8 prose rewritten to match the code. The previous amendment said
positions were `{x, y}` objects against a "typically 1024 × 512" grid,
but the generator, the six hand-authored templates, and all 2394
procedural seed files ship `[row, col]` integer arrays against a
`{"w": 512, "h": 256}` grid. The decision doc is now aligned with
reality: positions are `[row, col]`, the storage grid is 512 × 256,
and the row-first ordering is called out explicitly so readers can
cross-reference NumPy/flood-fill/A*/cost-grid conventions.
- §8 now follows the D-094 amendment pattern. The superseded 2026-04-10
prose is preserved verbatim as "Original (superseded)" with a dated
Amendment block on top — future readers can see what changed and why
instead of silently losing the history.
- brands.toml header gains a short Phase 2 boundary note. The 4 anchor
brands come from D-189 §5; the additional ~23 brands from D-189 §11
are deliberately deferred to Phase 3 — Phase 2 only needs the demand-
node plumbing and V-B01..V-B06 validation exercised end-to-end.
- systems-schema.sql `bodies.terrain_reference` comment now pins the
repo-root-relative path convention (wiki/star-systems/<slug>/bodies/
<body_id>/heightmap.png) so the three downstream pipelines (populate,
atlas generator, client loader) share a documented contract instead
of drifting against an unwritten convention.
Implements the Phase 3 atlas content generator per D-191 §3, §8, and §9.
Pipeline per body (terrain-aware, deterministic per seed + body):
1. Simulate terrain via planet_simulation.simulate().
2. Analyse continents (flood-fill), habitability (temp/moisture/slope +
coastal bonus), river mouths, and a terrain A* cost grid.
3. Place cities sequentially — capital first (habitability + river-mouth
bias), then corridor growth via multi-source Dijkstra, quadrant-spread
penalty after 2 cities in a quadrant, port-on-new-continent bonus at
cities 3–4. ±25% noise for seed variation.
4. Generate roads and railroads as an MST over city positions, with
A* paths on the terrain cost grid (rail follows roads where possible).
5. Place a transit POI at the capital (15% chance to scatter to a
secondary city).
Output (canonical markers.json schema, pixel space per D-191 §8):
- cities: {id, name, kind, center:[r,c], population}
- roads: {id, name, kind, path:[[r,c],...]}
- railroads: {id, name, kind, path:[[r,c],...]}
- pois: {id, name, kind, center:[r,c]}
- existing rivers/oceans/mountain_ranges preserved untouched.
City names are left empty for gemma_naming.py (#833). Body population is
split across cities with geometric decay (capital ~50%, each subsequent
city half the previous). The 6 hand-authored bodies (Lendel, Edict,
Vuurkloof, Røros, Cairnside, Estrade) are detected by existing
`cities` and skipped for regeneration; their markers are still synced
to the DB index below.
Atlas index in systems.db (new):
- atlas_body_grids, atlas_cities, atlas_roads, atlas_railroads,
atlas_pois, atlas_rivers, atlas_oceans, atlas_mountain_ranges
- Scalar metadata mirror of every markers.json — the implant atlas app
and development queries can lookup cities/POIs/features without
scanning 267 JSON files. Polyline geometry stays in the markers.json
files next to the heightmaps (used by the renderer); the DB only
stores filterable scalar fields plus `point_count` as a length proxy.
- Schema lives in server/data/systems-schema.sql; generate_atlas.py
mirrors the CREATE TABLE IF NOT EXISTS block so it runs against any
DB state (matches the economy-db importer pattern).
- Populated and refreshed on every run. Each body's rows are deleted
and reinserted deterministically — no stale state.
Also fixes a pre-existing WIP bug in the quadrant-saturation penalty
loop (a stray outer `for r in range(GRID_H)` with unreachable breaks
meant only the NW quadrant was ever checked).
Runtime: 280s for all 267 inhabited bodies on a single core. 265 bodies
updated this run, 6 hand-authored bodies synced to DB without
regeneration.
Atlas index after run:
atlas_cities 329 (15 hand-authored + 314 awaiting #833)
atlas_roads 46
atlas_railroads 44
atlas_pois 287
atlas_rivers 2034
atlas_oceans 696
atlas_mountain_ranges 1953
atlas_body_grids 267
Adds tooling/planet-gen/populate_terrain_reference.py and runs it against
systems.db. Resolves each body's expected wiki heightmap path (repo-root
relative) and writes it into bodies.terrain_reference. Missing heightmaps
are logged for remediation.
Result: 2380/3240 bodies populated, 860 still missing heightmaps. This
unblocks generate_atlas.py (#832) for every body that has a heightmap.
Adds the brand layer per D-189 §5:
- Schema: brand_products, brand_inputs, system_fiscal, corp_financial_state,
corp_lifecycle_events (+ 5 indexes).
- Importer: reads wiki/economics/corporations/brands.toml, populates the
new tables, validates V-B01–V-B05 structural rules, and derives
system_fiscal for inhabited systems.
- Data: 8 brand_products, 16 brand_inputs, 301 system_fiscal rows.
Brand products are demand nodes — they consume commodities; they are not
commodities themselves (D-185). Depends on copy PR #127 for the corp
records referenced by brands.toml.
Replaces the hardcoded seed=0 with the seed received in StartupMessage,
threading it through SimulationPlugin -> EconomyPlugin / SimRng. Integration
test fixtures updated for the new SimulationPlugin { seed } signature.
Review fixes from Hoshe + Tyre:
- EntityRng seeding: splitmix64(seed) ^ splitmix64(id) instead of
splitmix64(seed + id) — eliminates collision class where adjacent
seeds produce identical streams
- AtomicBool ordering: Relaxed → SeqCst for shutdown flag (correct
on weakly-ordered architectures)
- Worker Drop: join handles instead of detaching threads
- Normalize stub API: remove ChunkGenWorker convenience wrappers,
use .pool consistently across all 3 workers
- trigger_monologue: downgrade &mut to shared refs (no-op anchor
was blocking parallel systems)
- Remove dead SimRng inserts from migrated monologue tests
- Document determinism gap on poll_worker_results
- Document bevy_tasks/rayon dep rationale in Cargo.toml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generic BackgroundWorkerPool<Req, Resp> with crossbeam channels, closure
handlers, and 3 delivery strategies (Fallback, GracefulDegrade, ModalLock).
Stub workers registered as Bevy resources:
- ChunkGenWorker (2 threads) — terrain/props/navmesh generation
- NpcPrepWorker (1 thread) — pre-compute NPC state for incoming areas
- OffscreenTickWorker (1 thread) — advance NPCs outside active tier
Tick loop integration:
- PreInput: poll_worker_results drains completed work
- PostSnapshot: push_worker_requests queues new work (no-op until Phase 5)
Handlers are stubs — real computation plugs in when the phases that need
them arrive. The infrastructure (channels, threads, push/poll, shutdown)
is real and tested.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enable Bevy multi-threaded executor via bevy_tasks multi_threaded
feature. Systems within the same TickPhase that don't share mutable
resources now run in parallel automatically.
Add EntityRng component — per-entity ChaCha20Rng seeded from
world_seed + StableId via splitmix64 mixing. More deterministic than
shared SimRng (order-independent). Migrate all monologue systems
(4 of 13 SimRng consumers) to EntityRng, removing contention that
serialized them against conversation/dialogue systems.
Add rayon dependency (infrastructure only, no par_iter calls yet).
SimRng retained for world-level randomness: conversation pairing,
knowledge transfer, dialogue, ticker, storyteller.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
10-phase linear pipeline: PreInput → Input → Movement → Simulation →
Economy → Storyteller → Snapshot → PostSnapshot → Knowledge → TickAdvance.
Each system assigned to exactly one phase via .in_set(TickPhase::X).
Cross-phase .after()/.before() eliminated — only intra-phase ordering
remains. Prevents schedule cycles by construction.
SimulationPlugin refactored into sub-plugins by domain:
- InputPlugin (player actions, interactions, dialogue dispatch)
- MovementPlugin (pathfinding, movement validation, spatial indexing)
- SocialPlugin (conversations, sound, voice enrichment, follow state)
- EconomyPlugin (tâtonnement tick, IPC query serving)
- TimePlugin (chunk streaming, news ticker, tick advancement)
All other plugins (NPC, Knowledge, Perception, Storyteller, Settings,
Bridge) updated to use TickPhase assignments instead of cross-plugin
ordering constraints. BridgePlugin trimmed to bridge I/O concerns only.
Part A of #843. Parts B (multi-threaded executor) and C (background
workers) follow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tick_economy_simulation was ordered .after(advance_tick) which created a
cycle: observer_snapshot → send_snapshot → advance_tick → tick_economy →
observer_snapshot. Moved to .after(process_player_input) instead — the
economy checks time.tick which works regardless of advance order.
Also removed the .after(tick_economy_simulation) from handle_debug_commands
that was added during Sprint 34 review — same cycle root cause.
This is a symptom of #843 (ad-hoc ordering is fragile). Pair session
scheduled to replace with system set phases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Widen EventPort tick methods from u32 to u64 (prevents overflow)
- Add is_identity() guard on hot-path String allocation in modifiers
- Replace Vec::remove(0) with VecDeque::pop_front() in price history
- Add .after(tick_economy_simulation) ordering for debug commands
- Fix stale PROTOCOL_VERSION assertion (20 → 21) in serialization test
- Add D-181 Phase 2 visibility scope comment on serve_econ_state_query
- Eliminate double lookup in rebuild_signals via single-pass extraction
- Track economy seed TODO with backlog ticket reference
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expand overheard.ron from 5 zone types (16 conversations) to full
coverage of all 31 zone types with 94 new conversations. Each zone
type has 2-4 role-pair conversations following D-078 occlusion-
resilient authoring rules. Conversations carry investigative
knowledge payloads where appropriate — institutional cover-ups,
manifest discrepancies, suppressed inspections, and cultural signals
players can follow.
Zone types added: administrative_civil, administrative_judicial,
archaeological_site, commercial_market, commercial_transit,
detention_facility, diplomatic_elite, entertainment_venue,
extraction_platform, extraction_space, extraction_surface,
industrial_manufacturing, industrial_processing, medical_facility,
military_garrison, port_fishing, port_maritime, port_space,
port_surface, research_station, residential_surface, rural_aquaculture,
rural_orbital, rural_pastoral, security_checkpoint, wilderness_frontier.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- HashMap → BTreeMap throughout econ-sim for deterministic iteration (D-010)
- Fix cost_factor: multiplicative gate×zone instead of additive (trade.rs)
- Extract derive_seed to shared prng.rs, consolidate FNV-1a implementation
- Rename run_shock_test → run_no_explosion_check (not D-179 Test 3)
- Deduplicate cross-zone FX rate collection in Test 4
- Replace ORDER BY RANDOM() with deterministic ordering + ChaCha8Rng
- Make commodity coverage failure a hard error consistent with D-175
- Fix gap-fill off-by-one (4 corps → 3 when coverage = 0)
- Correct test report: EconEvent exists, location_type is body/station
All four D-179 stability tests still pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add D-180 EconEvent struct (target, effect, duration, visibility enums)
with no-op handler to satisfy #809 spec. Import MARK_PRIMARY and MIXED
currency zone assignments from wiki/economics/currency_zones.toml (D-172).
All four D-179 stability tests now pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds server/src/bin/generate_corporations and tooling/generate-corporations
wrapper. Generates ~5,000 Tier-3 corp instances from Tier-1/2 template
archetypes with seeded name generation (FNV-1a + corridor-weighted PRNG).
Writes wiki markdown stubs for each generated corporation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends import_economics.py from 6-step to 8-step pipeline:
- Loads wiki corporation markdown frontmatter as authoritative source
- Syncs corporations table (hard error on proper_name divergence per D-182)
- Populates corp_presence table (one row per corp × headquarters system)
- Splits validation: structural checks block commit; coverage checks post-commit
- D-175 Phase 2 gate: 3+ corps per commodity, 1+ corp per system with pop > 100K
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends systems.db schema with gate_links, commodities, production_chains,
chain_inputs, corp_presence, and currency_zones tables required for the
Phase 2 economics layer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 5 new tables: gate_links (668 bidirectional edges from star-map.json),
commodities (36 types from commodities.toml), production_chains (21
Leontief recipes), chain_inputs (52 input requirements), corp_presence
(empty, populated by future pipeline). Add currency_zone column to
star_systems (D-172), archetype columns to corporations (D-175).
New import pipeline: tooling/economy-db/import_economics.py reads
TOML/JSON source files and populates the DB. Idempotent — safe to
rerun via `make economy-db`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
"Biome" describes per-zone vegetation classification (Whittaker table).
"Planet class" describes overall planetary character. The conflation
caused the planet generator to misclassify ~270 bodies as barren.
Scope: systems.db column, schema SQL, Rust atlas code, wiki table
headers (Biome → Class), atlas proposal JSONs, all docs/decisions,
tooling scripts. Also normalizes atmosphere vocabulary (breathable →
standard) and expands planet class mapping to all 26 wiki values.
Unknown classes default to temperate for modder safety.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
20 changes across 18 systems implementing the approved remediation plan
from the cultural diversity audit (docs/design/cultural-diversity-audit.md).
DB changes (15 bodies/stations in systems.db):
- Thai bodies: Saikaew (GJ 635B), Nakhon Sung (GJ 667C), Doi Kaeo (GJ 117)
- Khmer bodies: Banteay (GJ 434), Srok Thmei (GJ 866A)
- Ukrainian: Chornozem body + Svoboda station (GJ 729)
- Caribbean: Lavenille body (GJ 873), Accompong Works station (GJ 406)
- Malay: Pulau Baru body (GJ 555), Balai station (GJ 449)
- Scientists: Huygens Platform (GJ 251), Ramanujan Station (GJ 699),
van Leeuwenhoek Station (GJ 820B), Turing Survey (GJ 663A)
Wiki calibration note additions:
- GJ 635B/667C/117/434: Thai cultural presence (survey teams, founding cohorts)
- GJ 866A/873: Khmer/Caribbean community naming origin
- GJ 729: Ukrainian founding cooperative in west_reach
- GJ 1245A/75: Igbo/Nigerian heritage explicitly acknowledged
- GJ 172: Malay kelong dual-heritage in Kaelong name
- GJ 601A: Russian secondary founding stream in Ostmark
Horizon stations not renamed. No corridor reassignments.
Conservative: systems with existing identity untouched.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove erroneous server/CHANGELOG.md and add the atlas split entry
to the project root CHANGELOG.md where it belongs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The refactor from &Commands to individual parameters exposed the
13-argument signature to clippy. Allow attribute is appropriate
here — the parameters map 1:1 to DB columns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract 2119-line monolithic atlas.rs into 8 focused modules under
src/bin/atlas/: main.rs (thin dispatch), common.rs (shared types and
DB helpers), show.rs, mutate.rs, stats.rs, systems.rs, author.rs,
and sync_wiki.rs. Commands enum stays in main.rs; each match arm
delegates to module::cmd_fn(&conn, args). No behavior change.
Closes#776.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review fixes:
- corridor-status: INNER JOIN → LEFT JOIN so systems without gate
records are included in counts instead of silently excluded
- corridor-status: output as JSON (serde_json) matching all other
atlas commands, instead of plain-text ASCII table
- generate_body_matrix: emit atmosphere "standard" instead of
"breathable" to match committed-system conventions
- Doc header: add corridor-status usage example
- CHANGELOG: note stale habitable_planet_count in pre-fix systems
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#762: Update habitable_planet_count filter to accept both "breathable"
and "standard" atmosphere values via matches! macro. Previously only
matched "breathable", causing all committed systems to report 0
habitable planets.
#744: Add corridor-status subcommand to atlas CLI. Shows remaining
unfinished systems grouped by geographic_sector and hop_distance,
with plain text table output and totals.
Cargo.lock updated for v0.1.29.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. GJ 111: spectral_class F5V → F5/F6V to match wiki (subgiant)
2. GJ 3943: spectral_class k-m → K5V+M3V (valid MK notation for binary)
3. GJ 903: write wiki prose — was empty stub with only body tables
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unsettled systems receive their GJ designation as proper_name, except
GJ 868 which is named "Encontro" per wiki lore (informal Portuguese
corridor name). 24 proposals updated, systems.db fully synced.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
commit-system was inserting bodies/stations but never writing proper_name
to star_systems. Bulk-updated all 276 authored system names. Fixed duplicate
body_id collisions in GJ 1156, GJ 139, GJ 34A, GJ 34B, GJ 661A, GJ 664,
GJ 780 (planet and gas giant sharing same letter suffix).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>