Add persistent HUD hook for tool mods

This commit is contained in:
Kevin Jacobson
2026-07-28 13:58:42 -07:00
parent aab980b05a
commit d09cf80da5
3 changed files with 49 additions and 0 deletions
+5
View File
@@ -164,5 +164,10 @@ for a tool to offer a fresh-session action before gameplay begins.
Ephemeral tools can wrap `save.write(next, game)` and return `false` to veto a
progress write before world state is captured or any bytes reach disk.
`render.hud` receives `(next, game, viewport)` after visible states draw and
before the UI canvas is presented. `viewport.width` and `viewport.height` are
the active UI dimensions, so status indicators can stay visible over normal
and widescreen screens without pushing an updating game state.
Developer mode also arms the mod loader's dev tripwire, which flags mods
that reach outside their permission set.
+8
View File
@@ -255,6 +255,14 @@ function Game:draw()
end
Renderer:beginFrame(worldBelow)
self.stack:draw()
-- Persistent tool status belongs above every game state but inside the
-- active UI canvas, so it composes with palette/render mods and survives
-- menus, battles, and transitions without becoming an updating state.
if ModRuntime.wantsHook("render.hud") then
local width, height = Renderer:uiSize()
ModRuntime.call("render.hud", function() end, self,
{ width = width, height = height })
end
-- SGB colorization: the topmost state that knows its palette owns the
-- screen (overlays like text boxes inherit from what's beneath them);
-- the overworld's world pass colors each visible map area separately
+36
View File
@@ -86,6 +86,42 @@ do
hooks:removeOwner("tool_fixture")
end
-- A tool status indicator must draw after every visible game state but before
-- the renderer presents that frame. This keeps the HUD visible over the
-- overworld, menus, battles, and compatible render pipelines.
do
local Renderer = require("src.render.Renderer")
local TouchControls = require("src.core.TouchControls")
local savedSetUISize, savedBegin, savedEnd, savedTouch =
Renderer.setUISize, Renderer.beginFrame, Renderer.endFrame,
TouchControls.draw
local order = {}
Renderer.setUISize = function() end
Renderer.beginFrame = function() end
Renderer.endFrame = function() order[#order + 1] = "present" end
TouchControls.draw = function() order[#order + 1] = "touch" end
local fake = { overworld = {}, stack = { states = {} } }
function fake.stack:visibleBase() return 1 end
function fake.stack:top() return {} end
function fake.stack:draw() order[#order + 1] = "states" end
hooks:wrap("render.hud", function(nextFn, game, viewport)
T.check(game == fake, "render.hud receives the live Game object")
T.eq(viewport.width, 160, "render.hud receives the UI width")
T.eq(viewport.height, 144, "render.hud receives the UI height")
order[#order + 1] = "hud"
return nextFn(game, viewport)
end, 0, "tool_fixture")
require("src.core.Game").draw(fake)
T.eq(table.concat(order, ","), "states,hud,present,touch",
"render.hud draws over states before frame presentation")
hooks:removeOwner("tool_fixture")
Renderer.setUISize, Renderer.beginFrame, Renderer.endFrame,
TouchControls.draw = savedSetUISize, savedBegin, savedEnd, savedTouch
end
Runtime.events, Runtime.hooks, Runtime.errors =
savedEvents, savedHooks, savedErrors