refactor(tooling): T-1288 — planet-gen becomes reach atlas planet

The 30-file tree moves under atlas as its third rung (D-243), ten verbs
fronting it. Each verb restates its module's options so `--help` describes
something; tooling/test_planet_router.py hands every declared option to the
module's own argparse and fails on drift, and now runs in make test-tooling.

The 2026-09-02 half of this move had converted the top-level imports and the
repo roots. Finishing it found what the half-move left:

- Lazy in-function imports, and all of sol_data/, still named siblings bare.
  They resolved only through sys.path.insert hacks, so under reach the first
  globe render in generate, batch or sol-import would have raised
  ModuleNotFoundError. Qualified; the hacks are gone.
- 247 print() calls and a stdout progress writer that fired once per 8 KB
  block. Report verbs (audit, quality) write through console.out, progress
  through console.event, and download progress is throttled to 10% steps
  so a job log is not tens of thousands of lines.
- Every error exit raises ReachError with a fix.

Two checks that could not fail:

- batch --verify-determinism printed a warning and exited 0 on a mismatch.
- import-provinces exited 0 with errors > 0.

Both now raise. The 271-body bake is only safe to re-run because the first
one holds.

sol-import --body is action="append" in the module but the router took one
value, so --body GJ0d --body GJ0e kept one. Now repeatable, and _flags repeats
list options.

test_conformance walked one level, so a nested group was reported as a verb
missing @command and its ten verbs were never checked. It recurses now;
proven by stripping @command from `planet quality` and watching it fail.

