The sandbox blocks love.system and love.filesystem, which orphans the
native step bridge (#452, #489): its one consumer can no longer call
syncHealthSteps or read steps_pending.json (#1186).
Adds a "steps" manifest permission (shown to the player like the
others) gating a mod.steps facade: available() probes the bridge
quietly, sync() forwards the async refresh, poll() hands the mod its
copy of a delivery. The engine owns the pending file -- mods never name
a path and receive only { steps, from, to }. Without the permission the
acting calls name it, following the network gate. No new events, hooks
or registries; nothing removed.
RFC 0009. Tests: tests/modkit/cases/steps_bridge.lua (no-mod cold
bridge, permissioned sync/poll, per-mod copies, contract-field
filtering, malformed-delivery drop, unpermissioned refusal, bridgeless
build).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Regression coverage for the four Yellow Pallet Town fixes on this
branch:
- tests/parity_J.lua: the old-man-style demo bag shows x1 in Yellow
(SimulatedInputBattleItemList), not just the pre-existing x50
(pokered's OldManItemList) case.
- tests/engine/push_battle_transition.lua: Commands.pushBattle calls
ctx.overworld:pushBattle when available and falls back with a
logged warning otherwise. Logger.warn is spied (pcall-safe, always
restored) rather than read off the shared Logger.history ring
buffer, so the fallback checks don't leave noise behind for
whatever else runs in the same process.
- tests/parity_yellow_pallet_pikachu.lua: drives the real onStep
closure through the real StateStack/OverworldState, with no mocked
battle -- Red never plays Music_MuseumGuy for this escort (Blue
shares the same code path: onStep only branches on
GameVersion.isYellow(), never isBlue(), so a separate Blue run
would exercise nothing new), and Yellow's hold before the Pikachu
battle is armed for exactly 2 frames before handing off to
BattleTransition rather than a bare stack push. Stops there rather
than also driving the demo battle to completion just to assert
Music_MuseumGuy fires in Yellow (that fix's own coverage): the
extra coupling to unrelated battle menu/bag/throw frame budgets
wasn't worth it for one more assertion. That side stays manually
verified. scenario() restores Game/GameVersion and re-inits
StateStack on the way out.
All three pass standalone (luajit tests/<path>). The Yellow E2E test
hits the same pre-existing "Music.playMap is nil" gap three other
map-warping parity tests already hit inside the aggregated
tests/run_tests.lua run (missing data/generated/audio.lua in this dev
environment) -- confirmed by diffing the identical error text against
parity_warp_after_warp_step.lua, which also passes clean standalone.