Files
settled-reach/server/tests/gen_fixtures.rs
T
jpmschweitzerandClaude Fable 5 933e1f4ee4 fix(simulation): one absolute-metre derive core (D-256, T-1174)
derive_district_profile is now a thin wrapper over the shared
derive_at_metres_with_riparian core at survey-cell-centre world metres
— one derive core, two position sets. The batch pseudo-grid and the
true D-243 district grid were two coordinate systems sharing one bare
(i32,i32) type; the new SurveyCellPos newtype re-keys every batch
product (BodyWorldState.districts, Layer1Output.survey_basin_dirs) so
the compiler rejects cross-namespace passing.

Fixes two latent same-position divergences the T-1174 investigation
surfaced: three inconsistent latitude conventions collapse into the
core's single inverse mapping, and the region-climate baseline now
floor-divides true world metres instead of collapsing the whole body
onto region (0,0)'s baseline — batch climate becomes latitude/region
graded (D-245 direction: every changed believability metric increased).

Binding preservations per D-256(c): basin_direction rides a post-call
override with the true L1 D8 survey-cell aggregate (layer1's map
re-keyed to SurveyCellPos, identity lookup — a floor-divide lookup
against the pseudo-keyed map would have silently defaulted every cell
North); the riparian verdict comes from near_perennial_water_at, never
the empty-slice default (which would have flipped riverside
vegetation_class).

Quarter-skeleton morphology_zone now resolves at the settlement's
exact world position via derive_at_metres at work-item execution
(where TerrainAnalysisCache lives), replacing the survey-cell-centre
map lookup (D-256(d)); settlement_district_pos fixed to true-district
floor-division in passing (same doc/impl mismatch class). Second
pixel-vs-metre conflation fixed in aliveness_probe's anchor-walk math.

Window path byte-unchanged (window_derivation_golden 6/6 byte-
identical); derivation_harness golden untouched; believability golden
regenerated. Full lib + integration suites green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 00:40:13 +02:00

