Threads open a fresh Lua state with a full standard library, so they
stayed blocked wholesale. PotatoVoxel's prebuilder wants to run its
pure geometry phase on worker threads; the mod declares the new
"compute" permission and the sandbox hands out love.thread only
then. The worker runs the mod's own source (source-only, like every
mod file) and receives data only through channels.
Font.drawBox leaves the caller's color at white; every other screen
resets to black right after calling it, but ManagerState.lua's draw()
and drawOverlay() never did. That's invisible on the vanilla tile font
(tile glyphs are black-on-transparent regardless of color) but renders
fully invisible white-on-white text once a mod's TTF font is active.
R.maps.objects was f.opt(f.list(f.any)): a static wild encounter's
species (OverworldController.lua's d.pokemon, handed straight to
BattleState.newWild) went completely unchecked at load time, unlike an
encounter slot's species. A typo'd or removed id sat in a loaded mod
and only surfaced as a crash the moment a player reached that object.
Objects share one array across every kind -- NPCs, signs, warps and
static encounters all coexist with no field the loader could use to
tell them apart ahead of time -- so a strict f.rec covering the whole
shape would reject every kind this schema does not enumerate. Added
f.partial, an open counterpart to f.rec: it type-checks (and, through
collectRefs, cross-reference-checks) only the fields it is given and
leaves everything else on the value alone, the same extensibility
f.rec already grants at a record's top level but nowhere further in.
R.maps.objects now types just `pokemon` through it, so a bad species
id is a load-time "unresolved reference" error instead of a runtime
crash, while an NPC object's sprite/movement/range/... fields -- never
named in this schema -- still pass through untouched.
The comment above the gatedFor skip in Schemas.crossValidate still
described growth_rates and evolution_methods as unconfirmable Gen 2
namespaces, the way they were before each got a real Gen 2 id space:
growth_rates keeps its Gen 1 target and is seeded from the extractor's
data.pokemon.growthRates (src/battle/gen2/Mon.lua), and
evolution_methods routes to gen2EvolutionMethods, a fixed literal set
(src/core/gen2/Evolution.lua) that exists with or without a ROM
import. Schemas.GEN2 does not gate either name -- gate_gen2_mod_api.lua
pins that directly, including a case that a bad evolution method on a
Gold species is still caught -- so the two are validated like any other
reference today, not skipped. Nothing here changes that behavior;
only the comment, which was describing an earlier state of the code,
is corrected.
DENIED_PREFIX blocked love.* and ffi.* submodule requires but had no
entry for jit, so require("jit.util") walked straight through to the
real module. jit.util is LuaJIT's own equivalent of the debug library
this file already denies by name: funcbc, funck and the rest read the
bytecode and constants of any function a chunk can reach, which is
enough to recover upvalues -- the real _G, love, io -- that the sandbox
exists to keep out of a mod's hands.
Adding "jit" to DENIED_PREFIX blocks jit.* submodule requires the same
way love.* and ffi.* already are, while leaving the bare jit global
(env.jit, handed over directly for jit.on/off/flush) and a bare
require("jit") untouched -- jit.util is not a field of that table
without its own require, so neither route was ever a way to reach it.
A diagnostic ring (boot evidence + recent lines + status) routinely exceeds 64 KiB on a long session: a 651-line evidence ring measured ~90 KB and was rejected with "log body too large" (mod.postLog returned nil and the send was dropped).
The transport stages the body to a file and streams it via curl, so the ceiling is a budget, not a memory spike. 512 KiB is generous for real support logs while staying far under the 5 MiB the reference loghook endpoint accepts.
We already ship the DLL, but mods can't ffi-load it under the sandbox, and
the compat love.system shim wasn't passing tls* through either. So wss://
still died on stock builds. Engine loads the dialer at boot; compat forwards
those keys; clipboard/openURL stay stubbed.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Added dedicated profile control in the MODS panel for easier profile management.
- Implemented dependency checking during mod installation and updates to ensure compatibility.
- Improved manifest parsing to support GitHub repository hints for dependencies.
- Enhanced UI interactions for saving and renaming profiles.
- Updated tests to cover new functionality and ensure stability.
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>