-- YataiDON-HSS — the GAME→RESULT transition, ROUND 13. -- -- The cabinet does NOT close a shutter here. `000_default/loading/loading_kuro_result.nulm` -- (root sprite 4, 166 frames, labels `init 0 / in 6 / stop 115 / out 145 / end 165`) is a -- SINGLE full-stage black shape whose **alpha** ramps linearly 0 → 1 over frames 6…115 — -- 109 frames = 1816.7 ms — then holds at `stop` until the result screen takes over. -- -- The engine's built-in rig (`src/objects/game/result_transition.cpp`) is a `move` -- animation with no alpha at all, so this could not be expressed as skin data; round 13 -- added the scripted hook (see ENGINE_BINDINGS.md) and this file is the consumer. -- The engine calls `start` / `update` / `draw` / `is_finished`; defining `draw` is what -- takes the shutter out of the picture, and defining `is_finished` is what stops the scene -- cutting to RESULT at 983 ms, when the screen would still be only ~54 % black. -- -- The ramp itself is NOT hand-fitted: it is the exported arcade timeline, -- `Scripts/anim/kuro_result.lua` (ANIM_PIPELINE.md). `anim_export.py` compressed the 318 -- dense rows to 4 breakpoints, which is itself the proof the ramp is linear between them, -- and `anim_check.py kuro_result` samples the shipped table through the shipped sampler at -- every arcade frame: `a max|d| = 0.001938`, inside the 0.004 (1/256) export tolerance. -- -- The fade-out half of the record (`stop 115` → `end 165`) is deliberately NOT played -- here: the result screen's own entry already fades its backdrop out over the board -- (`result` animation 15), so playing both would double the dip. local anim = require("anim/sampler") ResultTransition = {} ResultTransition.__index = ResultTransition local F_IN, F_STOP = 6, 115 -- loading_kuro_result frame labels local MS_PER_F = 1000.0 / 60.0 local TRACK = "#3@0" -- the black plate's own placement function ResultTransition.new(player_num) local self = setmetatable({}, ResultTransition) self.player_num = player_num self.t0 = nil self.alpha = 0.0 -- Belt and braces: the engine already refuses to forward `update` / `is_finished` -- before `start()` (it drives this object from the first frame of the song, and -- `is_finished` is what ends the GAME screen), but a script that can run its clock -- unstarted would cut the chart short, so refuse here too. self.started = false -- FAIL SOFT: `anim.safe` never raises — a missing or malformed generated table -- yields the NULL clip, whose readers return nil, and `update` falls back to the -- arcade's own documented linear ramp below. self.clip = anim.safe("anim/kuro_result") self.t_black = tex.load_texture("global/result_transition/fade") return self end function ResultTransition:start() self.t0 = nil self.alpha = 0.0 self.started = true end function ResultTransition:update(current_ms) if not self.started then return end if not self.t0 then self.t0 = current_ms end local f = F_IN + (current_ms - self.t0) / MS_PER_F if f > F_STOP then f = F_STOP end local a = self.clip:at(TRACK, f) if not a then -- the generated table is gone: the record is linear f6…f115, 0 → 1 a = (f - F_IN) / (F_STOP - F_IN) end if a < 0 then a = 0 elseif a > 1 then a = 1 end self.alpha = a end -- The engine hands the screen over once this is true AND the result voice has stopped. function ResultTransition:is_finished() return self.started and self.alpha >= 1.0 end function ResultTransition:draw() if not self.t_black then return end if self.alpha <= 0.004 then return end -- below the engine's own 1/256 quantisation local w = tex.get_screen_width() local h = tex.get_screen_height() -- `fade.png` is an 8×8 black plate; texture.json carries its own 8×8 size as the base -- destination rect and `x2`/`y2` are ADDED to it (texture.cpp:655-680), so this -- stretches it to exactly the screen at any resolution. tex.draw_texture(self.t_black, { x = 0, y = 0, x2 = w - 8, y2 = h - 8, fade = self.alpha }) end return ResultTransition