Wires the existing D-211 attractor-matching engine into the live generation
cascade so settlements are placed in-game, not just in tests — completing #955. Along the way, the Layer-1→3 decision path was converted to fully
integer-deterministic math (D-010) to remove cross-platform f32 divergence
risk before more layers build on it.
What changed
Layer-3 placement in the runtime cascade (this session — B/E/F)
CityContextReader::read_body_settlements — per-body query of atlas_city_names, ordered by id for determinism. NULL settlement_class
defaults to PopulationBudget, notNameLocked: 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.
AnalyzeBody work item carries Vec<CityRecord> and run_work_item
now runs up_to: Settlement (was Topography). A body with no settlements
yields empty placements at negligible cost.
Atlas proxy reads settlements on 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 opened in main.rs, mirroring BodySourceResolverResource.
Integer-deterministic placement core (earlier commits on this branch)
CompatibilityMatrix is a [[i32; 7]; 10] 0–100 affinity table
(CompatibilityMatrix::d195()); GeographicAttractor.strength is i32
(0–100) and terrain_modification_cost is an i32 percent (100 = baseline).
cell_score/match_cities/hungarian operate on i64; no f32 survives
in any placement decision. Quantization happens at the feature-extraction
boundary only.
Layer-1 sort keys, sub-biome cost, and the golden fixture were updated to the
integer representation. The Layer-0→1 golden selection is unchanged —
only the strength/cost representation differs (verified: identical
source SHA, river cells, attractor set).
D-195 amended in decisions/architecture.md.
Determinism
Cascade is a pure function of (heightmap, cities, seed); the placement path is
integer-only end to end (D-010). Same inputs → identical placements.
1295 lib tests + all integration tests pass. New: 3 read_body_settlements
tests (NULL→PopulationBudget, id ordering + explicit-class parse, role
fallback + empty body) and settlement_layer_places_cities_deterministically.
Golden fixture (cascade_layer1.json) rebaked — representational only.
atlas_response_ready.msgpack regenerated for the new attractor schema.
Not in this PR
A pre-existing broken doctest in server/src/bps.rs (use server::... →
should be settled_reach_server::..., broken since the log10_floor helper
landed in #145). Only surfaces in cargo test --doc, not --lib. Left for a
separate cleanup.
Persistent cascade cache (settlement positions are deterministic, so they can
be cached) is tracked as #1021.
## Summary
Wires the existing D-211 attractor-matching engine into the live generation
cascade so **settlements are placed in-game**, not just in tests — completing
#955. Along the way, the Layer-1→3 decision path was converted to fully
integer-deterministic math (D-010) to remove cross-platform `f32` divergence
risk before more layers build on it.
## What changed
### Layer-3 placement in the runtime cascade (this session — B/E/F)
- **`CityContextReader::read_body_settlements`** — per-body query of
`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`.
- **`AnalyzeBody` work item carries `Vec<CityRecord>`** and `run_work_item`
now runs `up_to: Settlement` (was `Topography`). A body with no settlements
yields empty placements at negligible cost.
- **Atlas proxy reads settlements on 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` opened in `main.rs`, mirroring
`BodySourceResolverResource`.
### Integer-deterministic placement core (earlier commits on this branch)
- `CompatibilityMatrix` is a `[[i32; 7]; 10]` 0–100 affinity table
(`CompatibilityMatrix::d195()`); `GeographicAttractor.strength` is `i32`
(0–100) and `terrain_modification_cost` is an `i32` percent (100 = baseline).
- `cell_score`/`match_cities`/`hungarian` operate on `i64`; no `f32` survives
in any placement decision. Quantization happens at the feature-extraction
boundary only.
- Layer-1 sort keys, sub-biome cost, and the golden fixture were updated to the
integer representation. The Layer-0→1 golden selection is **unchanged** —
only the strength/cost *representation* differs (verified: identical
source SHA, river cells, attractor set).
- D-195 amended in `decisions/architecture.md`.
## Determinism
Cascade is a pure function of (heightmap, cities, seed); the placement path is
integer-only end to end (D-010). Same inputs → identical placements.
## Test plan
- `cargo check` clean (lib + bins); `cargo fmt --check` clean.
- `cargo clippy --all-targets -- -D warnings` clean.
- **1295 lib tests + all integration tests pass.** New: 3 `read_body_settlements`
tests (NULL→PopulationBudget, id ordering + explicit-class parse, role
fallback + empty body) and `settlement_layer_places_cities_deterministically`.
- Golden fixture (`cascade_layer1.json`) rebaked — representational only.
- `atlas_response_ready.msgpack` regenerated for the new attractor schema.
### Not in this PR
- A pre-existing broken doctest in `server/src/bps.rs` (`use server::...` →
should be `settled_reach_server::...`, broken since the `log10_floor` helper
landed in #145). Only surfaces in `cargo test --doc`, not `--lib`. Left for a
separate cleanup.
- Persistent cascade cache (settlement positions are deterministic, so they can
be cached) is tracked as **#1021**.
Closes #955.
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>
all_fixtures_deserialize panicked with "unknown fixture naming
convention: atlas_response_ready" — the test reads every .msgpack in
client/tests/fixtures/msgpack but had no branch for the atlas_response
prefix (the AtlasLayerResponse fixtures gen_fixtures emits, #969). A
pre-existing gap (present on main; the suite isn't gated by the pre-push
hook, which runs cargo-deny/ruff/JSON/stamp, not cargo test).
Add an atlas_response branch deserializing AtlasLayerResponse, and
regenerate atlas_response_ready.msgpack so its bytes match the now-integer
GeographicAttractor types (#955).
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>
Mark Layer-3 settlement placement (#955) as running in-game now that the
B/E/F plumbing (per-body settlement read → work item → cache-miss proxy)
is wired.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review: layer3-settlement-placement → main (type: code)
Reviewers: Hoshe (QA/code quality) + Tyre (architecture). Both ran clean tooling
(clippy --all-targets -D warnings clean; 1295 lib tests pass). Runtime smoke
documented in the PR test plan.
Hoshe (QA / code quality): REQUEST_CHANGES
The integer-math conversion is correct and complete — no overflow risk, no f32
remaining in any decision path, clean quantization boundary. One real behavioral
defect plus two stale-doc issues.
Defect (medium). A non-NULL but unrecognizedsettlement_class string makes parse_settlement_class(...)? return Err, which propagates and fails the whole read_body_settlements call. layer_proxy then swallows it and enqueues the body with zero cities — one bad row kills placement for the entire body. Inconsistent with the NULL→PopulationBudget and NULL-role→residential fallbacks already in the same function. Fix: treat unknown variant like NULL (fall back to PopulationBudget + tracing::warn!), and add a test.
H2
features.rs line 16 (module comment)
Stale doc. Comment says "strength is f32 but is never used as a sort key." It's now i32and is used as a direct sort key (here and in layer1.rs). Update.
H3
cascade.rs line 39 (CascadeLayer::Settlement doc) + line 15 (module doc)
Incorrect doc. Both call Layer 3 the "first RNG-using layer (uses the carried SeedChain)." As shipped, run_layer3 takes no seed and match_cities uses no RNG — Layer 3 is a pure function of (attractors, cities). Misleads whoever builds Layer 4.
Tyre (architecture): REQUEST_CHANGES
Architecturally sound — D-225's DB-free contract is honored by pre-resolving
cities at enqueue time, the cache-miss→enqueue→drain→cache loop is intact, and
deferring the persistent cache to #1021 is the right call. Two issues.
Stale doc / drift. Comment says "v1 always runs the Layer-1 (Topography) cascade," but run_work_item now unconditionally runs CascadeLayer::Settlement and ignores req.up_to entirely. Doubly wrong. Update the comment to state v1 ignores the field and always runs through Settlement (honoring up_to deferred).
T2
cascade.rs ~line 257 (layers_are_ordered test)
Test gap. Only asserts Heightmap < Topography. The up_to >= Settlement guards depend on Topography < Settlement, which currently holds only implicitly via derive order. Add assert!(CascadeLayer::Topography < CascadeLayer::Settlement) to pin the invariant.
Verdict: CHANGES REQUESTED
All five comments accepted (no pushback). H1 is a genuine robustness fix; the
other four are doc/test correctness. Addressing on the branch next.
## Review: layer3-settlement-placement → main (type: code)
Reviewers: Hoshe (QA/code quality) + Tyre (architecture). Both ran clean tooling
(clippy `--all-targets -D warnings` clean; 1295 lib tests pass). Runtime smoke
documented in the PR test plan.
### Hoshe (QA / code quality): REQUEST_CHANGES
The integer-math conversion is correct and complete — no overflow risk, no f32
remaining in any decision path, clean quantization boundary. One real behavioral
defect plus two stale-doc issues.
| # | File | Issue |
|---|------|-------|
| H1 | `city_context_reader.rs` ~line 273 (`read_body_settlements`) | **Defect (medium).** A non-NULL but *unrecognized* `settlement_class` string makes `parse_settlement_class(...)?` return `Err`, which propagates and fails the whole `read_body_settlements` call. `layer_proxy` then swallows it and enqueues the body with **zero cities** — one bad row kills placement for the entire body. Inconsistent with the NULL→PopulationBudget and NULL-role→residential fallbacks already in the same function. Fix: treat unknown variant like NULL (fall back to `PopulationBudget` + `tracing::warn!`), and add a test. |
| H2 | `features.rs` line 16 (module comment) | **Stale doc.** Comment says "`strength` is f32 but is never used as a sort key." It's now `i32` *and* is used as a direct sort key (here and in `layer1.rs`). Update. |
| H3 | `cascade.rs` line 39 (`CascadeLayer::Settlement` doc) + line 15 (module doc) | **Incorrect doc.** Both call Layer 3 the "first RNG-using layer (uses the carried `SeedChain`)." As shipped, `run_layer3` takes no seed and `match_cities` uses no RNG — Layer 3 is a pure function of (attractors, cities). Misleads whoever builds Layer 4. |
### Tyre (architecture): REQUEST_CHANGES
Architecturally sound — D-225's DB-free contract is honored by pre-resolving
cities at enqueue time, the cache-miss→enqueue→drain→cache loop is intact, and
deferring the persistent cache to #1021 is the right call. Two issues.
| # | File | Issue |
|---|------|-------|
| T1 | `layer_proxy.rs` lines 28–30 (`AtlasLayerRequest.up_to` doc) | **Stale doc / drift.** Comment says "v1 always runs the Layer-1 (Topography) cascade," but `run_work_item` now unconditionally runs `CascadeLayer::Settlement` and ignores `req.up_to` entirely. Doubly wrong. Update the comment to state v1 ignores the field and always runs through Settlement (honoring `up_to` deferred). |
| T2 | `cascade.rs` ~line 257 (`layers_are_ordered` test) | **Test gap.** Only asserts `Heightmap < Topography`. The `up_to >= Settlement` guards depend on `Topography < Settlement`, which currently holds only implicitly via derive order. Add `assert!(CascadeLayer::Topography < CascadeLayer::Settlement)` to pin the invariant. |
### Verdict: CHANGES REQUESTED
All five comments accepted (no pushback). H1 is a genuine robustness fix; the
other four are doc/test correctness. Addressing on the branch next.
read_body_settlements propagated a parse error for any non-NULL but
unrecognized settlement_class, which the atlas proxy swallowed and then
enqueued the body with zero cities — one malformed row silently dropped
placement for the entire body. Treat an unknown variant like NULL: fall
back to PopulationBudget with a warning. parse_settlement_class stays
strict for the D-199 read_set path, which must abort on bad fields.
Found in PR #149 review (Hoshe H1). Adds a test covering the fallback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address the doc/test-correctness items from PR #149 review:
- cascade.rs: Layer 3 is RNG-free (pure fn of attractors+cities); it does
not consume the carried SeedChain. Corrected the "first RNG-using layer"
claims on the module doc and the Settlement variant (Hoshe H3).
- cascade.rs: layers_are_ordered now also asserts Topography < Settlement,
pinning the invariant the up_to >= Settlement guards rely on (Tyre T2).
- features.rs: strength is an integer 0-100 (quantized at extraction), not
f32, and consumers that rank by it use the integer value (Hoshe H2).
- layer_proxy.rs: AtlasLayerRequest.up_to is not yet honored — run_work_item
runs through Settlement unconditionally; per-request depth deferred to
#1021 (Tyre T1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All five comments accepted (no pushback). Two commits pushed:
9d8ff8b34 fix(simulation): fall back on unrecognized settlement_class (#955)
2d8367d3b docs(simulation): correct stale cascade docs and pin layer order (#955)
#
Comment
Resolution
H1
read_body_settlements hard-errors on unknown class → body gets zero cities
Fixed. Unknown non-NULL variant now falls back to PopulationBudget + tracing::warn!, same as NULL. parse_settlement_class stays strict for the D-199 read_set path. New test read_body_settlements_unknown_class_falls_back_without_dropping_body asserts one bad row doesn't drop the body.
H2
features.rs:16 stale "strength is f32 / never a sort key"
Fixed. Doc now says strength is an integer 0–100 (quantized at extraction) and notes consumers that rank by it (layer1::attach_feature_names) use the integer value. Module-header (0.0–1.0) line also corrected.
H3
cascade.rs calls Layer 3 the "first RNG-using layer"
Fixed. Module doc and the Settlement variant now state Layer 3 is RNG-free — a pure function of (attractors, cities); the carried SeedChain is reserved for Layer 4+.
## Review comments addressed
All five comments accepted (no pushback). Two commits pushed:
- `9d8ff8b34` fix(simulation): fall back on unrecognized settlement_class (#955)
- `2d8367d3b` docs(simulation): correct stale cascade docs and pin layer order (#955)
| # | Comment | Resolution |
|---|---------|-----------|
| H1 | `read_body_settlements` hard-errors on unknown class → body gets zero cities | **Fixed.** Unknown non-NULL variant now falls back to `PopulationBudget` + `tracing::warn!`, same as NULL. `parse_settlement_class` stays strict for the D-199 `read_set` path. New test `read_body_settlements_unknown_class_falls_back_without_dropping_body` asserts one bad row doesn't drop the body. |
| H2 | `features.rs:16` stale "strength is f32 / never a sort key" | **Fixed.** Doc now says strength is an integer 0–100 (quantized at extraction) and notes consumers that rank by it (`layer1::attach_feature_names`) use the integer value. Module-header `(0.0–1.0)` line also corrected. |
| H3 | `cascade.rs` calls Layer 3 the "first RNG-using layer" | **Fixed.** Module doc and the `Settlement` variant now state Layer 3 is RNG-free — a pure function of (attractors, cities); the carried `SeedChain` is reserved for Layer 4+. |
| T1 | `AtlasLayerRequest.up_to` doc says "v1 always runs Topography" | **Fixed.** Doc now states `up_to` is not yet honored — `run_work_item` runs through `Settlement` unconditionally; per-request depth deferred to #1021. |
| T2 | `layers_are_ordered` only asserts `Heightmap < Topography` | **Fixed.** Added `assert!(Topography < Settlement)` with a comment explaining the `up_to >=` guards depend on it. |
**Verification:** `cargo fmt --check` clean, `cargo clippy --all-targets -- -D warnings` clean, **1296 lib tests pass** (+1 new). Ready for re-review.
Re-review (round 2): layer3-settlement-placement → main (type: code)
After the two fix commits (9d8ff8b34, 2d8367d3b). Full cycle, not just delta.
Gate: cargo clippy --all-targets -- -D warnings clean; 1296 lib tests pass.
Hoshe (QA / code quality): APPROVE
All three first-round comments correctly resolved. H1's fallback uses unwrap_or_else on parse_settlement_class, emits tracing::warn!, and the new
test proves one unrecognized row leaves the others intact (cities.len() == 2).
Confirmed the empty-string case also routes through the fallback. H2/H3 doc
corrections are accurate (strength/terrain_modification_cost are i32
throughout; Layer 3 correctly characterized as RNG-free). No new issues.
Tyre (architecture): APPROVE
Both comments cleanly resolved. T1: the up_to doc now precisely describes the
distinction — the cascade respects up_to, but run_work_item hardcodes Settlement; per-request depth deferred to #1021. T2: the test pins both
ordering invariants with a comment tying them to the cascade guards. No new
drift; D-225 DB-free contract intact, cache loop unchanged, the single
f32→integer boundary correctly placed, D-195 amendment matches the code.
Verdict: APPROVED
Both reviewers approve. Ready to merge.
## Re-review (round 2): layer3-settlement-placement → main (type: code)
After the two fix commits (`9d8ff8b34`, `2d8367d3b`). Full cycle, not just delta.
Gate: `cargo clippy --all-targets -- -D warnings` clean; 1296 lib tests pass.
### Hoshe (QA / code quality): APPROVE
All three first-round comments correctly resolved. H1's fallback uses
`unwrap_or_else` on `parse_settlement_class`, emits `tracing::warn!`, and the new
test proves one unrecognized row leaves the others intact (`cities.len() == 2`).
Confirmed the empty-string case also routes through the fallback. H2/H3 doc
corrections are accurate (`strength`/`terrain_modification_cost` are `i32`
throughout; Layer 3 correctly characterized as RNG-free). No new issues.
### Tyre (architecture): APPROVE
Both comments cleanly resolved. T1: the `up_to` doc now precisely describes the
distinction — the cascade respects `up_to`, but `run_work_item` hardcodes
`Settlement`; per-request depth deferred to #1021. T2: the test pins both
ordering invariants with a comment tying them to the cascade guards. No new
drift; D-225 DB-free contract intact, cache loop unchanged, the single
f32→integer boundary correctly placed, D-195 amendment matches the code.
### Verdict: APPROVED
Both reviewers approve. Ready to merge.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Wires the existing D-211 attractor-matching engine into the live generation
cascade so settlements are placed in-game, not just in tests — completing
#955. Along the way, the Layer-1→3 decision path was converted to fully
integer-deterministic math (D-010) to remove cross-platform
f32divergencerisk before more layers build on it.
What changed
Layer-3 placement in the runtime cascade (this session — B/E/F)
CityContextReader::read_body_settlements— per-body query ofatlas_city_names, ordered byidfor determinism. NULLsettlement_classdefaults to
PopulationBudget, notNameLocked: the class is NULL untilplacement runs, and NameLocked would force every settlement Tier-A in
match_citiesand collapse population tiering (D-211). NULLeconomic_rolefalls back to
residential.AnalyzeBodywork item carriesVec<CityRecord>andrun_work_itemnow runs
up_to: Settlement(wasTopography). A body with no settlementsyields empty placements at negligible cost.
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
CityContextReaderResourceopened inmain.rs, mirroringBodySourceResolverResource.Integer-deterministic placement core (earlier commits on this branch)
CompatibilityMatrixis a[[i32; 7]; 10]0–100 affinity table(
CompatibilityMatrix::d195());GeographicAttractor.strengthisi32(0–100) and
terrain_modification_costis ani32percent (100 = baseline).cell_score/match_cities/hungarianoperate oni64; nof32survivesin any placement decision. Quantization happens at the feature-extraction
boundary only.
integer representation. The Layer-0→1 golden selection is unchanged —
only the strength/cost representation differs (verified: identical
source SHA, river cells, attractor set).
decisions/architecture.md.Determinism
Cascade is a pure function of (heightmap, cities, seed); the placement path is
integer-only end to end (D-010). Same inputs → identical placements.
Test plan
cargo checkclean (lib + bins);cargo fmt --checkclean.cargo clippy --all-targets -- -D warningsclean.read_body_settlementstests (NULL→PopulationBudget, id ordering + explicit-class parse, role
fallback + empty body) and
settlement_layer_places_cities_deterministically.cascade_layer1.json) rebaked — representational only.atlas_response_ready.msgpackregenerated for the new attractor schema.Not in this PR
server/src/bps.rs(use server::...→should be
settled_reach_server::..., broken since thelog10_floorhelperlanded in #145). Only surfaces in
cargo test --doc, not--lib. Left for aseparate cleanup.
be cached) is tracked as #1021.
Closes #955.
Review: layer3-settlement-placement → main (type: code)
Reviewers: Hoshe (QA/code quality) + Tyre (architecture). Both ran clean tooling
(clippy
--all-targets -D warningsclean; 1295 lib tests pass). Runtime smokedocumented in the PR test plan.
Hoshe (QA / code quality): REQUEST_CHANGES
The integer-math conversion is correct and complete — no overflow risk, no f32
remaining in any decision path, clean quantization boundary. One real behavioral
defect plus two stale-doc issues.
city_context_reader.rs~line 273 (read_body_settlements)settlement_classstring makesparse_settlement_class(...)?returnErr, which propagates and fails the wholeread_body_settlementscall.layer_proxythen swallows it and enqueues the body with zero cities — one bad row kills placement for the entire body. Inconsistent with the NULL→PopulationBudget and NULL-role→residential fallbacks already in the same function. Fix: treat unknown variant like NULL (fall back toPopulationBudget+tracing::warn!), and add a test.features.rsline 16 (module comment)strengthis f32 but is never used as a sort key." It's nowi32and is used as a direct sort key (here and inlayer1.rs). Update.cascade.rsline 39 (CascadeLayer::Settlementdoc) + line 15 (module doc)SeedChain)." As shipped,run_layer3takes no seed andmatch_citiesuses no RNG — Layer 3 is a pure function of (attractors, cities). Misleads whoever builds Layer 4.Tyre (architecture): REQUEST_CHANGES
Architecturally sound — D-225's DB-free contract is honored by pre-resolving
cities at enqueue time, the cache-miss→enqueue→drain→cache loop is intact, and
deferring the persistent cache to #1021 is the right call. Two issues.
layer_proxy.rslines 28–30 (AtlasLayerRequest.up_todoc)run_work_itemnow unconditionally runsCascadeLayer::Settlementand ignoresreq.up_toentirely. Doubly wrong. Update the comment to state v1 ignores the field and always runs through Settlement (honoringup_todeferred).cascade.rs~line 257 (layers_are_orderedtest)Heightmap < Topography. Theup_to >= Settlementguards depend onTopography < Settlement, which currently holds only implicitly via derive order. Addassert!(CascadeLayer::Topography < CascadeLayer::Settlement)to pin the invariant.Verdict: CHANGES REQUESTED
All five comments accepted (no pushback). H1 is a genuine robustness fix; the
other four are doc/test correctness. Addressing on the branch next.
Review comments addressed
All five comments accepted (no pushback). Two commits pushed:
9d8ff8b34fix(simulation): fall back on unrecognized settlement_class (#955)2d8367d3bdocs(simulation): correct stale cascade docs and pin layer order (#955)read_body_settlementshard-errors on unknown class → body gets zero citiesPopulationBudget+tracing::warn!, same as NULL.parse_settlement_classstays strict for the D-199read_setpath. New testread_body_settlements_unknown_class_falls_back_without_dropping_bodyasserts one bad row doesn't drop the body.features.rs:16stale "strength is f32 / never a sort key"layer1::attach_feature_names) use the integer value. Module-header(0.0–1.0)line also corrected.cascade.rscalls Layer 3 the "first RNG-using layer"Settlementvariant now state Layer 3 is RNG-free — a pure function of (attractors, cities); the carriedSeedChainis reserved for Layer 4+.AtlasLayerRequest.up_todoc says "v1 always runs Topography"up_tois not yet honored —run_work_itemruns throughSettlementunconditionally; per-request depth deferred to #1021.layers_are_orderedonly assertsHeightmap < Topographyassert!(Topography < Settlement)with a comment explaining theup_to >=guards depend on it.Verification:
cargo fmt --checkclean,cargo clippy --all-targets -- -D warningsclean, 1296 lib tests pass (+1 new). Ready for re-review.Re-review (round 2): layer3-settlement-placement → main (type: code)
After the two fix commits (
9d8ff8b34,2d8367d3b). Full cycle, not just delta.Gate:
cargo clippy --all-targets -- -D warningsclean; 1296 lib tests pass.Hoshe (QA / code quality): APPROVE
All three first-round comments correctly resolved. H1's fallback uses
unwrap_or_elseonparse_settlement_class, emitstracing::warn!, and the newtest proves one unrecognized row leaves the others intact (
cities.len() == 2).Confirmed the empty-string case also routes through the fallback. H2/H3 doc
corrections are accurate (
strength/terrain_modification_costarei32throughout; Layer 3 correctly characterized as RNG-free). No new issues.
Tyre (architecture): APPROVE
Both comments cleanly resolved. T1: the
up_todoc now precisely describes thedistinction — the cascade respects
up_to, butrun_work_itemhardcodesSettlement; per-request depth deferred to #1021. T2: the test pins bothordering invariants with a comment tying them to the cascade guards. No new
drift; D-225 DB-free contract intact, cache loop unchanged, the single
f32→integer boundary correctly placed, D-195 amendment matches the code.
Verdict: APPROVED
Both reviewers approve. Ready to merge.
Pull request closed