Files
settled-reach/server/tests/gen_fixtures.rs
T
jpmschweitzerandClaude Fable 5 172ce124c8 feat(simulation): T-1137 windowed district layer — queue-served, coalesced, determinism-tested (D-226 T-1124 SS1-SS4)
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>
2026-07-21 12:13:59 +02:00

751 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, REGION_TEMP_NONE_DC,
};
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)],
},
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,
district_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,
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
};
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(),
);
}