feat(simulation): feature-name pipeline wired + legacy window_granularity u32 retired (T-1169, T-1159)

One commit for two tickets whose changes share the bridge/plugin
plumbing files. T-1169 connects the three dormant feature-name pieces:
atlas_feature_names populated at regen (17,891 rows — 15,190 mountain,
2,701 river — via populate_atlas_feature_names mirroring the city-names
importer; systems.db regenerated, stamp fresh), attach_feature_names
wired into the cascade's Topography block with name pools threaded
DB-free through AnalyzeBody (D-225 pattern) and assignments stored on
Layer1Output/BodyWorldState for future consumers, and a
FeatureNamesRequest/Response read proxy as the bridge's 7th tagged
envelope (D-236 pattern, both SimBridge impls). Client label DRAW is
deliberately NOT here — implementation proved both river and mountain
labels need a wire-carried position (the pool is position-free; course
polylines aren't correlated with the named attractors by construction) —
deferred to T-1195's single design pass. cascade_layer1 golden re-pinned
(additive feature_names field).

T-1159 retires the legacy u32 granularity field fully shadowed by
window_granularity_v2: AtlasLayerRequest.window_granularity,
DistrictWindowLayer.granularity echo, the u32::MAX sentinel, and
resolve_window_granularity are gone server-side; client encode paths and
the caller-less atlas_window_cache legacy key component dropped;
msgpack fixtures regenerated; the T-1150 aliasing regression test now
drives through the surviving enum field. The district_window carrier
itself survives byte-compatible per D-255(c).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 16:10:27 +02:00
co-authored by Claude Fable 5
parent befdb689c1
commit 8da9670e0f
28 changed files with 1244 additions and 467 deletions
+60 -3
View File
@@ -15,7 +15,8 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use crate::atlas::atlas_data_proxy::{
handle_city_names_request, handle_star_map_request, StarMapDataPath,
handle_city_names_request, handle_feature_names_request, handle_star_map_request,
StarMapDataPath,
};
use crate::atlas::attractor_matching::CityPlacement;
use crate::atlas::body_params_reader::BodyParamsReaderResource;
@@ -52,7 +53,8 @@ use crate::atlas::trait_swerve::{
};
use crate::bridge::{
AtlasRequestBuffer, AtlasResponseBuffer, BrowseRequestBuffer, BrowseResponseBuffer,
CityNamesRequestBuffer, CityNamesResponseBuffer, StarMapRequestBuffer, StarMapResponseBuffer,
CityNamesRequestBuffer, CityNamesResponseBuffer, FeatureNamesRequestBuffer,
FeatureNamesResponseBuffer, StarMapRequestBuffer, StarMapResponseBuffer,
StepCanvasRequestBuffer, StepCanvasResponseBuffer,
};
use crate::seed::{SeedChain, SeedDomain};
@@ -83,6 +85,10 @@ impl Plugin for GenerationPlugin {
Update,
serve_city_names_requests.in_set(TickPhase::PreInput),
)
.add_systems(
Update,
serve_feature_names_requests.in_set(TickPhase::PreInput),
)
.add_systems(Update, serve_browse_requests.in_set(TickPhase::PreInput))
.add_systems(
Update,
@@ -201,6 +207,26 @@ fn serve_city_names_requests(
}
}
/// Drain inbound feature-names requests and serve each through the proxy
/// (T-1169): D-236 Sol check, then the names-only `atlas_feature_names` read.
/// Mirrors [`serve_city_names_requests`] exactly.
fn serve_feature_names_requests(
mut requests: ResMut<FeatureNamesRequestBuffer>,
mut responses: ResMut<FeatureNamesResponseBuffer>,
city_reader: Option<Res<CityContextReaderResource>>,
) {
if requests.0.is_empty() {
return;
}
let reader = city_reader.as_ref().map(|r| &r.0);
let pending: Vec<_> = requests.0.drain(..).collect();
for (conn_id, req) in pending {
responses
.0
.push((conn_id, handle_feature_names_request(&req, reader)));
}
}
/// Drain inbound data-browser requests and serve each through the proxy
/// (D-254 §4, T-1131): one of the six v1 registry-tier entity kinds, dispatched
/// to `BrowseReader` by `(kind, query)`.
@@ -1043,6 +1069,8 @@ mod tests {
cities: vec![],
dominant_faction: None,
body_params: None, // T-1023: no DB params in this unit test
river_names: vec![],
mountain_names: vec![],
},
GenPriority::Immediate,
);
@@ -1079,7 +1107,6 @@ mod tests {
up_to: CascadeLayer::Topography,
window_center: None,
window_n: 0,
window_granularity: 0,
window_granularity_v2: None,
window_min_wl_m: 0,
},
@@ -1193,6 +1220,36 @@ mod tests {
assert!(world.resource::<CityNamesRequestBuffer>().0.is_empty());
}
#[test]
fn serve_feature_names_without_reader_is_error() {
use crate::atlas::atlas_data_proxy::{FeatureNamesRequest, FeatureNamesStatus};
let mut world = World::new();
world.insert_resource(FeatureNamesRequestBuffer(vec![(
ConnectionId(0),
FeatureNamesRequest {
feature_names: true,
body_id: "GJ1c".to_string(),
},
)]));
world.insert_resource(FeatureNamesResponseBuffer::default());
// No CityContextReaderResource.
let mut sched = Schedule::default();
sched.add_systems(serve_feature_names_requests);
sched.run(&mut world);
let responses = world.resource::<FeatureNamesResponseBuffer>();
assert_eq!(responses.0.len(), 1);
assert_eq!(responses.0[0].0, ConnectionId(0), "connection id preserved");
assert_eq!(responses.0[0].1.body_id, "GJ1c");
assert!(matches!(
responses.0[0].1.status,
FeatureNamesStatus::Error(_)
));
assert!(world.resource::<FeatureNamesRequestBuffer>().0.is_empty());
}
fn sample_read_set() -> CityEconomicReadSet {
use crate::simulation::generator::SettlementClass;
CityEconomicReadSet {