759 lines
26 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Generate MessagePack fixture files for cross-language testing (D-030 Layer 1).
//! Run with: cargo test --test gen_fixtures -- --ignored
use settled_reach_server::atlas::body_world_state::{DrainageBasin, RiverNetwork};
use settled_reach_server::atlas::layer1::Layer1Output;
use settled_reach_server::atlas::layer_proxy::{
AtlasLayerResponse, AtlasLayerStatus, DistrictWindowLayer, QuarterFootprintEntry,
QuarterFootprintLayer, RegionGridLayer, RoadGraphEdge, RoadGraphLayer, RoadGraphNode,
SettlementEntry, SettlementLayer, SettlementSizeClass, WindowGranularity, REGION_TEMP_NONE_DC,
WINDOW_GRANULARITY_DISTRICT,
};
use settled_reach_server::atlas::region_profile::{SeasonPhase, WeatherState};
use settled_reach_server::atlas::road_graph::RoadNodeKind;
use settled_reach_server::bridge::types::*;
use settled_reach_server::simulation::generator::{
AttractorType, DistrictType, GeographicAttractor, MaintenanceAuthority, SubBiomeVariant,
ZoningType,
};
use settled_reach_server::simulation::poi::PoiCategory;
use settled_reach_server::simulation::time::{DayPhase, TickRate};
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
fn write_fixture(name: &str, bytes: &[u8]) {
// Write directly into the Godot project's test fixtures (single source of truth)
let dir = Path::new("../client/tests/fixtures/msgpack");
fs::create_dir_all(dir).expect("create fixture dir");
let path = dir.join(format!("{}.msgpack", name));
fs::write(&path, bytes).expect("write fixture");
eprintln!("Wrote {} ({} bytes)", path.display(), bytes.len());
}
/// Helper to create a minimal snapshot for fixtures (D-192: no version field)
fn fixture_snapshot(tick: u64, entities: Vec<VisibleEntity>) -> ObserverSnapshot {
ObserverSnapshot {
tick,
game_time: GameTime {
day: 0,
time_of_day: 0,
day_phase: DayPhase::Morning,
tick_rate: TickRate::Full,
},
player_facing: FacingDirection::North,
player_stance: MovementStance::default(),
player_inventory: vec![],
entities,
visible_tiles: vec![],
nearby_interactions: vec![],
current_monologue: None,
pending_recognitions: vec![],
dialogue_response: None,
blocked_entities: vec![],
scan_events: vec![],
sound_events: vec![],
follow_state: None,
character_pressure: None,
rng_seed: None,
poi_list: vec![],
examine_result: None,
player_knowledge: None,
save_result: None,
triangle_crisis_events: vec![],
state_hash: None,
debug_response: None,
sim_errors: vec![],
current_ticker: None,
settings_response: None,
economy_snapshot: None,
bookmark_catalog: None,
}
}
#[test]
#[ignore] // Run manually: cargo test --test gen_fixtures -- --ignored
fn generate_msgpack_fixtures() {
// Snapshot with one NPC entity
let snapshot = fixture_snapshot(
42,
vec![VisibleEntity {
entity_id: 1,
x: 10.0,
y: 20.0,
z: 0,
kind: EntityKind::Npc,
visibility: VisibilitySector::Forward,
relationship: RelationshipState::Unknown,
observation: EntityVisibility::Visible,
tell_state: None,
}],
);
write_fixture(
"snapshot_one_npc",
&rmp_serde::to_vec_named(&snapshot).unwrap(),
);
// Empty snapshot
let empty = fixture_snapshot(0, vec![]);
write_fixture("snapshot_empty", &rmp_serde::to_vec_named(&empty).unwrap());
// PlayerInput: MoveNorth
let input_north = PlayerInput {
tick: 100,
action: PlayerAction::MoveNorth,
};
write_fixture(
"input_move_north",
&rmp_serde::to_vec_named(&input_north).unwrap(),
);
// PlayerInput: UsePerceptionMode
let input_perception = PlayerInput {
tick: 200,
action: PlayerAction::UsePerceptionMode("thermal".to_string()),
};
write_fixture(
"input_perception_mode",
&rmp_serde::to_vec_named(&input_perception).unwrap(),
);
// Snapshot with Player entity
let snapshot_player = fixture_snapshot(
1,
vec![VisibleEntity {
entity_id: 100,
x: 16.5,
y: 16.5,
z: 0,
kind: EntityKind::Player,
visibility: VisibilitySector::Forward,
relationship: RelationshipState::Unknown,
observation: EntityVisibility::Visible,
tell_state: None,
}],
);
write_fixture(
"snapshot_player",
&rmp_serde::to_vec_named(&snapshot_player).unwrap(),
);
// Snapshot with multiple entities and all EntityKind variants
let snapshot_multi = fixture_snapshot(
999,
vec![
VisibleEntity {
entity_id: 1,
x: 16.5,
y: 16.5,
z: 0,
kind: EntityKind::Player,
visibility: VisibilitySector::Forward,
relationship: RelationshipState::Known,
observation: EntityVisibility::Visible,
tell_state: None,
},
VisibleEntity {
entity_id: 2,
x: 5.0,
y: 10.0,
z: 0,
kind: EntityKind::Npc,
visibility: VisibilitySector::Peripheral,
relationship: RelationshipState::Unknown,
observation: EntityVisibility::Visible,
tell_state: None,
},
VisibleEntity {
entity_id: 3,
x: 15.5,
y: 3.0,
z: 1,
kind: EntityKind::Object,
visibility: VisibilitySector::Forward,
relationship: RelationshipState::Unknown,
observation: EntityVisibility::Visible,
tell_state: None,
},
VisibleEntity {
entity_id: 4,
x: 0.0,
y: 0.0,
z: -1,
kind: EntityKind::Terrain,
visibility: VisibilitySector::Forward,
relationship: RelationshipState::Unknown,
observation: EntityVisibility::Visible,
tell_state: None,
},
],
);
write_fixture(
"snapshot_multi_entity",
&rmp_serde::to_vec_named(&snapshot_multi).unwrap(),
);
// v2 snapshot with visible_tiles and game_time populated
let snapshot_v2_full = ObserverSnapshot {
tick: 500,
game_time: GameTime {
day: 1,
time_of_day: 720,
day_phase: DayPhase::Evening,
tick_rate: TickRate::Full,
},
player_facing: FacingDirection::Southeast,
player_stance: MovementStance::default(),
player_inventory: vec![],
entities: vec![VisibleEntity {
entity_id: 1,
x: 10.5,
y: 10.5,
z: 0,
kind: EntityKind::Player,
visibility: VisibilitySector::Forward,
relationship: RelationshipState::Unknown,
observation: EntityVisibility::Visible,
tell_state: None,
}],
visible_tiles: vec![
VisibleTile {
x: 10,
y: 10,
z: 0,
visibility: VisibilitySector::Forward,
tile_kind: TileKind::Floor,
zone_id: Some(1),
},
VisibleTile {
x: 11,
y: 10,
z: 0,
visibility: VisibilitySector::Peripheral,
tile_kind: TileKind::Floor,
zone_id: Some(1),
},
VisibleTile {
x: 10,
y: 9,
z: 0,
visibility: VisibilitySector::Forward,
tile_kind: TileKind::Floor,
zone_id: None,
},
],
nearby_interactions: vec![],
current_monologue: None,
pending_recognitions: vec![],
dialogue_response: None,
blocked_entities: vec![],
scan_events: vec![],
sound_events: vec![],
follow_state: None,
character_pressure: None,
rng_seed: None,
poi_list: vec![],
examine_result: None,
player_knowledge: None,
save_result: None,
triangle_crisis_events: vec![],
state_hash: None,
debug_response: None,
sim_errors: vec![],
current_ticker: None,
settings_response: None,
economy_snapshot: None,
bookmark_catalog: None,
};
write_fixture(
"snapshot_v2_full",
&rmp_serde::to_vec_named(&snapshot_v2_full).unwrap(),
);
// Batch input: Vec<PlayerInput> with two actions (D-030 Layer 1 bidirectional symmetry)
let input_batch = vec![
PlayerInput {
tick: 0,
action: PlayerAction::MoveNorth,
},
PlayerInput {
tick: 0,
action: PlayerAction::Interact {
target_entity_id: None,
verb: None,
},
},
];
write_fixture(
"input_batch_two",
&rmp_serde::to_vec_named(&input_batch).unwrap(),
);
// PlayerInput: DialogueResponse (#539)
let input_dialogue_response = PlayerInput {
tick: 300,
action: PlayerAction::DialogueResponse {
target_entity_id: 42,
response_id: "kael-davan_d_001".to_string(),
},
};
write_fixture(
"input_dialogue_response",
&rmp_serde::to_vec_named(&input_dialogue_response).unwrap(),
);
// Diagonal movement fixtures (clockwise: NE, SE, SW, NW)
for (name, action) in [
("input_move_northeast", PlayerAction::MoveNortheast),
("input_move_southeast", PlayerAction::MoveSoutheast),
("input_move_southwest", PlayerAction::MoveSouthwest),
("input_move_northwest", PlayerAction::MoveNorthwest),
] {
let input = PlayerInput { tick: 100, action };
write_fixture(name, &rmp_serde::to_vec_named(&input).unwrap());
}
// === #271 fixtures: named fixtures for cross-language Layer 1 testing ===
// snapshot_minimal: tick=0, one Player entity, all optionals absent
let snapshot_minimal = fixture_snapshot(
0,
vec![VisibleEntity {
entity_id: 1,
x: 0.0,
y: 0.0,
z: 0,
kind: EntityKind::Player,
visibility: VisibilitySector::Forward,
relationship: RelationshipState::Unknown,
observation: EntityVisibility::Visible,
tell_state: None,
}],
);
write_fixture(
"snapshot_minimal",
&rmp_serde::to_vec_named(&snapshot_minimal).unwrap(),
);
// snapshot_full: tick=42, monologue + dialogue + inventory + POIs + KG dump
let snapshot_full = ObserverSnapshot {
tick: 42,
game_time: GameTime {
day: 3,
time_of_day: 840,
day_phase: DayPhase::Evening,
tick_rate: TickRate::Full,
},
player_facing: FacingDirection::East,
player_stance: MovementStance::Walk,
player_inventory: vec![InventoryItem {
item_id: 7,
name: "Forged Customs Cert".to_string(),
slot: 0,
}],
entities: vec![VisibleEntity {
entity_id: 1,
x: 10.0,
y: 10.0,
z: 0,
kind: EntityKind::Player,
visibility: VisibilitySector::Forward,
relationship: RelationshipState::Unknown,
observation: EntityVisibility::Visible,
tell_state: None,
}],
visible_tiles: vec![],
nearby_interactions: vec![],
current_monologue: Some(MonologueEvent {
id: "test_monologue_001".to_string(),
text: "Something feels off about this place.".to_string(),
duration_seconds: 4.0,
}),
pending_recognitions: vec![PendingRecognitionWire {
entity_id: 7,
x: 12.0,
y: 8.0,
z: 0,
remaining_ticks: 3,
total_delay_ticks: 10,
}],
dialogue_response: Some(DialogueResponseEvent {
line_id: "kael_d_001".to_string(),
text: "We need to talk about the shipment.".to_string(),
speaker_entity_id: 99,
speaker_color_index: 2,
speaker_name: "Kael".to_string(),
}),
blocked_entities: vec![5, 6],
scan_events: vec![],
sound_events: vec![],
follow_state: None,
character_pressure: None,
rng_seed: Some(0xDEADBEEF),
poi_list: vec![PoiWire {
poi_id: "docking_bay_7".to_string(),
name: "Docking Bay 7".to_string(),
x: 50,
y: 30,
z: 0,
category: PoiCategory::Location,
}],
examine_result: Some(ExamineResultWire {
entity_id: 42,
text: "A smuggler, probably. The way they hold themselves.".to_string(),
confidence: KnowledgeConfidence::KnowsOf,
}),
save_result: None,
player_knowledge: Some(PlayerKnowledgeWire {
entities: vec![KnownEntityWire {
entity_id: 99,
name: "Kael".to_string(),
confidence: KnowledgeConfidence::KnowsDetails,
source: "DirectObservation".to_string(),
state: KnowledgeState::Active,
relationship: RelationshipState::Known,
last_observed_tick: 40,
}],
facts: vec![KnownFactWire {
fact_id: "poi.docking_bay_7".to_string(),
confidence: KnowledgeConfidence::KnowsOf,
source: "DirectObservation".to_string(),
state: KnowledgeState::Active,
acquired_tick: 10,
}],
}),
triangle_crisis_events: vec![],
state_hash: None,
debug_response: None,
sim_errors: vec![],
current_ticker: None,
settings_response: None,
economy_snapshot: None,
bookmark_catalog: None,
};
write_fixture(
"snapshot_full",
&rmp_serde::to_vec_named(&snapshot_full).unwrap(),
);
// player_input_move: tick=1, MoveNorth
let input_move = PlayerInput {
tick: 1,
action: PlayerAction::MoveNorth,
};
write_fixture(
"player_input_move",
&rmp_serde::to_vec_named(&input_move).unwrap(),
);
// player_input_interact: tick=2, Interact { target: 99, verb: "Talk" }
let input_interact = PlayerInput {
tick: 2,
action: PlayerAction::Interact {
target_entity_id: Some(99),
verb: Some("Talk".to_string()),
},
};
write_fixture(
"player_input_interact",
&rmp_serde::to_vec_named(&input_interact).unwrap(),
);
// malformed: intentionally truncated bytes — tests error handling in both Rust and GDScript
// 0x82 = fixmap with 2 entries, 0xa4 = fixstr of length 4 — incomplete map, no key/value follows
write_fixture("malformed", &[0x82u8, 0xa4u8]);
// === Boundary value fixtures (#472) ===
// 14 raw integer values at encoding format boundaries (Appendix C).
// These are Rust-encoded MessagePack that GDScript must decode correctly.
// Covers every encoding format transition and the int16/int32 asymmetry zones.
let boundary_raw: [(u64, &str); 14] = [
// pos fixint boundaries
(0, "boundary_raw_0"),
(127, "boundary_raw_127"),
// uint 8 boundaries
(128, "boundary_raw_128"),
(255, "boundary_raw_255"),
// int16/uint16 asymmetry zone (GDScript: int_16, Rust: uint_16)
(256, "boundary_raw_256"),
(32767, "boundary_raw_32767"),
// uint 16 boundaries
(32768, "boundary_raw_32768"),
(65535, "boundary_raw_65535"),
// int32/uint32 asymmetry zone (GDScript: int_32, Rust: uint_32)
(65536, "boundary_raw_65536"),
(2147483647, "boundary_raw_2147483647"),
// uint 32 boundaries
(2147483648, "boundary_raw_2147483648"),
(4294967295, "boundary_raw_4294967295"),
// int 64 boundaries
(4294967296, "boundary_raw_4294967296"),
(u64::MAX >> 1, "boundary_raw_i64_max"), // 2^63-1 = i64::MAX
];
for (value, name) in &boundary_raw {
// Encode as u64 (matches how entity_id/tick are encoded in snapshots)
let bytes = rmp_serde::to_vec(value).expect("encode boundary value");
write_fixture(name, &bytes);
}
// 5 snapshot fixtures at boundary tick values.
// Tests that GDScript can decode full ObserverSnapshot structs when the tick
// field crosses encoding format boundaries.
let boundary_snapshots: [(u64, &str); 5] = [
(0, "snapshot_boundary_tick_0"), // pos fixint
(127, "snapshot_boundary_tick_127"), // pos fixint max
(32767, "snapshot_boundary_tick_32767"), // int16/uint16 asymmetry
(2147483647, "snapshot_boundary_tick_2b31m1"), // int32/uint32 asymmetry
(4294967296, "snapshot_boundary_tick_2b32"), // int64 minimum
];
for (tick, name) in &boundary_snapshots {
let snapshot = fixture_snapshot(*tick, vec![]);
write_fixture(name, &rmp_serde::to_vec_named(&snapshot).unwrap());
}
}
/// Generate a snapshot fixture with a populated `BookmarkCatalog`.
///
/// Client (#618) uses this to validate GDScript MessagePack decode against
/// real `rmp_serde` output — field order and string encoding may diverge
/// from GDScript-constructed data.
#[test]
#[ignore] // Run manually: cargo test --test gen_fixtures -- --ignored
fn generate_snapshot_with_bookmark_catalog() {
let catalog = BookmarkCatalog {
bookmarks: vec![BookmarkWire {
id: "tycoon".into(),
title: "Tycoon".into(),
subtitle: "Small business owner on the make.".into(),
flavor: String::new(),
default_location: "GJ 35".into(),
allowed_locations: vec!["GJ 35".into()],
allowed_locations_cultures: vec!["frontier_industrial".into()],
career: CareerKindWire::Tycoon,
starting_capital_tractus: 5_000,
}],
};
let mut snapshot = fixture_snapshot(0, vec![]);
snapshot.bookmark_catalog = Some(catalog);
let bytes =
rmp_serde::to_vec_named(&snapshot).expect("serialize snapshot_with_bookmark_catalog");
write_fixture("snapshot_with_bookmark_catalog", &bytes);
}
/// Atlas layer-stream responses (#969, D-225) — the client decodes these to
/// render the per-layer Atlas overlays (#960). Covers Ready (with a small
/// Layer1Output), Pending, and NotFound.
#[test]
#[ignore] // Run manually: cargo test --test gen_fixtures -- --ignored
fn generate_atlas_layer_response_fixtures() {
let layer1 = Layer1Output {
body_id: "GJ1c".into(),
river_network: RiverNetwork {
river_cells: vec![(12, 58), (12, 59)],
confluences: vec![],
mouths: vec![(12, 58)],
river_class: vec![1, 2],
river_downstream: vec![2, 8], // 2=E direction; 8=MOUTH sentinel
river_seaward: vec![(0, 0), (12, 60)], // meaningful only for the MOUTH entry
},
drainage_basins: vec![DrainageBasin {
basin_id: 1,
boundary: vec![(0, 0), (0, 10), (10, 10), (10, 0)],
area_pct: 0.42,
territorial_status:
settled_reach_server::simulation::generator::TerritorialStatus::FrontierUnclaimed,
}],
attractors: vec![GeographicAttractor {
position: (12, 58),
attractor_type: AttractorType::CoastalAccess,
strength: 90,
sub_biome: SubBiomeVariant::CoastalLowland,
terrain_modification_cost: 170,
water_bearing: 90,
}],
grid_w: 512,
grid_h: 256,
survey_basin_dirs: std::collections::BTreeMap::new(),
};
// T-960 §1/§2: a small populated RoadGraphLayer + SettlementLayer, one
// settlement (a capital) connected to one waypoint-free short edge.
let road_graph = RoadGraphLayer {
nodes: vec![
RoadGraphNode {
position: (12, 58),
kind: RoadNodeKind::Settlement,
city_id: Some(1),
},
RoadGraphNode {
position: (20, 70),
kind: RoadNodeKind::Settlement,
city_id: Some(2),
},
],
edges: vec![RoadGraphEdge {
from: 0,
to: 1,
path: vec![(12, 58), (16, 64), (20, 70)],
maintenance: MaintenanceAuthority::Administrative,
is_rail: false,
named_route_id: None,
}],
};
let settlements = SettlementLayer {
settlements: vec![
SettlementEntry {
city_id: 1,
name: "Port Aldren".into(),
position: (12, 58),
size_class: SettlementSizeClass::Major,
is_capital: true,
is_port: true,
},
SettlementEntry {
city_id: 2,
name: "Farmstead Rell".into(),
position: (20, 70),
size_class: SettlementSizeClass::Minor,
is_capital: false,
is_port: false,
},
],
};
// T-1113: a small populated RegionGridLayer — a 2×1 covering grid, one
// temperate region and one airless-style region (mean_temp_dc sentinel),
// mirroring the values exercised by
// `build_region_grid_encodes_dense_quantized_climate` in layer_proxy.rs.
let region_grid = RegionGridLayer {
cols: 2,
rows: 1,
season: vec![SeasonPhase::Summer as u8, SeasonPhase::Winter as u8],
weather: vec![WeatherState::Clear as u8, WeatherState::Snow as u8],
mean_temp_dc: vec![
123,
settled_reach_server::atlas::layer_proxy::REGION_TEMP_NONE_DC,
],
moisture_q: vec![80, 5],
};
// T-1119 (D-226 T-1112 amendment): a populated QuarterFootprintLayer
// covering both fixture settlements — city_id 1 (Port Aldren, a dense
// Commercial-dominant quarter with landmarks/corridors) and city_id 2
// (Farmstead Rell, a sparse Residential-dominant quarter with neither),
// so the fixture exercises both a "rich" entry and a "minimal" entry
// rather than only one shape.
let quarter_footprints = QuarterFootprintLayer {
entries: BTreeMap::from([
(
1,
QuarterFootprintEntry {
city_id: 1,
density_avg_pct: 62,
dominant_district_type: DistrictType::Commercial,
dominant_zoning: ZoningType::Commercial,
landmark_count: 3,
corridor_count: 4,
},
),
(
2,
QuarterFootprintEntry {
city_id: 2,
density_avg_pct: 18,
dominant_district_type: DistrictType::Residential,
dominant_zoning: ZoningType::Residential,
landmark_count: 0,
corridor_count: 1,
},
),
]),
};
let ready = AtlasLayerResponse {
body_id: "GJ1c".into(),
status: AtlasLayerStatus::Ready,
layer1: Some(layer1),
district_grid: None,
road_graph: Some(road_graph),
settlements: Some(settlements),
region_grid: Some(region_grid),
district_window: None,
quarter_footprints: Some(quarter_footprints),
};
write_fixture(
"atlas_response_ready",
&rmp_serde::to_vec_named(&ready).unwrap(),
);
let pending = AtlasLayerResponse {
body_id: "GJ1c".into(),
status: AtlasLayerStatus::Pending,
layer1: None,
district_grid: None,
road_graph: None,
settlements: None,
region_grid: None,
district_window: None,
quarter_footprints: None,
};
write_fixture(
"atlas_response_pending",
&rmp_serde::to_vec_named(&pending).unwrap(),
);
let not_found = AtlasLayerResponse {
body_id: "ghost".into(),
status: AtlasLayerStatus::NotFound,
layer1: None,
district_grid: None,
road_graph: None,
settlements: None,
region_grid: None,
district_window: None,
quarter_footprints: None,
};
write_fixture(
"atlas_response_not_found",
&rmp_serde::to_vec_named(&not_found).unwrap(),
);
// D-226 T-1124 amendment, T-1137: a Ready response carrying a populated
// district_window — the windowed-family field, distinct from the five
// whole-body layers above. A small n=2 window keeps the fixture readable
// while exercising every field (including the REGION_TEMP_NONE_DC
// sentinel and VegetationClass::Marine = 6, both non-negotiable per the
// amendment §3).
let window = DistrictWindowLayer {
center: (10, -5),
n: 2,
granularity: WINDOW_GRANULARITY_DISTRICT,
granularity_v2: WindowGranularity::District,
min_wl_m: 0,
morphology: vec![0, 8, 14, 16], // OpenOcean, AlluvialPlain, Alpine, Wetland
elev_q: vec![0, 45, 98, 60],
temp_dc: vec![205, 150, REGION_TEMP_NONE_DC, 80], // 20.5°C, 15.0°C, airless sentinel, 8.0°C
moisture_q: vec![90, 55, 0, 100],
vegetation: vec![6, 3, 0, 5], // Marine, Forest, Absent, RiparianThicket
glaciation: vec![0, 0, 4, 1], // None, None, IceCap, Light
courses: vec![],
};
let ready_with_window = AtlasLayerResponse {
body_id: "GJ1c".into(),
status: AtlasLayerStatus::Ready,
layer1: None,
district_grid: None,
road_graph: None,
settlements: None,
region_grid: None,
district_window: Some(window),
quarter_footprints: None,
};
write_fixture(
"atlas_response_ready_with_window",
&rmp_serde::to_vec_named(&ready_with_window).unwrap(),
);
}