Commit Graph
1933 Commits
Author SHA1 Message Date
jpmschweitzer 1efbbcaea9 feat(tooling): gemma_naming.py batch naming pipeline for atlas (#833)
New end-to-end pipeline that walks every markers.json in the reach and
fills empty `name` fields using the Gemma 2 voice pipeline via
`sr-voice serve --stdio`. Per D-191 §4: the same Gemma 2 pipeline the
client uses for NPC voicing also produces the atlas content, which is
dual-purposed as a quality test of the LLM plumbing.

Pipeline per body (hop-ordered, core-first):
  1. Load markers.json; identify feature records whose `name` is
     blank (null or ""). Hand-authored names are never overwritten;
     the 6 template bodies and any partial authoring stay put.
  2. Look up body context (planet_class, settlement_pattern,
     cultural_corridor, population, economic_role) from systems.db.
  3. Build a short corridor-aware few-shot prompt per feature type.
     Prompts carry 3 concrete `Style: X.   Answer: Y` examples so
     Gemma 2 2B completes a pattern instead of generating to an
     open-ended instruction — this is the single biggest lever
     against placeholder echoes on a small model.
  4. Stream the prompt into a long-lived sr-voice subprocess, read
     the JSONL response, post-process (strip markdown, label
     prefixes, brackets, reject 5+ word outputs and placeholder
     tokens), check the earth-name blocklist, check per-(corridor,
     feature_type) + per-body dedup, check the per-stem cap, retry
     up to 3 times with a bumped seed.
  5. On persistent failure, fall back to a deterministic palette
     generator so every feature ends up with a name.
  6. Write markers.json atomically and refresh atlas_* DB rows via
     sync_markers_to_db. Commit the DB per body so a crash loses
     at most one body of state.
  7. Restart the sr-voice subprocess every `--refresh` requests
     (default: 200) to prevent KV-cache context bleed.

Core design decisions:
- Determinism: per-(world_seed, body_id, feature_local_id, attempt)
  seed so the full run is reproducible.
- Ordering: bodies are processed in ascending `hop_distance_from_gateway`
  so core bodies get first pick at every unique Gemma output and
  outer sectors fall into the palette fallback when they lose the
  dedup race.
- Dedup scope: (cultural_corridor, feature_type) across the run,
  PLUS a per-body cross-type set so the same name can't be a river
  AND an ocean AND a mountain on the same world. Hand-authored names
  are seeded into both sets on load so templates win priority.
- Stem cap: each non-generic root token (e.g. 'Arcturus', 'Meridian')
  may appear at most `--stem-cap` times across the full run (default
  20), preventing single-word runaway. Fallback names bypass the cap.
- Earth blocklist: 181 curated entries covering major Earth cities,
  mountains, rivers, oceans, historical/colonial spellings, and
  Greek/Roman mythology that reads too literally. Prefixed variants
  ('Nouveau Paris', 'New Tokyo') explicitly allowed per the product
  intent that Earth-echo names are fine but must not dominate.
  Leading 'The ' is stripped before comparison so 'The Great Divide'
  also matches.

Operational features:
- `--shard N/M` slices the body list into M partitions for parallel
  runs. Two terminals × `--shard 0/2` + `--shard 1/2` fits the
  ~2.5 GB/instance VRAM footprint twice under the 50% cap on a
  16 GB AMD GPU and roughly halves wall time.
- `--log PATH` writes a timestamped tee of every status line to a
  file. Default: `.tmp/gemma_naming.shard{N}of{M}.log` when a
  non-trivial shard is in use.
- SQLite `PRAGMA journal_mode=WAL` + `busy_timeout=15000` so two
  concurrent shards serialize writes without lock errors.
- Per-body progress lines report `body K/N`, `sys K/N`, and
  `hop=H` so the user can watch core sectors finish first.
- Each body logs the new names it produced per feature type so the
  user can eyeball quality as the run progresses.
- Checkpoint summary every 25 bodies: cumulative names, rate,
  ETA — gives the log regular scroll points.
- `--mock` uses `server/sr-voice/mock-stdio.sh` for dry-fire
  pipeline validation without a model load (tested end-to-end).

Supporting files:
- `tooling/planet-gen/earth_blocklist.txt` — 181 curated entries.
- `tooling/db/backfill_cultural_corridor.py` — one-off migration
  that fills the `cultural_corridor` column on both `star_systems`
  and `bodies` from the `geographic_sector` values. Before this
  pass, 99.4% of rows (3221/3240) had a NULL cultural_corridor
  despite `wiki_sync.py` being aware of the column — the wiki
  index.md files only carry the sector header, which was never
  propagated to the DB column. Idempotent, safe to re-run after
  any wiki_sync rebuild, explicit transaction wrapper with
  rollback on failure.

Full batch runtime estimate: ~20 hours single-shard / ~10 hours
double-shard on this hardware. Smoke tests across five hardened
iterations (v1–v5) on GJ71b/c/d/d-1/e confirm the pipeline produces
clean, varied, culturally-coherent names with zero post-processing
residue.
2026-04-15 10:54:28 +02:00
jpmschweitzer 8d8fa9060f Merge remote-tracking branch 'origin/sprint-35/server' 2026-04-15 09:36:04 +02:00
jpmschweitzer d21c690293 fix(tooling): address PR #129 review — atlas generator, schema, Makefile
- ON DELETE CASCADE added to every atlas_* foreign key (atlas_body_grids,
  atlas_cities, atlas_roads, atlas_railroads, atlas_pois, atlas_rivers,
  atlas_oceans, atlas_mountain_ranges). Previously, deleting a body from
  the bodies table or NULL-ing its terrain_reference would leave orphan
  atlas rows forever — sync_markers_to_db only cleans up for bodies it
  re-processes. The existing atlas tables in systems.db were dropped and
  recreated with the new constraint; FK list now reports CASCADE.

- Atlas DDL deduplicated. systems-schema.sql is now the single source of
  truth, bracketed by `-- BEGIN ATLAS INDEX` / `-- END ATLAS INDEX`
  markers. generate_atlas.py reads that block via `_load_atlas_schema()`
  and applies it at runtime, so there is no second copy of the DDL to
  keep in sync. Adding a column requires one edit, not two.

- Uniqueness guard on city coordinates. `_enforce_unique_city_coords`
  runs at the end of `place_cities` and deterministically perturbs any
  duplicate (row, col) via a fixed spiral walk to the first free
  walkable land cell. Rare in practice but the MST collapses to a
  zero-distance edge otherwise, producing an empty A* path and silently
  dropping the road.

- Grid header validation. `load_markers` now raises `AtlasGridMismatch`
  if the loaded `grid: {w, h}` header does not match `GRID_W`/`GRID_H`.
  Both the incremental-skip path and the regenerate path route through
  this loader, so a hand-authored template shipping a different grid
  size fails loud with a per-body error rather than silently producing
  half-scale coordinates.

- Unused `seed_rng` parameter removed from `_analyse_terrain`. The
  function is RNG-free (continent flood-fill, habitability scoring,
  river-mouth dedup, cost grid — all pure functions of terrain). The
  false API contract made it look like terrain analysis consumed RNG
  state and had to be sequenced with downstream RNG use.

- `_score_capital_sites` river-mouth bonus now builds one sparse
  accumulator with all mouth points set at once and runs a single
  `gaussian_filter` call, instead of O(n_mouths) filter calls over
  single-point images.

- `binary_dilation(analysis["land_mask"] == False)` replaced with the
  idiomatic `~analysis["land_mask"]`, matching the convention used
  elsewhere in the file.

- `atlas-generate` Makefile target now guards on
  `SELECT COUNT(*) FROM bodies WHERE terrain_reference IS NOT NULL`.
  On a fresh DB that count is 0 and the generator previously exited
  "success" after processing zero bodies. The target now fails loud
  with a pointer to `populate_terrain_reference.py`.

- `main.rs` SimRng defensive re-insertion gains a long comment
  explaining the exact plugin-ordering hazard it guards against, so
  future readers don't treat the line as dead code. Tied to #826.
2026-04-15 09:25:46 +02:00
jpmschweitzer 57e52d7586 Merge remote-tracking branch 'origin/sprint-35/client' 2026-04-15 09:16:44 +02:00
jpmschweitzer 64bb83f749 fix(simulation): address PR #129 review — brand importer hardening
- V-B06 enum validation: the five VALID_* sets
  (VALID_BRAND_CATEGORIES, VALID_VALUE_TRAJECTORIES, VALID_SCARCITY_CLASSES,
  VALID_BRAND_TIERS, VALID_CURRENCY_DENOMINATIONS) were defined but never
  referenced. brand_products.brand_category etc. are plain TEXT with no
  CHECK constraints, so a typo like `brand_category = "terrior"` silently
  imported. `validate_brands` now runs a V-B06 pass that asserts every
  enum column is a member of its VALID_* set. V-B01..V-B05 + V-B06 all
  reported together on import failure.

- Explicit transaction wrapper: the clear-then-reimport cycle (10 DELETEs
  followed by 9 imports and structural validation) used to depend on
  Python's implicit-deferred-transaction semantics and sys.exit() on
  validation failure. A crash mid-import could leave the DB with some
  tables empty and others intact. The body now runs inside
  `conn.execute("BEGIN")` + try/except with an explicit `_ImportAborted`
  for validation failures and a `BaseException` catch-all for
  KeyboardInterrupt / programmer errors. All failure paths rollback
  before exit; the commit only fires after structural validation
  passes. Dry-run leaves the transaction open so the coverage check
  below can still SELECT against in-memory state.

- system_fiscal docstring: previously cited the D-189 §6 derived
  formula (`collection_efficiency = 1.0 - shadow_economy_intensity × 0.6`)
  while the implementation hardcodes `collection_efficiency = 0.85` for
  every system. The docstring now explicitly states these are Phase 2
  placeholder values (with named constants PHASE2_CORP_TAX_RATE and
  PHASE2_COLLECTION_EFFICIENCY) and calls out the shadow_economy.toml
  pipeline as the Phase 3 follow-up.
2026-04-15 09:15:16 +02:00
jpmschweitzer 56d524f37d docs(decisions): address PR #129 review — D-191 §8 amendment + boundary notes
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.
2026-04-15 09:12:08 +02:00
jpmschweitzer 22c17f1f15 style(ui): gdformat atlas implant files 2026-04-15 09:06:04 +02:00
jpmschweitzer 489aec48d8 style(ui): atlas review polish — guarded ids, stable keys, dead-code cleanup (#128)
10. Dropped the "no body data" fallback string in the picker panel — the
    generator always writes entry["bodies"], so the fallback was dead. Use
    the "—" convention the rest of the panel already follows.
12. Removed the autoload parse-order comment from AtlasPanel (it is
    scene-instanced, not an autoload, so the rule does not apply), and
    collapsed _build_heightmap_viewer to a direct AtlasViewer.new() —
    mirroring the rest of the file rather than dancing around a risk that
    is not real for this class.
13. AtlasMarkerOverlay._draw_cities now compares hover/selection by a
    stable _city_key() (name → city_id → pos → hash) instead of
    Dictionary.==, which was O(fields) per city per redraw. Preps the
    renderer for much larger city counts without a rewrite.
14. Orbital click/draw handlers guard against missing body_id /
    station_id by reading through str(dict.get(..., "")) and skipping
    empty ids. Matches the defensive style already used for parent_body_id
    and keeps a NULL id from crashing _draw_bodies / _handle_orbital_click.
2026-04-15 09:02:27 +02:00
jpmschweitzer f8b81e1fe7 refactor(ui): atlas review warnings — headers, public API, KEY_N, overlay source of truth (#128)
4. AtlasPanel and AtlasViewer now compose their title + hint from an
   ImplantHeader child rather than hand-rolling them via draw_string, so the
   D-169 "theme swap changes the implant hardware appearance" invariant
   holds end-to-end. _refresh_screen_header() drives content per level and
   on system navigation.

5. AtlasViewer exposes city_canvas_pos(), get_hovered_city(),
   get_selected_city(), and get_overlay_defs() as public API — the marker
   overlay no longer reaches into underscore-prefixed state, which is
   especially important because viewer is an untyped var in the overlay.

6. KEY_N now consumes unconditionally while the viewer is visible, and
   main.gd's global economics-monitor toggle is gated on
   !HudGroups.is_app_active("implant/map/atlas"). Previously pressing N
   without a selected city fell through and closed the fullscreen atlas as
   a side effect.

7. OVERLAY_DEFS lives in AtlasViewer as the single source of truth.
   AtlasOverlayBar reads the list via viewer.get_overlay_defs(), and
   AtlasViewer derives _overlay_visibility / _overlay_locked from the same
   table at _ready() — no more hand-maintained parallel lists, so the bar
   and the guard in set_overlay_visible can't drift.

8. AtlasOverlayBar drops `class_name`: it now loads via
   load("res://ui/implant/atlas_overlay_bar.gd") from AtlasViewer, the same
   pattern AtlasPanel uses for AtlasViewer. _init(viewer_ref = null) keeps
   the required-arg footgun off the editor's introspection path.

9. `star-map-data` make target added to regenerate
   client/data/star_map_data.json from systems.db + wiki, and
   `check-star-map` wired into pre-pr-validate + pre-pr-client so any
   commit that touches the generator (or any downstream systems.db change
   like server #839) fails pre-pr until the JSON is regenerated. The
   terrain_reference data-availability dependency is no longer tribal
   knowledge.

Also addresses review #15 (push_warning on unknown overlay id in
set_overlay_visible) and #16 (disabled always-on buttons drop handler
churn) as part of the same refactor.
2026-04-15 08:59:21 +02:00
jpmschweitzer 1328db3138 fix(ui): atlas review blockers — moon layout, stale grid dims, overlay schema (#128)
1. Moon placement on the orbital diagram divided by a hard-coded 4 — a gas
   giant with five or more moons would overlap satellites and make them
   unclickable. Count moons per parent and distribute them evenly.
2. AtlasViewer._load_markers seeded _grid_w/_grid_h from _tex_w/_tex_h
   before the terrain_reference null check, so opening a body with no
   heightmap after one that had a heightmap left the grid dimensions
   pointing at the previous texture and misplaced markers. Reset texture +
   grid dims to sentinel defaults at the top of _load_heightmap.
3. AtlasMarkerOverlay's three toggleable overlays (production_zones,
   shadow_economy, corp_presence) read markers.production_zones /
   markers.shadow_zones / markers.corp_presence — keys that don't exist in
   D-191 §8's markers schema, so toggling was a silent no-op. Derive them
   from cities[] instead: primary_function ∈ PRODUCTION_FUNCTIONS for
   production, absent Commission presence for shadow bands, Commission
   presence for corp dots. Follow-up ticket will formalise per-overlay
   arrays once the server schema lands.

Addresses PR #128 review blockers 1-3.
2026-04-15 08:47:14 +02:00
jpmschweitzer 5619218e31 feat(tooling): generate_atlas.py city + infrastructure pipeline and atlas DB index (#832)
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
2026-04-15 08:46:55 +02:00
jpmschweitzer 9afd6b53d2 style(ui): wrap atlas long lines to satisfy gdlint 2026-04-15 08:31:40 +02:00
jpmschweitzer 9987eaad8a feat(ui): atlas overlay toggle bar — 9 MVP overlays + 2 locked (#836)
AtlasOverlayBar is an HBoxContainer docked top-right of the heightmap viewer
with 11 short-label buttons mapping to D-191 §7 / D-181 signal visibility:

  always-on (5)  TER INF NAM GAT POL — terrain, infrastructure, named
                 features, gate markers, political zones. Pinned on; clicks
                 are swallowed so the layers can't accidentally be disabled.
  toggleable (4) POP PRD SHD CRP — population density, production zones,
                 shadow economy, corporate presence. Reflect and mutate
                 viewer overlay state.
  locked (2)    STK BSL — stockpile_weeks, production_vs_baseline. Disabled
                 and greyed out with unlock-requirement tooltips, per D-181
                 semi-private/private tiers; kept in the bar so players see
                 that deeper data exists and is gated.

Each button writes through AtlasViewer.set_overlay_visible(), which is the
single entry point into the viewer's _overlay_visibility dict consumed by
AtlasMarkerOverlay._draw(). Locked overlays short-circuit in that setter.

Per D-191 criterion 6.
2026-04-15 08:27:30 +02:00
jpmschweitzer c33ac8921f feat(ui): atlas heightmap viewer — pan/zoom, marker overlay, city data panel (#835)
Adds Level.HEIGHTMAP_VIEWER to AtlasPanel. The viewer loads a body's
terrain_reference heightmap PNG, pairs it with markers.json (roads, rail, POIs,
cities, rivers/oceans/mountains), and renders markers in texture-space via an
AtlasMarkerOverlay Node2D child of a transformed canvas — pan = offset,
zoom = scale.

Pan/zoom is cursor-centred (wheel zooms under the mouse, drag pans), with a
fit-to-view reset on R. Empty markers.json state renders a bare heightmap;
missing terrain_reference shows a themed "terrain data pending (#839)" notice
instead of crashing.

City data sidebar rebuilds from the selected city: name, pop tier, function,
currency zone, Commission presence, shadow zone, gate distance. Pressing N on
a selected city emits economics_link_requested(system_id) — main.gd bridges
this to EconomicsPanel.select_system() + HudGroups.open_app("implant/economics")
as an insert overlay, satisfying the D-191 Phase 2/3 cross-panel integration.

The overlay renders all nine D-191 overlay layers off of per-overlay visibility
flags in AtlasViewer. Overlay toggling for the regional view (#836) plugs into
set_overlay_visible(); the five always-on layers (terrain, infrastructure,
named features, gate markers, political zones) draw by default, the four
toggleable layers draw from placeholder data, and the two locked layers
(stockpile_weeks, production_vs_baseline) remain off until unlocked.

Per D-191 criteria 1, 4, 5.
2026-04-15 08:22:01 +02:00
jpmschweitzer d6d3b51098 docs(decisions): D-191 §8 canonical markers.json format is pixel space
The generator and the hand-authored templates (Edict, Vuurkloof, Røros,
Cairnside, Estrade) already store markers in heightmap pixel space with
a grid header. Update §8 to match: {x, y} integer pixels are the storage
format, and lat/lon strings become a display-time derivation in the
atlas UI (synthesized from position + grid dimensions + body radius).

Avoids double-conversion through an equirectangular projection and keeps
the hand-authored markers.json files as-is.
2026-04-14 17:30:37 +02:00
jpmschweitzer b127f63dc2 feat(tooling): populate terrain_reference column for inhabited bodies (#839)
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.
2026-04-14 17:25:56 +02:00
jpmschweitzer 03e0d1c022 feat(simulation): brand layer schema and import pipeline (#827)
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.
2026-04-14 17:24:51 +02:00
jpmschweitzer 760953aeba feat(ui): atlas implant panel — system picker, orbital diagram, body nav (#834)
3-level atlas navigation as an implant/map/atlas FULLSCREEN app (D-170 z=20):
system picker (◄ ► cycle, Enter opens orbital) → orbital diagram (star centre,
bodies grouped by orbit_index with moons sub-orbiting parents, stations as
markers, click-to-open body or mini station panel) → body entry (data sheet,
Enter stub for #835 heightmap viewer, Esc back). Composed from the ImplantPanel
component library (D-169).

Wires the panel into hud.tscn, adds an A-key toggle in main.gd, propagates
insert_state via snapshot_consumers. Extends generate-star-map-data.py to emit
per-system orbit_bodies + stations arrays (with currency_zone, atmosphere,
population, terrain_reference, etc.) from systems.db; star_map_data.json
regenerated deterministically.

Per D-191 §6, Phase 3.
2026-04-14 17:24:50 +02:00
jpmschweitzer 826b6fe1c7 Merge remote-tracking branch 'origin/main' into sprint-35/server 2026-04-14 17:22:09 +02:00
jpmschweitzer 920ea0582f feat(simulation): thread world seed from StartupMessage into economy (#826)
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.
2026-04-14 17:21:53 +02:00
jpmschweitzerandClaude Opus 4.6 9439a20253 content(wiki): PR #127 review fix pass — 15 findings
Applies all review findings from Hoshe + Paula + Miri on PR #127.

Glossary (wiki/glossary.md):
- Remove self-referential "tâtonnement" from its own NOT list.
- Add NOT/NOTE annotation-prefix preamble.
- Replace ambient "open question" note on Commission with explicit
  Q-095 citation; add "Lattice Commission" to NOT list.

Decisions:
- Claim Q-095 (content) — "Commission formal name — authoritative
  designation"; register in decisions/questions.md index.

Marker POI renames (glossary.md line 25 — "Syndicate" is prohibited;
glossary.md lines 14–16 — "Lattice Commission" is undocumented):
- GJ280Ad: "Syndicate Editorial Complex" → "Parallax Media Centre";
  "Commission Content Review Office" → "Parallax Standards Office".
- GJ66Bc: "Syndicate Trade Office" → "Compact Trade Representative";
  "Voss Gate Terminal" → "Røros Gate Terminal".
- GJ244Ad: "Lattice Commission — Sirius Office" → "Commission — Sirius Office".
- GJ35c: "Prime Surface Depot — Gate Terminal" → "Terras Gate Terminal".
- GJ892d (Cairnside): "Cairnside Primary" → "Okafor Base" (frontier
  founder-surname pattern per glossary.md line 74); "Primary Access
  Road" → "Cairn Scarp Track" (derived from Cairn Reach Scarp).

Brand templates (wiki/economics/archetypes/brand_templates.toml):
- Deep-harvest seafood archetype: Korean/Tagalog → Korean/Japanese
  across naming_pattern and description fields, per D-189 §11.
- 11 terroir + heritage_craft _reach_wide variants now show BOTH
  corridor-origin and neutral example names, per cross-cultural
  mixing rule; inner_corridor/neutral monoculture removed.
- commodity_inputs header comment: add explicit "per tick" unit
  cross-reference to D-189 §5 and server/src/simulation/economy.rs.

Tier-1 corporations (wiki/economics/corporations/tier1.toml):
- Vins de Grand Vide: currency_preference "mixed" → "mark" — HQ
  booking currency must mirror the HQ-system canonical zone
  (GJ 395 Confluent → MARK_PRIMARY); "mixed" is reserved for hop 5–6
  transition systems.
- Bífröst Marmor: add NOTE surfacing the orbital-age formation
  anomaly (wiki/corporations/bifrost-marmor.md:70–71), alongside
  the existing reserve-silence comment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:07:20 +02:00
jpmschweitzerandClaude Sonnet 4.6 c26d74f45d content(atlas): hand-author markers for Estrade (GJ 280A) — Parallax media capital
Estrade (GJ280Ad) is the Reach's information/media hub: 1.8B population,
inner_corridor, service-mixed economy. Names 10 rivers, 2 water bodies,
3 mountain ranges; places 4 cities (Strata 1.1B, Vantage 450M, Ledger 150M,
Margin 100M); adds gate terminal + 3 institutional POIs, 2 roads, 1 railroad.

Complements #837 atlas template set (Lendel, Edict, Røros, Vuurkloof, Cairnside)
with a high-population urban service world example.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 19:50:15 +02:00
jpmschweitzerandClaude Opus 4.6 2d3361e24d content(atlas): hand-author markers.json for Edict, Vuurkloof, Røros, Cairnside (#837)
Author Phase 3 atlas quality-bar templates for 4 core systems alongside
the pre-existing Lendel template. Names, cities, roads/rail, and POIs
hand-placed against each body's heightmap and tied to the system's
corridor naming tradition.

- GJ244Ad Edict (Sirius system, inner_corridor) — Assembly institutional
  capital: Mandate 387M, Station Edict 13M, Founder's Range, Accord
  Peaks, Founding Ocean, Edict Deep Line rail.
- GJ35c Vuurkloof (Van Maanen's Star, south_reach) — volcanic geothermal
  settlement: Terras 290K, Kloofbas 50K, Groot Breuk, Ysterkop, Rantlyne
  underground rail. Afrikaans geology naming per wiki canon.
- GJ66Bc Røros (Voss system, west_reach) — Compact mining world:
  Storbjerg 3.2M, Hammervik 800K, Nordfjell, Rørosfjell, Storhav,
  Glåmelva. Norwegian/Scandinavian heritage naming.
- GJ892d Cairnside (deep frontier, research domes) — Cairnside Primary
  75M, Survey Post Kappa 5M, The Terraces, Baseline Lake, Baseline Rail.
  Technical frontier naming pattern.

All files match the implemented Lendel markers.json format (grid + pixel
coordinates, raw population, gate terminal as named POI). D-191 section 8
describes the schema in abstract lat/lon terms; server team should align
generate_atlas.py and D-191 prose with the on-disk format before #832
and #833 consume these as few-shot examples.

These templates serve as the quality bar for generator tuning and as
few-shot examples for the Gemma 2 naming pipeline (#833).

Ticket: #837
Decisions: D-191 (Phase 3 atlas), D-036 (Sova/Vuurkloof canon), D-144
(Sirius/Concord seat)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 19:47:21 +02:00
jpmschweitzerandClaude Sonnet 4.6 92ad6b9515 feat(wiki): Sprint 35 copy deliverables — glossary, brand corps, brand templates
- wiki/glossary.md: canonical proper nouns, factions, currencies, corridors,
  key locations, economics terminology, drift forms (tasks #840)
- wiki/economics/corporations/tier1.toml: add Calloway Distillery, Vins de Grand
  Vide, thrds, Bífröst Marmor brand corp records (#830)
- wiki/economics/archetypes/brand_templates.toml: 126 templates (42 archetypes ×
  3 scale tiers) covering all 8 brand categories and all 6 corridors (#831)
- wiki/index.md: add glossary link under Reference section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 19:43:49 +02:00
jpmschweitzerandClaude Sonnet 4.6 521c682175 chore(meta): plan Sprint 35: Atlas
Phase 3 launch sprint. 13 tickets across server, client, copy.
Atlas generation pipeline (terrain_reference, generate_atlas.py, Gemma naming),
brand layer DB schema, Atlas implant panel (3 levels + heightmap viewer + overlays),
hand-authored templates for Lendel + 4 core systems, brand corp TOML,
brand_templates.toml (120-130 archetypes), wiki glossary, world seed wiring.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:01:42 +02:00
jpmschweitzerandClaude Opus 4.6 26a6fd233c style(simulation): cargo fmt monologue trigger query
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:23:44 +02:00
jpmschweitzerandClaude Opus 4.6 5f678dac48 fix(simulation): address #843 review — seeding, atomics, API consistency
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>
2026-04-11 00:22:44 +02:00
jpmschweitzerandClaude Opus 4.6 0ae3542550 style(simulation): cargo fmt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:15:56 +02:00
jpmschweitzerandClaude Opus 4.6 0098ee5a41 feat(simulation): background worker pool infrastructure (#843 Part C)
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>
2026-04-11 00:15:05 +02:00
jpmschweitzerandClaude Opus 4.6 2175e9b31c feat(simulation): multi-threaded executor + EntityRng (#843 Part B)
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>
2026-04-11 00:08:41 +02:00
jpmschweitzerandClaude Opus 4.6 309c05d441 style(simulation): cargo fmt + fix clippy doc-nested-refdefs warning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:57:48 +02:00
jpmschweitzerandClaude Opus 4.6 cf21904a0f fix(ui): show system cursor during fullscreen implant apps
CursorRenderer now toggles Input.MOUSE_MODE_VISIBLE when gameplay is
occluded, restores MOUSE_MODE_HIDDEN when gameplay resumes. Without
this, fullscreen apps (star map, future atlas) had no cursor at all —
the custom diegetic cursor hid correctly but the system cursor was
never restored.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:56:56 +02:00
jpmschweitzerandClaude Opus 4.6 b5dff8c55e fix(ui): gameplay_occluded signal never fired on first fullscreen toggle
was_occluded was computed AFTER _active_mode and _active_app were
updated to the new values, so it always matched now_occluded on the
first toggle (both TRUE). The signal condition (was != now) never
triggered. Moved the check before the state mutation.

This bug affected every GameplayRenderer (world, entities, fog,
cursor) and the stance indicator — none of them hid on first
fullscreen app open.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:53:54 +02:00
jpmschweitzerandClaude Opus 4.6 6f02b9f4f4 fix(client): eliminate runtime warnings — radial size deferred, storyteller log removed
- world_radial.gd: use set_deferred("size", ...) to avoid anchor conflict warning
- storyteller: remove noisy "no Simmering triangles" warn (normal state, not exceptional)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:51:17 +02:00
jpmschweitzerandClaude Opus 4.6 47d4918cc8 refactor(simulation): replace ad-hoc system ordering with TickPhase pipeline (#843)
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>
2026-04-10 23:51:06 +02:00
jpmschweitzerandClaude Opus 4.6 d434235985 chore(meta): add pair session work mode, prune scrapped NPC systems
- CLAUDE.md: add "Pair session" as formal work mode alongside sprint mode
- Scrap NPC ambient systems (R-012): D-078 marked superseded, content
  pattern note scrapped, overheard conversation system will be rebuilt
  from scratch after a walkable environment exists
- Agent profiles: remove NPC-drift references from Paula, Dudley, Miri;
  add cascade discipline to Miri's role

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:01:11 +02:00
jpmschweitzerandClaude Opus 4.6 9edbe40de9 fix(simulation): resolve Bevy schedule cycle — economy system ordering
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>
2026-04-10 23:00:55 +02:00
jpmschweitzerandClaude Opus 4.6 e91bd1c7e4 fix(client): post-sprint polish — protocol v21, schedule workaround, UI fixes
- Bump client protocol version 20 → 21 to match server (#822)
- Fix render_priority parameter name (was _render_priority, unused prefix)
- Fix debug console type inference (var sub := → var sub: String =)
- Economics panel: add population row, improve key hint text
- Stance indicator: hide on gameplay_occluded (D-170 fullscreen apps)
- Remove 5 broken clothing items from manifest and delete their GLBs
  (boots_work, coveralls_basic, jacket_utility, pants_cargo, shirt_henley)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:00:40 +02:00
jpmschweitzerandClaude Opus 4.6 2319c6f6ca chore(meta): release v0.1.34
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.1.34
2026-04-10 22:21:52 +02:00
jpmschweitzerandClaude Opus 4.6 e0d2ea32a5 fix(decisions): address PR #126 review — cross-refs, index, arithmetic
- Fix D-189 cross-ref: D-131 → content.md (was scope.md), fix anchor slug
- Fix D-191 cross-refs: D-093, D-095, D-138 → content.md (were architecture.md)
- Fix architecture.md footer counter: 52 → 53
- Fix D-190 ratio arithmetic: 1:100 → 1:80, 1:20-50 → 1:16-40
- Merge Destilaria Confluência/Lento into single row, update corp count ~28 → ~27
- Fix platform shorthand → platform_catalogue in D-189 §11
- Add D-189, D-190, D-191 to decisions/README.md index

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 22:06:23 +02:00
jpmschweitzerandClaude Opus 4.6 b69645d35c docs(decisions): D-189 brand layer architecture, D-190 volume calibration, D-191 Atlas Phase 3 scope
Sprint 34 planning workshop output:
- D-189: Brand layer architecture — 8 categories, administered pricing model,
  halo/volume tiers, 10K minor brands, Gemma 2 naming, corp tax/GDP, verb ladder
- D-190: Brand volume calibration — population-relative scale for ~80B Reach
- D-191: Atlas of the Reach Phase 3 — terrain-aware city placement, sequential
  settlement growth, Gemma 2 geographic naming, 9 MVP overlays, heightmap viewer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 21:04:41 +02:00
jpmschweitzerandClaude Opus 4.6 a9bd3ca408 Merge remote-tracking branch 'origin/sprint-34/client'
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:19:52 +02:00
jpmschweitzerandClaude Opus 4.6 4d4dc2014d fix(ui): rebind economics panel toggle from E to N (#124 review R2)
E is claimed by InputMap "interact" action — InputMapper consumes
it before _unhandled_key_input. N is free, adjacent to M (star map),
reads as "Numbers" for the economics monitor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 14:25:07 +02:00
jpmschweitzer 08297a9cae Merge remote-tracking branch 'origin/sprint-34/copy' 2026-04-10 14:19:09 +02:00
jpmschweitzerandClaude Opus 4.6 ac6c9dd2cd style(simulation): cargo fmt economy.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:07:14 +02:00
jpmschweitzerandClaude Opus 4.6 1498115aba fix(simulation): address PR #125 review — tick truncation, ordering, perf, protocol test
- 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>
2026-04-10 14:06:54 +02:00
jpmschweitzerandClaude Opus 4.6 28d95c23a8 fix(ui): address PR #124 review — navigation, error handling, cleanup
- Economics panel: replace dead _gui_input LEFT/RIGHT with public
  navigate() method, wire [ ] keys in main.gd (avoids movement
  key conflict, fixes focus_mode=NONE issue)
- Debug console: add explicit effect guard in econ inject no-commodity
  branch so invalid effects don't fall to commodity-form error
- Snapshot consumer: null-clear GameState.economy_snapshot after
  consuming (matches one-shot consumer invariant)
- Star map: remove duplicate doc comment above set_insert_active()
- Generate script: remove stale comment, dead _WORKTREE_PARENT var,
  dead field extraction in parse_wiki_index, add try/except around
  DB queries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 14:03:49 +02:00
jpmschweitzerandClaude Opus 4.6 f41ab6c8e8 fix(content): address PR #123 round 2 — Founder terminology, names
Fix 3 issues from round 2 review:

- ovh_arc_003: "Gatebuilder" → "Founder structure" (canonical term)
- ovh_ror_002: Subordinate → Colleague (peers, not hierarchy)
- ovh_mfg_001: "Orien" → "Krev" (too close to Earth constellation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 14:03:41 +02:00
jpmschweitzerandClaude Opus 4.6 f301fdecc7 fix(content): address PR #123 review — registers, names, patterns
Fix 15 issues from Hoshe, Paula, and Miri review:

QA: correct changelog count (78 new, not 94), fix "attach" → "attaché"
in ovh_dip_002.

Narrative: fix register mismatches (ovh_rag_002 Social→Gossip,
ovh_ctx_002 Social→Gossip, ovh_med_003 Gossip→Work), fix inverted
authority dynamic in ovh_hos_002, add progression to ovh_evn_002 third
turn, replace frontier_medic/liaison with ranger/settlement_administrator
in ovh_dis_002, break extraction/industrial template pattern by
rewriting ovh_mfg_001 (social/family) and ovh_epl_001 (social/plans).

World consistency: replace Earth-ethnic names (Kessler-Dunn→Vasara-Lenn,
Kovacs→Tanev, Harlan→Sareth, Henwick→Merata), replace unanchored
location (Old Pemmar→Old Tarassa), replace lore-colliding name
(Calloway→Vedara).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 13:37:47 +02:00
jpmschweitzerandClaude Sonnet 4.6 77232434f4 style(simulation): cargo fmt + fix clippy warnings after economics integration
- `economy.rs`: fix empty_line_after_doc_comments (section ordering),
  use `is_multiple_of` for ECON_TICK_RATE check
- `debug.rs`, `input.rs`, `mod.rs`: rustfmt import ordering + indentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 13:22:47 +02:00