chore(tooling): T-1272 + T-1274 — close E3: no hyphens left, and the lint ignores come off

T-1272 (a verification, as rescoped). No directory Python imports carries a
hyphen any more. The hyphenated script trees were emptied by the per-domain
moves, not renamed. What still has a hyphen is never imported: the three Rust
crates, and the provenance under tooling/archive/, which has no __init__.py.
CLAUDE.md and DEVOPS still pointed at tooling/db/, and pyproject still
predicted the rename; all three fixed.

T-1274. E402, E702 and F841 were ignored for the whole tree from T-1066 on
(43 / 41 / 21 violations). All three are back on:

- E402: the planet modules' imports only sat below their path constants
  because they used to follow a sys.path insert, gone since T-1288. Hoisted.
  The Blender payloads keep a per-file exception, because they extend
  sys.path under Blender's own Python.
- E702: the paired component assignments in three planet maths files are
  deliberate, so they get a per-file exception scoped to those files.
- F841: 10 dead locals removed from live code, each checked for side effects
  first; logo_uv keeps its call, which creates the UV layer.
- tooling/archive/ is excluded: it is provenance, and "fixing" a one-shot
  falsifies the record of what actually ran.

Evidence the lint is real: violations fed through stdin fire in a domain
module, and E402 stays quiet only on a payload path. Evidence nothing moved:
globe renders are pixel-identical before and after for an oceanic, a frozen
and a gas-giant body, and the ledger edit was regenerated (stamp fresh,
generated_brands.toml unchanged).

