T-1151: build_district_window_layer dispatches one Rayon task per row
(pure derive_window_cell via derive_at_metres), scattered row-major into
the flat arrays; a cfg(test) serial path backs the bit-identical
parallel-vs-serial golden.
T-1150: serde-default window_granularity (1=district, 4=quarter) +
window_min_wl_m on AtlasLayerRequest — additive, no sixth demux shape,
old frames decode unchanged (tested). Quarter mode = full
reclassification at 512m spacing over the same world rect ((4n)x(4n)
cells); WIRE_CAP_CELLS=4096 enforces n*granularity <= cap (quarter
clamps n to 16, the design doc's worked example). Granularity + min_wl
key ALL five touch points: DistrictWindowLayer echo, server FIFO-256
cache key (now a 5-tuple), per-connection coalescing key, client
request codec (omitted-at-default wire fields), client LRU key.
Mandatory aliasing regressions on both ends: identical (body, center, n)
at granularity 1 vs 4 produce distinct cache entries and correct
per-granularity payload shapes (server, 3-thread queue to avoid the
AnalyzeBody thread contention found while writing it) and distinct
client cache keys (gdUnit). Replay fixture regenerated — the layer
struct grew two echoed fields (231->254 bytes, content verified).
Client requests stay district-granularity by default — quarter requests
arrive with T-1153's rung selection.
C1 (Tyre): the DeriveWindow branch re-ran the ~45ms run_layer1 for
EVERY uncached window — the dominant pan-around-one-body path paid
~52ms per new window while the doc claimed one-time-per-body.
TerrainAnalysisCache: private per-body LRU (capacity 8, ~2MB/entry,
true access-recency eviction — which body the player keeps panning IS
a recency signal) held as Arc<Mutex<>> on GenerationQueue itself, the
in_flight field pattern, because run_work_item executes on a Rayon
worker where the main-thread caches are unreachable. DeriveWindow now
calls get_or_derive; the variant doc states the real model (first
window per body pays ~45ms once; subsequent windows any center/n hit
the LRU; eviction re-pays). End-to-end test proves two windows on one
body via two connections share exactly one cache entry.
C3 (Tyre): the determinism test reused one TerrainAnalysis — proving
the packer, not the derivation. New test runs run_layer1 twice
independently, asserts field-by-field analysis agreement, then
byte-identical packed windows end to end (D-227 save-critical proof).
Packer-only test kept alongside.
atlas:: 564/564; full lib 1778/1778; timing-sensitive suites 3x stable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AtlasLayerRequest gains window_center/window_n (serde-default, absent
= whole-body, wire back-compat; demux untouched — up_to stays the
discriminator). DistrictWindowLayer echoes center/n + six parallel
arrays (morphology, elev_q, temp_dc i16 with the region sentinel,
moisture_q, vegetation incl Marine=6, glaciation).
Serving per the amendment's binding model: NEVER inline —
GenWorkItem::DeriveWindow rides the Rayon queue, completion drain
caches into DistrictWindowCache (bounded FIFO 256; no staleness by
D-227 purity, capacity bound only), serve_district_window polls the
cache and returns Pending-shaped None until derived. Per-connection
coalescing: submit_window supersedes a still-pending item for the
same (ConnectionId, body) — the surviving item is the newer one,
proven by dedicated tests.
TerrainAnalysis decision (option b, numbers in ticket/PR): re-derive
via run_layer1 in the DeriveWindow branch rather than caching ~1.5MB
x 50 LRU slots (~100MB permanent, the exact D-203 bloat T-1044's own
text guarded against); ~45ms one-time on the Rayon path, invisible to
the tick thread. T-1044 confirmed within-cascade-only (cascade.rs:358
still drops the analysis before BodyWorldState) — the fork was open.
Window derive loop promoted from aliveness_probe::render_window_panels;
determinism promoted from probe-only proof to a real test (two passes
byte-identical). New fixture atlas_response_ready_with_window
exercises all six arrays incl. the airless sentinel and Marine; three
existing fixtures gain district_window: None. Server clamps window_n
to 1..=DISTRICT_WINDOW_MAX_N=64 (never trust the wire).
1774/1774 lib + 19/19 bridge_tcp green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CascadeLayer::Region appended per the enum's append-only Ord rule (depends only on seed/body_params/heightmap dims — documented); BodyWorldState.regions BTreeMap per the districts precedent; RegionGridLayer dense row-major all-integer encoding (season/weather repr(u8) discriminants, mean_temp deci-degC i16 with i16::MIN airless sentinel, moisture_q u8); build_region_grid mirrors build_district_grid; protocol.gd region_grid passthrough (visual overlay deliberately out of scope); wire fixtures regenerated via make fixtures. atlas:: suite 507 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
T-988 — BuildingExteriorTag {wall_material, roof_form, facade_rhythm,
setback_tier, color:HsvColor, street_surface}. New trait_exterior.rs:
3-step era-free derivation (visual_bundle filter -> zone-bias weighted
pick -> density->setback), color seed-sampled within the register band.
Resolved at GenerateSkeleton plan time (T-994 precedent), frozen on
BuildingPropertyTag; FillChunk only reads it. Four append-only
integer-discriminant enums (WallMaterial/RoofForm/FacadeRhythm/
StreetSurface), unknown token -> axis Generic + warn. trait_catalog_reader
now parses visual_bundle + the two new tables (OnceLock-cached).
New SeedDomain::TraitExterior (per-axis sub-chains — no cross-field
correlation).
T-959 — FillChunk reads the frozen exterior tag: wall_material/roof_form
onto Wall/Roof shell voxels (FilledChunk.surface_material). Additive,
ShellVoxel untouched, all shell tests pass. (Interstitial-fill-from-setback
split to T-1098 — needs a FillChunk block-metadata + geometry design pass;
nothing consumes FillChunk until Phase 5.)
T-1097 — BlockSkeleton.interstitial_character (OpenSpace|OperationsSurface),
D-233 bulk-driven: bulk-industry blocks' non-roofed remainder tags as built
economic infrastructure, not generic open space.
cargo check --all-targets clean; 1666 lib tests pass; no golden impact
(harnesses top out at CascadeLayer::RoadGraph, unreachable by Layer 4/5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
T-994 — three-phase draw replacing the flat splitmix64 flavor pick:
- Phase 1 body K-draw (trait_draw.rs): hard gates -> soft weights (two-part
geographic_sector join, PR #148) -> SeedChain::for_body-seeded weighted
draw of K (5/3/1/0 by ComplexityTier), pins count toward K, coverage-aware
over a new body-level district-type-mix aggregate threaded into
CityGenerationContext at L3->L4 dispatch.
- Phase 2 district-dominant pick keyed by the D-243 2048m District cell —
settlements sharing a cell independently derive the identical template.
Resolved at dispatch, never in FillChunk (T-987 stays pure).
- trait_catalog_reader.rs: systems.db reader for trait_templates +
atlas_body_trait_bias (D-225 resource pattern), registered in main.rs
with graceful degradation.
- BlockSkeleton carries district_type; assign_block_tags is a pure lookup.
T-1003 — deviation/swerve system (trait_swerve.rs):
- ArchitectureFlavorRef is now InVocabulary(u8) | Swerve(tag).
- Rare per-building wildcard: foreign-import pool (other corridors +
cross_corridor) driven by Epicenter/Passage tier, mixed faction,
road-graph degree; heritage-callback pool (own-corridor heritage) driven
by isolation, remote tier, founding_age_years. Integer bps, base 100,
cap 300 per driver — placeholder constants pending calibration.
- Sparsity escape hatch = same mechanism, necessity-triggered at the
phase-2 pick (deterministic max-weight over the hard-gate-eligible pool).
- New SeedDomains: TraitVocabulary(13) / TraitDistrict(14) / TraitSwerve(15).
Governance: D-232 amended (district pinned to the D-243 2048m tier — the
D-222/D-243 redefinition was never captured; swerve driver mappings +
placeholder rates recorded), D-229 flavor_ref field updated to the enum
shape, D-235 amended (T-995 vocabulary ratification note; rides here due
to single-file entanglement).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement the D-230 on-demand derive phase (data-production half). New
atlas/shell.rs derives a sparse building shell for one 64 m chunk from the
plan-phase BuildingPropertyTags: rectangle-containment (footprint ∩ chunk) ×
z-range (per-floor voxel bands from FloorExtent), in a quarter-ground z frame
(D-110). Walls on the footprint perimeter, FloorSlab on interior floor bases,
Roof above the top floor; interior air is Void and never stored.
- gen_queue.rs: GenWorkItem::FillChunk carries pre-resolved block_tags +
block_pos + sub_chunk (run_work_item stays cache-free, mirroring
GenerateSkeleton); GenCompletion::ChunkFilled carries Box<FilledChunk>; the
arm calls shell::fill_chunk; pure build_fill_chunk_item(&QuarterWorldState,..)
added.
- plugin.rs: ChunkFilled handler accepts the shell (trace-only — the Phase-5
rendering consumer and the on-demand streaming dispatch trigger are deferred;
do not build on legacy chunk_streaming.rs before Phase 5, gated by T-962).
Tests: determinism, sparsity-vs-dense-volume, wall/floor/roof correctness,
basement z-origin, sub-chunk clipping, full submit→drain→ChunkFilled round-trip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2 of the D-243 re-scale. The ~1km carrier is renamed and re-scaled to
the 2km district, and every consumer reads scale from the canonical scale.rs
instead of a local literal.
- region_profile.rs -> district_profile.rs; RegionProfile -> DistrictProfile,
RegionPos -> DistrictPos (re-exported from scale::DistrictPos), derive_*_regions
-> derive_*_districts, across all 10 consumers + the derivation harness.
- chunk_context now references scale:: for the carrier cell: district 1024m -> 2048m,
shift 4 -> 5 (CHUNK_DISTRICT_SHIFT). This caught a real latent bug: the seed-district
index used a literal '>> 4' while the anchor used the constant, so same-district
chunks could derive different anchors — now both use scale::CHUNK_DISTRICT_SHIFT.
- derivation_harness scale literals ('>> 4', '* 1024', '0..16') converted to
scale:: constants (the same drift the canonical source eliminates). Believability
tests pass at 2km; the voxel determinism golden re-pinned (anchors moved with the
re-scale — deterministic, intended).
Full suite + clippy --all-targets -D warnings green. The district DERIVATION still
tiles the heightmap at 8 cells (the old coarse path); replacing it with on-demand
heightmap interpolation + detail-scatter is phase 3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New deterministic cascade layer (CascadeLayer::RoadGraph) building the
transport topology between Layer-3 settlement placements, stored on
BodyWorldState.road_graph (session-cached, never serialized).
- MST force-connect (strict Kruskal over squared distance) + secondary
links across a 30-60% body-scale band where the MST detours >1.5x.
- A* over the Layer-1 terrain-cost field on a coarse ~64-wide routing grid:
ocean/lake impassable, river corridors 1/2 cost, slope >=35deg (D-210
roughness ~0.7) 10x, else the D-210 terrain_modification_cost. Columns
wrap (equirectangular); integer costs + index tie-break keep A*
deterministic.
- MaintenanceAuthority per edge (new generator.rs enum) from endpoint
political archetypes + haul length; waypoint nodes at long-edge midpoints;
named-route identity join (designed-for, pool empty post-D-223); junction
detection (high_connectivity_junctions, degree>=3).
Wired into the cascade (gen_queue requests CascadeLayer::RoadGraph). Adds
Copy to PoliticalArchetype. 10 road_graph unit tests + a cascade e2e test;
full suite + clippy --all-targets -D warnings green.
Deferred to T-1076 (gated on the D-242 settlement model): the RailHeadFacing
junction pass (needs a D-213 FoundingOrientation amendment + Layer-4 handling)
and the hub-selection refinement (needs baked population/specialization).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First foundation slice of the Atlas-to-tile derivation model (epic T-974),
building the carrier layer ahead of its T-1027+ consumers.
T-1026 — Anti-squaring domain warp (D-239 §4): stateless pure
fn(seed,body_id,pos)->(f64,f64), ±8m, f64 to the final voxel then as-i32
truncation for IEEE-754 cross-target determinism. New domain_warp.rs,
SeedDomain::DomainWarp; golden-vector + cross-thread tests. Position math
only — D-010 integer discipline preserved downstream. Marked dead_code
until the T-1028 VoxelColumn pipeline consumes it.
T-1023 — RegionProfile carrier (D-239 §1,§10): new RegionProfile +
TectonicClass/GlaciationGrade/PrecipitationClass enums + BodyParams; derived
per-region river_threshold replacing the global 200 for tile consumers.
regions: BTreeMap on BodyWorldState, populated via the cascade's new
RegionProfile layer (runs when body_params is Some, else falls back to
Settlement). D-010 integer discipline, BTree ordering.
T-1024 — District climate primitives (D-239 §2): nullable temperature_c +
moisture on RegionProfile, mean-annual scalar (no clock dep; dynamic branch
deferred to Q-105). Hybrid inputs — new bodies.axial_tilt_deg column imported
from planet-gen body-defs (populate_axial_tilt_deg, 2611 bodies), luminosity
and orbital distance derived at runtime; greenhouse + diurnal-swing tables in
source-canonical climate_constants.toml. D-239 implementation note added.
cargo test: 1498 passed, 0 failed. clippy clean (pre-existing
large_enum_variant only). make check-systems-db: stamp fresh.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build the per-quarter building geometry + tags (absorbs #976's footprint
subdivision into #957 — one coherent walkable-quarter deliverable):
- subdivide_block_footprints: deterministic BSP of each 128x128-tile block into
variable axis-aligned building plots (D-229 fast-path). Lot size + setback
scale with D-220 density (Frontier -> few big lots/wide gaps; Compressed ->
many small lots/shared walls); the D-233 BulkClass roofed-coverage fraction
decides building-vs-interstitial. morphology is wired in for the D-234
waterfront/street layer (needs Layer-1 terrain water-adjacency threaded up).
- assign_all_block_tags: subdivides + tags every non-reserved block's footprints
via the #957 derivation helpers, populating QuarterWorldState.block_tags in the
GenerateSkeleton plan-phase (D-230). Reserved blocks get no standard fill.
- Remove the write-only BlockSkeleton.era String stub - construction era now
lives per-footprint on BuildingPropertyTag.era (typed ConstructionEra).
All integer-deterministic (D-010). 5 new tests. Doors stay Vec::new() (#979).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derive and store each placed settlement's spatial character on top of the
#955 placement (D-212/213/214/215). Derivation is pure + integer-deterministic
(D-010); the cascade stays DB-free (D-225) — the enqueuer pre-resolves the
body's system faction onto the work item, like #955's settlements.
- TerritorialStatus (D-212): mapped from the authored dominant_faction
(territorial_status_from_faction). Adds an AutonomistHeld variant for the
Compact of Westphalia (self-governing bloc that rejects Assembly authority —
neither Commission, Corp, Contested, nor truly Frontier). Grounded in
wiki/factions/. Stored per-province on DrainageBasin.territorial_status
(uniform per body for now; forward-compatible for per-province faction data).
- PoliticalArchetype (D-214): political_archetype(status, role), status takes
precedence over economic_role.
- ArrangementPattern (D-215): new 5-variant enum + arrangement_pattern(); the
block-adjacency *enforcement* stays deferred to the Quarter-skeleton gen (#957)
— this only derives + stores which pattern applies.
- FoundingOrientation (D-213): existing fn extended with a seed-derived Free
bearing so pioneer/open-terrain grids vary per seed.
Wiring: dominant_faction threaded through CityContextReader
(read_body_dominant_faction) → atlas proxy (cache-miss read) → AnalyzeBody work
item → run_cascade → run_layer3/match_cities. Enrichment stored on CityPlacement
(political_archetype, arrangement_pattern, founding_orientation).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR #151 review (Hoshe/Tyre): four doc comments in gen_queue.rs still said
"district" next to the renamed quarter_id field / generate_quarter_skeleton.
Pure comment update, no code change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
D-222 renamed the 512m generation cell from District to Quarter (District
is now a new 2048m tier above it). Align the generation skeleton code to
the canonical vocabulary. Pure naming — no behavior change; all 1296 lib
tests + integration tests pass unchanged.
Renamed (spatial-cell identifiers):
- DistrictSkeleton → QuarterSkeleton, DistrictWorldState → QuarterWorldState
- DistrictId → QuarterId, DistrictContext/DistrictBoundaries → Quarter*
- field district_id → quarter_id, district_type → quarter_type
- BodyWorldState.districts map → .quarters
- generate_skeleton → generate_quarter_skeleton
Deliberately left as-is (these name functional ZONING, not the spatial
tier — orthogonal to D-222): DistrictType, DistrictLayoutMode, the
district_mix module (DistrictMix/compute_district_mix), and the
GenWorkItem::GenerateSkeleton / GenCompletion::SkeletonGenerated variants.
Also aligned the perception "sim tile" → "subtile" vocabulary (D-222:
Subtile = 0.5m) in decisions/perception.md and the generation-cascade code
comments. Historical D-066/D-094/D-201/D-220 decision bodies keep their
existing D-222 amendment notes (not rewritten in place); the public
max_offset_sim_tiles fn name is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the existing D-211 attractor-matching engine into the live
generation cascade so settlements are placed in-game, not just in tests.
- CityContextReader::read_body_settlements reads a body's settlements
from atlas_city_names (ordered by id for determinism). NULL
settlement_class defaults to PopulationBudget, not NameLocked: the
class is NULL until placement runs, and NameLocked would force every
settlement Tier-A in match_cities and collapse population tiering
(D-211). NULL economic_role falls back to residential.
- The AnalyzeBody work item carries the body's Vec<CityRecord>, and
run_work_item now runs up_to Settlement (was Topography). A body with
no settlements yields empty placements at negligible cost.
- The atlas layer proxy reads settlements on a cache miss and pins them
onto the work item, keeping the Rayon task DB-free (D-225). A read
failure is non-fatal: log and place no cities (Layer 1 still runs).
Threaded through a new CityContextReaderResource Bevy resource opened
in main.rs, mirroring BodySourceResolverResource.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the existing attractor-matching engine (#919/#925) into the
generation cascade as Layer 3, and make the whole placement-scoring path
integer-deterministic.
Layer 3 (D-211):
- CascadeLayer::Settlement + Layer3Output (placements) on the snapshot;
BodyWorldState gains a `placements` field (the D-203 hot cache).
- run_layer3 runs the five-phase match_cities against Layer-1 attractors
via the authored D-195 compatibility matrix; pure function of
(attractors, cities) — no RNG. cities are passed in by the caller so the
cascade stays DB-free and testable. A `// cache seam` marks where a
persistent cache wraps it later (#1021).
- gen_queue passes &[] for now (Topography needs no cities); the runtime
settlement read (gen_queue/layer_proxy) is the #955 follow-on.
Integer determinism (D-010 / D-227 — D-195 amended):
- Wiring match_cities into the deterministic cascade made its f32 scoring
a live cross-platform divergence risk (a near-tie comparison or the
Hungarian's f32 reductions can round differently per platform → a
different world from the same seed). Converted the entire path to
integers: CompatibilityMatrix is a 0-100 affinity table; attractor
strength is 0-100 and terrain cost is a percent (100 = baseline),
quantized once at the Layer-1 feature boundary; cell_score, the
Hungarian, and CityPlacement.score are i64. No f32 in any placement or
ranking decision.
- Layer-1 golden fixture rebaked: confirmed selection/positions are
unchanged (same 256 attractors, 93 river cells) — only the strength/cost
representation changed.
Tests: lib green (1292); new settlement_layer_places_cities_deterministically
covers placement + determinism + propagation into BodyWorldState.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR #145 review. Convert prosperity from f32 to integer basis
points (matching the existing prosperity_bps, D-010 integer-only),
removing both float-determinism bugs by construction:
- New server/src/bps.rs: log10_floor (integer order-of-magnitude) +
bps_to_f32 edge helper, with boundary tests.
- city_context_reader: prosperity_baseline_bps (u32, 0-10000). role base
+ pop bonus (400*log10_floor, cap 1200) + symmetric noise via integer
modulo (fixes the always-negative bug) + clamp [1000,9500]. No floats.
- CityGenerationContext.prosperity_baseline -> prosperity_baseline_bps;
updated the two test context builders. Not serialized — no wire break.
- district_mix population_tier now uses log10_floor (same determinism
bug class as the comment claimed to avoid).
- Tests in bps + assert positive noise is achievable (the case the old
test hid).
cargo check/clippy --all-targets -D warnings clean; 1291 lib tests pass;
fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Layer 2 clean half. GenerateSkeleton now builds a CityGenerationContext
from a body's real economic read-set instead of stub defaults.
- New CityContextReader (server/src/atlas/city_context_reader.rs): reads
the 6-field D-199 set (economic_role, prosperity_baseline, population,
dominant_faction, founding_age_years, settlement_class) via one JOIN;
prosperity_baseline derived per D-197.
- GenWorkItem::GenerateSkeleton carries the resolved context; run_work_item
builds the real context instead of DistrictWorldState::default().
- Left at defaults (deferred, documented): dominant_bulk_class /
dominant_production_ubiquity (#982, design-blocked), trait_selection
(#1005), morphology_zone + Layer-3 fields (attractor placement / D-204 /
D-214).
16 new tests; cargo check/clippy --all-targets -D warnings clean; 1279
lib tests pass; fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add D-230 skeleton: DistrictSkeleton field to DistrictWorldState;
cascade Default to DistrictSkeleton + contained enums/structs. Box the
GenCompletion::SkeletonGenerated state to avoid large_enum_variant.
- Derive PartialEq on FloorExtent/FloorHeightProfile/DistrictWorldState/
CityGenerationContext (+ minimal cascade) for downstream assert_eq tests.
- Add 4 unit tests for floor_at_voxel_z / voxel_range_for_floor (uniform,
basement, variable heights, boundary) — the Q-104 deliverable.
- Drop unused smallvec direct dep (stays transitive via bevy_ecs).
- Key districts insert by skeleton.district_id, sharpen TODO(#957).
clippy --all-targets -D warnings clean; 1263 lib tests pass; fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Define the type-definition layer the Phase-4 fill-seam tickets depend on
(D-229/D-230/D-231/D-232/D-233), compiling with stubs/defaults; behavior
logic lands in #982-985/#998.
- New types in generator.rs: BuildingPropertyTag, FloorExtent +
FloorHeightProfile (floor_at_voxel_z/voxel_range_for_floor, resolves
Q-104), BuildingEntryClass, ConstructionEra, ZoneTypeId, MorphologyZone,
BulkClass(5), ProductionUbiquity, DoorSpec, InteriorDescriptor,
DistrictWorldState.
- Rename spatial AccessTier -> ZoneAccessTier to free the name for the new
per-building BuildingEntryClass.
- CityGenerationContext: +morphology_zone, +trait_selection,
+dominant_bulk_class, +dominant_production_ubiquity.
- BodyWorldState: +districts (DistrictWorldState w/ block_tags).
- GenCompletion::SkeletonGenerated carries body_id + DistrictWorldState;
plugin handler inserts into BodyWorldState.districts.
- Add smallvec as a direct dep (DoorSpec list stays Vec for now, TODO).
cargo check --all-targets / clippy clean; 1259 lib tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
gen_queue: add in_flight_count tracking for all work item types (not
just AnalyzeBody). Rewrite saturation test with AnalyzeBody items.
Fix priority_ordering test thread count to match new gate.
rng: collapse to single AtlasRng::new(seed) constructor — callers
own their seed transform.
import_province_boundaries: fix "savepoint" comment to "transaction".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>