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:
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user