You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Map settings slice + dev tooling, command logging, and fixes
Map settings (slice 3): sun direction, sun lighting, atmosphere, water
params, map-rendering, and global-LOS ported as Rust-only commands under
native/src/sbc/map_settings/, with integration tests. Water params now
handle RGBA colours (the editor's colour pickers send 4 components, not 3)
and water textures (texture/foamTexture/normalTexture), applied and undone
via the new SetWaterTexture/GetWaterTexture engine bindings; colours and
textures snapshot for undo.
Dev tooling:
- .env (gitignored) holds engine paths; .env.example is the template.
launch.sh, run_sbc.py, and the justfile read SBC_ENGINE_DIR from it, so
no personal paths are committed.
- run_sbc.py's prepare() is the single source of truth for launching SBC;
launch.sh is a thin wrapper over its --manual mode.
- justfile: one `just lint` (fmt + clippy + lua + check); test-smoke folded
into test-integration.
- Persistent fontconfig cache (~/.cache/sbc-fontcache) so launches stop
rebuilding it every run (~20s saved after the first).
- springignore.txt: keep native/tools/docs out of the VFS scan and archive
hash.
Logging: every command Lua sends to Rust is appended to $SBC_COMMAND_LOG
(<write_dir>/commands.jsonl), logged at the route() entry point so a
command that fails to deserialize can be inspected as exact bytes.
Fixes:
- json.lua: coerce numeric table keys to strings in encodeString; the UTF-8
rewrite regressed brush commands ("attempt to get length of a number").
- command_manager.lua: _SafeCall now reports the failing command and the
real error instead of generic spam that recursed into a C stack overflow.
Docs: commands.md (setup + just commands), the stepdown-ordering rule in
conventions.md, and queue/slice updates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/porting/conventions.md
+25-20Lines changed: 25 additions & 20 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,19 +9,19 @@ description: How the Lua → Rust port is carried out — process, structure, qu
9
9
10
10
The work happens in **two side-by-side directories**, both checkouts of this repo:
11
11
12
-
-**wip** — `/home/gajop/projects/spring-projects/SBC.sdd` (this dir, branch `rust`). Claude works here. Permanent dirty tree, fat and growing. **Claude never commits here, ever.** Loses no work because nothing gets trimmed away.
13
-
-**stable** — `/home/gajop/worktrees/SBC.sdd/SBC-rust-stable.sdd` (git worktree, branch `SBC-rust-stable.sdd`). User reviews and tests here. Only the slimmed-down, review-ready slice exists here. All commits happen here. User pushes from here.
12
+
-**wip** — `/home/gajop/projects/spring-projects/SBC.sdd` (this dir, branch `rust-wip`). Claude works here. Permanent dirty tree, fat and growing. **Claude never commits here, ever.** Loses no work because nothing gets trimmed away.
13
+
-**stable** — `/home/gajop/worktrees/SBC.sdd/SBC-rust-stable.sdd` (git worktree, branch `rust-stable`). User reviews and tests here. Only the slimmed-down, review-ready slice exists here. All commits happen here. User pushes from here.
14
14
15
15
Both share the same `.git`. Sync runs two directions:
16
16
17
17
-**wip → stable**: `cp`, one file at a time. The normal forward flow — a finished slice is copied into stable and trimmed there.
18
-
-**stable → wip**: `git rebase`. Review-time edits (rename, reorder, fix) are committed in stable, and wip rebases `rust` onto `SBC-rust-stable.sdd` to absorb them. Never `cp` backwards — the rebase is what carries reviewed edits home, so they survive the next forward `cp`.
18
+
-**stable → wip**: `git rebase`. Review-time edits (rename, reorder, fix) are committed in stable, and wip rebases `rust-wip` onto `rust-stable` to absorb them. Never `cp` backwards — the rebase is what carries reviewed edits home, so they survive the next forward `cp`.
19
19
20
20
### The loop
21
21
22
22
For each review item:
23
23
24
-
1.**Build & test in wip** — full fat tree. `cargo check`, `cargo test`, and the integration tests (`uv run pytest` in `tools/smoke/`).
24
+
1.**Build & test in wip** — full fat tree. `just check`, `just test-unit`, and `just test-integration`.
25
25
2.**Copy → trim in stable** — `cp` the files in (one at a time, no wildcards), trim to the minimal version for this slice. Never trim in wip.
26
26
3.**Test in stable** — same suite, run inside stable.
27
27
4.**Review** — add a row to [review-queue.md](review-queue.md). User reviews → tests → authorizes; the user may edit files in stable during review. Claude commits in stable, user pushes.
@@ -39,7 +39,7 @@ For each review item:
39
39
wip has no commits of its own, just a dirty tree, so park it across the rebase:
40
40
41
41
1.`git stash --include-untracked`
42
-
2.`git rebase SBC-rust-stable.sdd`
42
+
2.`git rebase rust-stable`
43
43
3.`git stash pop`, then resolve conflicts toward stable's reviewed version.
44
44
45
45
Between slices only, never mid-slice. If the tree won't stash cleanly, finish or discard the local change first.
@@ -55,7 +55,7 @@ Between slices only, never mid-slice. If the tree won't stash cleanly, finish or
55
55
1.**Review** — user reads the Rust code.
56
56
2.**Manual test** — user runs SBC, exercises the feature.
57
57
58
-
Claude does lint (`cargo fmt`, `cargo clippy -D warnings`), unit tests, and the integration tests. Claude never marks anything done.
58
+
Claude does `just lint`, `just test-unit`, and `just test-integration`. Claude never marks anything done.
59
59
60
60
**Commits.** All commits happen in **stable**, never in wip. Claude runs `git commit -m` in stable, only after user authorizes. Small commits: one port (or a small batch of related ports) per commit. The dispatch flip can ride along or come in a follow-up.
61
61
@@ -104,11 +104,14 @@ If `inventory` becomes problematic (e.g. platforms where life-before-`main` link
104
104
105
105
**Where ported code lives in the tree:**
106
106
107
-
- Per-command file under [native/src/sbc/commands/](../../native/src/sbc/commands/)`<slice>/` — owns its serde struct, its `inventory::submit!` registration, and its execute/unexecute logic.
108
-
- Per-manager file: a per-slice `model`/manager module.
109
-
- The command-system internals + the single public `commands_api` surface live under [native/src/sbc/commands/command_system/](../../native/src/sbc/commands/command_system/).
110
-
- Cross-slice managers (texture undo stack, heightmap) live where the first slice that needs them puts them; later slices reuse.
111
-
- Each slice is a directory; `mod.rs` files only wire submodules, never hold code.
107
+
The layout is **feature-first**: each slice is a directory `native/src/sbc/<slice>/`
108
+
holding its own `commands/`, `model/`, and `tests/`.
109
+
110
+
- Per-command file under [native/src/sbc/](../../native/src/sbc/)`<slice>/commands/` — owns its serde struct, its `register_command!` registration, and its execute/unexecute logic.
111
+
- Per-manager/model file: under `<slice>/model/`, self-registered as a `ModelFactory` (`inventory`) and reached type-erased via `ctx.model::<T>()`.
112
+
- The command-system internals + the single public `commands_api` surface live under [native/src/sbc/command_system/](../../native/src/sbc/command_system/) (domain-agnostic — no per-feature edits).
113
+
- Cross-slice models (texture undo stack, heightmap/terrain) live in the slice that first needs them; later slices reuse.
114
+
-`mod.rs` files only wire submodules, never hold code.
112
115
113
116
| Layer | Self-contained unit | Registration |
114
117
|-------|--------------------|--------------|
@@ -155,18 +158,17 @@ new shared subsystem.
155
158
156
159
## Build invariant
157
160
158
-
`cargo check` in `native/` must pass at every commit (in **stable**). The Lua app must still run at every commit. Both halves of the parallel impl exist; only flip one at a time. If a port needs an unfinished dependency, gate behind `cfg`, don't break the build.
161
+
`just check` must pass at every commit (in **stable**). The Lua app must still run at every commit. Both halves of the parallel impl exist; only flip one at a time. If a port needs an unfinished dependency, gate behind `cfg`, don't break the build.
159
162
160
163
The wip dir has no commit invariant — it builds when it builds. If wip is temporarily broken because a refactor is mid-flight, that's fine; the stable side is unaffected.
161
164
162
165
## Quality bar — pre-handoff
163
166
164
167
Target: user review + manual test under 2 minutes per item. To get there, every item passes these *before* entering the queue. No "I'll add tests later."
165
168
166
-
-`cargo fmt --check` clean
167
-
-`cargo clippy --all-targets -- -D warnings` zero output (warnings fixed or `#[allow]`-ed with a one-line reason)
168
-
-`cargo test` green, including new tests for new logic
169
-
- Integration tests green (`uv run pytest` in `tools/smoke/`)
169
+
-`just lint` clean — fmt, clippy (`-D warnings`; fix or `#[allow]` with a one-line reason), lua, and `check`
170
+
-`just test-unit` green, including new tests for new logic
171
+
-`just test-integration` green
170
172
- Self-review: re-read the diff as if reviewing it; cut dead code, fix bad names
171
173
- No `unwrap()`/`panic!()` outside tests; use `?` or `expect("specific reason")`
172
174
- No leftover `TODO`s
@@ -190,9 +192,12 @@ the things it calls, then their callees, with private leaf helpers last. A reade
190
192
scrolling top-to-bottom meets each function *before* the helpers it depends on —
191
193
the file reads like a newspaper (headline first, detail below).
192
194
193
-
- In a command file: the `impl Command` block (`execute` / `unexecute` — what the
194
-
framework calls) goes first, then its `stamp`/private methods, then leaf helpers
195
-
(`brush`, `generate_*`), then free functions, then the `register_command!` line.
195
+
- In a command file: the public command (its struct + `impl Command`, the
196
+
`execute` / `unexecute` the framework calls) goes first; everything it leans on —
197
+
`stamp`/private methods, support types (`Atmosphere`, `Water`), leaf helpers
198
+
(`brush`, `generate_*`), free functions — goes below it. Put `register_command!`
199
+
wherever reads cleanly (top with the command or bottom of the file); it carries no
200
+
ordering meaning. The rule is only: the public command on top, helpers underneath.
196
201
- Keep one `impl` block per type — don't split a type's methods across several
197
202
`impl` blocks to satisfy ordering; order the methods *within* the block instead.
198
203
- Free helper functions go below the code that calls them; a leaf used by several
@@ -237,7 +242,7 @@ To confirm a command actually crosses the bridge into the native plugin:
237
242
1. In [native/log4rs.yaml](../../native/log4rs.yaml), set the `rust_plugin::sbc`
238
243
logger to `level: debug` (the existing commented-out `command_runner` logger
239
244
names a module that doesn't exist — `rust_plugin::sbc` is the right target).
240
-
2. Rebuild: `cd native && cargo build --release`.
245
+
2. Rebuild: `just build`.
241
246
3. Boot SBC and exercise *any* command (e.g. one terrain brush stroke):
242
247
`cd tools/smoke && uv run python -m run_sbc` prints the write dir, or launch
Copy file name to clipboardExpand all lines: docs/porting/review-queue.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,7 +11,7 @@ Each row has: link to the Rust file, the Lua file it replaces, a one-line descri
11
11
12
12
| # | Item | Files | State | What to test |
13
13
|--:|------|-------|-------|--------------|
14
-
| 2 | **Heightmap load + image import/export** (slice 2). Whole-map load + 16-bit-greyscale PNG import/export on the background IO worker, all Rust-only (`nativeCommandsOnly`). Load reads the `.data` file (LE `f32`) by **path** — the path crosses the bridge, not the bytes. Import/export replace the spring-launcher round-trip. Lands the command IO seam: `Context::submit_io` → drained into the worker after execute → `SBC::drain_io`; `wait_for_io`/`wait_for_file` test helpers. | New: [`heightmap/commands/`](../../native/src/sbc/heightmap/commands/)`{load_map,import_heightmap,export_heightmap}_command.rs`, [`heightmap/model/heightmap_io.rs`](../../native/src/sbc/heightmap/model/heightmap_io.rs). Seam: [context.rs](../../native/src/sbc/command_system/context.rs), [sbc.rs](../../native/src/sbc/sbc.rs), [tests_api.rs](../../native/src/sbc/tests/tests_api.rs), `image` dep. Lua: [load_map_command.lua](../../scen_edit/command/load_map_command.lua) (path, not bytes), [load_project_command_widget.lua](../../scen_edit/command/project/load_project_command_widget.lua) (passes the path), flip in [command_manager.lua](../../scen_edit/command/command_manager.lua). | review | `cd tools/smoke && uv run pytest` → `heightmap_load` + `heightmap_import` + `heightmap_roundtrip` pass (load applies heights from a written `.data`; import/export at 16-bit tolerance). In-editor (now Rust-only): **load a saved project** → terrain restores; import a PNG → terrain matches; export → 16-bit PNG that re-imports. Export reads **live** engine heights (Lua read a saved file). |
14
+
| 3 | **Map settings** (slice 3). Sun direction + sun lighting, atmosphere (sky/fog), water params, map-rendering (splat scales/mults, void water/ground), and global-LOS — all Rust-only (`nativeCommandsOnly`). Each setter applies via `UnsyncedCtrl`/`SyncedCtrl` and snapshots the keys it touches via the matching `Gfx::Get*` for undo (map-rendering and global-LOS have no undo, matching their Lua). Water re-selects the current water mode after applying (`Display::GetWaterMode` + `Messages::SendCommands("water", N)`) so the renderer picks up new params — the native equivalent of Lua's `SendCommands('water '..GetWaterMode())`. Partial opts (editors send one key at a time). | New: [`map_settings/`](../../native/src/sbc/map_settings/)`commands/{set_sun_parameters,set_sun_lighting,set_atmosphere,set_water_params,set_map_rendering_params,set_global_los}_command.rs`, [`map_settings/tests/test_map_settings.rs`](../../native/src/sbc/map_settings/tests/test_map_settings.rs). Wiring: `mod map_settings;` in [sbc/mod.rs](../../native/src/sbc/mod.rs), flips in [command_manager.lua](../../scen_edit/command/command_manager.lua). **Lua command files unchanged** — only the dispatch flip. | review | `just test-integration map_settings` → 6 tests pass (each sets a value, reads it back via `Gfx::Get*`, undoes where applicable). In-editor: Lighting / Sky / Water / Terrain-settings editors → change sun direction, a ground color, fog color, a water param, splat scales → visible change; **Ctrl+Z** restores sun / lighting / atmosphere / water (map-rendering & global-LOS intentionally don't undo). |
0 commit comments