diff --git a/.pql/changelog/ticket_history/2026-09.sql b/.pql/changelog/ticket_history/2026-09.sql index f991dd7fa..418aa43a2 100644 --- a/.pql/changelog/ticket_history/2026-09.sql +++ b/.pql/changelog/ticket_history/2026-09.sql @@ -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; diff --git a/.pql/changelog/tickets/2026-09.sql b/.pql/changelog/tickets/2026-09.sql index 906ff4de7..12cf820d7 100644 --- a/.pql/changelog/tickets/2026-09.sql +++ b/.pql/changelog/tickets/2026-09.sql @@ -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; diff --git a/CLAUDE.md b/CLAUDE.md index d22512b89..047640735 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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) ``` diff --git a/docs/DEVOPS.md b/docs/DEVOPS.md index 50482acde..39e046685 100644 --- a/docs/DEVOPS.md +++ b/docs/DEVOPS.md @@ -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). diff --git a/pyproject.toml b/pyproject.toml index 673bce440..00fa83180 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"] diff --git a/server/data/systems.db b/server/data/systems.db index 4ca489829..f9342276d 100644 Binary files a/server/data/systems.db and b/server/data/systems.db differ diff --git a/tooling/domains/atlas/planet/atlas_cohesion_audit.py b/tooling/domains/atlas/planet/atlas_cohesion_audit.py index 1818d7aa5..4a6427447 100644 --- a/tooling/domains/atlas/planet/atlas_cohesion_audit.py +++ b/tooling/domains/atlas/planet/atlas_cohesion_audit.py @@ -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"]) ) diff --git a/tooling/domains/atlas/planet/batch.py b/tooling/domains/atlas/planet/batch.py index 61a8605c1..3b27bba72 100644 --- a/tooling/domains/atlas/planet/batch.py +++ b/tooling/domains/atlas/planet/batch.py @@ -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"} diff --git a/tooling/domains/atlas/planet/body_definition_parser.py b/tooling/domains/atlas/planet/body_definition_parser.py index 6f98b9d04..3878a206b 100644 --- a/tooling/domains/atlas/planet/body_definition_parser.py +++ b/tooling/domains/atlas/planet/body_definition_parser.py @@ -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 = { diff --git a/tooling/domains/atlas/planet/generate.py b/tooling/domains/atlas/planet/generate.py index 4e13e8d87..42a5d438a 100644 --- a/tooling/domains/atlas/planet/generate.py +++ b/tooling/domains/atlas/planet/generate.py @@ -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}, diff --git a/tooling/domains/atlas/planet/import_province_boundaries.py b/tooling/domains/atlas/planet/import_province_boundaries.py index 2664ba7fe..6c22216cb 100644 --- a/tooling/domains/atlas/planet/import_province_boundaries.py +++ b/tooling/domains/atlas/planet/import_province_boundaries.py @@ -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)] diff --git a/tooling/domains/atlas/planet/planet_renderer.py b/tooling/domains/atlas/planet/planet_renderer.py index 232022476..7bcca2fb1 100644 --- a/tooling/domains/atlas/planet/planet_renderer.py +++ b/tooling/domains/atlas/planet/planet_renderer.py @@ -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) diff --git a/tooling/domains/atlas/planet/scaffold_bodies.py b/tooling/domains/atlas/planet/scaffold_bodies.py index c140b6967..90459f0b0 100644 --- a/tooling/domains/atlas/planet/scaffold_bodies.py +++ b/tooling/domains/atlas/planet/scaffold_bodies.py @@ -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: diff --git a/tooling/domains/atlas/planet/sol_data/titan.py b/tooling/domains/atlas/planet/sol_data/titan.py index bb38ca09b..12f56e6b2 100644 --- a/tooling/domains/atlas/planet/sol_data/titan.py +++ b/tooling/domains/atlas/planet/sol_data/titan.py @@ -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 diff --git a/tooling/domains/atlas/planet/sol_import.py b/tooling/domains/atlas/planet/sol_import.py index a6855e4c5..e14dd1164 100644 --- a/tooling/domains/atlas/planet/sol_import.py +++ b/tooling/domains/atlas/planet/sol_import.py @@ -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" diff --git a/tooling/domains/ledger/economy_import/specialization.py b/tooling/domains/ledger/economy_import/specialization.py index bc2d06b32..031a72345 100644 --- a/tooling/domains/ledger/economy_import/specialization.py +++ b/tooling/domains/ledger/economy_import/specialization.py @@ -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). diff --git a/tooling/scripts/blender/blender_author_offset_shell.py b/tooling/scripts/blender/blender_author_offset_shell.py index 570ad1af6..f4ed970a1 100644 --- a/tooling/scripts/blender/blender_author_offset_shell.py +++ b/tooling/scripts/blender/blender_author_offset_shell.py @@ -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()