singe/testScripts/scene52.singe
2026-09-14 14:20:17 -05:00

102 lines
3.8 KiB
Text

-- Forge (FORGE.md): compiles all four sample descriptions and plays the platformer.
--
-- The descriptions share nothing -- physics with a character, disc frames and time windows, a
-- shoot-em-up of spawned kinds, a gun over video -- so compiling all of them here is the test
-- that the core has no genre baked into it.
-- Nothing in this scene knows what a platformer is; it drives keys and looks at the result.
dofile("Forge/AuthorCompile.singe")
local font = fontLoad("Singe/FreeSansBold.ttf", 18)
local built = {}
local out = singeGetDataPath()
fontSelect(font)
fontQuality(FONT_QUALITY_BLENDED)
overlaySetResolution(720, 480)
for _, name in ipairs({ "platformer", "qte", "shmup", "gunvideo" }) do
local game = authorLoad("testScripts/author/" .. name .. ".game")
local problems = authorCheck(game)
built[name] = authorBuild("testScripts/author/" .. name .. ".game", out .. name .. ".singe")
debugPrint("AUTHOR built " .. built[name] .. " with " .. #problems .. " problems")
for _, problem in ipairs(problems) do
debugPrint("AUTHOR FAIL " .. name .. ": " .. problem)
end
end
-- Both must at least load as Lua; a compiler that emits something unparseable is the failure this
-- catches earliest, and it costs nothing to check both even though only one is played.
for name, path in pairs(built) do
local chunk, problem = loadfile(path)
if chunk == nil then
debugPrint("AUTHOR FAIL " .. name .. " does not parse: " .. tostring(problem))
else
debugPrint("AUTHOR parsed " .. name)
end
end
-- Now run the platformer, as the engine would if it had been launched directly.
dofile(built.platformer)
local gameUpdate = onOverlayUpdate
local frames = 0
-- Driving by what the game is doing, not by a clock or a frame count.
--
-- Both of those were tried and both are wrong. A frame count is wrong because the authored game
-- moves in seconds. A clock is wrong because physics steps at most MAX_STEPS_PER_FRAME (4, so
-- 66 ms) per frame and deliberately falls behind wall clock on a machine that cannot keep up --
-- which the sanitizer build, with a disc to decode, does. A test that watches the hero instead
-- gives the same answer at any frame rate, and exercises onGround from this side as well.
local JUMP_AT = 340 -- Where the hero leaves the ground to reach the ledge.
local GIVE_UP = 20.0 -- Seconds. Generous: this is a stuck detector, not a schedule.
local held = false
local jumped = false
local won = 0
local shots = 0
function onOverlayUpdate()
local t = authorTime()
local hero = authorEntity("hero")
local hx, hy = authorPosition(hero)
frames = frames + 1
if not held then
authorKeyDown(0, SCANCODE.RIGHT.value)
held = true
end
-- Jump once, when the hero is under way and has ground beneath it.
if (not jumped) and (hx >= JUMP_AT) and playerIsOnGround(hero.node) then
authorKeyDown(0, SCANCODE.SPACE.value)
jumped = true
elseif jumped then
authorKeyUp(0, SCANCODE.SPACE.value)
end
local r = gameUpdate()
-- The engine's own text over the authored game, so a screenshot says what it is looking at.
colorForeground(255, 255, 255, 255)
fontPrint(12, 450, string.format("authored platformer, %.1fs, score %d", t, AUTHOR_VARS.score))
-- Four shots at the moments that matter, found by watching rather than by timing: the start,
-- the run, the jump, and the frame after the prize was taken.
if (shots == 0) or (shots == 1 and hx > 250) or (shots == 2 and jumped and not playerIsOnGround(hero.node)) or (shots == 3 and won > 0 and t > won + 0.3) then
shots = shots + 1
singeScreenshot()
end
if AUTHOR_VARS.won and won == 0 then
won = t
end
if (won > 0 and t > won + 0.6) or (t > GIVE_UP) then
debugPrint("AUTHOR RESULT score=" .. AUTHOR_VARS.score .. " flagWon=" .. tostring(AUTHOR_VARS.won) .. " after=" .. string.format("%.1fs", t))
singeQuit()
end
return r
end