Skip to content

Commit d53b4f2

Browse files
committed
Port object commands to native
1 parent c8bd0b3 commit d53b4f2

59 files changed

Lines changed: 4595 additions & 128 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.luacheckrc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,30 @@ std=lua51
2121

2222
files["libs_sb/utils/luaunit.lua"] = { ignore = {"581"} }
2323

24+
-- Third-party code bundled into the tree (git submodules, vendored libraries,
25+
-- Chili UI skins, engine widgets/gadgets). Not SBC editor source, so not linted.
26+
exclude_files = {
27+
"libs_sb/chiliui/**",
28+
"libs_sb/chonsole/**",
29+
"libs_sb/s11n/**",
30+
"libs_sb/spring-launcher/**",
31+
"libs_sb/i18n/**",
32+
"libs_sb/kernel/**",
33+
"libs_sb/lcs/**",
34+
"libs_sb/springmon/**",
35+
"libs_sb/chilifx/**",
36+
"libs_sb/chotify/**",
37+
"libs_sb/json.lua",
38+
"libs_sb/MessagePack.lua",
39+
"libs_sb/savetable.lua",
40+
"LuaUI/Configs/chili/**",
41+
"LuaUI/Configs/chilitip_conf.lua",
42+
"LuaUI/widgets/gui_chili_selections_and_cursortip.lua",
43+
"LuaUI/widgets/hide_default_layout.lua",
44+
"LuaRules/Configs/icon_generator.lua",
45+
"LuaRules/Gadgets/unit_icongenerator.lua",
46+
}
47+
2448
globals = {
2549
-- std extensions
2650
"math.round", "math.bit_or",

LuaUI/widgets/api_sb_chili.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,12 @@ end
226226

227227

228228
local keyPressed = true
229-
function widget:KeyPress(key, mods, isRepeat, label, unicode)
229+
function widget:KeyPress(key, keyMods, isRepeat, label, unicode)
230230
if Spring.IsGUIHidden() or totalHideInterface then
231231
return false
232232
end
233233

234-
keyPressed = screen0:KeyPress(key, mods, isRepeat, label, unicode)
234+
keyPressed = screen0:KeyPress(key, keyMods, isRepeat, label, unicode)
235235
return keyPressed
236236
end
237237

docs/design/objects.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
name: Object Commands
3+
description: Add/remove/set flow for units, features, and areas in native SpringBoard
4+
---
5+
6+
# Object Commands
7+
8+
Objects are addressed by **model id**, not Spring id. The model id is stable
9+
across undo/redo and save/load. Units/features also have a Spring id; areas do
10+
not, so their model id is the object id. `ObjectManager` owns the per-kind
11+
handlers and asks each handler to translate model id to Spring id when needed.
12+
13+
```mermaid
14+
flowchart LR
15+
Lua["Lua command JSON"] --> Cmd["Rust command"]
16+
Cmd --> Mgr["ObjectManager"]
17+
Mgr --> Kind["ObjectHandler: area/unit/feature"]
18+
Kind --> Fields["typed field registry"]
19+
Fields --> State["engine or editor state"]
20+
Kind --> Events["ObjectEvent queue"]
21+
Mgr --> Bridge["event_bridge"]
22+
Bridge --> LuaUI["LuaUI object mirror"]
23+
```
24+
25+
`AddObjectCommand` parses wire params through `objects::codec` into typed
26+
`ObjectData`, creates the object, applies its fields, and records an added
27+
event. `RemoveObjectCommand` reads the object before destruction so undo can
28+
re-add the same model id. `SetObjectParamCommand` captures old typed field
29+
values on first execute, parses new typed values, then applies one field or a
30+
field set.
31+
32+
Fields are registered next to each kind's s11n with `inventory::submit!`.
33+
Each field implements `TypedField<Model>` and declares its Rust `Value`,
34+
descriptor, getter, and setter together. Runtime dispatch erases values only to
35+
share one registry; field implementations themselves get typed values, not JSON.
36+
37+
The current wire format is still JSON. `codec.rs` is the boundary that converts
38+
JSON to typed `FieldValue` and back for command input, save-shaped reads, tests,
39+
and LuaUI notifications. Model code should not serialize just to apply fields.
40+
41+
Set-many intentionally iterates over the supplied typed fields. Unit/feature
42+
handlers also preserve the old Lua ordering quirks around rotation and movement,
43+
because engine position updates can disturb facing. That behavior belongs in the
44+
kind handler, not the UI.
45+
46+
Models emit `ObjectEvent::{Added,Removed,Updated}` only. They do not know about
47+
Lua. `ObjectManager` drains events after add/remove/set; `event_bridge` turns
48+
those events into widget commands so LuaUI's object mirror stays current. Later
49+
Rust UI/state consumers can subscribe at the same event boundary without changing
50+
the models.
51+
52+
Known pressure points: generic object commands still carry JSON at the command
53+
boundary; descriptors are collected into `Vec`s from inventory on lookup; field
54+
application is name-based after parsing; notifications currently still bridge to
55+
LuaUI because the frontend mirror is Lua-owned during the port.

docs/porting/01-slices.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ spread across the slices they belong to, not deferred as a catch-all.
3434
| 2 | [Heightmap](#2-heightmap) — load + save + import / export (async IO) | done (stable; native IO seam, 16-bit PNG, raw-f32 save/load by path, import undoable) |
3535
| 3 | [Map settings](#3-map-settings) — sun / atmosphere / water / map-rendering | review (in stable; all setters Rust-only with `Gfx`-snapshot undo) |
3636
| 4 | [Textures](#4-textures) — diffuse / shading / terrain texture / cache + grass + DNTS | review (in stable; Rust owns paint + cache + stroke close + undo/redo) |
37-
| 5 | [Objects](#5-objects) — units & features add / remove / set / move (needs s11n) | wip (not in stable) |
37+
| 5 | [Objects](#5-objects) — units & features add / remove / set / move (needs s11n) | review (in stable) |
3838
| 6 | [Areas](#6-areas) | wip (not in stable) |
3939
| 7 | [Teams & diplomacy](#7-teams--diplomacy) | wip (not in stable) |
4040
| 8 | [Project lifecycle](#8-project-lifecycle) — save / load / export / sync / start / stop + scenario-info | wip — core (not in stable) |

docs/porting/review-queue.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,39 @@ description: Async review pipeline — items Claude has implemented that are wai
77

88
Items Claude has finished implementing, awaiting human gates. Top of the list = oldest pending.
99

10-
Each row has: link to the Rust file, the Lua file it replaces, a one-line description, and what to verify when testing. Claude appends; user moves items off as they're reviewed → tested → committed.
10+
Each item is a one-line description and the steps to verify it in-game (read the diff for the code). Claude appends; user removes items as they're reviewed → tested → committed. Oldest first.
1111

12-
| # | Item | Files | State | What to test |
13-
|--:|------|-------|-------|--------------|
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). |
12+
## Objects (slice 5) — units, features & areas add / remove / move / set-param — *review*
13+
14+
`just test-integration objects` → 4 tests pass. In-editor:
15+
16+
1. Place a **unit** and a **feature**; drag to move, rotate, edit a property (health/mass) in the property window — each change shows in the engine.
17+
2. Add an **area** (rect); drag to move and resize it.
18+
3. **Ctrl+Z / Ctrl+Y** each of the above — undo restores, redo re-applies.
19+
4. Place an **aircraft** unit and confirm its `crashing` state round-trips (only field not covered by automated tests).
20+
5. **Identity probe:** add a unit and an area → undo → redo → **save + reload the project**; the objects and any trigger referencing them must survive.
1521

1622
## States
1723

1824
- **review** — Claude says implemented, lint + tests green; user hasn't read the code yet
1925
- **tested** — user read it and ran it in-game; ready to commit
20-
- **(removed from this file)** — committed; row deleted, phase doc updated to **done**
26+
- **(removed from this file)** — committed; item deleted, phase doc updated to **done**
2127

2228
## Claude's flow
2329

2430
When finishing a port:
2531

2632
1. Update the matching phase doc — set status **review**.
27-
2. Append a row here with a short test recipe.
33+
2. Append an item here (a `##` section) with a short test recipe.
2834
3. Move on to the next item (do not wait).
2935

3036
## User's flow
3137

32-
1. **Review**: read the linked Rust file. If happy, mark row **tested** after testing it next.
33-
2. **Test**: launch SBC, do the steps in "What to test". If happy, tell Claude to commit.
34-
3. **Commit**: Claude stages the right files, runs `git commit -m`, deletes the row, updates the phase doc to **done**.
38+
1. **Review**: read the diff. If happy, mark the item **tested** after testing it next.
39+
2. **Test**: launch SBC, do the listed steps. If happy, tell Claude to commit.
40+
3. **Commit**: Claude stages the right files, runs `git commit -m`, deletes the item, updates the phase doc to **done**.
3541

36-
If something fails at any gate: leave a note in the row, kick back to Claude (re-mark the phase-doc entry as **in progress**).
42+
If something fails at any gate: leave a note on the item, kick back to Claude (re-mark the phase-doc entry as **in progress**).
3743

3844
## Dependency rule
3945

docs/porting/todo.md

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,39 @@ stable mid-review.
1212

1313
---
1414

15-
## 1. Unit-struct commands are a registration gotcha
15+
## 1. Objects commands: make them concrete and fully typed
16+
17+
**What.** The three object commands are generic over `objType` and still carry
18+
`serde_json::Value` (`AddObjectCommand.params`, `SetObjectParamCommand.key`/`value`).
19+
Split them into concrete per-kind commands whose struct fields *are* the object's
20+
fields — typed, deserialized straight from the wire, no `serde_json::Value`:
21+
22+
- Add: `AddAreaCommand`, `AddFeatureCommand`, `AddUnitCommand`
23+
- Remove: `RemoveAreaCommand`, `RemoveFeatureCommand`, `RemoveUnitCommand`
24+
- Set: `SetAreaParamCommand`, `SetFeatureParamCommand`, `SetUnitParamCommand`
25+
26+
Each `Add*`/`Set*` lists every field of its kind explicitly (`Set*` all
27+
`Option<_>`; `Add*` requires the create params). Field names/types/ranges live in
28+
`native/src/sbc/objects/model/<kind>_s11n/fields.rs` — keep in sync.
29+
30+
- **Area:** pos, size
31+
- **Feature:** defName, pos, rot, vel, dir, mass, midAimPos, blocking,
32+
radiusHeight, collision, team, health, resources, rules
33+
- **Unit:** defName, pos, rot, vel, dir, mass, midAimPos, maxRange, blocking,
34+
radiusHeight, collision, team, health, maxHealth, paralyze, capture, build,
35+
tooltip, stockpile, experience, neutral, fuel, movectrl, gravity,
36+
harvestStorage, resources, armored, crashing, rules, states, commands
37+
38+
**Why.** Compile-time field validation, no per-command JSON parse, undo state
39+
already typed. Removes the last JSON from the objects command path, so the codec's
40+
`json_to_object`/`parse_named_field` fall away.
41+
42+
**How.** One concrete command per kind via `register_command!`; serde derives do
43+
the parsing. `ObjectManager`'s `ObjectKind` dispatch stays as-is.
44+
45+
---
46+
47+
## 2. Unit-struct commands are a registration gotcha
1648

1749
**What.** A control command with no payload is written as a braced struct with an
1850
empty body specifically so it deserializes:
@@ -79,7 +111,7 @@ in place and commented at each site
79111

80112
---
81113

82-
## 2. In-engine tests bypass the Lua command_manager (dispatch bugs invisible to CI)
114+
## 3. In-engine tests bypass the Lua command_manager (dispatch bugs invisible to CI)
83115

84116
**What.** The in-engine integration tests
85117
([native/src/sbc/tests/](../../native/src/sbc/tests/)) drive Rust **directly**
@@ -110,7 +142,7 @@ and only caught by hand.
110142

111143
---
112144

113-
## 3. Brush data structures: ditch nested HashMaps for vectors
145+
## 4. Brush data structures: ditch nested HashMaps for vectors
114146

115147
[brush_filter_generator.rs](../../native/src/sbc/commands/heightmap/brush_filter_generator.rs)
116148
ports SpringBoard's brush-filter cache verbatim as
@@ -120,9 +152,17 @@ around) is a mechanical port of the Lua; vectors / a flat keyed struct would be
120152
far more efficient and readable. Refactor the whole brush system off nested
121153
HashMaps once the behavior is locked in.
122154

123-
## 4. Load the brush greyscale directly in Rust
155+
## 5. Load the brush greyscale directly in Rust
124156

125157
[set_heightmap_brush_command.rs](../../native/src/sbc/commands/set_heightmap_brush_command.rs)
126158
receives the greyscale shape as a large array marshalled from Lua on every
127159
`SetHeightmapBrushCommand`. Load the brush image directly in Rust so the big
128160
array doesn't cross the bridge (ties into the async-IO image work in slice 2).
161+
162+
## 6. Object field descriptors allocate on every lookup
163+
164+
`ObjectHandler::descriptors()` currently returns `Vec<ObjectFieldDescriptor>` by
165+
collecting each kind's `inventory` entries. This keeps field registration simple,
166+
but `ObjectManager::descriptor()` and command parsing can allocate repeatedly for
167+
what is effectively static metadata. Cache per-kind descriptor slices/maps once,
168+
or generate static descriptor arrays alongside the field registry.

0 commit comments

Comments
 (0)