Stray PNGs from the 2026-09-03 runaway router-test run are parked in
.cache/t1288-stray-pngs/, not committed. Their reliefmaps differ from HEAD
while the heightmap regenerated byte-identical — filed as T-1291.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 16:08:02 +02:00
co-authored by Claude Opus 5.5
parent 201dabd19b
commit 668772075c
56 changed files with 1130 additions and 509 deletions
+56
View File
@@ -289,3 +289,59 @@ and 3 paths in `docs/architecture/character-asset-organization.md`.
Gate: `ruff check tooling/` clean, `make test-tooling` PASS (13 checks),
`test_lazy_domains` now sees 10 domains.', NULL, '2026-09-02 18:55:28', '2026-09-02 18:55:28.421', '2026-09-02 18:55:28.421', NULL, '1352ceb77da9a63b23ee98f26fa76b1e', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G5FZDN9YBP0MZ021N3TJCFXM', 'status', 'backlog', 'done', NULL, '2026-09-02 18:55:28', '2026-09-02 18:55:28.441', '2026-09-02 18:55:28.441', NULL, 'effde7946e61e7d9e8cdbaf14324676d', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65NSJ4RG6SJ12TQE78S6TKR', 'description', 'The largest single port left: `tooling/planet-gen/` (30 Python files) becomes
`reach atlas planet <verb>`, the third rung of the spatial ladder (D-243), per
the nesting the domain map fixed in T-1271 — `planet` is NOT a top-level domain,
it lives under `atlas` because atlas coordinates world-location generation.
BEFORE MOVING ANYTHING, check `__file__`-relative roots. Five ports so far have
hit this and it fails SILENTLY: a script that computed `Path(__file__).parent.parent`
as the repo root resolves somewhere else once it lives two directories deeper,
and the usual symptom is a gate that reports "nothing to check, OK" and exits 0.
`validate-checklist` did exactly that. Grep for `__file__` first and convert to
`config.repo_root()` / `config.path(...)` as part of the move, not after.
WATCH the ones that are already gated:
- `test_sim_determinism.py` and `test_oasis_ring_scaling.py` are run by
`make test-tooling` and must keep working — they are tests, not verbs. Decide
whether they move with the domain or stay as test scripts, and update the
Makefile either way.
- Anything registered in `tooling/canvas_sources.py` (the canvas-generation
version gate, T-1242) — moving a path there without updating the registry
breaks `make check-canvas-version`, which has no override.
- `import_heightmaps` / `import_province_boundaries` are one-time build imports,
deliberately NOT stamped (`.claude/rules/asset-pipeline.md`). Do not fold them
into the regen path while porting.
Standard port acceptance as on T-1281/T-1286: output parity (exit codes match
exactly, no fact lost, failures name a remedy), every command carries `@command`,
nothing prints but `console`, no `subprocess` outside `core/process`.', 'The largest single port left: `tooling/planet-gen/` (30 Python files) becomes
`reach atlas planet <verb>`, the third rung of the spatial ladder (D-243), per
the nesting the domain map fixed in T-1271 — `planet` is NOT a top-level domain,
it lives under `atlas` because atlas coordinates world-location generation.
BEFORE MOVING ANYTHING, check `__file__`-relative roots. Five ports so far have
hit this and it fails SILENTLY: a script that computed `Path(__file__).parent.parent`
as the repo root resolves somewhere else once it lives two directories deeper,
and the usual symptom is a gate that reports "nothing to check, OK" and exits 0.
`validate-checklist` did exactly that. Grep for `__file__` first and convert to
`config.repo_root()` / `config.path(...)` as part of the move, not after.
WATCH the ones that are already gated:
- `test_sim_determinism.py` and `test_oasis_ring_scaling.py` are run by
`make test-tooling` and must keep working — they are tests, not verbs. Decide
whether they move with the domain or stay as test scripts, and update the
Makefile either way.
- Anything registered in `tooling/canvas_sources.py` (the canvas-generation
version gate, T-1242) — moving a path there without updating the registry
breaks `make check-canvas-version`, which has no override.
- `import_heightmaps` / `import_province_boundaries` are one-time build imports,
deliberately NOT stamped (`.claude/rules/asset-pipeline.md`). Do not fold them
into the regen path while porting.
Standard port acceptance as on T-1281/T-1286: output parity (exit codes match
exactly, no fact lost, failures name a remedy), every command carries `@command`,
nothing prints but `console`, no `subprocess` outside `core/process`.
DONE 2026-09-23. reach atlas planet: ten verbs over the moved package; tooling/test_planet_router.py (in make test-tooling) fails if a router option drifts from its module''s argparse. Finishing the 2026-09-02 half-move found: (1) lazy in-function imports and all of sol_data/ still used bare sibling names that only resolved via sys.path hacks — generate/batch/sol-import would have failed on first globe render under reach; qualified, hacks removed. (2) 247 print() + a per-8KB-block stdout progress writer routed through console (report verbs -> out, progress -> event, throttled download progress). (3) Every error exit now raises ReachError with fix=. (4) TWO CHECKS THAT COULD NOT FAIL: batch --verify-determinism and import-provinces both exited 0 on detected failures; both now raise. (5) sol-import --body is action=append but the router took one value — now repeatable. (6) test_conformance only walked one level, so the nested group''s ten verbs were never checked; now recursive, proven against a mutant. Stray PNGs from the 2026-09-03 runaway test run (8 modified reliefmaps, 6 new heightmaps) parked in .cache/t1288-stray-pngs/, reliefmaps restored from HEAD. NOTE: the reliefmaps differed from the committed bake, i.e. the current renderer no longer reproduces committed reliefmap bytes — not investigated here.', NULL, '2026-09-23 14:07:42', '2026-09-23 14:07:42.675', '2026-09-23 14:07:42.675', NULL, 'b6c6d1e211eada353630e3946da96310', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65NSJ4RG6SJ12TQE78S6TKR', 'status', 'backlog', 'done', NULL, '2026-09-23 14:07:43', '2026-09-23 14:07:43.036', '2026-09-23 14:07:43.036', NULL, 'e3329ba1f64d0a63c635d4a9049d56b6', 2) ON CONFLICT(hash) DO NOTHING;
+1
View File
@@ -5,3 +5,4 @@ INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65NSJ4RG6SJ12TQE78S6TKR', 'T-1288', '2026-09-02 15:57:45.383', '2026-09-02 15:57:45.383', NULL, 'db4b46dbe9d6484c4d618bcbc0c3d04d', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= ticket_idmap.updated_at;
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65T2N1WAT491WY8SV736J3G', 'T-1289', '2026-09-02 16:16:28.431', '2026-09-02 16:16:28.431', NULL, '976a8d383d0581caafca5a4bc2e0fb61', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= ticket_idmap.updated_at;
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65TC359QGCVBEPXHJ4N0MRG', 'T-1290', '2026-09-02 16:17:45.770', '2026-09-02 16:17:45.770', NULL, '748d7ed02f4821a7ee5059a76d2fd3fe', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= ticket_idmap.updated_at;
INSERT INTO ticket_idmap (record_id, ticket_id, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06GCX60QRMD7KJ1TVWTR7FWK28', 'T-1291', '2026-09-23 14:07:49.190', '2026-09-23 14:07:49.190', NULL, '20abfe457c23ea4e00d3c8109f2e39f2', 2) ON CONFLICT(record_id) DO UPDATE SET ticket_id=excluded.ticket_id, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= ticket_idmap.updated_at;
+59
View File
@@ -442,3 +442,62 @@ and 3 paths in `docs/architecture/character-asset-organization.md`.
Gate: `ruff check tooling/` clean, `make test-tooling` PASS (13 checks),
`test_lazy_domains` now sees 10 domains.', 'done', 'medium', NULL, NULL, 'D-263', '2026-08-31 13:23:59.951', '2026-09-02 18:55:28.441', NULL, '41fbc2c73d4d33a6f6224cf6e97f7aac', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65NSJ4RG6SJ12TQE78S6TKR', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Port atlas planet — the 30-file planet-gen tree', 'The largest single port left: `tooling/planet-gen/` (30 Python files) becomes
`reach atlas planet <verb>`, the third rung of the spatial ladder (D-243), per
the nesting the domain map fixed in T-1271 — `planet` is NOT a top-level domain,
it lives under `atlas` because atlas coordinates world-location generation.
BEFORE MOVING ANYTHING, check `__file__`-relative roots. Five ports so far have
hit this and it fails SILENTLY: a script that computed `Path(__file__).parent.parent`
as the repo root resolves somewhere else once it lives two directories deeper,
and the usual symptom is a gate that reports "nothing to check, OK" and exits 0.
`validate-checklist` did exactly that. Grep for `__file__` first and convert to
`config.repo_root()` / `config.path(...)` as part of the move, not after.
WATCH the ones that are already gated:
- `test_sim_determinism.py` and `test_oasis_ring_scaling.py` are run by
`make test-tooling` and must keep working — they are tests, not verbs. Decide
whether they move with the domain or stay as test scripts, and update the
Makefile either way.
- Anything registered in `tooling/canvas_sources.py` (the canvas-generation
version gate, T-1242) — moving a path there without updating the registry
breaks `make check-canvas-version`, which has no override.
- `import_heightmaps` / `import_province_boundaries` are one-time build imports,
deliberately NOT stamped (`.claude/rules/asset-pipeline.md`). Do not fold them
into the regen path while porting.
Standard port acceptance as on T-1281/T-1286: output parity (exit codes match
exactly, no fact lost, failures name a remedy), every command carries `@command`,
nothing prints but `console`, no `subprocess` outside `core/process`.
DONE 2026-09-23. reach atlas planet: ten verbs over the moved package; tooling/test_planet_router.py (in make test-tooling) fails if a router option drifts from its module''s argparse. Finishing the 2026-09-02 half-move found: (1) lazy in-function imports and all of sol_data/ still used bare sibling names that only resolved via sys.path hacks — generate/batch/sol-import would have failed on first globe render under reach; qualified, hacks removed. (2) 247 print() + a per-8KB-block stdout progress writer routed through console (report verbs -> out, progress -> event, throttled download progress). (3) Every error exit now raises ReachError with fix=. (4) TWO CHECKS THAT COULD NOT FAIL: batch --verify-determinism and import-provinces both exited 0 on detected failures; both now raise. (5) sol-import --body is action=append but the router took one value — now repeatable. (6) test_conformance only walked one level, so the nested group''s ten verbs were never checked; now recursive, proven against a mutant. Stray PNGs from the 2026-09-03 runaway test run (8 modified reliefmaps, 6 new heightmaps) parked in .cache/t1288-stray-pngs/, reliefmaps restored from HEAD. NOTE: the reliefmaps differed from the committed bake, i.e. the current renderer no longer reproduces committed reliefmap bytes — not investigated here.', 'backlog', 'high', NULL, 'tooling', 'D-263', '2026-09-02 15:57:45.382', '2026-09-23 14:07:42.675', NULL, '71f6451699ed5f0d38612fe15d1cde35', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G65NSJ4RG6SJ12TQE78S6TKR', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Port atlas planet — the 30-file planet-gen tree', 'The largest single port left: `tooling/planet-gen/` (30 Python files) becomes
`reach atlas planet <verb>`, the third rung of the spatial ladder (D-243), per
the nesting the domain map fixed in T-1271 — `planet` is NOT a top-level domain,
it lives under `atlas` because atlas coordinates world-location generation.
BEFORE MOVING ANYTHING, check `__file__`-relative roots. Five ports so far have
hit this and it fails SILENTLY: a script that computed `Path(__file__).parent.parent`
as the repo root resolves somewhere else once it lives two directories deeper,
and the usual symptom is a gate that reports "nothing to check, OK" and exits 0.
`validate-checklist` did exactly that. Grep for `__file__` first and convert to
`config.repo_root()` / `config.path(...)` as part of the move, not after.
WATCH the ones that are already gated:
- `test_sim_determinism.py` and `test_oasis_ring_scaling.py` are run by
`make test-tooling` and must keep working — they are tests, not verbs. Decide
whether they move with the domain or stay as test scripts, and update the
Makefile either way.
- Anything registered in `tooling/canvas_sources.py` (the canvas-generation
version gate, T-1242) — moving a path there without updating the registry
breaks `make check-canvas-version`, which has no override.
- `import_heightmaps` / `import_province_boundaries` are one-time build imports,
deliberately NOT stamped (`.claude/rules/asset-pipeline.md`). Do not fold them
into the regen path while porting.
Standard port acceptance as on T-1281/T-1286: output parity (exit codes match
exactly, no fact lost, failures name a remedy), every command carries `@command`,
nothing prints but `console`, no `subprocess` outside `core/process`.
DONE 2026-09-23. reach atlas planet: ten verbs over the moved package; tooling/test_planet_router.py (in make test-tooling) fails if a router option drifts from its module''s argparse. Finishing the 2026-09-02 half-move found: (1) lazy in-function imports and all of sol_data/ still used bare sibling names that only resolved via sys.path hacks — generate/batch/sol-import would have failed on first globe render under reach; qualified, hacks removed. (2) 247 print() + a per-8KB-block stdout progress writer routed through console (report verbs -> out, progress -> event, throttled download progress). (3) Every error exit now raises ReachError with fix=. (4) TWO CHECKS THAT COULD NOT FAIL: batch --verify-determinism and import-provinces both exited 0 on detected failures; both now raise. (5) sol-import --body is action=append but the router took one value — now repeatable. (6) test_conformance only walked one level, so the nested group''s ten verbs were never checked; now recursive, proven against a mutant. Stray PNGs from the 2026-09-03 runaway test run (8 modified reliefmaps, 6 new heightmaps) parked in .cache/t1288-stray-pngs/, reliefmaps restored from HEAD. NOTE: the reliefmaps differed from the committed bake, i.e. the current renderer no longer reproduces committed reliefmap bytes — not investigated here.', 'done', 'high', NULL, 'tooling', 'D-263', '2026-09-02 15:57:45.382', '2026-09-23 14:07:43.036', NULL, '2a78f8f8646d915600ed55c5dc5d0993', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06GCX60QRMD7KJ1TVWTR7FWK28', 'bug', '06FBPPMZNNEV052DBYYY3A897C', 'Planet renderer no longer reproduces committed reliefmap.png bytes (8 bodies differ on re-render)', 'Found during T-1288. A 2026-09-03 accidental generate run re-rendered bodies in GJ-1002, GJ-1116B, GJ-139, GJ-147, GJ-216A, GJ-234A, GJ-273, GJ-282B: every reliefmap.png came back a few hundred bytes different from HEAD, while GJ1002b''s heightmap.png came back byte-identical (so the simulation is deterministic — make test-tooling confirms — and the drift is in the reliefmap RENDER path or its PNG encoding). Copies of the re-rendered files are in .cache/t1288-stray-pngs/. Question to answer first: is this a visible change (renderer code evolved since the April bake) or encoder-level noise (zlib/Pillow version, metadata)? Pixel-diff one pair before anything else. If visible: decide whether committed reliefmaps should be re-baked. If noise: make the encode deterministic so a re-render is a no-op in git.', 'backlog', 'low', NULL, 'tooling', NULL, '2026-09-23 14:07:49.189', '2026-09-23 14:07:49.189', NULL, '215478973901c77cdd0e6b5c446ff19e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;