Stop silently swallowing script load/update/on_event errors - #31
Merged
Conversation
WorldScene.updateScripts discarded every error from DoFile/DoString and
CallScriptUpdate (continue, no log), and CallOnEvent's error was thrown
away with `_ =` at all three call sites. A script with a syntax error or a
runtime exception left its entity silently frozen with zero diagnostic
signal, and a failed load was never marked as attempted, so the broken
file was retried (and would now be logged) every single frame forever.
Now: a load failure is logged once and marks the script permanently
failed (stops the per-frame retry loop); a CallScriptUpdate failure is
logged only on the success->failure transition (and again on recovery),
not every frame it keeps failing, keeping this compliant with the
"no per-frame logging" rule even for a script erroring continuously; the
three CallOnEvent call sites now log their error instead of discarding it
(these are already event-driven, not per-frame, so no rate-limiting is
needed there).
A live smoke run surfaced a real, previously invisible bug this fix was
meant to catch: games/demo1/scripts/knight_gamelogic.lua is an empty
stub, so its "update" global is nil — LuaEngine.CallScriptUpdate calls it
unconditionally without checking, unlike PythonEngine, which no-ops when
the function isn't defined. This predates this change (the error was
always returned and always discarded) and demo1's Lua scripts are legacy
per CLAUDE.md ("do not update or maintain"), so left as-is; noting it here
since it's now visible in logs where it wasn't before. metalslug_demo
(Python, actively developed) produces a clean run with no such errors.
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WorldScene.updateScripts(frameengine/view/scene/world_scene.go) discarded every error fromDoFile/DoStringandCallScriptUpdatewith a barecontinue— no log, nothing. The threeCallOnEventcall sites (script-emitted events,BeginContact,EndContact) threw their erroraway with
_ =. Practical effect: a Python/Lua script with a syntax error or a runtime exceptionleft its entity silently frozen, with zero signal anywhere about why. Worse, a failed load never
marked
loadedScripts[s.Path] = true, so the engine retried loading the same broken file everyframe, forever, just as silently.
Changes
DoFile/DoString/fs.ReadFile) is now logged once and the script is markedpermanently failed (
scriptLoadFailed), which also stops the per-frame retry loop — one fixcovers both the missing log and the wasted repeated work.
CallScriptUpdatefailure is logged only on the success→failure transition (and again onrecovery), not on every frame it keeps failing — keeps this compliant with CLAUDE.md's "no
per-frame logging" rule even for a script that errors continuously.
CallOnEventcall sites (ScriptEmitted,BeginContact,EndContact) now log theirerror instead of discarding it. These fire per-event, not per-frame, so no rate-limiting is
needed there.
world_scene_script_errors_test.go: a minimalscript.Enginetest double(per CLAUDE.md's "mock interfaces, not concrete types" convention) exercising
updateScriptsdirectly, covering: a failing load isn't retried every frame, a load failure is logged, an
update failure logs once (not once per frame) while it keeps failing, and recovery is logged.
A real bug this immediately surfaced
Running
games/demo1live during validation now prints:games/demo1/scripts/knight_gamelogic.luais an empty (0-line) stub.LuaEngine.CallScriptUpdatecalls the named function unconditionally without checking it's defined first — unlike
PythonEngine, which no-ops when the function is missing (per theEngineinterface's owndocumented contract: "Returns nil if the function is not defined (not an error)"). This predates
this change — the error was always being returned and always being silently discarded — so it's
not a regression from this PR. Left as-is deliberately: demo1's Lua scripts are legacy per
CLAUDE.md ("do not update or maintain them"), and fixing the Lua/Python behavioral inconsistency
itself is a separate, unrelated change. Flagging it here since it's now visible for the first
time.
games/metalslug_demo(Python, the actively-developed game) runs clean with no such errors.Validation
go build ./...,go vet ./...,gofmt -l .— cleango test ./...— all pass;go test -race ./...— same result, except the pre-existing,unrelated
box2d-gocheckptrfailure under-race(reconfirmed not caused by this change)GOOS=js GOARCH=wasm go build— cleangames/demo1andgames/metalslug_demo— see finding above; metalslugclean, demo1 now surfaces its pre-existing dead script (expected, not a regression)
Generated by Claude Code