feat(simulation): feature-name pipeline wired + legacy window_granularity u32 retired (T-1169, T-1159)

One commit for two tickets whose changes share the bridge/plugin
plumbing files. T-1169 connects the three dormant feature-name pieces:
atlas_feature_names populated at regen (17,891 rows — 15,190 mountain,
2,701 river — via populate_atlas_feature_names mirroring the city-names
importer; systems.db regenerated, stamp fresh), attach_feature_names
wired into the cascade's Topography block with name pools threaded
DB-free through AnalyzeBody (D-225 pattern) and assignments stored on
Layer1Output/BodyWorldState for future consumers, and a
FeatureNamesRequest/Response read proxy as the bridge's 7th tagged
envelope (D-236 pattern, both SimBridge impls). Client label DRAW is
deliberately NOT here — implementation proved both river and mountain
labels need a wire-carried position (the pool is position-free; course
polylines aren't correlated with the named attractors by construction) —
deferred to T-1195's single design pass. cascade_layer1 golden re-pinned
(additive feature_names field).

T-1159 retires the legacy u32 granularity field fully shadowed by
window_granularity_v2: AtlasLayerRequest.window_granularity,
DistrictWindowLayer.granularity echo, the u32::MAX sentinel, and
resolve_window_granularity are gone server-side; client encode paths and
the caller-less atlas_window_cache legacy key component dropped;
msgpack fixtures regenerated; the T-1150 aliasing regression test now
drives through the surviving enum field. The district_window carrier
itself survives byte-compatible per D-255(c).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 16:10:27 +02:00
co-authored by Claude Fable 5
parent befdb689c1
commit 8da9670e0f
28 changed files with 1244 additions and 467 deletions
@@ -164,6 +164,93 @@ def populate_atlas_city_names(conn: sqlite3.Connection, dry_run: bool) -> int:
return len(rows)
# markers.json `names` key -> atlas_feature_names.feature_type value. Only
# rivers/mountain_ranges are populated here (T-1169 scope — the two pools
# server/src/atlas/layer1.rs's `attach_feature_names` actually consumes,
# river_names/mountain_names). `oceans`/`passes`/`regions` pools are NOT
# wired to any production consumer yet (no `attach_feature_names`-equivalent
# reads them) — populating rows for them here would be dead data with
# nothing to keep it honest, the same D-223 discipline that keeps
# atlas_city_names to names an actual placement pipeline consumes.
_FEATURE_NAME_POOL_KEYS: dict[str, str] = {
"rivers": "river",
"mountain_ranges": "mountain",
}
def populate_atlas_feature_names(conn: sqlite3.Connection, dry_run: bool) -> int:
"""Populate atlas_feature_names from the names-only markers.json pools
(D-223, T-1169) — mirrors `populate_atlas_city_names` exactly, over the
`rivers`/`mountain_ranges` marker keys instead of `cities`:
- body_id : directory name (e.g. GJ0e)
- name : pooled feature name
- feature_type : 'river' | 'mountain' (see `_FEATURE_NAME_POOL_KEYS`)
- priority : 0 — no authored ranking signal in markers.json; the
server-side name attachment (`layer1::attach_feature_names`)
ranks by computed mouth/peak strength at assignment time,
not by a priority stored on the pool row.
markers.json is a names-only flavoured pool (D-223): it carries no
geometry or position. `attach_feature_names` (Phase 4 cascade) attaches
these names to computed river mouths / alpine peaks at generation time;
this importer just loads the pool, exactly as `populate_atlas_city_names`
loads the city pool for the settlement placement pipeline.
Deterministic rebuild: clears atlas_feature_names first, so re-runs are
idempotent — there is no UNIQUE(body_id, name, feature_type), so without
the clear a re-run would accumulate duplicates. Skips body directories not
found in the bodies table (missing FK), and Sol bodies (D-223 permanent
exemption — same as `populate_atlas_city_names`, Sol keeps authored
geometry-bearing markers.json via sol_import.py, not the names pool).
"""
valid_body_ids: set[str] = {
r[0] for r in conn.execute("SELECT body_id FROM bodies").fetchall()
}
sol_body_ids: set[str] = {
r[0] for r in conn.execute(
"SELECT body_id FROM bodies WHERE system_id = 'GJ 0'"
).fetchall()
}
rows: list[tuple] = []
skipped_bodies: list[str] = []
pattern = str(WIKI_STAR_SYSTEMS / "*" / "bodies" / "*" / "markers.json")
for markers_path in sorted(glob.glob(pattern)):
body_id = markers_path.split("/bodies/")[1].split("/")[0]
if body_id not in valid_body_ids or body_id in sol_body_ids:
if body_id not in valid_body_ids:
skipped_bodies.append(body_id)
continue
with open(markers_path) as fh:
data = json.load(fh)
names = data.get("names") or {}
for pool_key, feature_type in _FEATURE_NAME_POOL_KEYS.items():
for raw_name in names.get(pool_key) or []:
name = (raw_name or "").strip()
if not name:
continue
rows.append((body_id, name, feature_type, 0))
if skipped_bodies:
unique = sorted(set(skipped_bodies))
print(f" warning: {len(unique)} body dirs not in DB — skipped: {unique[:5]}")
if not dry_run:
conn.execute("DELETE FROM atlas_feature_names")
if rows:
conn.executemany(
"""INSERT INTO atlas_feature_names
(body_id, name, feature_type, priority)
VALUES (?, ?, ?, ?)""",
rows,
)
return len(rows)
# body_type rank for the most_populated_body_in_system LAST-RESORT tiebreak —
# lower sorts first. This is NOT "belts can't host settlements" (they can and
# DO in this world: GJ845-belt hosts Orkney Ceramics, GJ268-belt hosts Jeju
+9
View File
@@ -228,6 +228,15 @@ def main() -> None:
n_cities = atlas.populate_atlas_city_names(conn, args.dry_run)
print(f" {n_cities} city name rows")
# 12b. atlas_feature_names (rivers/mountains) from wiki markers.json
# (D-223, T-1169) — mirrors step 12's pool-load shape exactly, over a
# different names-only markers.json key set. Independent of the
# settlement pool, so order relative to step 13 doesn't matter; placed
# here to stay adjacent to its sibling pool-load step.
print(" [12b/13] Populating atlas_feature_names from wiki content...")
n_features = atlas.populate_atlas_feature_names(conn, args.dry_run)
print(f" {n_features} feature name rows")
# 13. Standalone-HQ settlements + CityTenant city links (D-242, T-1074) — Phase B.
# SUPERSEDES the retired corp-HQ cross-reference (D-207, #909) — that
# step inserted one atlas_city_names row per corp HQ with no