Files
settled-reach/server/src/atlas/block_irregularity.rs
T
jpmschweitzerandClaude Opus 4.8 c71c26a1a6 refactor(simulation): rename DistrictSkeleton→QuarterSkeleton per D-222 (#950)
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>
2026-06-05 13:06:44 +02:00

120 lines
4.2 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.
//! BlockIrregularity derivation from founding_age and PoliticalArchetype (D-216).
//!
//! `block_irregularity` (0.0–1.0) controls how much a block deviates from
//! the district's canonical grid. Minimum 0.05 — no block is perfectly regular.
//!
//! **Formula (D-216):**
//! ```text
//! base_irregularity = (founding_age_years / 1000.0).min(1.0)
//! archetype_step = Commission|Military → -0.3, Corporate|Academic → -0.1,
//! Industrial → 0.0, Pioneer → +0.3
//! block_irregularity = (base + step).clamp(0.05, 1.0)
//! ```
//!
//! **Determinism (D-010, D-216):** Integer-scaled intermediates; archetype_step
//! stored as basis points (i32, 1 bp = 0.001). Final result is f32 from integer
//! arithmetic to match the D-216 formula.
use crate::simulation::generator::PoliticalArchetype;
/// Compute the `block_irregularity` value for one block.
///
/// - `founding_age_years`: years since the settlement was founded (integer).
/// - `archetype`: the settlement's political archetype.
///
/// Returns a value in [0.05, 1.0].
pub fn block_irregularity(founding_age_years: u32, archetype: &PoliticalArchetype) -> f32 {
// base_irregularity in integer basis-points (0–1000, where 1000 = 1.0).
let base_bp: i32 = (founding_age_years as i32).min(1000);
// archetype_step in basis-points.
let step_bp: i32 = archetype_step_bp(archetype);
// block_irregularity_bp clamped to [50, 1000] (0.05–1.0).
let result_bp = (base_bp + step_bp).clamp(50, 1000);
result_bp as f32 / 1000.0
}
fn archetype_step_bp(archetype: &PoliticalArchetype) -> i32 {
match archetype {
PoliticalArchetype::Commission | PoliticalArchetype::Military => -300,
PoliticalArchetype::Corporate | PoliticalArchetype::Academic => -100,
PoliticalArchetype::Industrial => 0,
PoliticalArchetype::Pioneer => 300,
}
}
/// Derive the maximum block offset in subtiles from `block_irregularity`.
///
/// Used by `DistrictLayoutMode::Organic`: `max_offset = (irregularity × 16.0) as i16`.
pub fn max_offset_sim_tiles(irregularity: f32) -> i16 {
(irregularity * 16.0) as i16
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minimum_is_0_05() {
// New Commission city (age 0) → base 0, step -300 → clamp to 50bp = 0.05.
let v = block_irregularity(0, &PoliticalArchetype::Commission);
assert!((v - 0.05).abs() < 1e-6, "expected 0.05, got {v}");
}
#[test]
fn maximum_is_1_0() {
// Old Pioneer city (age 1000+) → base 1000, step +300 → clamp to 1000bp = 1.0.
let v = block_irregularity(1500, &PoliticalArchetype::Pioneer);
assert!((v - 1.0).abs() < 1e-6, "expected 1.0, got {v}");
}
#[test]
fn pioneer_more_irregular_than_commission() {
let pioneer = block_irregularity(400, &PoliticalArchetype::Pioneer);
let commission = block_irregularity(400, &PoliticalArchetype::Commission);
assert!(
pioneer > commission,
"Pioneer ({pioneer}) should be more irregular than Commission ({commission})"
);
}
#[test]
fn age_increases_irregularity() {
let young = block_irregularity(50, &PoliticalArchetype::Industrial);
let old = block_irregularity(800, &PoliticalArchetype::Industrial);
assert!(
old > young,
"Older settlement ({old}) should be more irregular than young ({young})"
);
}
#[test]
fn max_offset_scales_with_irregularity() {
assert_eq!(max_offset_sim_tiles(0.05), 0); // 0.05 × 16 = 0.8 → 0
assert_eq!(max_offset_sim_tiles(1.0), 16);
assert_eq!(max_offset_sim_tiles(0.5), 8);
}
#[test]
fn all_archetypes_produce_valid_range() {
let archetypes = [
PoliticalArchetype::Commission,
PoliticalArchetype::Corporate,
PoliticalArchetype::Pioneer,
PoliticalArchetype::Military,
PoliticalArchetype::Academic,
PoliticalArchetype::Industrial,
];
for a in &archetypes {
let v = block_irregularity(300, a);
assert!(
(0.05..=1.0).contains(&v),
"archetype {:?} gave {v} out of [0.05, 1.0]",
a
);
}
}
}