chore(meta): docs/build sweep + tooling test gate (T-1069, T-1066)

- make test-tooling: planet-gen determinism guard + import_economics
  --dry-run, wired into pre-push on TOOLING_CHANGED; ruff widened to
  E4/E7/E9/F/W (90 safe auto-fixes applied; E402/E702/F841 ignored with
  documented counts)
- one-generator reality fixed in DEVOPS.md, asset-pipeline rule, CLAUDE.md
  (import_economics sole generator since #951/D-223); dead check-protocol
  target deleted; DEVOPS hook/config sections rewritten from the actual
  hook sources; team-patterns gate description updated (client+tooling)
- project.yaml: 0.2.0 → 0.4.0 per the 0.{phase}.{n} scheme, description
  refreshed from the v0.1 Sova narration to cascade reality
- stale comment sweep: voxel.rs stub claims (all 8 families implemented),
  cascade.rs TODO recited to T-1044, main.rs D-192 handshake claim,
  relationships.rs/chunk_streaming.rs version targets → phase language
- gitignore: client/settings.db* e2e-run artifacts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 16:22:55 +02:00
co-authored by Claude Fable 5
parent 0bd895fcac
commit 346d87df7a
47 changed files with 280 additions and 180 deletions
+19 -10
View File
@@ -11,7 +11,8 @@ client can ship it without a build step, but **it is never the source of truth**
> **Edit sources, not the DB.**
If you need to change economics data, modify the TOML/JSON source files.
If you need to change atlas markers, modify the `markers.json` files.
If you need to change the atlas city-name pool, modify the names-only
`markers.json` files (D-223 — they carry no geometry or population).
Never run `UPDATE` or `INSERT` directly on `server/data/systems.db` outside of a
migration — those changes will be silently overwritten by the next `make regen-db`.
@@ -19,12 +20,12 @@ migration — those changes will be silently overwritten by the next `make regen
## What produces systems.db
Two generators write to `systems.db`:
| Generator | Command | Source files (all contribute to the meta stamp SHA) |
|-----------|---------|--------------|
| `import_economics` | `python3 tooling/economy-db/import_economics.py` | `tooling/economy-db/import_economics.py` + the Rust brand binary sources it invokes: `server/src/bin/generate_brands/main.rs`, `server/src/bin/generate_brands/names.rs`, `tooling/generate-brands` + shared `tooling/schema_version.py` |
| `generate_atlas` | `python3 tooling/planet-gen/generate_atlas.py --seed 42` | `tooling/planet-gen/generate_atlas.py` + shared `tooling/schema_version.py` |
**One generator** writes to `systems.db`: `import_economics`
(`python3 tooling/economy-db/import_economics.py`, run via `make regen-db`).
The former atlas geometry generator (`generate_atlas`) was retired in #951
(D-223); `import_economics` now also owns the atlas index — it loads the
names-only `markers.json` city pool into `atlas_city_names` and empties the
geometry tables (the server cascade fills them, Phase 4).
`import_economics` shells out to the Rust `generate_brands` binary as its first
step to refresh `wiki/economics/corporations/generated_brands.toml`, then reads
@@ -33,17 +34,25 @@ of the Python importer, not an independent generator — changes to its source
invalidate the `import_economics` meta stamp even though the Python file
itself didn't change.
`make regen-db` runs both in the correct order (economics first, atlas second).
The full set of source files contributing to the meta stamp SHA (the Python
importer, the Rust brand binary sources, `tooling/schema_version.py`, and the
authored economics TOMLs) is registered in the `GENERATOR_SOURCES` dict at the
top of `tooling/check-systems-db-stamp` — that dict is the single source of
truth, mirrored by `IMPORT_ECONOMICS_SOURCES` in `import_economics.py`.
The surviving planet-gen importers (`import_heightmaps`,
`import_province_boundaries`) are one-time build imports baked into the
committed DB — not part of `make regen-db`, and intentionally not stamped.
---
## The meta table stamp (T-855, T-856)
After every successful non-dry-run, each generator writes a row to the `meta` table:
After every successful non-dry-run, the generator writes a row to the `meta` table:
```sql
CREATE TABLE meta (
generator_name TEXT PRIMARY KEY, -- 'import_economics' | 'generate_atlas'
generator_name TEXT PRIMARY KEY, -- 'import_economics' (sole generator since #951/D-223)
schema_version TEXT NOT NULL, -- monotonic semver string (e.g. "1.0.0") — see T-888
schema_sha TEXT, -- SHA-1 of server/data/systems-schema.sql (tamper detection)
generator_sha TEXT NOT NULL, -- SHA-1 of the generator source file(s)
+6 -4
View File
@@ -34,10 +34,12 @@ When leading a team (workshop, batch, or any multi-agent session):
## Agent verification — don't duplicate the push gate
The pre-push hook (`.config/hooks/pre-push`) is the source of truth for Rust
verification and **runs on every push the lead makes** when `server/` changed:
`cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test`
(plus `cargo deny`, ruff, JSON validation, systems.db stamp). There is no CI (no
The pre-push hook (`.config/hooks/pre-push`) is the source of truth for
verification and **runs on every push the lead makes**: when `server/` changed
`cargo fmt --check`, `cargo clippy --all-targets -- -D warnings`, and `cargo test`;
when `client/` changed — the full gdUnit4 suite via `tests/run-godot` (T-1065);
when `tooling/` changed — `make test-tooling` (plus `cargo deny`, ruff, JSON
validation, systems.db stamp). There is no CI (no
`.gitea`/`.github`/`.forgejo` workflows) — the push gate is the only automatic
verification, so it is deliberately comprehensive.
+5
View File
@@ -109,3 +109,8 @@ wiki/economics/corporations/generated_corporations.toml
# pql's durable, git-versioned planning state (LWW, idempotent replay).
.pql/*.db
.pql/*.db-*
# Client runtime artifacts (settings store written by e2e test runs)
client/settings.db
client/settings.db-shm
client/settings.db-wal
+3 -2
View File
@@ -27,8 +27,9 @@ See [docs/DEVOPS.md](docs/DEVOPS.md) for build, test, lint, and CI procedures. A
### Asset pipeline
`server/data/systems.db` is a read-only canonical snapshot produced by the generator
pipeline — never edit it directly. To regenerate: `make regen-db`. Full rules in
`server/data/systems.db` is a read-only canonical snapshot produced by a single
generator (`import_economics`, which runs `generate_brands` internally) — never edit
it directly. To regenerate: `make regen-db`. Full rules in
`.claude/rules/asset-pipeline.md`.
## Development Cascade — First Things First
+33 -12
View File
@@ -1,6 +1,6 @@
GODOT := $(shell command -v godot4 2>/dev/null || command -v godot 2>/dev/null)
.PHONY: help setup build check-protocol client server game stop test lint lint-python setup-venv ci ci-client ci-server clean \
.PHONY: help setup build client server game stop test test-tooling lint lint-python setup-venv ci ci-client ci-server clean \
decisions-sync decisions-active decisions-validate \
validate-content check-fact-ids setup-hooks install-hooks \
audit deny atlas-verify economy-db regen-db check-systems-db \
@@ -30,6 +30,7 @@ help:
@echo " make client Run the Godot client (test mode)"
@echo " make server Run the Rust simulation server"
@echo " make test Run all tests"
@echo " make test-tooling Tooling test gate: sim determinism + economics dry-run"
@echo " make test-ipc-fixtures Layer 1: IPC serialization fixtures"
@echo " make test-ipc-protocol Layer 2: mock IPC protocol tests"
@echo " make test-ipc-integration Layer 3: real subprocess round-trip"
@@ -39,7 +40,6 @@ help:
@echo " make ci Run full CI pipeline locally"
@echo " make ci-client Run client CI checks"
@echo " make ci-server Run server CI checks"
@echo " make check-protocol Verify server/client protocol versions match"
@echo " make clean Remove build artifacts and caches"
@echo ""
@echo " make decisions-sync Sync governance/*.md decision records into pql.db"
@@ -120,16 +120,7 @@ setup-venv:
# --- Build ---
check-protocol:
@SERVER_V=$$(grep 'pub const PROTOCOL_VERSION' server/src/bridge/types.rs | sed 's/.*= *//;s/[^0-9]//g'); \
CLIENT_V=$$(grep 'const PROTOCOL_VERSION' client/scripts/protocol/protocol.gd | sed 's/.*= *//;s/[^0-9]//g'); \
if [ "$$SERVER_V" != "$$CLIENT_V" ]; then \
echo "ERROR: Protocol version mismatch — server=$$SERVER_V, client=$$CLIENT_V"; \
echo " Fix: update client/scripts/protocol/protocol.gd to match server/src/bridge/types.rs"; \
exit 1; \
fi
build: check-protocol build-server build-client
build: build-server build-client
build-server:
cd server && cargo build
@@ -227,6 +218,36 @@ test-ipc-integration:
test-ipc-benchmark:
tests/run-ipc-benchmark
# Python with tooling deps (numpy/scipy) — .venv from `make setup-venv`, else system python3
VENV_PY := $(shell test -x .venv/bin/python && echo .venv/bin/python || echo python3)
# Tooling test gate (T-1066) — called by pre-push when tooling/ changed.
# 1. planet-gen determinism guard (#963): simulating the same body twice must
# be bit-identical (guards the expensive 271-body heightmap bake).
# Exit 2 = no testable body found — warn, don't block.
# 2. import_economics --dry-run against the committed DB: full parse +
# structural/coverage validation, no writes. Exit 2 = coverage-gate
# warning (D-175) — tolerated, matching regen-db's treatment.
test-tooling:
@echo " [test-tooling] planet-gen determinism guard (#963)..."
@rc=0; $(VENV_PY) tooling/planet-gen/test_sim_determinism.py || rc=$$?; \
if [ $$rc -eq 2 ]; then \
echo " WARNING: determinism guard found no testable body (exit 2) — not blocking"; \
elif [ $$rc -ne 0 ]; then \
echo " FAIL: planet_simulation determinism drift (exit $$rc)"; exit $$rc; \
fi
@echo " [test-tooling] import_economics --dry-run (committed DB)..."
@mkdir -p .cache
@rc=0; python3 tooling/economy-db/import_economics.py --dry-run \
> .cache/test-tooling-dryrun.log 2>&1 || rc=$$?; \
if [ $$rc -eq 2 ]; then \
echo " WARNING: coverage gate warning (exit 2) — not blocking (matches regen-db)"; \
elif [ $$rc -ne 0 ]; then \
echo " FAIL: import_economics --dry-run exited $$rc — log follows:"; \
cat .cache/test-tooling-dryrun.log; exit $$rc; \
fi
@echo " test-tooling: PASS"
# --- Clean ---
clean-imports:
+61 -27
View File
@@ -73,6 +73,7 @@ The server must be running before the client connects (subprocess launch will be
make test # Run all tests (test-server + test-client)
make test-server # Rust tests via tests/run-rust (cargo nextest, JSON summary)
make test-client # Godot tests via tests/run-godot (gdUnit4 headless, JSON summary)
make test-tooling # Tooling gate: planet-gen determinism guard + import_economics --dry-run (T-1066)
```
The IPC test layers (D-030) have dedicated targets:
@@ -210,20 +211,21 @@ Schema: `content/_schema/checklist.schema.json`. The checklist format feeds into
## Asset Pipeline — Generator-Driven DB (#855, #856, #857)
`server/data/systems.db` is a **read-only canonical snapshot** produced by three
generators. It is committed to the repo so the client can ship it, but it is never
`server/data/systems.db` is a **read-only canonical snapshot** produced by a single
generator. It is committed to the repo so the client can ship it, but it is never
the source of truth. Direct edits are forbidden — they are silently overwritten by
the next regeneration.
### Generators
### Generator
| Generator | Source | Runs via |
|-----------|--------|----------|
| `generate_brands` | `server/src/bin/generate_brands/main.rs` | `tooling/generate-brands` |
| `import_economics` | `tooling/economy-db/import_economics.py` | `python3 tooling/economy-db/import_economics.py` |
| `generate_atlas` | `tooling/planet-gen/generate_atlas.py` | `python3 tooling/planet-gen/generate_atlas.py --seed 42` |
`import_economics` (`tooling/economy-db/import_economics.py`) is the sole
generator. As its first step it shells out to the Rust `generate_brands` binary
(via `tooling/generate-brands`) to refresh `generated_brands.toml`, then imports
economics data and the atlas index (names-only city pool; geometry tables stay
empty for the Phase 4 server cascade). The former atlas geometry generator
(`generate_atlas`) was retired in #951 (D-223).
Run all three at once with:
Run it with:
```bash
make regen-db
@@ -231,8 +233,9 @@ make regen-db
### Meta table stamp
After every successful non-dry-run, each generator writes a row to the `meta` table in
`systems.db` recording the SHA-1 of its source file(s) and the schema file.
After every successful non-dry-run, the generator writes a row to the `meta` table in
`systems.db` recording the SHA-1 of its source files and the schema file. The source
registry is the `GENERATOR_SOURCES` dict in `tooling/check-systems-db-stamp`.
```bash
make check-systems-db # Verify the stamp is fresh (exit 1 = stale)
@@ -265,16 +268,43 @@ Or manually:
git config core.hooksPath .config/hooks
```
Active checks:
### Pre-commit checks (`.config/hooks/pre-commit`)
| Hook | Check | Script | Behavior |
|------|-------|--------|----------|
| pre-commit | fact_id validation | `tooling/check-fact-ids` | Warns if catalogs are stubs; fails on unknown fact_ids when populated |
| pre-push | GDScript parse | internal | Fails on any SCRIPT ERROR |
| pre-push | Rust lint | internal | fmt + clippy |
| pre-push | Python lint | internal | ruff |
| pre-push | JSON syntax | internal | python3 -m json.tool |
| pre-push | systems.db stamp | `tooling/check-systems-db-stamp` | Rejects stale DB when pushed (#857) |
| Check | Script | Behavior |
|-------|--------|----------|
| fact_id validation | `tooling/check-fact-ids` | Warns if catalogs are stubs; fails on unknown fact_ids when populated |
| Decision records | `pql decisions validate` | Blocks on malformed decision records (warns if pql not on PATH) |
| Planning changelog | `pql plan export --stage` | Flushes ticket mutations to `.pql/changelog/` and stages them into the commit (warn-only on failure) |
| cargo audit | `cargo audit` | Only when `Cargo.toml`/`Cargo.lock` is staged; blocks on advisories (warns if cargo-audit not installed) |
### Pre-push checks (`.config/hooks/pre-push`)
There is **no CI** — the pre-push gate is the only automatic verification, so it is
deliberately comprehensive. Checks are scoped to what actually changed vs the remote
(`origin/<branch>`, falling back to `origin/main` for first pushes): `client/` changes
gate the GDScript checks, `server/` the Rust checks, `tooling/` + `pyproject.toml` the
Python checks.
| Check | Runs when | Behavior |
|-------|-----------|----------|
| GDScript parse (headless Godot) | `client/` changed | Blocks on any SCRIPT ERROR (skipped if no `client/.godot/` import) |
| gdlint | `client/` changed | **Advisory** until the codebase is clean |
| gdformat --check | `client/` changed | **Advisory** until the codebase is clean |
| `cargo fmt --check` | `server/` changed | Blocks |
| `cargo clippy --all-targets -- -D warnings` | `server/` changed | Blocks (skipped if no `server/target/` — cold worktree) |
| `cargo test` | `server/` changed | Blocks — the only automatic correctness gate (skipped if no `server/target/`) |
| `cargo deny check` | `server/` changed | Blocks (only if cargo-deny installed and `server/deny.toml` exists) |
| `ruff check tooling/` | `tooling/` changed | Blocks |
| `make test-tooling` | `tooling/` changed | Blocks — sim determinism guard + economics dry-run (T-1066) |
| JSON syntax (`python3 -m json.tool`) | any changed `*.json` | Blocks on syntax errors |
| systems.db stamp | `server/data/systems.db` in push | Blocks on stale stamp (#857); missing meta table warns only |
| Clerk review (D-221) | **disabled** (#965) | Force-run with `SR_RUN_CLERK=1` |
### Post-merge / post-checkout / post-rewrite
These hooks replay the pql planning changelog (`pql plan import` / `rebuild`) and
re-sync decisions from `governance/*.md` so the planning DB stays in step after
merges, checkouts, and history rewrites.
The `core.hooksPath` setting uses a relative path (`.config/hooks`) that resolves per worktree, so it works correctly across all worktrees in the repository.
@@ -282,18 +312,22 @@ To bypass hooks in an emergency:
```bash
git commit --no-verify -m "fix: emergency hotfix"
git push --no-verify
```
## Configuration Files
The `.config/` directory holds shared configuration for linters, formatters, and CI. Examples of what goes here:
The `.config/` directory currently holds exactly one thing: the version-controlled
git hooks in `.config/hooks/` (pre-commit, pre-push, post-merge, post-checkout,
post-rewrite), activated via `git config core.hooksPath .config/hooks`
(`make install-hooks`).
- Clippy configuration overrides
- gdlint/gdformat rules
- CI workflow definitions (before moving to `.github/workflows/`)
- Editor config templates
Project-specific config that lives in subdirectories (e.g., `server/Cargo.toml`, `client/project.godot`) stays in those directories. `.config/` is for cross-cutting or shared configuration.
Linter and formatter configuration lives with the code it governs, not in `.config/`:
ruff is configured in the root `pyproject.toml` (`[tool.ruff]`), Rust uses cargo
defaults plus `server/deny.toml`, and the client uses gdlint/gdformat defaults.
Project-specific config (e.g. `server/Cargo.toml`, `client/project.godot`) stays in
those directories. `.config/` is reserved for future cross-cutting configuration
that has no better home.
## Cache Directory
+19 -9
View File
@@ -1,19 +1,29 @@
name: The Settled Reach
version: 0.2.0
# Version scheme: 0.{phase}.{n} — phase = active Development Cascade phase (D-166).
# Phase 4 (deterministic world generation) is active.
version: 0.4.0
repository: settled-reach
description: >
Top-down immersive sim with occlusion-based detection mechanics and
combat elements. Single-character perspective where asymmetric information
is the core gameplay mechanic. A Rimworld-style storyteller drives
emergent narrative across a detective-smuggler dual-lens campaign.
Top-down life-sim set in an original science fiction universe. Asymmetric
information and occlusion-based perception from a single-character
perspective, with a Rimworld-style storyteller. Systems interactions like
The Sims, combat and visuals like single-character Rimworld, world
generation inheriting from Dwarf Fortress and NMS, economy inspired by
X4/EVE. Every system is interactable but ignorable — the world is alive
for any given run.
setting: >
Original science fiction universe — the Settled Reach, a network of
star systems connected by Founder Gates. Neural lattice technology
enables soft immortality, forking, and re-embodiment. The v0.1
vertical slice takes place in Sova Transit District, Van Maanen's Star.
The Settled Reach a network of star systems connected by Founder Gates.
Neural lattice technology enables soft immortality, forking, and
re-embodiment.
development: >
Built outside-in along the six-phase Development Cascade (D-166): wiki
content and star map, economics layer, planetary maps and atlas,
deterministic world generation (active), player control and in-world
rendering, room-level detail.
architecture:
client: Godot 4 (GDScript)
+13 -5
View File
@@ -24,8 +24,16 @@ line-length = 100
target-version = "py311"
[tool.ruff.lint]
# E9xx: Runtime syntax/encoding errors
# F401: Unused imports
# F811: Redefinition of unused name
# F821: Undefined name (catches missing imports like bare `os`)
select = ["E9", "F401", "F811", "F821"]
# Widened from {E9, F401, F811, F821} to the full ruff-default tiers + W (T-1066).
# E4: import placement/style
# E7: statement-level pitfalls (== None, bare except, lambda assignment, ...)
# E9: runtime syntax/encoding errors
# 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"]
+1 -1
View File
@@ -216,7 +216,7 @@ pub fn run_cascade_from_heightmap(
// result nor the TerrainAnalysis is stored on Layer1Output, so we re-run both
// here. Pure → determinism preserved, but the drainage re-run is NOT free at
// the ~6 000-regions/body working scale (D-203).
// PERF/TODO(T-1028): cache TerrainAnalysis on Layer1Output to drop this
// PERF/TODO(T-1044): cache TerrainAnalysis on Layer1Output to drop this
// redundant drainage pass, and validate the combined cost against the D-239 §10
// ~45 ms/body budget in the T-1031 verification harness. This is now a LIVE
// production cost: T-1032 wired the real body_params read, so every analyzed
+13 -21
View File
@@ -38,14 +38,14 @@
//! eviction is by generation-counter LRU (oldest entry evicted when capacity
//! is reached). Never written to disk.
//!
//! ## Family dispatch (D-239 §5, T-1028)
//! ## Family dispatch (D-239 §5, T-1028/T-1029)
//!
//! The 8-family tree is dispatched through `MorphologyFamily`. Only
//! `AlluvialPlain` is implemented here (T-1028). The other 7 families are
//! T-1029: their stubs return the AlluvialPlain output as a documented
//! placeholder — **production will not crash on them**, but the output is not
//! the correct final geometry for that family. The stubs are clearly marked
//! `// T-1029 — NOT YET IMPLEMENTED` so T-1029 can find and replace them.
//! The 8-family tree is dispatched through `MorphologyFamily`. All 8 family
//! generators are implemented: `AlluvialPlain` (the D-239 §5 fallback) landed
//! in T-1028; the other 7 (LavaField, FjordWall, CliffCoast, BraidedDelta,
//! DuneStrand, IncisedGorge, MeanderReach) landed in T-1029. Flat/water zones
//! without a dedicated family (OpenOcean, Lake, TidalFlat, Wetland) map to
//! `AlluvialPlain` per D-239 §5.
//!
//! ## D-010 compliance
//!
@@ -249,33 +249,25 @@ pub type VoxelPos = (i32, i32);
/// pre-computed at zone classification time (RegionProfile); the family is the
/// structural decision that drives voxel geometry.
///
/// Only `AlluvialPlain` is implemented in T-1028. Other families return a
/// documented placeholder that falls back to AlluvialPlain geometry (not
/// `panic!` / `unimplemented!`). T-1029 replaces the stubs.
/// All 8 families have dedicated generators: `AlluvialPlain` (T-1028, also the
/// D-239 §5 fallback for flat/water zones) and the other 7 (T-1029).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MorphologyFamily {
/// Lava field / shield slope. Requires TectonicClass::Volcanic (D-239 §5).
/// T-1029 — NOT YET IMPLEMENTED: falls back to AlluvialPlain.
LavaField,
/// Fjord wall. Requires GlaciationGrade ≥ 2 (D-239 §5).
/// T-1029 — NOT YET IMPLEMENTED: falls back to AlluvialPlain.
FjordWall,
/// Cliff coast. High slope + coastal (D-239 §5).
/// T-1029 — NOT YET IMPLEMENTED: falls back to AlluvialPlain.
CliffCoast,
/// Braided delta. Very flat + low elevation + coastal (D-239 §5).
/// T-1029 — NOT YET IMPLEMENTED: falls back to AlluvialPlain.
BraidedDelta,
/// Dune strand. Low slope + coastal + arid (D-239 §5).
/// T-1029 — NOT YET IMPLEMENTED: falls back to AlluvialPlain.
DuneStrand,
/// Incised gorge / mountain pass. High slope + inland + high elev (D-239 §5).
/// T-1029 — NOT YET IMPLEMENTED: falls back to AlluvialPlain.
IncisedGorge,
/// Meander reach. Gentle slope + water presence (D-239 §5).
/// T-1029 — NOT YET IMPLEMENTED: falls back to AlluvialPlain.
MeanderReach,
/// Alluvial plain — fallback (D-239 §5). **IMPLEMENTED** in T-1028.
/// Alluvial plain — the D-239 §5 fallback family (T-1028).
AlluvialPlain,
}
@@ -301,8 +293,8 @@ fn zone_to_family(zone: &MorphologyZone) -> MorphologyFamily {
MorphologyFamily::IncisedGorge
}
MorphologyZone::MeanderReach | MorphologyZone::RiverBank => MorphologyFamily::MeanderReach,
// AlluvialPlain fallback covers: AlluvialPlain, OpenOcean, Lake, TidalFlat,
// Wetland — any zone without a fully-implemented T-1029 generator.
// AlluvialPlain fallback covers the flat/water zones without a dedicated
// family: AlluvialPlain, OpenOcean, Lake, TidalFlat, Wetland (D-239 §5).
MorphologyZone::AlluvialPlain
| MorphologyZone::OpenOcean
| MorphologyZone::Lake
@@ -401,7 +393,7 @@ pub fn derive_voxel_column(
// AlluvialPlain generator (T-1028, D-239 §5 fallback)
// ---------------------------------------------------------------------------
/// AlluvialPlain voxel generator — the only fully implemented family in T-1028.
/// AlluvialPlain voxel generator — the D-239 §5 fallback family (T-1028).
///
/// Flat floodplain with:
/// - `Soil` terrain material (D-239 §8 Soil law: rolling/floodplain)
+2 -1
View File
@@ -124,7 +124,8 @@ fn main() {
tracing::info!("Client connected, sending protocol handshake");
// Protocol handshake: first framed message on the wire (#555).
// Client reads this and validates protocol_version before sending any input.
// Carries no version field (D-192 dropped the PROTOCOL_VERSION lockstep);
// the client reads it and replies with its StartupMessage.
use settled_reach_server::bridge::SimBridge;
bridge.send_handshake().unwrap_or_else(|e| {
tracing::error!("Failed to send handshake: {}", e);
+3 -2
View File
@@ -89,8 +89,9 @@ pub struct RelationshipEdge {
/// BTreeMap<(subject, target), edge> for deterministic iteration (D-010).
/// Directed graph: edge (A, B) represents how A feels about B.
///
/// TODO(v0.2): RelationshipGraph is per-world. Multiplayer needs per-observer
/// relationship views (D-010).
/// TODO(post-Phase-6): RelationshipGraph is per-world. Multiplayer — if it ever
/// lands, it is beyond the D-166 cascade — needs per-observer relationship
/// views (D-010).
#[derive(Resource, Debug, Clone, Default, Serialize, Deserialize)]
pub struct RelationshipGraph {
edges: BTreeMap<(StableId, StableId), RelationshipEdge>,
+6 -4
View File
@@ -1,9 +1,10 @@
//! Chunk streaming system (#578, D-012).
//!
//! Loads chunks near the player and unloads distant chunks based on a
//! configurable radius. For v0.1 the radius covers the entire hand-authored
//! configurable radius. Until Phase 5 the radius covers the entire hand-authored
//! district (256×256 visual tiles = 8×8 chunks of 32 tiles), so all chunks
//! remain loaded. The architecture supports future per-demand loading (v0.3+).
//! remain loaded. The architecture supports per-demand loading over the
//! generated world once player control lands (Phase 5).
//!
//! The system runs on a configurable tick cadence (default: every 10 ticks).
//! It queries the player's TilePosition, computes which chunks should be
@@ -17,8 +18,9 @@ use crate::simulation::time::SimulationTime;
/// How many chunks around the player to keep loaded (Chebyshev distance).
///
/// Default: 8, which covers the full v0.1 district (256×256 = 8×8 chunks).
/// For v0.3+ borderless generation, set to 3-4 for memory-bounded streaming.
/// Default: 8, which covers the full hand-authored district (256×256 = 8×8
/// chunks). For Phase 5 borderless streaming over the generated world, set to
/// 3-4 for memory-bounded loading.
#[derive(Resource, Debug, Clone)]
pub struct ChunkLoadRadius {
pub radius: i32,
+1 -1
View File
@@ -256,7 +256,7 @@ def main():
for node in nodes:
sp = simplify_spectral(node.get("spectral_class", ""))
type_counts[sp] = type_counts.get(sp, 0) + 1
print(f"\nSimplified spectral distribution:")
print("\nSimplified spectral distribution:")
for t in ["G", "K", "M", "F", "unusual"]:
print(f" {t:8s} {type_counts.get(t, 0):3d} ({type_counts.get(t, 0)/len(nodes)*100:.0f}%)")
+2 -2
View File
@@ -266,7 +266,7 @@ if __name__ == "__main__":
print(f"ERROR: Reference body directory not found: {avg_m_dir}")
sys.exit(1)
print(f"\nClothing Reference Mesh Creator (v0.2 placeholder)")
print("\nClothing Reference Mesh Creator (v0.2 placeholder)")
print(f" Bodies dir: {bodies_dir}")
print(f" Output dir: {clothing_output_dir}")
print(f" Reference: {REFERENCE_BODY}")
@@ -280,7 +280,7 @@ if __name__ == "__main__":
# Summary
print(f"\n{'='*60}")
print(f"=== Clothing reference creation complete ===")
print("=== Clothing reference creation complete ===")
ok_items = [k for k, v in results.items() if v]
fail_items = [k for k, v in results.items() if not v]
for item_id in CLOTHING_ITEMS:
+1 -1
View File
@@ -131,7 +131,7 @@ if __name__ == "__main__":
# Summary
print(f"\n{'='*60}")
print(f"=== Body type segmentation complete ===")
print("=== Body type segmentation complete ===")
total_exported = 0
for body_type, exported, skipped, is_fork in results:
flag = " [FORK]" if is_fork else ""
+1 -1
View File
@@ -232,7 +232,7 @@ if __name__ == "__main__":
results.append((tag, key, os.path.getsize(glb_path)))
# bald.glb placeholder
print(f"\n[hair] bald (placeholder)")
print("\n[hair] bald (placeholder)")
bald_path = os.path.join(hair_out, "bald.glb")
make_bald_placeholder(bald_path)
results.append(("hair", "bald", os.path.getsize(bald_path)))
+1 -1
View File
@@ -82,7 +82,7 @@ def process_head(blend_path: str, output_dir: str, head_id: str) -> None:
bpy.ops.object.select_all(action='SELECT')
# Export GLB — embedded textures, no animations
print(f" Exporting GLB...")
print(" Exporting GLB...")
bpy.ops.export_scene.gltf(
filepath=glb_path,
use_selection=True,
+6 -6
View File
@@ -296,7 +296,7 @@ def process_body_type(body_type, reference_glb, bodies_dir, output_dir):
# average_m is a direct copy -- no deform required
if body_type == REFERENCE_BODY:
print(f" Reference body type -- copying reference directly")
print(" Reference body type -- copying reference directly")
shutil.copy2(reference_glb, output_path)
return {"body_type": body_type, "status": "ok", "method": "copy"}
@@ -322,7 +322,7 @@ def process_body_type(body_type, reference_glb, bodies_dir, output_dir):
print(f" Clothing mesh: {len(clothing_obj.data.vertices)} vertices")
# --- Build body surface ---
print(f" Building body surface from segments...")
print(" Building body surface from segments...")
body_surface = build_body_surface(bodies_dir, body_type)
if body_surface is None:
return {"body_type": body_type, "status": "error",
@@ -386,7 +386,7 @@ if __name__ == "__main__":
print(f"\nWARNING: Missing body type directories (will skip): "
f"{', '.join(missing_types)}")
print(f"\nSurface Deform Batch Pipeline")
print("\nSurface Deform Batch Pipeline")
print(f" Reference: {reference_glb}")
print(f" Bodies: {bodies_dir}")
print(f" Output: {output_dir}")
@@ -405,7 +405,7 @@ if __name__ == "__main__":
# Summary
print(f"\n{'='*60}")
print(f"=== Surface Deform batch complete ===")
print("=== Surface Deform batch complete ===")
ok = [r for r in results if r["status"] == "ok"]
errors = [r for r in results if r["status"] == "error"]
skipped = [r for r in results if r["status"] == "skipped"]
@@ -434,8 +434,8 @@ if __name__ == "__main__":
print(f" SKIPPED: {len(skipped)}")
if sw_count > 0:
print(f"\n WARNING: {sw_count} body type(s) used Shrinkwrap fallback.")
print(f" Visually verify these variants at gameplay zoom — Shrinkwrap")
print(f" may produce pinching at extremities on extreme body types.")
print(" Visually verify these variants at gameplay zoom — Shrinkwrap")
print(" may produce pinching at extremities on extreme body types.")
# Write pipeline_log.json for provenance tracking
log_path = os.path.join(output_dir, "pipeline_log.json")
+2 -1
View File
@@ -1,5 +1,6 @@
"""List all objects in a Quaternius .blend file."""
import sys, bpy
import sys
import bpy
if __name__ == "__main__":
argv = sys.argv
+3 -3
View File
@@ -235,14 +235,14 @@ def main():
# Health check if any SAO assets
sao_assets = [a for a in assets if a.get("method") == "sao"]
if sao_assets and not dry_run:
print(f"Checking SAO API health...", file=sys.stderr)
print("Checking SAO API health...", file=sys.stderr)
health_cmd = [sys.executable, os.path.join(script_dir, "audio_connector.py"), "health"]
result = subprocess.run(health_cmd, capture_output=True, text=True)
if result.returncode != 0:
print(json.dumps({"ok": False, "error": "SAO API health check failed",
"details": result.stdout.strip()}))
sys.exit(1)
print(f" SAO API is up.", file=sys.stderr)
print(" SAO API is up.", file=sys.stderr)
total = len(assets)
results = []
@@ -264,7 +264,7 @@ def main():
if skip_existing:
ogg_path = os.path.join(output_dir, filename)
if os.path.exists(ogg_path):
print(f" Skipping — already exists", file=sys.stderr)
print(" Skipping — already exists", file=sys.stderr)
results.append({"id": asset_id, "status": "skipped", "reason": "exists"})
skipped += 1
continue
+1 -1
View File
@@ -135,7 +135,7 @@ def generate(prompt, duration=10.0, steps=100, cfg=7.0, output=None, timeout=600
method="POST"
)
print(f"Submitting generation request...", file=sys.stderr)
print("Submitting generation request...", file=sys.stderr)
print(f" Prompt: {prompt}", file=sys.stderr)
print(f" Duration: {duration}s, Steps: {steps}, CFG: {cfg}", file=sys.stderr)
+1 -1
View File
@@ -112,7 +112,7 @@ def cmd_pipeline(args):
# Step 3: convert
run_ffmpeg(
["-i", normalized, "-c:a", "libvorbis", "-q:a", str(args.quality), output],
f"step 3/3: converting to OGG"
"step 3/3: converting to OGG"
)
# Clean up intermediates
+8 -8
View File
@@ -72,12 +72,12 @@ def main():
"SELECT COUNT(*) FROM bodies WHERE cultural_corridor IS NULL"
).fetchone()[0]
print(f"\n cultural_corridor backfill")
print("\n cultural_corridor backfill")
print(f" DB: {db_path}")
if args.dry_run:
print(f" Mode: DRY RUN")
print(" Mode: DRY RUN")
print()
print(f" Before:")
print(" Before:")
print(f" star_systems.cultural_corridor NULL: {before_systems_null}")
print(f" bodies.cultural_corridor NULL: {before_bodies_null}")
@@ -118,14 +118,14 @@ def main():
if args.dry_run:
conn.rollback()
print()
print(f" Would update:")
print(" Would update:")
print(f" star_systems: {sys_rows_updated}")
print(f" bodies: {body_rows_updated}")
print(f"\n Dry run — no changes written.")
print("\n Dry run — no changes written.")
else:
conn.commit()
print()
print(f" Updated:")
print(" Updated:")
print(f" star_systems: {sys_rows_updated}")
print(f" bodies: {body_rows_updated}")
@@ -137,13 +137,13 @@ def main():
"SELECT COUNT(*) FROM bodies WHERE cultural_corridor IS NULL"
).fetchone()[0]
print()
print(f" After:")
print(" After:")
print(f" star_systems.cultural_corridor NULL: {after_systems_null}")
print(f" bodies.cultural_corridor NULL: {after_bodies_null}")
# Show the distribution so the outcome is visible.
print()
print(f" star_systems.cultural_corridor distribution:")
print(" star_systems.cultural_corridor distribution:")
for corridor, count in conn.execute(
"SELECT cultural_corridor, COUNT(*) FROM star_systems "
"GROUP BY cultural_corridor ORDER BY COUNT(*) DESC"
+1 -1
View File
@@ -139,7 +139,7 @@ def generate(prompt, output=None, aspect_ratio="1:1", image_size=None,
method="POST"
)
print(f"Generating image...", file=sys.stderr)
print("Generating image...", file=sys.stderr)
print(f" Prompt: {prompt}", file=sys.stderr)
if input_image:
print(f" Input image: {input_image}", file=sys.stderr)
+4 -4
View File
@@ -144,11 +144,11 @@ def main():
except sqlite3.OperationalError:
pass # column already exists
print(f"\n populate_gttr_hook.py")
print("\n populate_gttr_hook.py")
print(f" DB: {db_path}")
print(f" max words: {args.max_words}")
if args.dry_run:
print(f" Mode: DRY RUN")
print(" Mode: DRY RUN")
print()
rows = conn.execute(
@@ -202,12 +202,12 @@ def main():
conn.close()
print(f"\n Done:")
print("\n Done:")
print(f" updated: {updated}")
print(f" missing: {missing}")
print(f" unmatched: {unmatched}")
if args.dry_run:
print(f"\n Dry run — no DB writes.")
print("\n Dry run — no DB writes.")
print()
+2 -1
View File
@@ -222,7 +222,8 @@ def generate(image_path, output=None, simplify=0.95, texture_size=1024,
# Generate a session hash — Gradio uses this to maintain gr.State between
# separate API calls. Without it, image_to_3d's output state is lost before
# extract_glb can read it.
import random, string
import random
import string
session = ''.join(random.choices(string.ascii_lowercase + string.digits, k=12))
print(f" Session: {session}", file=sys.stderr)
+9 -4
View File
@@ -5,7 +5,11 @@ Usage: python3 tooling/generate-star-map-svg.py [output.svg]
Default output: docs/diagrams/design/star-map-concentric.svg
"""
import json, sqlite3, math, sys, os
import json
import sqlite3
import math
import sys
import os
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(SCRIPT_DIR)
@@ -238,10 +242,10 @@ grad_r = max(width, height)
start_pct = fade_start / grad_r * 100
end_pct = fade_end / grad_r * 100
svg.append(f'<radialGradient id="fadeEdge" cx="{cx}" cy="{cy}" r="{grad_r}" gradientUnits="userSpaceOnUse">')
svg.append(f' <stop offset="0%" stop-color="#0a0a1a" stop-opacity="0"/>')
svg.append(' <stop offset="0%" stop-color="#0a0a1a" stop-opacity="0"/>')
svg.append(f' <stop offset="{start_pct:.1f}%" stop-color="#0a0a1a" stop-opacity="0"/>')
svg.append(f' <stop offset="{end_pct:.1f}%" stop-color="#0a0a1a" stop-opacity="1"/>')
svg.append(f' <stop offset="100%" stop-color="#0a0a1a" stop-opacity="1"/>')
svg.append(' <stop offset="100%" stop-color="#0a0a1a" stop-opacity="1"/>')
svg.append('</radialGradient>')
svg.append('</defs>')
@@ -338,7 +342,8 @@ print(f'{len(positions)} nodes, {len(starmap["edges"])} edges')
print(f'Hops 0-{FOLD_HOP-1} individual, {FOLD_HOP}+ folded ({len(hop_groups.get(FOLD_HOP, []))} nodes)')
# Also render PNG
import shutil, subprocess
import shutil
import subprocess
png_path = output_path.replace('.svg', '.png')
if shutil.which('magick'):
cmd = ['magick', '-density', '150', '-background', '#0a0a1a', output_path, png_path]
+17 -17
View File
@@ -1171,8 +1171,8 @@ def generate_sector_d2(
lines = []
lines.append(f"# Star Map — {sector_label}")
lines.append(f"# Sector map. Cross-sector connections shown as stub nodes (dashed border).")
lines.append(f"# Node color = settlement wave. Shape = topology type.")
lines.append("# Sector map. Cross-sector connections shown as stub nodes (dashed border).")
lines.append("# Node color = settlement wave. Shape = topology type.")
lines.append("")
lines.append("vars: {")
lines.append(f' bg: "{D2_BG}"')
@@ -1182,7 +1182,7 @@ def generate_sector_d2(
lines.append("")
# Root style
lines.append(f"direction: right")
lines.append("direction: right")
lines.append("")
lines.append(f'style.fill: "{D2_BG}"')
lines.append(f'style.stroke: "{D2_ACC}"')
@@ -1192,7 +1192,7 @@ def generate_sector_d2(
# Legend
lines.append("legend: Legend {")
lines.append(f' style.fill: "{D2_BG}"; style.stroke: "{D2_ACC}"; style.font-color: "{D2_TXT}"')
lines.append(f' style.font-size: 10')
lines.append(' style.font-size: 10')
for wave, stroke in WAVE_STROKE.items():
fill = WAVE_FILL[wave]
w_label = wave.replace("_", " ").title()
@@ -1227,9 +1227,9 @@ def generate_sector_d2(
lines.append(f' style.fill: "{fill}"')
lines.append(f' style.stroke: "{stroke}"')
lines.append(f' style.font-color: "{D2_TXT}"')
lines.append(f' style.font-size: 9')
lines.append(' style.font-size: 9')
if nid == "S-001":
lines.append(f' style.stroke-width: 3')
lines.append(' style.stroke-width: 3')
lines.append("}")
lines.append("")
@@ -1240,12 +1240,12 @@ def generate_sector_d2(
stub_sector_label = SECTOR_LABELS[stub_node["geographic_sector"]]
label = f"{stub_id}\\n[{stub_sector_label}]"
lines.append(f'{d2id}: "{label}" {{')
lines.append(f' shape: rectangle')
lines.append(' shape: rectangle')
lines.append(f' style.fill: "{STUB_FILL}"')
lines.append(f' style.stroke: "{STUB_STROKE}"')
lines.append(f' style.stroke-dash: 5')
lines.append(' style.stroke-dash: 5')
lines.append(f' style.font-color: "{STUB_STROKE}"')
lines.append(f' style.font-size: 9')
lines.append(' style.font-size: 9')
lines.append("}")
lines.append("")
@@ -1281,8 +1281,8 @@ def generate_sector_d2(
if is_cross:
lines.append(f"{edge_line}: {{")
lines.append(f' style.stroke: "{color}"')
lines.append(f' style.stroke-dash: 4')
lines.append(f' style.stroke-width: 1')
lines.append(' style.stroke-dash: 4')
lines.append(' style.stroke-width: 1')
lines.append("}")
else:
lines.append(f"{edge_line}: {{")
@@ -1331,7 +1331,7 @@ def generate_overview_d2(
lines.append(f' acc: "{D2_ACC}"')
lines.append("}")
lines.append("")
lines.append(f'direction: right')
lines.append('direction: right')
lines.append(f'style.fill: "{D2_BG}"')
lines.append(f'style.stroke: "{D2_ACC}"')
lines.append(f'style.font-color: "{D2_TXT}"')
@@ -1354,23 +1354,23 @@ def generate_overview_d2(
fill, stroke = sector_colors[sector]
d2id = sector.replace("_", "")
lines.append(f'{d2id}: "{label}\\n{count} systems · {hubs} hubs" {{')
lines.append(f' shape: rectangle')
lines.append(' shape: rectangle')
lines.append(f' style.fill: "{fill}"')
lines.append(f' style.stroke: "{stroke}"')
lines.append(f' style.font-color: "{D2_TXT}"')
lines.append(f' style.border-radius: 8')
lines.append(' style.border-radius: 8')
lines.append("}")
lines.append("")
# Special Gateway callout
lines.append('gateway_note: "GATEWAY (S-001)\\nDiplomatic Periphery · Core\\nSol aperture: dormant" {')
lines.append(f' shape: hexagon')
lines.append(' shape: hexagon')
lines.append(f' style.fill: "{GATEWAY_FILL}"')
lines.append(f' style.stroke: "{GATEWAY_STROKE}"')
lines.append(f' style.font-color: "{D2_TXT}"')
lines.append(f' style.stroke-width: 3')
lines.append(' style.stroke-width: 3')
lines.append("}")
lines.append(f'gateway_note -> core: "located in" {{')
lines.append('gateway_note -> core: "located in" {')
lines.append(f' style.stroke: "{GATEWAY_STROKE}"; style.stroke-dash: 3')
lines.append("}")
lines.append("")
+1 -1
View File
@@ -154,7 +154,7 @@ def process_file(input_path: str, output_path: str):
count, names = strip_utility_nodes(gltf)
if count == 0:
print(f" No utility nodes found — file unchanged")
print(" No utility nodes found — file unchanged")
if output_path != input_path:
import shutil
shutil.copy2(input_path, output_path)
+2 -2
View File
@@ -97,7 +97,7 @@ def migrate_database(mapping):
print("Database not found, skipping.")
return
print(f"Migrating systems.db...")
print("Migrating systems.db...")
conn = sqlite3.connect(SYSTEMS_DB)
cur = conn.cursor()
@@ -155,7 +155,7 @@ def migrate_database(mapping):
def migrate_wiki_pages(mapping):
"""Replace S-numbers in wiki page headers and topology sections."""
print(f"Migrating wiki pages...")
print("Migrating wiki pages...")
count = 0
# Build reverse: need to match S-numbers in text
+1 -1
View File
@@ -364,7 +364,7 @@ def main() -> None:
if row:
system_id = row["system_id"]
print(f"\nAtlas Cohesion Audit")
print("\nAtlas Cohesion Audit")
print(f" DB: {db}")
if system_id:
print(f" System: {system_id}")
+3 -3
View File
@@ -334,11 +334,11 @@ def main():
total_invalid = 0
error_rate_threshold = 0.50
print(f"\n Planet Generator — Batch Mode")
print("\n Planet Generator — Batch Mode")
print(f" Systems: {len(system_dirs)}")
print(f" Heightmap: {hmap_w}×{hmap_h} Globe: {args.globe_size}×{args.globe_size}")
if args.dry_run:
print(f" Mode: DRY RUN (validation only)")
print(" Mode: DRY RUN (validation only)")
print()
for system_dir in system_dirs:
@@ -545,7 +545,7 @@ def _verify_determinism(wiki_systems: Path, n_samples: int,
print(f" passed: {passed} failed: {failed}")
if failed > 0:
print(f" WARNING: non-deterministic output detected!")
print(" WARNING: non-deterministic output detected!")
if __name__ == "__main__":
@@ -479,12 +479,12 @@ def main() -> None:
print(f"error: {db_path} not found", file=sys.stderr)
sys.exit(1)
print(f"\n Province Boundary Import (#907)")
print("\n Province Boundary Import (#907)")
print(f" DB: {db_path}")
if args.dry_run:
print(f" Mode: DRY RUN (no DB writes)")
print(" Mode: DRY RUN (no DB writes)")
if args.force:
print(f" Force: enabled (will overwrite existing rows)")
print(" Force: enabled (will overwrite existing rows)")
print()
conn = sqlite3.connect(str(db_path))
+3 -1
View File
@@ -861,7 +861,9 @@ def render_globe(
# ---------------------------------------------------------------------------
if __name__ == "__main__":
import sys, os, time
import sys
import os
import time
TEST_BODIES = [
{
+4 -1
View File
@@ -858,7 +858,10 @@ def simulate(body_def: dict) -> dict:
# ---------------------------------------------------------------------------
if __name__ == "__main__":
import sys, json, time, os
import sys
import json
import time
import os
from PIL import Image
if len(sys.argv) < 2:
@@ -81,10 +81,10 @@ def main():
ORDER BY b.system_id, b.body_id
""").fetchall()
print(f"\n terrain_reference population pass")
print("\n terrain_reference population pass")
print(f" DB: {db_path}")
if args.dry_run:
print(f" Mode: DRY RUN")
print(" Mode: DRY RUN")
print(f"\n {len(rows)} bodies with NULL terrain_reference\n")
found = []
@@ -119,12 +119,12 @@ def main():
conn.commit()
print(f"\n Committed {len(found)} terrain_reference updates.")
else:
print(f"\n Dry run — no changes written.")
print("\n Dry run — no changes written.")
conn.close()
# Summary
print(f"\n Summary:")
print("\n Summary:")
print(f" Updated: {len(found)}")
print(f" Missing: {len(missing)}")
print(f" Total: {len(rows)}\n")
@@ -132,7 +132,7 @@ def main():
if missing:
print(f" Action required: generate heightmaps for {len(missing)} bodies "
f"before running the atlas importers (#901).")
print(f" Use: make generate-terrain (or run generate.py per body)\n")
print(" Use: make generate-terrain (or run generate.py per body)\n")
if __name__ == "__main__":
+3 -1
View File
@@ -418,7 +418,9 @@ def render_heightmap(body_def: dict,
# ---------------------------------------------------------------------------
if __name__ == "__main__":
import sys, json, time
import sys
import json
import time
if len(sys.argv) < 2:
print("Usage: python3 render_heightmap.py body_def.json [--large]")
+1 -1
View File
@@ -95,7 +95,7 @@ def build_terrain(body_def: dict) -> dict:
sea_level = 0.0
surface_water = np.zeros((GRID_H, GRID_W), dtype=bool)
print(f" elevation normalised")
print(" elevation normalised")
# ── 2. Temperature ──────────────────────────────────────────────────
temperature_K = temperature_grid_analytical(
+1 -1
View File
@@ -92,7 +92,7 @@ def build_terrain(body_def: dict) -> dict:
# Normalise to [0, 1]
elevation = normalize_01(elevation_m, MARS_MIN_ELEV_M, MARS_MAX_ELEV_M)
print(f" elevation normalised")
print(" elevation normalised")
# ── 2. Temperature ──────────────────────────────────────────────────
# Analytical: equatorial ~210K, polar ~150K, elevation lapse
+1 -1
View File
@@ -70,7 +70,7 @@ def _load_magellan() -> np.ndarray:
print(f" PDS also failed ({e3})")
# All sources failed — fall through to procedural generation
print(f" WARNING: all Magellan sources failed, using procedural")
print(" WARNING: all Magellan sources failed, using procedural")
return None
+3 -3
View File
@@ -190,7 +190,7 @@ def _generate_body(body_def: dict, hmap_w: int, hmap_h: int,
if is_gas:
# Gas giants: no terrain, renderer handles bands procedurally
terrain = {}
print(f" terrain: gas giant (procedural bands)")
print(" terrain: gas giant (procedural bands)")
elif body_id in REAL_DATA_BODIES:
# Real-world data import
module_name = REAL_DATA_BODIES[body_id]
@@ -198,11 +198,11 @@ def _generate_body(body_def: dict, hmap_w: int, hmap_h: int,
importer = _load_importer(module_name)
terrain = importer.build_terrain(body_def)
if download_only:
print(f" download complete, skipping render")
print(" download complete, skipping render")
return
elif body_id in PROCEDURAL_BODIES:
# Fall through to standard procedural simulation
print(f" procedural simulation (irregular body)...")
print(" procedural simulation (irregular body)...")
terrain = simulate(body_def)
else:
print(f" WARNING: no importer for {body_id}, using procedural")
+1 -1
View File
@@ -78,7 +78,7 @@ def main():
all_skips += [(f.split('/main/',1)[-1], s) for s in skips]
if APPLY and rewrites:
open(f, "w", encoding="utf-8").write(new)
print(f"\n--- SKIPPED (not rewritten), grouped by reason ---")
print("\n--- SKIPPED (not rewritten), grouped by reason ---")
by_reason = {}
for rel, (tok, reason) in all_skips:
by_reason.setdefault(reason, []).append(f"{tok} ({os.path.basename(rel)})")
+1 -1
View File
@@ -79,7 +79,7 @@ def main():
print(f" ticket_deps: {len(deps)}")
print(f" ticket_labels: {len(labels)} (existing) + {len(ms_labels)} (milestone-derived)")
print(f" ticket_history: {len(history)}")
print(f" milestone label mapping: " + ", ".join(f"{mid}->{ms_label(mid)}" for mid in milestones))
print(" milestone label mapping: " + ", ".join(f"{mid}->{ms_label(mid)}" for mid in milestones))
print(f" multi/space decision_refs kept verbatim: {len(multi)}")
print(f" comma-team tickets kept verbatim: {comma_team}")
+1 -1
View File
@@ -174,7 +174,7 @@ if __name__ == "__main__":
clothing_dir = sys.argv[1]
print(f"\nClothing Metadata Setup")
print("\nClothing Metadata Setup")
print(f" Output dir: {clothing_dir}")
print(f" Items: {len(CLOTHING_ITEMS)}")
+1 -1
View File
@@ -841,7 +841,7 @@ def main() -> None:
assert gw_node["gate_topology"] == "hub", "Gateway topology changed!"
assert gw_node["aperture_count"] == 5, f"Gateway aperture_count={gw_node['aperture_count']} (should be 5)"
assert len(adj[GATEWAY_ID]) == 4, f"Gateway degree={len(adj[GATEWAY_ID])} (should be 4)"
print(f"\n Gateway constraint check: OK (degree=4, aperture=5, topology=hub)")
print("\n Gateway constraint check: OK (degree=4, aperture=5, topology=hub)")
# ── Verify no aperture violations ────────────────────────────────────────
violations = [
+1 -1
View File
@@ -229,7 +229,7 @@ def main():
print(f"Output directory: {WIKI_DIR}")
# Print a few examples
print(f"\nExample directories:")
print("\nExample directories:")
dirs = sorted(WIKI_DIR.iterdir())[:5]
for d in dirs:
if d.is_dir():