One finding, noted in the code rather than fixed: planet_renderer computed an
oblate-spheroid ray scale and never used it, so `oblateness` shapes no globe.
Wiring it in would change every globe render; that is a decision to make
deliberately, not a lint fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 20:05:07 +02:00
co-authored by Claude Opus 5.5
parent 59b3fc4caa
commit 6fb0ba0e3d
17 changed files with 110 additions and 69 deletions
+15
View File
@@ -553,3 +553,18 @@ DONE 2026-09-23. reach atlas map data [--check] + reach atlas map svg, nested un
ARCHIVED, NOT PORTED: generate-star-map.py + star-map-seed.json + sculpt + tune (tooling/archive/map-bootstrap/). The generator unconditionally rewrites docs/design/star-map.json from an S-NNN-keyed seed, so re-running it would erase the GJ migration and every hand edit since. sculpt and tune operate only on S-NNN edges. .claude/rules/diagrams.md used to tell agents to "edit the generator and re-run it"; it now documents three cases: concentric SVG is a live render, the 7 sector d2 files are frozen S-keyed output (T-1294), and realcoords/topology SVGs have no generator at all.', NULL, '2026-09-23 17:59:58', '2026-09-23 17:59:58.221', '2026-09-23 17:59:58.221', NULL, 'fa1c80235ce06db1f459dae246ba37dc', 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 ('06GCYT8JBRCX13ZD51SY4SX1WW', 'status', 'backlog', 'done', NULL, '2026-09-23 17:59:58', '2026-09-23 17:59:58.641', '2026-09-23 17:59:58.641', NULL, '410fcc34e85dc80deb099cf6cb59a31f', 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 ('06G5FZCVZXH6M00C57EXMZD0SG', 'description', 'economy-db, garment-fit, garment-qa, planet-gen and pql-migrate are not importable — a hyphen is not a valid Python identifier — so nothing under them can be reached from the package. Rename to economy_db, garment_fit, garment_qa, planet_gen, pql_migrate (final names to be confirmed by the domain map, T-1271, which may fold some of them into a domain rather than keep them as top-level packages). NOT renamed, and this corrects the epic description: tooling/econ-sim, tooling/line-previewer and tooling/test-client are RUST crates — Cargo.toml plus src/, zero .py files. A hyphen only matters for something Python must import, so all three keep their names and simply stay outside package discovery. The epic named only econ-sim; the other two were found in the survey. Use git mv so history follows, and expect the blast radius to be wide: the Makefile references these paths in many targets, the pre-push hook runs tests from planet-gen and economy-db by path, tooling/generator_sources.py registers economy-db source paths for the systems.db stamp, and .claude/settings.json has per-path permission entries. Grep for each old name across Makefile, .config/hooks, .claude, docs and tooling itself before declaring done. VERIFY BY RUNNING, not by grepping: make test-tooling must pass, since it invokes planet-gen and economy-db tests directly and is the only thing that will actually prove the paths still resolve.
RESCOPED 2026-08-31 on picking up the epic — this is now a VERIFICATION, not a rename, and it runs last rather than first. The domain map (T-1271) empties all five hyphenated directories rather than renaming them: planet-gen becomes domains/planet, economy-db becomes domains/db, garment-fit is 22 Blender payloads plus one module so it splits between the carve-out and character, garment-qa goes to character, and pql-migrate moves to archive/ as provenance. Renaming each to an underscore form FIRST and then moving its contents would be two moves where one suffices, through an intermediate name that never appears in any commit anyone reads. So the deliverable becomes: after the per-domain ports land, confirm that no directory Python must import still carries a hyphen, and that nothing references the old paths — Makefile, .config/hooks, .claude, docs, tooling/generator_sources.py. The three Rust crates (econ-sim, line-previewer, test-client) keep their hyphens and are excluded from discovery; a hyphen only matters for something Python imports. Blocked on the per-domain ports rather than blocking them.', 'economy-db, garment-fit, garment-qa, planet-gen and pql-migrate are not importable — a hyphen is not a valid Python identifier — so nothing under them can be reached from the package. Rename to economy_db, garment_fit, garment_qa, planet_gen, pql_migrate (final names to be confirmed by the domain map, T-1271, which may fold some of them into a domain rather than keep them as top-level packages). NOT renamed, and this corrects the epic description: tooling/econ-sim, tooling/line-previewer and tooling/test-client are RUST crates — Cargo.toml plus src/, zero .py files. A hyphen only matters for something Python must import, so all three keep their names and simply stay outside package discovery. The epic named only econ-sim; the other two were found in the survey. Use git mv so history follows, and expect the blast radius to be wide: the Makefile references these paths in many targets, the pre-push hook runs tests from planet-gen and economy-db by path, tooling/generator_sources.py registers economy-db source paths for the systems.db stamp, and .claude/settings.json has per-path permission entries. Grep for each old name across Makefile, .config/hooks, .claude, docs and tooling itself before declaring done. VERIFY BY RUNNING, not by grepping: make test-tooling must pass, since it invokes planet-gen and economy-db tests directly and is the only thing that will actually prove the paths still resolve.
RESCOPED 2026-08-31 on picking up the epic — this is now a VERIFICATION, not a rename, and it runs last rather than first. The domain map (T-1271) empties all five hyphenated directories rather than renaming them: planet-gen becomes domains/planet, economy-db becomes domains/db, garment-fit is 22 Blender payloads plus one module so it splits between the carve-out and character, garment-qa goes to character, and pql-migrate moves to archive/ as provenance. Renaming each to an underscore form FIRST and then moving its contents would be two moves where one suffices, through an intermediate name that never appears in any commit anyone reads. So the deliverable becomes: after the per-domain ports land, confirm that no directory Python must import still carries a hyphen, and that nothing references the old paths — Makefile, .config/hooks, .claude, docs, tooling/generator_sources.py. The three Rust crates (econ-sim, line-previewer, test-client) keep their hyphens and are excluded from discovery; a hyphen only matters for something Python imports. Blocked on the per-domain ports rather than blocking them.
DONE 2026-09-23 (verification, as rescoped). No directory Python imports carries a hyphen. What still does: the three Rust crates (econ-sim, line-previewer, test-client) and the provenance subdirs of tooling/archive/, none of which has an __init__.py (checked), so package discovery cannot reach them. Stale path references fixed: CLAUDE.md and docs/DEVOPS.md still pointed asset connectors at tooling/db/; pyproject''s package-discovery comment still predicted a rename. Remaining mentions of the old names are deliberate "Formerly ..." provenance in module docstrings, legacy notes in the ticket-cli rule, and .cache/garment-qa output paths (a cache dir, not code). make test-tooling passes, which is the run-not-grep proof the ticket asked for.', NULL, '2026-09-23 18:04:57', '2026-09-23 18:04:57.393', '2026-09-23 18:04:57.393', NULL, '341beb7798caf26f2023116f77e48658', 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 ('06G5FZEM6JT5KKVE1RZ68DENSC', 'description', 'The ruff config currently ignores E402 (module-level import not at top of file), E702 (multiple statements on one line) and F841 (unused local variable). The E402 debt is script-style sys.path.insert-before-imports, which is exactly what a real package removes — so most of it should evaporate once files move under tooling/domains/ and import normally. Deliverable: after the moves land, re-enable E402 and fix or explicitly per-file-ignore whatever remains; then assess E702 and F841 on their own evidence rather than assuming they go the same way. Do this LAST in the epic, since running it mid-move means fixing violations in files that are about to move again. Note the honest possibility that E402 cannot be fully re-enabled: the Blender payloads (T-1273) may legitimately need sys.path manipulation before imports because they run under Blender''s bundled interpreter — if so, a per-file ignore scoped to tooling/scripts/blender/ is the right answer, not carrying a global ignore for the whole tree. A narrow documented exception is worth more than a broad silent one.', 'The ruff config currently ignores E402 (module-level import not at top of file), E702 (multiple statements on one line) and F841 (unused local variable). The E402 debt is script-style sys.path.insert-before-imports, which is exactly what a real package removes — so most of it should evaporate once files move under tooling/domains/ and import normally. Deliverable: after the moves land, re-enable E402 and fix or explicitly per-file-ignore whatever remains; then assess E702 and F841 on their own evidence rather than assuming they go the same way. Do this LAST in the epic, since running it mid-move means fixing violations in files that are about to move again. Note the honest possibility that E402 cannot be fully re-enabled: the Blender payloads (T-1273) may legitimately need sys.path manipulation before imports because they run under Blender''s bundled interpreter — if so, a per-file ignore scoped to tooling/scripts/blender/ is the right answer, not carrying a global ignore for the whole tree. A narrow documented exception is worth more than a broad silent one.
DONE 2026-09-23. E402, E702 and F841 re-enabled tree-wide (they were ignored for all of tooling/ since T-1066: 43/41/21). tooling/archive/ is excluded from lint: it is provenance that never runs, and a "fixed" one-shot stops being a record of what ran. Two narrow per-file exceptions, each documented in pyproject: E402 for tooling/scripts/blender/*.py (payloads extend sys.path under Blender''s Python), and E702 for the three planet maths files, where paired component assignments are deliberate. Both proven: violations fed via stdin fire in a domain module, and E402 does not fire for a payload path.
E402: the planet modules'' late imports hoisted. They only sat low because they used to follow a sys.path insert (gone since T-1288).
F841: 10 dead locals removed from live code, each checked for side effects. logo_uv keeps its call, since creating the UV layer IS the effect. One FINDING, left as a note: planet_renderer computed an oblate-spheroid ray scale and never used it, so `oblateness` does not shape any globe. Wiring it in would change every globe render, so that is a decision for later, not a lint fix. Globe renders were checked pixel-identical before/after for an oceanic, a frozen and a gas-giant body. The one ledger edit (specialization.py) is a stamped source: regen run, stamp fresh, generated_brands.toml unchanged.', NULL, '2026-09-23 18:04:57', '2026-09-23 18:04:57.989', '2026-09-23 18:04:57.989', NULL, '3872d462d93fc083a89ea7995aa4a92f', 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 ('06G5FZCVZXH6M00C57EXMZD0SG', 'status', 'backlog', 'done', NULL, '2026-09-23 18:04:58', '2026-09-23 18:04:58.465', '2026-09-23 18:04:58.465', NULL, '0193b698d7a17d0fb079832ba05810ce', 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 ('06G5FZEM6JT5KKVE1RZ68DENSC', 'status', 'backlog', 'done', NULL, '2026-09-23 18:04:58', '2026-09-23 18:04:58.472', '2026-09-23 18:04:58.472', NULL, 'ce88f7efd90155d73670ce9c27d47b2c', 2) ON CONFLICT(hash) DO NOTHING;
+22
View File
@@ -688,3 +688,25 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
DONE 2026-09-23. reach atlas map data [--check] + reach atlas map svg, nested under atlas like planet. Parity: star_map_data.json regenerates with a ONE-LINE diff, the _meta.note that named the old script path; the SVG is byte-identical to the old script''s output on the same data (the committed SVG itself is stale vs current data, T-1294). make check-star-map / star-map-data kept as one-line delegates because pre-pr-client and pre-pr-validate depend on check-star-map.
ARCHIVED, NOT PORTED: generate-star-map.py + star-map-seed.json + sculpt + tune (tooling/archive/map-bootstrap/). The generator unconditionally rewrites docs/design/star-map.json from an S-NNN-keyed seed, so re-running it would erase the GJ migration and every hand edit since. sculpt and tune operate only on S-NNN edges. .claude/rules/diagrams.md used to tell agents to "edit the generator and re-run it"; it now documents three cases: concentric SVG is a live render, the 7 sector d2 files are frozen S-keyed output (T-1294), and realcoords/topology SVGs have no generator at all.', 'done', 'medium', NULL, 'tooling', 'D-263', '2026-09-23 17:56:04.830', '2026-09-23 17:59:58.640', NULL, '030333d5dbd367b41dd9429c17ca3ac2', 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 ('06G5FZCVZXH6M00C57EXMZD0SG', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Verify no hyphenated Python directory survives the moves', 'economy-db, garment-fit, garment-qa, planet-gen and pql-migrate are not importable — a hyphen is not a valid Python identifier — so nothing under them can be reached from the package. Rename to economy_db, garment_fit, garment_qa, planet_gen, pql_migrate (final names to be confirmed by the domain map, T-1271, which may fold some of them into a domain rather than keep them as top-level packages). NOT renamed, and this corrects the epic description: tooling/econ-sim, tooling/line-previewer and tooling/test-client are RUST crates — Cargo.toml plus src/, zero .py files. A hyphen only matters for something Python must import, so all three keep their names and simply stay outside package discovery. The epic named only econ-sim; the other two were found in the survey. Use git mv so history follows, and expect the blast radius to be wide: the Makefile references these paths in many targets, the pre-push hook runs tests from planet-gen and economy-db by path, tooling/generator_sources.py registers economy-db source paths for the systems.db stamp, and .claude/settings.json has per-path permission entries. Grep for each old name across Makefile, .config/hooks, .claude, docs and tooling itself before declaring done. VERIFY BY RUNNING, not by grepping: make test-tooling must pass, since it invokes planet-gen and economy-db tests directly and is the only thing that will actually prove the paths still resolve.
RESCOPED 2026-08-31 on picking up the epic — this is now a VERIFICATION, not a rename, and it runs last rather than first. The domain map (T-1271) empties all five hyphenated directories rather than renaming them: planet-gen becomes domains/planet, economy-db becomes domains/db, garment-fit is 22 Blender payloads plus one module so it splits between the carve-out and character, garment-qa goes to character, and pql-migrate moves to archive/ as provenance. Renaming each to an underscore form FIRST and then moving its contents would be two moves where one suffices, through an intermediate name that never appears in any commit anyone reads. So the deliverable becomes: after the per-domain ports land, confirm that no directory Python must import still carries a hyphen, and that nothing references the old paths — Makefile, .config/hooks, .claude, docs, tooling/generator_sources.py. The three Rust crates (econ-sim, line-previewer, test-client) keep their hyphens and are excluded from discovery; a hyphen only matters for something Python imports. Blocked on the per-domain ports rather than blocking them.
DONE 2026-09-23 (verification, as rescoped). No directory Python imports carries a hyphen. What still does: the three Rust crates (econ-sim, line-previewer, test-client) and the provenance subdirs of tooling/archive/, none of which has an __init__.py (checked), so package discovery cannot reach them. Stale path references fixed: CLAUDE.md and docs/DEVOPS.md still pointed asset connectors at tooling/db/; pyproject''s package-discovery comment still predicted a rename. Remaining mentions of the old names are deliberate "Formerly ..." provenance in module docstrings, legacy notes in the ticket-cli rule, and .cache/garment-qa output paths (a cache dir, not code). make test-tooling passes, which is the run-not-grep proof the ticket asked for.', 'backlog', 'high', NULL, NULL, 'D-263', '2026-08-31 13:23:53.471', '2026-09-23 18:04:57.393', NULL, '37caf488cc64c19db6f3389aa2699d59', 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 ('06G5FZEM6JT5KKVE1RZ68DENSC', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Retire the ruff ignores the package makes unnecessary', 'The ruff config currently ignores E402 (module-level import not at top of file), E702 (multiple statements on one line) and F841 (unused local variable). The E402 debt is script-style sys.path.insert-before-imports, which is exactly what a real package removes — so most of it should evaporate once files move under tooling/domains/ and import normally. Deliverable: after the moves land, re-enable E402 and fix or explicitly per-file-ignore whatever remains; then assess E702 and F841 on their own evidence rather than assuming they go the same way. Do this LAST in the epic, since running it mid-move means fixing violations in files that are about to move again. Note the honest possibility that E402 cannot be fully re-enabled: the Blender payloads (T-1273) may legitimately need sys.path manipulation before imports because they run under Blender''s bundled interpreter — if so, a per-file ignore scoped to tooling/scripts/blender/ is the right answer, not carrying a global ignore for the whole tree. A narrow documented exception is worth more than a broad silent one.
DONE 2026-09-23. E402, E702 and F841 re-enabled tree-wide (they were ignored for all of tooling/ since T-1066: 43/41/21). tooling/archive/ is excluded from lint: it is provenance that never runs, and a "fixed" one-shot stops being a record of what ran. Two narrow per-file exceptions, each documented in pyproject: E402 for tooling/scripts/blender/*.py (payloads extend sys.path under Blender''s Python), and E702 for the three planet maths files, where paired component assignments are deliberate. Both proven: violations fed via stdin fire in a domain module, and E402 does not fire for a payload path.
E402: the planet modules'' late imports hoisted. They only sat low because they used to follow a sys.path insert (gone since T-1288).
F841: 10 dead locals removed from live code, each checked for side effects. logo_uv keeps its call, since creating the UV layer IS the effect. One FINDING, left as a note: planet_renderer computed an oblate-spheroid ray scale and never used it, so `oblateness` does not shape any globe. Wiring it in would change every globe render, so that is a decision for later, not a lint fix. Globe renders were checked pixel-identical before/after for an oceanic, a frozen and a gas-giant body. The one ledger edit (specialization.py) is a stamped source: regen run, stamp fresh, generated_brands.toml unchanged.', 'backlog', 'low', NULL, NULL, 'D-263', '2026-08-31 13:24:07.860', '2026-09-23 18:04:57.989', NULL, '778edc1e9a5f0864f993c1d5d5d1ba8f', 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 ('06G5FZCVZXH6M00C57EXMZD0SG', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Verify no hyphenated Python directory survives the moves', 'economy-db, garment-fit, garment-qa, planet-gen and pql-migrate are not importable — a hyphen is not a valid Python identifier — so nothing under them can be reached from the package. Rename to economy_db, garment_fit, garment_qa, planet_gen, pql_migrate (final names to be confirmed by the domain map, T-1271, which may fold some of them into a domain rather than keep them as top-level packages). NOT renamed, and this corrects the epic description: tooling/econ-sim, tooling/line-previewer and tooling/test-client are RUST crates — Cargo.toml plus src/, zero .py files. A hyphen only matters for something Python must import, so all three keep their names and simply stay outside package discovery. The epic named only econ-sim; the other two were found in the survey. Use git mv so history follows, and expect the blast radius to be wide: the Makefile references these paths in many targets, the pre-push hook runs tests from planet-gen and economy-db by path, tooling/generator_sources.py registers economy-db source paths for the systems.db stamp, and .claude/settings.json has per-path permission entries. Grep for each old name across Makefile, .config/hooks, .claude, docs and tooling itself before declaring done. VERIFY BY RUNNING, not by grepping: make test-tooling must pass, since it invokes planet-gen and economy-db tests directly and is the only thing that will actually prove the paths still resolve.
RESCOPED 2026-08-31 on picking up the epic — this is now a VERIFICATION, not a rename, and it runs last rather than first. The domain map (T-1271) empties all five hyphenated directories rather than renaming them: planet-gen becomes domains/planet, economy-db becomes domains/db, garment-fit is 22 Blender payloads plus one module so it splits between the carve-out and character, garment-qa goes to character, and pql-migrate moves to archive/ as provenance. Renaming each to an underscore form FIRST and then moving its contents would be two moves where one suffices, through an intermediate name that never appears in any commit anyone reads. So the deliverable becomes: after the per-domain ports land, confirm that no directory Python must import still carries a hyphen, and that nothing references the old paths — Makefile, .config/hooks, .claude, docs, tooling/generator_sources.py. The three Rust crates (econ-sim, line-previewer, test-client) keep their hyphens and are excluded from discovery; a hyphen only matters for something Python imports. Blocked on the per-domain ports rather than blocking them.
DONE 2026-09-23 (verification, as rescoped). No directory Python imports carries a hyphen. What still does: the three Rust crates (econ-sim, line-previewer, test-client) and the provenance subdirs of tooling/archive/, none of which has an __init__.py (checked), so package discovery cannot reach them. Stale path references fixed: CLAUDE.md and docs/DEVOPS.md still pointed asset connectors at tooling/db/; pyproject''s package-discovery comment still predicted a rename. Remaining mentions of the old names are deliberate "Formerly ..." provenance in module docstrings, legacy notes in the ticket-cli rule, and .cache/garment-qa output paths (a cache dir, not code). make test-tooling passes, which is the run-not-grep proof the ticket asked for.', 'done', 'high', NULL, NULL, 'D-263', '2026-08-31 13:23:53.471', '2026-09-23 18:04:58.465', NULL, 'fb95d8dc691d821950d0116becbaaef2', 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 ('06G5FZEM6JT5KKVE1RZ68DENSC', 'task', '06G1S3D0M1TQW0GMFBBPQZG3ZM', 'Retire the ruff ignores the package makes unnecessary', 'The ruff config currently ignores E402 (module-level import not at top of file), E702 (multiple statements on one line) and F841 (unused local variable). The E402 debt is script-style sys.path.insert-before-imports, which is exactly what a real package removes — so most of it should evaporate once files move under tooling/domains/ and import normally. Deliverable: after the moves land, re-enable E402 and fix or explicitly per-file-ignore whatever remains; then assess E702 and F841 on their own evidence rather than assuming they go the same way. Do this LAST in the epic, since running it mid-move means fixing violations in files that are about to move again. Note the honest possibility that E402 cannot be fully re-enabled: the Blender payloads (T-1273) may legitimately need sys.path manipulation before imports because they run under Blender''s bundled interpreter — if so, a per-file ignore scoped to tooling/scripts/blender/ is the right answer, not carrying a global ignore for the whole tree. A narrow documented exception is worth more than a broad silent one.
DONE 2026-09-23. E402, E702 and F841 re-enabled tree-wide (they were ignored for all of tooling/ since T-1066: 43/41/21). tooling/archive/ is excluded from lint: it is provenance that never runs, and a "fixed" one-shot stops being a record of what ran. Two narrow per-file exceptions, each documented in pyproject: E402 for tooling/scripts/blender/*.py (payloads extend sys.path under Blender''s Python), and E702 for the three planet maths files, where paired component assignments are deliberate. Both proven: violations fed via stdin fire in a domain module, and E402 does not fire for a payload path.
E402: the planet modules'' late imports hoisted. They only sat low because they used to follow a sys.path insert (gone since T-1288).
F841: 10 dead locals removed from live code, each checked for side effects. logo_uv keeps its call, since creating the UV layer IS the effect. One FINDING, left as a note: planet_renderer computed an oblate-spheroid ray scale and never used it, so `oblateness` does not shape any globe. Wiring it in would change every globe render, so that is a decision for later, not a lint fix. Globe renders were checked pixel-identical before/after for an oceanic, a frozen and a gas-giant body. The one ledger edit (specialization.py) is a stamped source: regen run, stamp fresh, generated_brands.toml unchanged.', 'done', 'low', NULL, NULL, 'D-263', '2026-08-31 13:24:07.860', '2026-09-23 18:04:58.472', NULL, 'b2db3b5f93dd2d2f04a4981bb30a2813', 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;
+2 -2
View File
@@ -12,10 +12,10 @@ A top-down life-sim — asymmetric information, occlusion-based perception, sing
wiki/ # THE SEED — authored world content that the generator varies from
client/ # Godot 4 client
server/ # Rust/bevy_ecs simulation server
tooling/ # Build tools, scripts, asset pipelines
tooling/ # ONE package behind the `reach` CLI (D-263) — `reach --help` is the index
tests/ # Integration and end-to-end tests
docs/ # Architecture, design, briefings, workshops
db/ # Schema + seed data (connectors moved to tooling/db/)
db/ # Schema + seed data (asset connectors: tooling/domains/assets/)
.claude/ # Agents, skills, rules
governance/ # Decision records — decisions/ questions/ rejected/ per domain (pql DQR tree)
```
+1 -1
View File
@@ -21,7 +21,7 @@ governance/ Decision records — decisions/ questions/ rejected/ per d
.config/ Configuration files (linters, formatters, CI)
.cache/ Local caches for testing/linting (gitignored)
docs/ Design, architecture, briefings, workshops
db/ Schema + seed data (asset connectors at tooling/db/)
db/ Schema + seed data (asset connectors: tooling/domains/assets/)
```
Unit tests live inside their respective projects (`server/` uses `#[cfg(test)]` inline + `tests/` directory per D-030). The top-level `tests/` directory is for integration tests that cross the client-server boundary (IPC round-trip, serialization fixtures, divergence tests).
+26 -11
View File
@@ -48,11 +48,13 @@ reach = "tooling.main:main"
# docs/, wiki/, db/ and tests/ alongside tooling/, and auto-discovery either
# errors on the ambiguity or quietly ships something unintended (T-1258).
#
# Nothing needs excluding yet: the hyphenated directories (planet-gen,
# economy-db, garment-fit, pql-migrate) are invisible to package discovery
# because a hyphen is not a valid Python identifier, and tooling/econ-sim is a
# Rust crate with no __init__.py. That changes in T-1250, which renames them —
# at which point they become real packages and this include starts matching them.
# Nothing needs excluding: discovery only finds directories with an
# __init__.py. The hyphenated script trees were emptied by T-1250's per-domain
# moves rather than renamed (verified in T-1272); what still carries a hyphen
# is never imported — the three Rust crates (econ-sim, line-previewer,
# test-client) and the provenance under tooling/archive/, which has no
# __init__.py on purpose. tooling/scripts/blender/ likewise has none: its
# payloads run under Blender's Python and must not become package modules.
include = ["tooling*"]
[project.optional-dependencies]
@@ -64,6 +66,10 @@ dev = [
[tool.ruff]
line-length = 100
target-version = "py311"
# Provenance, never run (tooling/archive/README.md). Linting it produces fixes
# to code nobody should execute, and a "fixed" one-shot is no longer a faithful
# record of what actually ran.
extend-exclude = ["tooling/archive"]
[tool.ruff.lint]
# Widened from {E9, F401, F811, F821} to the full ruff-default tiers + W (T-1066).
@@ -73,9 +79,18 @@ target-version = "py311"
# F: all pyflakes (unused imports/names, undefined names, f-string misuse, ...)
# W: whitespace + invalid escape sequences (zero violations at adoption)
select = ["E4", "E7", "E9", "F", "W"]
# Rules excluded at adoption because the existing violation count was not
# trivially fixable (T-1066) — re-enable per-rule as the debt is paid down:
# E402 (43×): module-import-not-at-top — script-style sys.path.insert before import
# E702 (41×): semicolon-paired assignments, deliberate style in planet-gen noise math
# F841 (21×): unused locals, mostly in numeric/diagnostic code — needs manual review
ignore = ["E402", "E702", "F841"]
# E402, E702 and F841 were ignored tree-wide at adoption (T-1066: 43/41/21
# violations). T-1274 re-enabled all three once the package existed. What
# remains is narrow and says why, per file, below — a documented exception is
# worth more than a broad silent one.
[tool.ruff.lint.per-file-ignores]
# Blender payloads run under Blender's bundled Python, which cannot see the
# repo venv; the ones that import helpers extend sys.path first (T-1273).
"tooling/scripts/blender/*.py" = ["E402"]
# Semicolon-paired component assignments (`rdx /= mag; rdy /= mag; ...`) are a
# deliberate style in the terrain noise and shading maths: the three axes of
# one vector read as one statement. Scoped to the files that use it.
"tooling/domains/atlas/planet/planet_simulation.py" = ["E702"]
"tooling/domains/atlas/planet/planet_renderer.py" = ["E702"]
"tooling/domains/atlas/planet/render_heightmap.py" = ["E702"]
Binary file not shown.
@@ -212,7 +212,6 @@ def report_earth_echoes(conn, system_id: str | None, body_id: str | None) -> Non
[f"%{pattern.replace('%', '')}%"] + params_base,
).fetchall()
for r in rows:
key = (r["body_id"], label, r["local_id"])
by_body.setdefault(r["body_id"], []).append(
(label, r["local_id"], r["name"])
)
+7 -8
View File
@@ -35,7 +35,13 @@ from pathlib import Path
from tooling.core import config, console
from tooling.core.errors import ReachError
# Venv bootstrap
import numpy as np
import yaml
from tooling.domains.atlas.planet.body_definition_parser import parse_system
from tooling.domains.atlas.planet.planet_simulation import simulate
from tooling.domains.atlas.planet.render_heightmap import render_heightmap
PLANET_DIR = Path(__file__).resolve().parent
WORKTREE_ROOT = config.repo_root()
# The venv re-exec that used to sit here is gone (T-1288). It relaunched the
@@ -44,13 +50,6 @@ WORKTREE_ROOT = config.repo_root()
# them — and an os.execv into a different interpreter, carrying reach's
# argv, would have relaunched something that is not this command at all.
import yaml
import numpy as np
from tooling.domains.atlas.planet.body_definition_parser import parse_system
from tooling.domains.atlas.planet.planet_simulation import simulate
from tooling.domains.atlas.planet.render_heightmap import render_heightmap
# Systems to skip in batch mode (require manual handling)
SKIP_SYSTEMS = {"GJ-0"}
@@ -46,6 +46,9 @@ from typing import Optional
import numpy as np
from tooling.core import console
from tooling.domains.atlas.planet.biome_config import GAS_PALETTE_SELECTION as GAS_PALETTES
logging.basicConfig(level=logging.INFO, format=" %(levelname)s %(message)s")
log = logging.getLogger(__name__)
@@ -205,9 +208,7 @@ CLASS_OBLATENESS = {
"barren": (0.000, 0.003),
}
# Gas giant band palettes available
from tooling.domains.atlas.planet.biome_config import GAS_PALETTE_SELECTION as GAS_PALETTES
from tooling.core import console
# Gas giant band palettes available: GAS_PALETTES, imported at the top.
# planet_class → cloud coverage base range
CLASS_CLOUD = {
+5 -7
View File
@@ -22,11 +22,15 @@ import json
import os
import time
# Venv bootstrap — re-exec into .venv/bin/python if not already there.
from pathlib import Path
import numpy as np
from tooling.core import config, console
from tooling.core.errors import ReachError
from tooling.domains.atlas.planet.planet_simulation import simulate
from tooling.domains.atlas.planet.render_heightmap import render_heightmap
PLANET_DIR = Path(__file__).resolve().parent
WORKTREE_ROOT = config.repo_root()
# The venv re-exec that used to sit here is gone (T-1288). It relaunched the
@@ -35,11 +39,6 @@ WORKTREE_ROOT = config.repo_root()
# them — and an os.execv into a different interpreter, carrying reach's
# argv, would have relaunched something that is not this command at all.
import numpy as np
from tooling.domains.atlas.planet.planet_simulation import simulate
from tooling.domains.atlas.planet.render_heightmap import render_heightmap
def _build_markers(body_def: dict, terrain: dict) -> dict:
@@ -51,7 +50,6 @@ def _build_markers(body_def: dict, terrain: dict) -> dict:
sea_level = terrain["sea_level"]
elevation = terrain["elevation"]
surface_water = terrain["surface_water"]
biome = terrain["biome"]
markers = {
"grid": {"w": grid_w, "h": grid_h},
@@ -50,17 +50,9 @@ from pathlib import Path
from tooling.core import config, console
from tooling.core.errors import ReachError
PLANET_DIR = Path(__file__).resolve().parent
REPO_ROOT = config.repo_root()
# The venv re-exec that used to sit here is gone (T-1288). It relaunched the
# script under .venv/bin/python so numpy would resolve when run by path.
# reach declares numpy and Pillow itself, so its own environment already has
# them — and an os.execv into a different interpreter, carrying reach's
# argv, would have relaunched something that is not this command at all.
import sqlite3
import numpy as np
import sqlite3
from tooling.domains.atlas.planet.atlas_common import (
DB_PATH,
@@ -68,6 +60,14 @@ from tooling.domains.atlas.planet.atlas_common import (
query_inhabited_bodies,
)
PLANET_DIR = Path(__file__).resolve().parent
REPO_ROOT = config.repo_root()
# The venv re-exec that used to sit here is gone (T-1288). It relaunched the
# script under .venv/bin/python so numpy would resolve when run by path.
# reach declares numpy and Pillow itself, so its own environment already has
# them — and an os.execv into a different interpreter, carrying reach's
# argv, would have relaunched something that is not this command at all.
# D8 neighbor offsets: (dr, dc)
_D8 = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
@@ -168,8 +168,9 @@ def _raytrace(size: int, r: float = 1.0, oblateness: float = 0.0):
mag = np.sqrt(rdx**2 + rdy**2 + rdz**2)
rdx /= mag; rdy /= mag; rdz /= mag
# Scale Y for oblate spheroid
rdy_s = rdy / (1.0 - oblateness + 1e-9)
# NOTE (T-1274): an oblate-spheroid ray scale (rdy / (1 - oblateness)) was
# computed here and never used, so `oblateness` does not shape the globe.
# The dead line is removed; wiring it in would change every globe render.
b = 2.0 * oz * rdz
c = oz**2 - r**2
@@ -237,8 +238,8 @@ def _apply_lighting(
star_type = body_def.get("star", {}).get("type", "G")
star_tint = np.array(STAR_TINTS.get(star_type, (1,1,1)), dtype=np.float32)
# View direction (camera at 0,0,3, looking at origin)
vz = -1.0 # simplified: view dir is ~(0,0,-1) at pixel center
# View direction is ~(0,0,-1) at the pixel centre (camera at 0,0,3); only
# its z enters the specular term below, as -rz.
# Dot products
NdotL = nx * lx + ny * ly + nz * lz # (H,W)
@@ -265,8 +266,6 @@ def _apply_lighting(
# --- Ocean specular ---
if do_spec and surface_water is not None:
rx = -lx + 2.0 * NdotL * nx
ry = -ly + 2.0 * NdotL * ny
rz = -lz + 2.0 * NdotL * nz
spec = np.clip(-rz, 0.0, 1.0) ** 70 # tight highlight
spec *= surface_water.astype(np.float32)
@@ -360,8 +359,6 @@ def _surface_color_terrestrial(
return col.astype(np.float32), water
# --- Procedural fallback ---
rng = np.random.default_rng(seed)
# Continent mask — low-freq noise, threshold to land_fraction
lf = body_def.get("terrain", {}).get("land_fraction", 0.35)
cont_noise = fbm(u * 3, v * 2, seed, octaves=5, gain=0.55)
@@ -25,15 +25,6 @@ from pathlib import Path
from tooling.core import config, console
from tooling.core.errors import ReachError
# Venv bootstrap
PLANET_DIR = Path(__file__).resolve().parent
WORKTREE_ROOT = config.repo_root()
# The venv re-exec that used to sit here is gone (T-1288). It relaunched the
# script under .venv/bin/python so numpy would resolve when run by path.
# reach declares numpy and Pillow itself, so its own environment already has
# them — and an os.execv into a different interpreter, carrying reach's
# argv, would have relaunched something that is not this command at all.
try:
import yaml
except ImportError:
@@ -42,6 +33,13 @@ except ImportError:
from tooling.domains.atlas.planet.body_definition_parser import parse_system
PLANET_DIR = Path(__file__).resolve().parent
WORKTREE_ROOT = config.repo_root()
# The venv re-exec that used to sit here is gone (T-1288). It relaunched the
# script under .venv/bin/python so numpy would resolve when run by path.
# reach declares numpy and Pillow itself, so its own environment already has
# them — and an os.execv into a different interpreter, carrying reach's
# argv, would have relaunched something that is not this command at all.
def _body_to_frontmatter(bd: dict) -> str:
@@ -83,7 +83,6 @@ def build_terrain(body_def: dict) -> dict:
# Concentrate lakes near north pole (real Titan has lakes mostly 60-90°N)
v = np.linspace(0, 1, GRID_H, dtype=np.float32)
lat_abs = np.abs(v - 0.5) * 2.0 # 0=equator, 1=poles
north_mask = v < 0.2 # north polar region (top 20% of grid = 72-90°N)
# Allow lakes only in polar regions — mask out equatorial/southern "seas"
equatorial_mask = (lat_abs < 0.6)[:, np.newaxis] * np.ones(GRID_W, dtype=bool)
surface_water = surface_water & ~equatorial_mask
+6 -7
View File
@@ -20,11 +20,16 @@ import argparse
import json
import time
# Venv bootstrap — re-exec into .venv/bin/python if not already there.
from pathlib import Path
import numpy as np
from tooling.core import config, console
from tooling.core.errors import ReachError
from tooling.domains.atlas.planet.generate import _build_markers
from tooling.domains.atlas.planet.planet_simulation import simulate
from tooling.domains.atlas.planet.render_heightmap import render_heightmap
PLANET_DIR = Path(__file__).resolve().parent
WORKTREE_ROOT = config.repo_root()
# The venv re-exec that used to sit here is gone (T-1288). It relaunched the
@@ -33,12 +38,6 @@ WORKTREE_ROOT = config.repo_root()
# them — and an os.execv into a different interpreter, carrying reach's
# argv, would have relaunched something that is not this command at all.
import numpy as np
from tooling.domains.atlas.planet.planet_simulation import simulate
from tooling.domains.atlas.planet.render_heightmap import render_heightmap
from tooling.domains.atlas.planet.generate import _build_markers
# Per-body importers (lazy-loaded)
SOL_INDEX = WORKTREE_ROOT / "wiki" / "star-systems" / "GJ-0" / "index.md"
@@ -328,7 +328,6 @@ def _specialization_checks(
pass
vocab_pu = {k: (v.get("production_ubiquity_projected")) for k, v in vocab.items()}
vocab_commodity = {k: v.get("commodity_id") for k, v in vocab.items()}
# V-SES-02: every inhabited system must resolve a non-null economic value
# (authored here, or via the #1014 fallback once it exists).
@@ -715,7 +715,7 @@ def author_logo_uv(shell, thr):
# Keep exactly two UV layers: primary (albedo/mask) + logo. Remove extras.
while len(me.uv_layers) > 1:
me.uv_layers.remove(me.uv_layers[-1])
logo_uv = me.uv_layers.new(name="logo_uv")
me.uv_layers.new(name="logo_uv") # created for its side effect: the logo UV layer
me.uv_layers.active = me.uv_layers[0] # keep albedo layer active for mask bake safety
bm = bmesh.new()