is_standalone_hq threaded through CityRecord/CityPlacement via corporations LEFT JOIN (both readers); hubs = top-cap by population among non-HQ settlements (HUB_SPACING_DIAG_PX=64, HUB_CAP_MIN=6); exact-name collapse keeping lowest city_id with loud warn; nearest-point-on-polyline snap (SNAP_MAX_PX=8) with Junction edge-split preserving from<to via norm_edge, else A*-spur to nearest hub; FoundingOrientation::RailHeadFacing{bearing_degrees} assigned at degree>=3 junctions (octant bearing toward dominant incident edge), consumed by skeleton_gen railhead_edge() through the D-234b flush-frontage machinery; believability reader now honours baked settlement_class (stale hardcode since T-1075). 11 new targeted tests; existing suites green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
H1 demux: ShapeProbe defensive multi-shape rejection (union frames now Err, not first-match; +2 tests) and doc claim made honest. H2/T1 SystemIndex.reset_test_state() folded into SimBridge.reset_test_state() (load() inline per autoload rule) + has_pending_request() accessor. H3 no-op tests now assert the replay flag both directions. H4 retry test actually ingests a failure and asserts the retry semantic. H5 error fixture uses the normalized status string. H6 bridge_tcp e2e sends all five frame shapes over real TCP (star-map + city-names buffers asserted). H7 positive replay-on-CONNECTED test via the test_local_bridge test-mode-flip precedent (stub bridge captures + decodes the request bytes). H8/T2 stale PLACEHOLDER doc replaced with the confirmed contract. H9 is_capital doc matches the COALESCE reality. T-r1 demux ceiling written down (next shape = tagged envelope). T-r2 AtlasLayerResponse governance ceiling comment. Lead item: the four cargo-fmt-formatted files from the gate round are now committed (layer_proxy/plugin/bridge-mod/main). H10 note for the record: the 13 snapshot_*.msgpack fixtures in commit 845737617 were regenerated because they were stale against their own generator (pre-existing version-key removal) — verified harmless, no client reads that key.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
T-960: RoadGraphLayer (L2 edges/junctions, MaintenanceAuthority, rail flag, named routes) + SettlementLayer (name/position/size-class/is_capital/is_port from T-955 placements) as new Option fields on AtlasLayerResponse — T-1046 precedent; layer_proxy doc comment corrected (cascade runs through RoadGraph, client up_to ignored). T-949: new StarMapRequest/StarMapResponse (raw JSON passthrough of the star_map_data.json bake) + CityNamesRequest/CityNamesResponse (atlas_city_names via CityContextReader) with defense-in-depth Sol exclusion (GJ-0 system id OR settlement_wave='origin', D-236). Inbound demux extended with mandatory boolean discriminator fields (serde ignores unknown fields — optional-shape sniffing would be ambiguous); AtlasLayerRequest unchanged. SimBridge trait +2 methods, implemented on TcpBridge + LocalBridge. CityPlacement/CityRecord gained name/population/is_capital (BodyWorldState cache-hit path stays DB-free). ~30 new unit tests incl. demux disambiguation + Sol branches; gen_fixtures extended for road/settlement samples.
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>
T1: heritage corridor_pool excluded from the ordinary phase-1 lottery and
coverage repair (D-232 reserves the heritage sub-pool for the remoteness
dial); reachable via hero pin, necessity swerve, and the T-1003 heritage
pool only. 3 new tests.
T2+H4: coverage repair no longer grows trait_selection past K when all
slots are pinned (phase-2 necessity swerve serves the type instead);
runtime warn when authored pins exceed K; V-TT-05 importer guardrail
bounds pins per body at 5 (max ComplexityTier K). 2 new tests + 2 python
tests.
H1: body-level dispatch aggregation extracted to pure
aggregate_body_dispatch_inputs + tested directly (union mix, MAX
prosperity/K); threading test asserts identical vocabulary/pools across
co-body settlements with per-settlement swerve rates. 2 new tests.
H2: tooling/economy-db/test_traits.py — 14 stdlib unittest cases over
V-TT-03/04/05 failure branches, wired into make test-tooling.
H3: hard-gate JSON parsers now tracing::warn on malformed blobs (silent
gate-widening) matching the sibling map parsers.
H5: catalog read memoized (OnceLock) — SQL+parse once per server run,
bias stays per-body. 1 new test.
T3: TraitDistrict seed-domain doc aligned with the two-level derive chain.
T4: D-225 misattribution dropped from the reader module doc.
systems.db regenerated + stamped (traits.py is a stamped source).
Gates: full cargo test 1638 green (goldens intact), clippy -D warnings,
ruff, make test-tooling (now incl. the traits units).
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>
T-1044: run_layer1 now returns TerrainAnalysis (carried transiently on
CascadeSnapshot, dropped after the district + road-graph passes), eliminating the
redundant per-body drainage::analyze + TerrainAnalysis::analyze re-run flagged by
PERF/TODO(T-1044). Not persisted on the LRU-cached state (D-203/T-1048 size concern).
T-1047: basin_direction is now derived from the real D8 thalweg. run_layer1
aggregates a per-district dominant D8 direction from the live fdir grid (carried
transiently on DrainageResult), threaded via Layer1Output.district_basin_dirs ->
derive_all_districts -> DistrictProfile.basin_direction; derive_chunk_context reads
it directly. Removed the false derive_basin_direction (it branched on ocean_fraction_q
then read seed bits despite a doc comment claiming an elev_q/slope_q D8 proxy) +
corrected the module contract. D-239 §8 (D8 thalweg) now actually honoured.
1559 tests pass; golden byte-identical (district_basin_dirs is #[serde(skip)], transient).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hoshe H1: the morphology_zone AlluvialPlain fallback fired silently. Now logs
tracing::debug! when the district grid is empty (expected: params-missing body /
early cascade) and tracing::warn! when a populated grid misses the key (indicates a
pixel->district convention bug) — no more silently-wrong topology.
Hoshe H2: the arrangement_pattern parity tripwire only covered transit_hub with
Commission + Corporate. Added Pioneer/Industrial/Military/Academic rows so an
accidental archetype-conditionalization of the cross-archetype override is caught.
Tyre (non-blocking): clarified the build_skeleton_work_item doc — the
arrangement_pattern re-derivation call lives in generate_quarter_skeleton (the
consumer), not in this function.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the GenerateSkeleton dispatch T-1022 wired (which only carried
founding_orientation). build_skeleton_work_item now also threads:
T-1039:
- political_archetype — real value from CityPlacement, replacing the Commission stub.
- morphology_zone — looked up from the covering DistrictProfile via new
scale::heightmap_pixel_to_district (exported HEIGHTMAP_CELLS_PER_DISTRICT;
cascade.rs drops its local CELLS_PER_REGION literal for the shared const).
- arrangement_pattern — RE-DERIVED at L4 (option b) via the single pure fn
attractor_matching::arrangement_pattern(&archetype, &economic_role); both
inputs already at L4. No CityGenerationContext field, no stored derived state
(DB-as-cache). Parity test asserts L4 re-derivation == L3-stored value.
T-1043:
- road_entry_directions — derived from T-1038's BodyWorldState.road_graph:
octant = bearing of each incident road edge, deduped per octant, ordered by
MaintenanceAuthority rank (the AdminFacing prestige-edge consumer). Feeds
derive_access_points -> AccessKind::QuarterEdge; BlockJunction fallback now
fires only for genuinely isolated settlements.
Acceptance covered: Corporate+Fjord -> Ribbon topology (not mesh+Commission);
settlement with a road -> QuarterEdge on the correct octant. 11 new tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server half of wiring the derived tier into production. The D-225 proxy
(AtlasLayerResponse) carried only Layer1Output, so the Atlas could not show the
DistrictProfile tier that the cascade derives. Adds DistrictGridLayer (cols/rows
+ row-major MorphologyZone discriminants + elev_q for relief shading) and
build_district_grid(), populated on a cache hit. Because the response is
rmp_serde msgpack end-to-end, the new field reaches the client automatically.
This is the planetary-scale coarse grid (the Atlas map view); the on-demand 2km
derive_district (T-1077) is for in-world Phase 5, not the map. MorphologyZone
gains Copy (fieldless repr(u8) enum; additive).
Next: client decode (protocol.gd) + the D-226 generation overlay in the Atlas
viewer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The D-239 carrier layer (T-1023/1024/1026) only ran in tests because every
production AnalyzeBody enqueue passed body_params: None. Wire the real path:
- New BodyParamsReader (server/src/atlas/body_params_reader.rs): read-only
systems.db reader, joins bodies -> star_systems (LEFT JOIN) for the climate/
tectonic inputs. SQL verified against systems-schema.sql. All fields Option,
NULLs handled; tectonic_activity absent from schema -> None (derives from
planet_class). 5 unit tests.
- layer_proxy.rs: on cache miss, read the body's params and pass
Some(Box::new(..)). On read error, warn + fall back to None (cascade stops at
Settlement, no panic) — graceful degradation.
- plugin.rs / main.rs: register BodyParamsReaderResource (CityContextReader
pattern) and thread it through serve_atlas_requests.
BodyWorldState.regions now populates for real bodies in the D-206 background
pass. End-to-end tests cover wired (regions populated) + unwired (empty) paths.
cargo test 1504 pass, clippy -D warnings clean, fmt clean.
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>
The Layer-4 quarter-skeleton geometry (D-234: morphology-correct streets +
the D-234b waterfront rule) was fully implemented but dormant — no
production call site dispatched GenerateSkeleton from a settlement
placement, so founding_orientation stayed pinned to the
context_from_read_set Cardinal stub.
Expand the BodyAnalyzed arm of drain_generation_completions to, before
caching the body, submit one GenerateSkeleton per CityPlacement: build the
D-199 context from the read-set, override founding_orientation with the
attractor-matched value carried on the placement (D-213), and derive a
canonical namespace-isolated quarter_id from (world_seed, body, city)
(D-194/D-230) instead of the city_id*10 placeholder. Dispatch logic is
extracted into the pure build_skeleton_work_item helper for DB-free testing.
Reader + world seed are optional system params (mirrors serve_atlas_requests);
absent either, dispatch is skipped and the body is still cached. Empty
placements and per-placement read_set errors are handled (warn + skip).
Station/multi-source dispatch (Q-109) is out of scope — BodyAnalyzed only
fires on the planetary AnalyzeBody path.
Tests: orientation-override + canonical-quarter_id unit test and a
determinism/city-scoping test on the helper. Full lib suite green (1327).
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>
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>
- 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>
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>
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>