Expose live mod actors and difficulty and support dense event queries

This commit is contained in:
raptorreplica committed 2026-10-17 17:36:05 +11:00
1 parent 287b324e62
commit 93ea7064ba
10 files changed
+302 -11

No files matched your search

+2 -2
View File
@@ -159,7 +159,7 @@ fn on_fire(event, state, random) {
| `on_mission_start` | Mission: `tick = 0`. Runs when the first simulation step dispatches events. |
| `on_mission_end` | Mission: `success`, `tick`. |
Every dispatched event also includes `tick`, `player`, `entities`, `projectiles`, `pickups` and `objectives`. `player` is a snapshot including position, velocity, shields, credits, inventory and selected equipment. `entities` is the current enemy array; each entry includes `entity`, `definition`, position, `width`, `height`, health, animation, movement state and the definition's `ground` boolean. Filter on `ground` when an attack affects only airborne or ground targets. Projectiles and pickups likewise carry stable entity IDs. These are read-only snapshots: return commands to change the simulation. Later callbacks in a tick can observe commands committed by earlier callbacks. Events raised by commands, such as a spawned enemy, are dispatched in subsequent batches of the same tick, within the 8,192-event limit. Rejected command batches report the mod, script path and callback, and roll back that simulation step and script state.
Every World-dispatched event also includes `tick`, `difficulty`, `actor`, `player`, `entities`, `projectiles`, `pickups` and `objectives`. `difficulty` is the active World difficulty as an integer: `0` Training, `1` Rookie, `2` Veteran, `3` Elite. It is supplied centrally at dispatch for every hook, including events raised by commands; scripts can use it to select their own patterns. It does not change spawn difficulty gates or add a command that changes difficulty. Pilot saves retain difficulty alongside script state and RNG; recreating a mission from that pilot supplies the restored difficulty to its callbacks. `actor` is the addressed enemy's current snapshot, with exactly the same fields as its `entities` entry, including `ground`. It is `null` for mission callbacks, player entity `0`, nonenemy addresses and enemies already removed. A zero-health enemy still present in the World has an actor snapshot with `health = 0`; scripts should check presence and health before issuing commands. The actor is resolved when the callback dispatches, so it reflects movement, damage or removal committed by earlier callbacks; event-local `x`, `y` or `health` can still describe the earlier queued event. Scripts can read `event.actor` directly instead of scanning `event.entities` for themselves. This reuses the existing enemy-snapshot traversal and does not add another enemy scan or change the cost of constructing `entities`. `player` is a snapshot including position, velocity, shields, credits, inventory and selected equipment. `entities` is the current enemy array; each entry includes `entity`, `definition`, position, `width`, `height`, health, animation, movement state and the definition's `ground` boolean. Filter on `ground` when an attack affects only airborne or ground targets. Projectiles and pickups likewise carry stable entity IDs. These are read-only snapshots: return commands to change the simulation. Later callbacks in a tick can observe commands committed by earlier callbacks. Events raised by commands, such as a spawned enemy, are dispatched in subsequent batches of the same tick, within the 8,192-event limit. Rejected command batches report the mod, script path and callback, and roll back that simulation step and script state.
Use `behavior: "scripted"` for hitscan, chain lightning, proximity zaps or other attacks implemented entirely with commands. Player selection, firing input and weapon cooldowns still apply. An owned item with `special: false` fires its weapon alongside other ordinary equipment when Fire is held; `special: true` makes it selectable. Neither player nor enemy scripted weapons receive a default engine projectile. A script may explicitly emit `projectile`, or pair `damage` with `arc` for an immediate visible discharge. `target` describes the weapon's capability for the shop and governs explicit projectiles; scripts must enforce the intended targets and range for their own `damage` commands.
@@ -216,7 +216,7 @@ Lightning arcs render in both SD and HD using irregular fractal channels, thin t
`anchor` attaches the starting endpoint, and `to_anchor` independently attaches the destination. Supply absolute world coordinates at creation; each endpoint preserves its offset from the anchor's top-left `x/y` origin as the ship moves. Omitting an anchor keeps that endpoint fixed. Both anchors may be `0` to make a ring segment follow the player, or use `anchor: 0` and `to_anchor: enemy.entity` for a ship-to-ship bolt. Anchor IDs must exist when the command runs; a missing anchor rejects the batch. Endpoints update after simulation movement and API movement commands, and freeze at their last position when the anchored enemy disappears. Cosmetic coordinates clamp to ±32,768 when an attached ship moves beyond those bounds.
Rhai uses an explicit package allowlist for arithmetic, strings, collections, iteration and function helpers. The default language/time packages are excluded, so wall-clock timestamps and native `sleep` cannot be reached, including through function pointers. There are no exposed filesystem, network or OS interfaces. Dynamic `eval` and imports are disabled. Limits are 100,000 operations per callback and a shared 500,000-operation budget per simulation tick; 32 call levels; expression depths of 64/32; arrays of 4,096 entries; maps of 1,024 entries; strings of 16 KiB; 256 commands per callback; and 64 KiB of serialized state per script. See the [Rhai execution-limit API](https://docs.rs/rhai/latest/rhai/struct.Engine.html) for the underlying engine controls. Scripts cannot increase these limits.
Rhai uses an explicit package allowlist for arithmetic, strings, collections, iteration and function helpers. The default language/time packages are excluded, so wall-clock timestamps and native `sleep` cannot be reached, including through function pointers. There are no exposed filesystem, network or OS interfaces. Dynamic `eval` and imports are disabled. Limits are 100,000 operations per callback and a shared 500,000-operation budget per simulation tick; 32 call levels; expression depths of 64/32; arrays of 4,096 entries; object maps with at most 16,384 aggregate properties across their nested map tree; strings of 16 KiB; 256 commands per callback; and 64 KiB of serialized state per script. See the [Rhai execution-limit API](https://docs.rs/rhai/latest/rhai/struct.Engine.html) for the underlying engine controls. [Rhai counts nested properties recursively](https://rhai.rs/book/safety/max-map-size.html), so the map bound accommodates complete enemy/projectile snapshots rather than only a map's top-level fields. The separate 64 KiB serialized callback-state and command/operation bounds remain unchanged. Scripts cannot increase these limits.
## Saves and compatibility
+8
View File
@@ -15,6 +15,14 @@ Receipts for this one-picture append are in
`.reference/hd-root-next-publication-after5339/`; immutable intake is
`handoff-5340-20261017`.
The included Linux and Windows runtimes now expose active script difficulty and
current enemy snapshots. A finite 16,384-property Rhai map limit fixes documented
queries failing on ordinary enemy groups; operation, command and 64 KiB saved-state
limits remain unchanged. All 45 modding regression tests pass. Three actual 1080p
SDL captures (menu, S1 Wave 7, S3 Wave 9) match the previous binary byte for byte.
Windows cross-build, PE imports and shared-data verification pass; this source
revision still needs its own native Windows CI result.
Iron Coast retuning is active in `.reference/campaign-retune-20261017/`.
The baseline covers all 27 original and 14 mod missions at all four difficulties.
Mission waves, enemy movement, bosses and weapon presentation are being validated
+14
View File
@@ -1,5 +1,19 @@
# Rust engine and validation
## 17 October 2026: live mod snapshots and dense-event queries
World callbacks now receive `difficulty` and the addressed enemy's current `actor`
snapshot, including damage, movement and removal committed by earlier callbacks.
Dense enemy snapshots exposed Rhai's recursive map limit: even an ordinary query
could reject a 24-enemy event. The bounded limit is now 16,384 properties; existing
operation, command and serialized-state limits remain unchanged.
All 45 modding tests pass, including all four difficulties, save/RNG continuation,
stale callbacks, 64-enemy queries and oversized intermediate/output rejection.
Linux and Windows executables are rebuilt. Menu and two original-mission 1080p
SDL captures are byte-identical across the change (6,220,800 pixels compared).
Windows execution is verified separately by the post-push native workflow.
Rust is the default engine. The Rust binary never calls or embeds the C++ engine. SDL2 provides window/input/controller/audio services. Simulation and the validation CLI build without SDL2. The retained C++ checkout is optional reference material.
| Module | Responsibility |