fix(simulation): address PR #142 review — 11 issues resolved

Rust fixes: D-218 Backwater complexity (moved to Full arm), priority
queue dispatch gated on thread saturation, panic→soft-fail in
name_index, duplicated LCG unified into atlas::rng module, misleading
Safety comment removed, D-210 SubBiomeVariant deferred to #948.

Schema/data fixes: atlas_city_positions table (D-211 persistence),
settlement_class column on atlas_city_names, per-body transaction
boundaries in import_province_boundaries, import_city_names.py added
to GENERATOR_SOURCES, Python perf documented.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 15:30:50 +02:00
co-authored by Claude Opus 4.6
parent 1a2bc58e27
commit 332b9404be
13 changed files with 239 additions and 101 deletions
+1
View File
@@ -51,6 +51,7 @@ GENERATOR_SOURCES: dict[str, list[Path]] = {
REPO_ROOT / "tooling" / "planet-gen" / "generate_atlas.py",
REPO_ROOT / "tooling" / "planet-gen" / "gemma_naming.py",
REPO_ROOT / "tooling" / "planet-gen" / "naming_core.py",
REPO_ROOT / "tooling" / "planet-gen" / "import_city_names.py",
REPO_ROOT / "tooling" / "planet-gen" / "import_heightmaps.py",
REPO_ROOT / "tooling" / "planet-gen" / "import_province_boundaries.py",
REPO_ROOT / "tooling" / "schema_version.py",
+22 -9
View File
@@ -303,15 +303,16 @@ CREATE INDEX IF NOT EXISTS idx_atlas_body_heightmaps_body ON atlas_body_heightma
-- City name reservations (D-207, #902)
CREATE TABLE IF NOT EXISTS atlas_city_names (
id INTEGER PRIMARY KEY AUTOINCREMENT,
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
name TEXT NOT NULL,
kind TEXT NOT NULL DEFAULT 'city',
economic_role TEXT NOT NULL,
population INTEGER NOT NULL,
corp_id TEXT REFERENCES corporations(corp_id),
reserved INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
id INTEGER PRIMARY KEY AUTOINCREMENT,
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
name TEXT NOT NULL,
kind TEXT NOT NULL DEFAULT 'city',
economic_role TEXT NOT NULL,
population INTEGER NOT NULL,
settlement_class TEXT,
corp_id TEXT REFERENCES corporations(corp_id),
reserved INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_atlas_city_names_body ON atlas_city_names(body_id);
CREATE INDEX IF NOT EXISTS idx_atlas_city_names_kind ON atlas_city_names(kind);
@@ -338,6 +339,17 @@ CREATE TABLE IF NOT EXISTS atlas_province_boundaries (
);
CREATE INDEX IF NOT EXISTS idx_atlas_province_boundaries_body ON atlas_province_boundaries(body_id);
-- City positions — attractor-matched placement output (D-211, #34)
CREATE TABLE IF NOT EXISTS atlas_city_positions (
city_names_id INTEGER PRIMARY KEY REFERENCES atlas_city_names(id) ON DELETE CASCADE,
body_id TEXT NOT NULL REFERENCES bodies(body_id) ON DELETE CASCADE,
row INTEGER NOT NULL,
col INTEGER NOT NULL,
attractor_type TEXT NOT NULL,
score REAL NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_atlas_city_positions_body ON atlas_city_positions(body_id);
-- Normalize bodies.economic_role to the D-194 canonical 10-value set (#911).
-- Idempotent: each UPDATE is a no-op if the old value is already gone.
UPDATE bodies SET economic_role = 'agricultural' WHERE economic_role IN ('agriculture', 'mixed-agriculture');
@@ -357,6 +369,7 @@ COLUMN_MIGRATIONS = [
("brand_products", "price_tier", "TEXT"),
("bodies", "body_radius_km", "REAL"), # D-204 — physical radius in km, nullable
("meta", "schema_sha", "TEXT"),
("atlas_city_names", "settlement_class", "TEXT"), # D-196 — NULL until placement (#37)
]
+1
View File
@@ -130,6 +130,7 @@ def _write_stamp(conn: sqlite3.Connection) -> None:
Path(__file__),
_atlas_dir / "gemma_naming.py",
_atlas_dir / "naming_core.py",
_atlas_dir / "import_city_names.py",
_atlas_dir / "import_heightmaps.py",
_atlas_dir / "import_province_boundaries.py",
REPO_ROOT / "tooling" / "schema_version.py",
@@ -21,6 +21,13 @@ Algorithm:
Province count target: 4–12 per body (D-205). Bodies with low relief get fewer,
larger provinces; high-relief worlds get more.
Performance: ~3–5s per body on a single CPU core at canonical 512×256 resolution.
The bottleneck is the pure-Python depression-fill + flow-direction scan (O(H×W) each,
~131k cells). For a full run of ~270 inhabited bodies expect ~15–20 minutes.
Hot loops (_depression_fill, _flow_direction) are candidates for NumPy vectorization
if build time becomes a bottleneck; the current scalar implementation is correct
and deterministic, which takes priority at this stage.
Incremental: bodies that already have rows in atlas_province_boundaries are skipped
unless --force is passed.
@@ -437,17 +444,18 @@ def import_body_provinces(
print(f" {body_id}: {len(basins)} basins — {', '.join(areas)}")
if not dry_run:
conn.execute(
"DELETE FROM atlas_province_boundaries WHERE body_id = ?",
(body_id,),
)
for b in basins:
with conn: # per-body savepoint: rolls back this body on exception, keeps prior commits
conn.execute(
"""INSERT INTO atlas_province_boundaries
(body_id, basin_id, path, area_pct)
VALUES (?, ?, ?, ?)""",
(body_id, b["basin_id"], json.dumps(b["path"]), b["area_pct"]),
"DELETE FROM atlas_province_boundaries WHERE body_id = ?",
(body_id,),
)
for b in basins:
conn.execute(
"""INSERT INTO atlas_province_boundaries
(body_id, basin_id, path, area_pct)
VALUES (?, ?, ?, ?)""",
(body_id, b["basin_id"], json.dumps(b["path"]), b["area_pct"]),
)
return {"status": "imported", "imported": len(basins)}
@@ -523,9 +531,6 @@ def main() -> None:
n_errors += 1
print(f" [{i+1}/{len(bodies)}] {body_id:20s} ERROR: {result.get('message', '')}")
if not args.dry_run:
conn.commit()
conn.close()
elapsed_total = time.time() - t_total