fix(simulation): PR #178 review round — H1-H4, T1, T2

H1: split_edge_at now splits length_cells geometric-distance proportionally (fixed-order f64 polyline sums, halves sum exactly to parent) — subsumes the 2-point-parent all-or-nothing bug. H2: chained-snap test (minor onto minor spur) + determinism assertion actually exercising attach_minors + direct 2-point split unit test. H3: cascade-level RailHeadFacing end-to-end assertion on a plus-shaped landmass fixture (2 deterministic degree-3 settlement junctions). H4+T2: STANDALONE_HQ_JOIN_SQL shared constant, both readers compose it; believability lockstep unit test. T1: HUB_SPACING_DIAG_PX doc cross-refs D-243 elastic seam.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 17:03:30 +02:00
co-authored by Claude Fable 5
parent 5d37d205e1
commit 1fee6fd9da
4 changed files with 404 additions and 38 deletions
+35 -20
View File
@@ -62,6 +62,25 @@ use crate::simulation::generator::{
ProductionUbiquity, SettingType, SettlementClass, WorldTier,
};
// ---------------------------------------------------------------------------
// Shared SQL fragments
// ---------------------------------------------------------------------------
/// The D-242 standalone-corp-HQ LEFT JOIN (T-1076 §1; single source of truth,
/// PR #178 T2). Marks an `atlas_city_names` row (aliased `acn`) as a Standalone
/// corp-HQ company town when a `corporations` row (aliased `c`) matches on
/// `hq_placement = 'Standalone'`, `headquarters_body = body_id`, and
/// `proper_name = name` — the exact shape
/// `populate_standalone_hq_settlements` (economy_import/corporations.py) emits
/// settlement rows with. Composed into a query whose FROM clause aliases
/// `atlas_city_names AS acn`; the flag is read as `c.corp_id IS NOT NULL`.
/// Used by [`CityContextReader::read_body_settlements`] and the believability
/// harness's `read_cities` — extend both if the join shape ever changes.
pub(crate) const STANDALONE_HQ_JOIN_SQL: &str = "LEFT JOIN corporations AS c
ON c.hq_placement = 'Standalone'
AND c.headquarters_body = acn.body_id
AND c.proper_name = acn.name";
// ---------------------------------------------------------------------------
// Error type
// ---------------------------------------------------------------------------
@@ -266,14 +285,12 @@ impl CityContextReader {
/// any malformed field), this method is best-effort: it never fails on row
/// content, only on a DB/connection error.
///
/// `is_standalone_hq` (T-1076 §1): a LEFT JOIN against `corporations`
/// marks the rows that are D-242 Standalone corp-HQ company towns
/// (`hq_placement = 'Standalone'`, `headquarters_body = body_id`,
/// `proper_name = name` — the exact shape `populate_standalone_hq_settlements`
/// emits them with). The road-graph hub rule demotes these to minor nodes
/// regardless of population. The believability harness's own settlement
/// read (`believability::read_cities`) performs the identical join — keep
/// the two in lockstep.
/// `is_standalone_hq` (T-1076 §1): the [`STANDALONE_HQ_JOIN_SQL`] LEFT JOIN
/// marks the rows that are D-242 Standalone corp-HQ company towns. The
/// road-graph hub rule demotes these to minor nodes regardless of
/// population. The believability harness's own settlement read
/// (`believability::read_cities`) composes the same shared constant —
/// single source of truth for the join (PR #178 T2).
pub fn read_body_settlements(
&self,
body_id: &str,
@@ -282,19 +299,17 @@ impl CityContextReader {
.conn
.lock()
.map_err(|e| CityContextReadError::Db(format!("mutex poisoned: {e}")))?;
let sql = format!(
"SELECT acn.id, acn.name, acn.economic_role, acn.population,
acn.settlement_class, COALESCE(acn.kind, 'city'),
c.corp_id IS NOT NULL
FROM atlas_city_names AS acn
{STANDALONE_HQ_JOIN_SQL}
WHERE acn.body_id = ?1
ORDER BY acn.id"
);
let mut stmt = conn
.prepare(
"SELECT acn.id, acn.name, acn.economic_role, acn.population,
acn.settlement_class, COALESCE(acn.kind, 'city'),
c.corp_id IS NOT NULL
FROM atlas_city_names AS acn
LEFT JOIN corporations AS c
ON c.hq_placement = 'Standalone'
AND c.headquarters_body = acn.body_id
AND c.proper_name = acn.name
WHERE acn.body_id = ?1
ORDER BY acn.id",
)
.prepare(&sql)
.map_err(|e| CityContextReadError::Db(e.to_string()))?;
let rows = stmt
.query_map([body_id], |row| {