- make test-tooling: planet-gen determinism guard + import_economics
--dry-run, wired into pre-push on TOOLING_CHANGED; ruff widened to
E4/E7/E9/F/W (90 safe auto-fixes applied; E402/E702/F841 ignored with
documented counts)
- one-generator reality fixed in DEVOPS.md, asset-pipeline rule, CLAUDE.md
(import_economics sole generator since #951/D-223); dead check-protocol
target deleted; DEVOPS hook/config sections rewritten from the actual
hook sources; team-patterns gate description updated (client+tooling)
- project.yaml: 0.2.0 → 0.4.0 per the 0.{phase}.{n} scheme, description
refreshed from the v0.1 Sova narration to cascade reality
- stale comment sweep: voxel.rs stub claims (all 8 families implemented),
cascade.rs TODO recited to T-1044, main.rs D-192 handshake claim,
relationships.rs/chunk_streaming.rs version targets → phase language
- gitignore: client/settings.db* e2e-run artifacts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
163 lines
5.6 KiB
Python
Executable File
163 lines
5.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Backfill star_systems.cultural_corridor and bodies.cultural_corridor.
|
|
|
|
The schema has a `cultural_corridor` column on both tables, but
|
|
`wiki_sync.py` never populated it from the wiki index.md files — the
|
|
cultural/geographic identity of each system lives in
|
|
`star_systems.geographic_sector` instead (values: core, north_reach,
|
|
south_reach, east_reach, west_reach, deep_frontier). These two fields
|
|
refer to the same concept: which arc of the reach the system belongs
|
|
to. Leaving `cultural_corridor` NULL on 99%+ of rows defeats every
|
|
downstream consumer that actually wants to filter by corridor
|
|
(the atlas UI, narrative tools, and the server generation cascade).
|
|
|
|
This script treats `geographic_sector` as the source of truth and
|
|
copies it into `cultural_corridor`:
|
|
|
|
star_systems.cultural_corridor := star_systems.geographic_sector
|
|
WHERE cultural_corridor IS NULL
|
|
|
|
bodies.cultural_corridor := parent star_systems.cultural_corridor
|
|
WHERE bodies.cultural_corridor IS NULL
|
|
|
|
It is safe to re-run — idempotent, NULL-only updates, explicit
|
|
transaction wrapper so a crash never leaves a half-populated state.
|
|
Run it after any `wiki_sync.py` pass that creates fresh systems.db
|
|
rows.
|
|
|
|
Usage:
|
|
tooling/db/backfill_cultural_corridor.py
|
|
tooling/db/backfill_cultural_corridor.py --db path/to/systems.db
|
|
tooling/db/backfill_cultural_corridor.py --dry-run
|
|
|
|
Decisions: D-191 (atlas pipeline — downstream consumer)
|
|
"""
|
|
|
|
import argparse
|
|
import sqlite3
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
REPO_ROOT = (SCRIPT_DIR / ".." / "..").resolve()
|
|
DB_PATH = REPO_ROOT / "server" / "data" / "systems.db"
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Backfill cultural_corridor on star_systems and bodies"
|
|
)
|
|
parser.add_argument("--db", default=str(DB_PATH), help="Path to systems.db")
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Report counts without writing",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
db_path = Path(args.db)
|
|
if not db_path.exists():
|
|
print(f"error: {db_path} not found", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
conn = sqlite3.connect(str(db_path))
|
|
conn.execute("PRAGMA foreign_keys=ON")
|
|
|
|
# Counts before.
|
|
before_systems_null = conn.execute(
|
|
"SELECT COUNT(*) FROM star_systems WHERE cultural_corridor IS NULL"
|
|
).fetchone()[0]
|
|
before_bodies_null = conn.execute(
|
|
"SELECT COUNT(*) FROM bodies WHERE cultural_corridor IS NULL"
|
|
).fetchone()[0]
|
|
|
|
print("\n cultural_corridor backfill")
|
|
print(f" DB: {db_path}")
|
|
if args.dry_run:
|
|
print(" Mode: DRY RUN")
|
|
print()
|
|
print(" Before:")
|
|
print(f" star_systems.cultural_corridor NULL: {before_systems_null}")
|
|
print(f" bodies.cultural_corridor NULL: {before_bodies_null}")
|
|
|
|
conn.execute("BEGIN")
|
|
try:
|
|
# 1. Star systems — copy geographic_sector into cultural_corridor
|
|
# where the latter is still NULL. If geographic_sector is also
|
|
# NULL, leave cultural_corridor NULL — there is nothing to
|
|
# copy and a bogus placeholder is worse than honest NULL.
|
|
sys_rows_updated = conn.execute(
|
|
"""
|
|
UPDATE star_systems
|
|
SET cultural_corridor = geographic_sector
|
|
WHERE cultural_corridor IS NULL
|
|
AND geographic_sector IS NOT NULL
|
|
"""
|
|
).rowcount
|
|
|
|
# 2. Bodies — inherit from the parent star_systems row.
|
|
body_rows_updated = conn.execute(
|
|
"""
|
|
UPDATE bodies
|
|
SET cultural_corridor = (
|
|
SELECT s.cultural_corridor
|
|
FROM star_systems s
|
|
WHERE s.system_id = bodies.system_id
|
|
)
|
|
WHERE cultural_corridor IS NULL
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM star_systems s
|
|
WHERE s.system_id = bodies.system_id
|
|
AND s.cultural_corridor IS NOT NULL
|
|
)
|
|
"""
|
|
).rowcount
|
|
|
|
if args.dry_run:
|
|
conn.rollback()
|
|
print()
|
|
print(" Would update:")
|
|
print(f" star_systems: {sys_rows_updated}")
|
|
print(f" bodies: {body_rows_updated}")
|
|
print("\n Dry run — no changes written.")
|
|
else:
|
|
conn.commit()
|
|
print()
|
|
print(" Updated:")
|
|
print(f" star_systems: {sys_rows_updated}")
|
|
print(f" bodies: {body_rows_updated}")
|
|
|
|
# Counts after.
|
|
after_systems_null = conn.execute(
|
|
"SELECT COUNT(*) FROM star_systems WHERE cultural_corridor IS NULL"
|
|
).fetchone()[0]
|
|
after_bodies_null = conn.execute(
|
|
"SELECT COUNT(*) FROM bodies WHERE cultural_corridor IS NULL"
|
|
).fetchone()[0]
|
|
print()
|
|
print(" After:")
|
|
print(f" star_systems.cultural_corridor NULL: {after_systems_null}")
|
|
print(f" bodies.cultural_corridor NULL: {after_bodies_null}")
|
|
|
|
# Show the distribution so the outcome is visible.
|
|
print()
|
|
print(" star_systems.cultural_corridor distribution:")
|
|
for corridor, count in conn.execute(
|
|
"SELECT cultural_corridor, COUNT(*) FROM star_systems "
|
|
"GROUP BY cultural_corridor ORDER BY COUNT(*) DESC"
|
|
).fetchall():
|
|
print(f" {corridor!r}: {count}")
|
|
except BaseException:
|
|
conn.rollback()
|
|
conn.close()
|
|
raise
|
|
|
|
conn.close()
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|