530 lines
26 KiB
Lua
530 lines
26 KiB
Lua
-- YataiDON-HSS — the 段位道場 shutter (doors + scrim + kanji stamp).
|
|
--
|
|
-- ROUND 23 RECONSTRUCTION. This file and its Graphics tree (`Graphics/**/dan_doors*`)
|
|
-- were confirmed missing from disk by ROUND 22 (see Graphics/dan_select/MAPPING.md
|
|
-- ROUND 22) — gone since at least a round-19-era scratchpad snapshot, silently, because
|
|
-- both call sites `pcall`-guard the `require`. This round rebuilt it from three sources
|
|
-- ONLY, per the standing "never invent data" rule:
|
|
-- 1. Graphics/dan_select/MAPPING.md ROUND 11 / 14 / 17 — every frame number, every
|
|
-- constant (CLOSE_FROM/TO, STAMP_FROM/TO, OPEN_FROM/TO, the kanji cell centres,
|
|
-- the se_daniodai_v12e/intro -> dan_intro sound history) is documented there.
|
|
-- 2. scratchpad/r17dn/patch_doors.py and patch_doors2.py — the FINAL, tested ROUND 17
|
|
-- function bodies (draw_frame/draw/draw_open/phases/frame_of/set_progress),
|
|
-- recovered verbatim (they were python string-patches against this exact file, so
|
|
-- their "old"/"new" blocks ARE the shipped Lua source at that point in history).
|
|
-- 3. Scripts/anim/dani_intro.lua — the exported the reference build track data itself, which was
|
|
-- NEVER deleted (only this consuming module was). Its rows for #1@3 (scrim),
|
|
-- #5@1/#5@2 (right/left door), #7@4/#9@5/#11@6/#13@7 (段/位/道/場) match the
|
|
-- MAPPING doc's frame numbers exactly, confirming the reconstruction.
|
|
--
|
|
-- What could NOT be recovered from those three sources and was therefore NOT invented:
|
|
-- nothing load-bearing turned out to be missing. `Graphics/song_select/dan_transition/
|
|
-- background.png` — the 2000x1080 atlas ROUND 11's "What ships" table describes
|
|
-- (door panel (0,0,1200,1080); kanji cells (1200,0)/(1600,0)/(1200,400)/(1600,400),
|
|
-- 400x400 each; scrim patch (1200,800,64,64)) — turned out to still be on disk, verified
|
|
-- this round by cropping all six regions and confirming non-transparent content in each
|
|
-- (see ROUND 23 section below for the exact bboxes measured). Only the *Lua module*
|
|
-- that reads it was gone, not the art. `Sounds/song_select/dan_intro.ogg` was also
|
|
-- still present, and `Graphics/song_select/animation.json` id 38's `duration` (2300 ms)
|
|
-- and comment ("Scripts/song_select/dan_doors.lua draws both halves") had ALSO survived
|
|
-- untouched — this file is the only piece that needed rebuilding.
|
|
|
|
local anim = require("anim/sampler")
|
|
local CLIP = anim.safe("anim/dani_intro")
|
|
|
|
local M = {}
|
|
|
|
-- Stage geometry, from the MAPPING doc's "arcade rig, decoded" table (ROUND 11).
|
|
local SCREEN_W, SCREEN_H = 1920, 1080
|
|
local DOOR_W = 1200 -- door panel local width (main__shape5)
|
|
local KANJI = 400 -- each kanji cell is 400x400 (main__shape7/9/11/13)
|
|
|
|
-- background.png atlas layout, ROUND 11 "What ships":
|
|
-- (0,0,1200,1080) door panel | (1200,0)/(1600,0)/(1200,400)/(1600,400) the four
|
|
-- kanji, 400x400 each | (1200,800,64,64) an opaque black patch for the scrim.
|
|
local SCRIM_SRC = { x = 1200, y = 800, width = 64, height = 64 }
|
|
|
|
-- kanji cell -> its arcade track name, in reading order 段位道場 (ROUND 11 table:
|
|
-- #7@4=段 centre(455,255), #9@5=位 centre(1420,255), #11@6=道 centre(455,765),
|
|
-- #13@7=場 centre(1420,765) — the (i<=2)/(i odd) fallback centres below mirror that).
|
|
local CELLS = {
|
|
{ track = "#7@4", sx = 1200, sy = 0 }, -- 段
|
|
{ track = "#9@5", sx = 1600, sy = 0 }, -- 位
|
|
{ track = "#11@6", sx = 1200, sy = 400 }, -- 道
|
|
{ track = "#13@7", sx = 1600, sy = 400 }, -- 場
|
|
}
|
|
|
|
-- Frame map.
|
|
--
|
|
-- ROUND 64 (r64-danselect-fidelity) RE-DERIVATION. The report was 「段位道場的
|
|
-- entry動畫有誤,重新查」, and re-reading the cabinet's own script found two
|
|
-- real errors in ROUND 14/17's map. The export itself is NOT at fault (the
|
|
-- ROUND 61 trap): `anim/dani_intro.lua` was dumped `--range 0,262 --all` and
|
|
-- `lumen_anim_dump loading_dani_intro.nulm --list` reports sprite 15 with
|
|
-- exactly 263 frames and labels init@0 / in@5 / stop@248 / out@253 / end@262,
|
|
-- so every leg below is covered by real data.
|
|
--
|
|
-- WHO PLAYS IT, and WHERE. `script_lua/dani_select/dani_select_introduction_
|
|
-- main.lua` loads `lumen/000_default/enso_dani/loading_dani/loading_dani_intro`
|
|
-- as `introduction_instance` -- root depth 16 of dani_select.nulm, i.e. the
|
|
-- TOP-most layer of the DAN_SELECT scene -- and `dani_select_all.lua`'s state
|
|
-- machine plays it from the `IntroductionMain` state, which runs AFTER
|
|
-- Begin/Loading/SetUp/StartWait, i.e. after the whole screen has loaded:
|
|
-- PlayIntroduction : TSound:Play("se_daniodai_v12e","intro")
|
|
-- mc_main.main:GotoAndPlay("in") -- frame 5
|
|
-- Is_EndAnimation : if not IsPlay() then GotoAndPlay("out") end
|
|
-- -> DaniSelectMain: timer_:StartCount(); name_plate_:SetVisible(true)
|
|
-- So on the cabinet the ENTIRE 4.2 s runs on the dan-select screen and nothing
|
|
-- of it runs on song select. Our engine has to cover its own (blocking,
|
|
-- multi-second) DAN_SELECT load, which the cabinet does in a state with
|
|
-- nothing on screen, so the song-select half stays -- but it must stop
|
|
-- inventing motion the cabinet does not have.
|
|
--
|
|
-- ERROR 1 (fixed here): the close leg played the DOOR track BACKWARDS,
|
|
-- f200 -> f182, so our shutter slid in from off-screen. There is no such move
|
|
-- anywhere in the clip. `#5@1`/`#5@2` are PLACED at f35 already shut (tx 960,
|
|
-- sx +-1) and do not move again until f187. What actually happens over f0..62
|
|
-- is the SCRIM: `#1@3` is alpha 1 (a full-screen black) from f0 to f55 and
|
|
-- eases to 0.5 by f62 -- the screen is simply black, then 50 % black over the
|
|
-- shut shutter. The doors never move on the way in. The close leg is therefore
|
|
-- now a pure OPACITY ramp over the f62 pose: invented (we are fading out of a
|
|
-- live wheel, the cabinet is fading out of nothing), but it no longer
|
|
-- fabricates a door slide.
|
|
--
|
|
-- ERROR 2 (fixed here): the DAN_SELECT half stopped at f203. It is not movie
|
|
-- tail past there: `#3@0` -- the dan-dojo BACKGROUND, the same night-torch art
|
|
-- as Graphics/dan_select/global/bg.png, verified by rendering
|
|
-- `lumen_shape_dump -m -r 15 -f 215` -- pushes in from scale 1.0 to 1.25 over
|
|
-- f187..253 and only then cross-fades out over f253..262 (`out`). Cutting at
|
|
-- f203 threw away the push-in and the cross-fade, which is the half of the
|
|
-- entry animation the player actually reads as "entering the dojo".
|
|
local CLOSE_POSE = 62 -- shut doors, scrim already eased to 0.5
|
|
local STAMP_FROM = 62 -- the arcade's scrim has just settled to 0.5
|
|
local STAMP_TO = 182 -- all four landed, before the fade
|
|
local OPEN_FROM = 182 -- kanji fade (182-185), doors part (187-200),
|
|
local OPEN_TO = 262 -- scrim clears (203), bg push-in + `out` fade
|
|
|
|
local MS_PER_FRAME = 1000.0 / 60.0
|
|
-- INVENTED, and the only invented number left in the map: the wheel has to go
|
|
-- somewhere. 300 ms is ROUND 17's value, kept so Graphics/song_select/
|
|
-- animation.json id 38's `duration` (2300.0) still equals CLOSE_MS + STAMP_MS
|
|
-- exactly and no timing outside this file moves.
|
|
local CLOSE_MS = 300.0
|
|
local STAMP_MS = (STAMP_TO - STAMP_FROM) * MS_PER_FRAME -- 120 frames = 2000 ms
|
|
local OPEN_MS = (OPEN_TO - OPEN_FROM) * MS_PER_FRAME -- 80 frames = 1333 ms
|
|
|
|
-- Total ms of the SONG_SELECT half. MUST agree with Graphics/song_select/animation.json
|
|
-- id 38 `duration` (2300.0, confirmed still on disk this round: exactly CLOSE_MS+STAMP_MS).
|
|
-- The engine hands the real progress in via set_progress() every frame on a build with
|
|
-- `SongSelect:draw_top`, so this constant is only load-bearing on an engine WITHOUT it
|
|
-- (the self-clocked fallback in M.progress() below).
|
|
local DURATION_MS = 2300.0
|
|
local function duration_ms() return DURATION_MS end
|
|
|
|
local FB_SCRIM = 0.5 -- #1@3's settled alpha (f62..193 in the arcade data)
|
|
|
|
local function clamp01(v) return math.max(0.0, math.min(1.0, v)) end
|
|
|
|
local function clip() return CLIP end
|
|
|
|
-- ── the atlas ────────────────────────────────────────────────────────────────
|
|
local _id, _id_tried
|
|
local function tex_id()
|
|
if not _id_tried then
|
|
_id_tried = true
|
|
_id = tex.load_texture("song_select/dan_transition/background")
|
|
end
|
|
return _id
|
|
end
|
|
|
|
-- ROUND 64 -- the DAN_SELECT screen's own background, which is also the intro
|
|
-- clip's `#3@0`. Resident only while DAN_SELECT is live; nil everywhere else,
|
|
-- which is exactly the gate the push-in needs.
|
|
local _bg, _bg_tried
|
|
local function bg_id()
|
|
if not _bg_tried then
|
|
_bg_tried = true
|
|
_bg = tex.load_texture("dan_select/global/bg")
|
|
end
|
|
return _bg
|
|
end
|
|
|
|
-- ── fallbacks, reached only if anim/dani_intro.lua is itself missing/malformed
|
|
-- (anim.safe hands back the NULL clip) — the closed-form end states the frame map
|
|
-- above degrades to, so a renamed/corrupted data table cannot take the screen down.
|
|
-- ROUND 64: the shut pose is the ONLY pose the fallback can honestly assert --
|
|
-- the doors never travel except over f187..200, whose curve is exactly the data
|
|
-- this fallback exists because we lost. Right door (#5@1) tx 960; left door
|
|
-- (#5@2) tx 960 with sx -1, i.e. its drawn left edge at 960 - 1200 = -240.
|
|
local function fallback_door()
|
|
return -240, 960, DOOR_W, 0, FB_SCRIM
|
|
end
|
|
|
|
local function fallback_kanji(i, shown)
|
|
if not shown or shown <= 0 then return nil end
|
|
return 1.0, 1.0 -- scale 1, alpha 1 — the landed pose, nothing finer is recoverable
|
|
end
|
|
|
|
-- ── sound. ROUND 14b: resident name is the file's STEM, not its skin-relative path
|
|
-- (`audio.cpp` prefixes only files inside a screen subfolder); the corrected cue is
|
|
-- `Sounds/song_select/dan_intro.ogg`, still present on disk.
|
|
local function fire_sfx()
|
|
if M.sfx_done then return end
|
|
if audio and audio.play_sound then audio.play_sound("dan_intro", "sound") end
|
|
M.sfx_done = true
|
|
end
|
|
|
|
-- ROUND 17: two segments only — close then stamp — and the half ENDS parked on
|
|
-- STAMP_TO. A `duration` shorter than close+stamp still compresses the two
|
|
-- proportionally (that is what keeps the old 1100 ms value producing the round-11
|
|
-- look); anything longer parks the leftover on STAMP_TO, which is the cabinet's
|
|
-- own hold pose. The shipped 2300 ms leaves none, because our hold is now the
|
|
-- real DAN_SELECT load rather than invented dead time.
|
|
local function phases()
|
|
local d = duration_ms()
|
|
local move = CLOSE_MS + STAMP_MS
|
|
local k = (d >= move) and 1.0 or (d / move) -- compress only if we must
|
|
local c = CLOSE_MS * k
|
|
local st = STAMP_MS * k
|
|
return c / d, (c + st) / d
|
|
end
|
|
|
|
-- u (0..1 across our transition) -> arcade frame.
|
|
-- ROUND 64: the close leg no longer travels; it holds CLOSE_POSE and only the
|
|
-- overall opacity moves (see the frame-map comment).
|
|
local function frame_of(u)
|
|
local close_u, stamp_u = phases()
|
|
if u <= close_u then
|
|
return CLOSE_POSE
|
|
elseif u <= stamp_u then
|
|
local v = (u - close_u) / (stamp_u - close_u)
|
|
return STAMP_FROM + (STAMP_TO - STAMP_FROM) * v
|
|
end
|
|
return STAMP_TO -- parked: doors shut, kanji up
|
|
end
|
|
|
|
-- ── the shared frame painter ────────────────────────────────────────────────
|
|
-- ROUND 17: both halves paint the SAME arcade frame map, so the painting is one
|
|
-- function taking an arcade frame.
|
|
-- ROUND 64: the second argument is now an OVERALL OPACITY (nil = 1), not a
|
|
-- "closedness". The cabinet's shutter has exactly one pose on the way in -- shut
|
|
-- -- so our invented close is a fade of that pose rather than a fabricated
|
|
-- slide, and the fade has to reach the doors too, not only the scrim.
|
|
local function draw_frame(f, op)
|
|
local id = tex_id()
|
|
if not id then return false end
|
|
local c = clip()
|
|
op = (op == nil) and 1.0 or clamp01(op)
|
|
if op <= 0 then return true end
|
|
|
|
-- ROUND 64 -- the background, `#3@0`, arcade depth 0: BELOW both doors and
|
|
-- the scrim. It is the dan-dojo night art (= Graphics/dan_select/global/
|
|
-- bg.png), placed at f35 and holding scale 1 until f187, then pushing in to
|
|
-- 1.25 by f253 and cross-fading out over f253..262. Only the DAN_SELECT
|
|
-- half ever sees it (during the stamp the opaque shutter covers it, and on
|
|
-- SONG_SELECT the texture is not even resident) -- bg_id() is nil there and
|
|
-- the whole block is skipped, so nothing regresses on an engine or screen
|
|
-- without it.
|
|
local btx, bty, bsx, _, ba = c:at("#3@0", f)
|
|
if btx and ba and ba > 0 then
|
|
local bid = bg_id()
|
|
if bid then
|
|
local w, h = SCREEN_W * (bsx or 1), SCREEN_H * (bsx or 1)
|
|
tex.draw_texture(bid, {
|
|
x = (btx or 960) - w * 0.5, y = (bty or 540) - h * 0.5,
|
|
x2 = w - SCREEN_W, y2 = h - SCREEN_H, fade = ba * op,
|
|
})
|
|
end
|
|
end
|
|
|
|
-- doors -----------------------------------------------------------------
|
|
-- A track row is (tx, ty, sx, sy, a) in the arcade's 1920x1080 stage. The
|
|
-- panel's local box is x 0..1200, y 0..1080 with its origin at the placement,
|
|
-- so screen span = tx .. tx + 1200*sx and top = ty - 540. sx < 0 IS the
|
|
-- mirror (the left door is the same shape flipped), which is why the left
|
|
-- door's x is tx + 1200*sx, i.e. tx minus the drawn width.
|
|
local lx, rx, dw, dtop, scrim
|
|
local rtx, rty, rsx = c:at("#5@1", f)
|
|
local ltx, _, lsx = c:at("#5@2", f)
|
|
if rtx and ltx then
|
|
dw = DOOR_W * math.abs(rsx)
|
|
rx = rtx
|
|
lx = ltx + DOOR_W * lsx
|
|
dtop = rty - SCREEN_H * 0.5
|
|
scrim = c:get("#1@3", f, "a") or FB_SCRIM
|
|
else
|
|
lx, rx, dw, dtop, scrim = fallback_door()
|
|
end
|
|
|
|
-- left door = the same panel mirrored (a negative src width is raylib's flip)
|
|
tex.draw_texture(id, { src = { x = 0, y = 0, width = -DOOR_W, height = SCREEN_H },
|
|
x = lx, y = dtop, x2 = dw, y2 = SCREEN_H, fade = op })
|
|
tex.draw_texture(id, { src = { x = 0, y = 0, width = DOOR_W, height = SCREEN_H },
|
|
x = rx, y = dtop, x2 = dw, y2 = SCREEN_H, fade = op })
|
|
|
|
-- the full-screen black at depth 3: above both doors, below all four kanji
|
|
if scrim and scrim > 0 then
|
|
tex.draw_texture(id, { src = SCRIM_SRC,
|
|
x = 0, y = 0, x2 = SCREEN_W, y2 = SCREEN_H,
|
|
fade = scrim * op })
|
|
end
|
|
|
|
-- kanji ------------------------------------------------------------------
|
|
for i = 1, #CELLS do
|
|
local cell = CELLS[i]
|
|
local cx, cy, s, a
|
|
local tx, ty, sx, _, al = c:at(cell.track, f)
|
|
-- A track's rows start at the frame the glyph is PLACED; the sampler
|
|
-- clamps below that, so an explicit gate is needed or 段 would be on
|
|
-- screen from frame 0.
|
|
local first = c:has(cell.track) and c.tracks[cell.track][1][1] or nil
|
|
if tx and first and f >= first then
|
|
cx, cy, s, a = tx, ty, sx, al
|
|
elseif not tx then
|
|
local fs, fa = fallback_kanji(i, (f >= STAMP_TO) and 1 or 0)
|
|
if fs then
|
|
cx = (i == 1 or i == 3) and 455 or 1420
|
|
cy = (i <= 2) and 255 or 765
|
|
s, a = fs, fa
|
|
end
|
|
end
|
|
if cx and a and a > 0 then
|
|
local w = KANJI * s
|
|
tex.draw_texture(id, {
|
|
src = { x = cell.sx, y = cell.sy, width = KANJI, height = KANJI },
|
|
x = cx - w * 0.5,
|
|
y = cy - w * 0.5,
|
|
x2 = w, y2 = w, fade = a * op,
|
|
})
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- ── arming / progress ───────────────────────────────────────────────────────
|
|
-- `M.arm(active)` is called every SONG_SELECT frame from `draw_overlays(state)`
|
|
-- with `state == STATE_DAN_SELECTED`; it is the ONLY signal on an engine without
|
|
-- `draw_top` (the self-clocked fallback below). `M.set_progress(p)` is called
|
|
-- every SONG_SELECT frame from `draw_top` with `DanTransition::progress()` (0..1,
|
|
-- or a negative value when no transition is running) on an engine that has it —
|
|
-- when present it always wins over the fallback clock.
|
|
M.armed = false
|
|
M.engine_u = nil
|
|
M.has_draw_top = false
|
|
M.t0 = nil
|
|
M.armed_open = false
|
|
M.open_t0 = nil
|
|
M.sfx_done = false
|
|
|
|
function M.arm(active)
|
|
M.armed = active and true or false
|
|
if not M.armed then
|
|
M.t0 = nil -- let the fallback clock restart cleanly next time
|
|
end
|
|
end
|
|
|
|
function M.set_progress(p)
|
|
M.has_draw_top = true
|
|
if p and p >= 0 then
|
|
M.engine_u = p
|
|
else
|
|
M.engine_u = nil
|
|
-- ROUND 17 -- TEARDOWN. This is called from SongSelect:draw_top on EVERY
|
|
-- song-select frame, with -1 when no transition is running, so it is the one
|
|
-- place guaranteed to run when the rig must not be on screen. Clearing the
|
|
-- DAN_SELECT half's state here is what stops a stale overlay surviving a
|
|
-- back-out of DAN_SELECT (or an aborted transition) and re-appearing over the
|
|
-- wheel: whatever half-open pose was left is dropped the first frame the
|
|
-- song-select screen is live again.
|
|
M.armed_open = false
|
|
M.open_t0 = nil
|
|
end
|
|
end
|
|
|
|
function M.progress()
|
|
if M.has_draw_top then
|
|
-- the engine supplies real progress every frame; nil/negative = not running
|
|
if M.engine_u and M.engine_u >= 0 then return clamp01(M.engine_u) end
|
|
return nil
|
|
end
|
|
-- fallback: no draw_top on this engine build, self-clock off arm()
|
|
if not M.armed then return nil end
|
|
local now = tex.get_current_ms()
|
|
if not M.t0 then M.t0 = now end
|
|
local d = duration_ms()
|
|
local u = (now - M.t0) / d
|
|
if u >= 1.0 then return nil end
|
|
return u
|
|
end
|
|
|
|
-- ── half 1: SONG_SELECT (close + stamp) ─────────────────────────────────────
|
|
-- Returns true when it drew anything.
|
|
function M.draw()
|
|
local u = M.progress()
|
|
if not u then return false end
|
|
|
|
fire_sfx(u)
|
|
|
|
local close_u = phases()
|
|
local f = frame_of(u)
|
|
-- ROUND 64: over the invented close leg the pose is frozen at CLOSE_POSE and
|
|
-- only the opacity ramps in; from there on the clip's own alphas rule.
|
|
if not draw_frame(f, (u <= close_u) and (u / close_u) or nil) then return false end
|
|
|
|
-- ROUND 17: arm the DAN_SELECT half. Set on every frame the rig is live, so
|
|
-- an abort (the transition never completing) leaves it armed only until the
|
|
-- next set_progress(-1), which is the very next song-select frame.
|
|
--
|
|
-- ROUND 23 FIX: guard the re-arm to first-arm only. `Scripts/global/
|
|
-- indicator.lua`'s own fallback slot (`Indicator:draw()`, kept for an engine
|
|
-- without draw_top) calls `dan_doors.draw()` on EVERY screen `Indicator` is
|
|
-- used on, DAN_SELECT included -- and once `set_progress` has been called at
|
|
-- least once, `M.progress()`'s engine-driven branch returns the LAST
|
|
-- `engine_u` it was given (SONG_SELECT stops calling `set_progress` the
|
|
-- instant the screen changes to DAN_SELECT, so that value is never updated
|
|
-- to nil again). The fallback therefore kept calling `M.draw()` -> this
|
|
-- branch -> `M.open_t0 = nil` on every single DAN_SELECT frame, which reset
|
|
-- `M.draw_open()`'s wall clock before it could ever accumulate past 0 --
|
|
-- the doors visibly never opened, verified as stuck at the shut/stamped
|
|
-- pose for 6.5+ seconds in `scratchpad/r23dn/open_probe/`. Only re-arming
|
|
-- when NOT already armed lets `draw_open()`'s own clock run once started.
|
|
if not M.armed_open then
|
|
M.armed_open = true
|
|
M.open_t0 = nil
|
|
end
|
|
M._drawn_ms = tex.get_current_ms()
|
|
return true
|
|
end
|
|
|
|
-- Pure layering helper for `Scripts/global/indicator.lua`'s fallback slot: true
|
|
-- iff `M.draw()` already painted this exact engine frame, so the fallback call
|
|
-- (kept only for an engine without the real SONG_SELECT top slot) never draws
|
|
-- the rig twice in one frame.
|
|
function M.drawn_this_frame()
|
|
return M._drawn_ms ~= nil and M._drawn_ms == tex.get_current_ms()
|
|
end
|
|
|
|
-- ── half 2: DAN_SELECT (the reveal) ─────────────────────────────────────────
|
|
-- Called from `Indicator:draw_top()`, the top-most slot on DAN_SELECT (added
|
|
-- round 17: DanSelectScreen::draw() calls it after the coin overlay, the
|
|
-- DAN_SELECT plate and the allnet chip). ROUND 64: plays arcade f182..262 -- the
|
|
-- WHOLE remaining clip, not the f182..203 stub -- on a plain wall clock at 1:1
|
|
-- with the cabinet, then tears itself down so nothing survives into the screen.
|
|
-- Returns true while it is drawing. 80 frames = 1333 ms, which is the window
|
|
-- DanSelectScreen::tick_timer waits out before StartCount()ing the credit clock
|
|
-- (the cabinet's IntroductionMain -> DaniSelectMain hand-off).
|
|
--
|
|
-- The clock starts on the FIRST frame this runs rather than at the screen
|
|
-- change, which is deliberate: DAN_SELECT's init can block the render loop for
|
|
-- a couple of seconds and anchoring to the change would make the doors jump
|
|
-- straight to open on the first frame the loop comes back. Starting here means
|
|
-- the open plays in full, right after the load.
|
|
--
|
|
-- ROUND 86 (r86) -- THE SECOND ENTRY PATH, and the defect it caused.
|
|
--
|
|
-- Reported: 「段位道場的進入動畫消失了」. ROUND 83 promoted 段位道場 to a mode board on
|
|
-- the ENTRY screen, so the dojo is now reachable as ENTRY -> DAN_SELECT with no
|
|
-- SONG_SELECT in between. `M.armed_open` is set in exactly ONE place -- `M.draw()`
|
|
-- above, the SONG_SELECT half -- so on that route it was never set and this function
|
|
-- returned false on every frame. Nothing about the movie was broken; it was never armed.
|
|
--
|
|
-- What the cabinet does on ITS mode-board route is play the WHOLE clip here:
|
|
-- `dani_select_all.lua`'s IntroductionMain runs `mc_main.main:GotoAndPlay("in")` and `in`
|
|
-- is frame 5, through stop@248 / out@253..262. f5..55 is the full-black scrim (`#1@3`
|
|
-- alpha 1.0), i.e. the blackout the player reads as leaving the entry screen, then the
|
|
-- kanji stamp, then the reveal. So:
|
|
-- __hss_dan_intro == "full" (engine: previous screen was ENTRY) -> arm here, f5..262
|
|
-- __hss_dan_intro == "open" (previous screen was SONG_SELECT) -> ROUND 64 f182..262,
|
|
-- armed by M.draw()
|
|
-- The engine (`DanSelectScreen::on_screen_start`) writes this global once per screen
|
|
-- entry; we consume it. A skin/engine pair without it keeps the old behaviour exactly,
|
|
-- because `open_from` then falls back to OPEN_FROM.
|
|
local INTRO_FROM = 5 -- clip label `in`
|
|
|
|
-- ── ROUND 95 (r95-danselect-concurrent-intro) ────────────────────────────────
|
|
--
|
|
-- Reported: 「他現在比較像先載入dan, 才載入段位道場動畫,你要邊放動畫邊讓他同時載入
|
|
-- dan」. The engine used to run its (blocking, multi-second) course scan BEFORE
|
|
-- arming this movie, so the movie whose entire purpose is to hide the load played
|
|
-- after it. `DanSelectScreen::on_screen_start` now arms first and scans on a
|
|
-- worker, which means the ribbon behind these doors may still be empty when the
|
|
-- clip reaches the reveal.
|
|
--
|
|
-- f182 (`OPEN_FROM`) is the first frame that shows the player anything behind the
|
|
-- doors, so it is the clamp frame -- the exact analogue of ROUND 85's f113 clamp
|
|
-- one frame short of the genre-board swap. While `__hss_dan_ready` is false the
|
|
-- clip is held on f182 (doors shut, scrim up); the frame it turns true the clock
|
|
-- is RE-ANCHORED so f == 182 at that instant and the 80-frame / 1333.3 ms reveal
|
|
-- runs from there at 1:1.
|
|
--
|
|
-- scan wins the race -> nothing is ever clamped, the clip plays f5..f262 (or
|
|
-- f182..f262) in full. It is NOT cut short: the cabinet's
|
|
-- `Is_EndAnimation` is `mc_main.main:IsPlay() == false`,
|
|
-- purely the clip (dani_select_introduction_main.lua).
|
|
-- clip wins the race -> hold on f182 for as long as it takes. The cabinet's
|
|
-- equivalent wait is `DaniSelectMain`'s per-frame
|
|
-- `if daniselectdani_.is_setUpAllOk == true` gate before
|
|
-- `timer_:StartCount()` (dani_select_all.lua:126-128).
|
|
-- The CLAMP itself is ours, not the cabinet's -- the
|
|
-- cabinet's `CreateDaniData` reads a resident datatable
|
|
-- and is instant, so it never has anything to hide.
|
|
--
|
|
-- `DanSelectScreen::tick_timer` derives the credit clock's start from the same
|
|
-- three numbers, so the clock and the movie cannot disagree.
|
|
-- OLD ENGINE: `__hss_dan_ready` nil is treated as ready, so a skin on a
|
|
-- pre-ROUND-95 engine behaves exactly as ROUND 86.
|
|
local REVEAL_F = OPEN_FROM -- 182
|
|
|
|
function M.draw_open()
|
|
local req = __hss_dan_intro
|
|
if req ~= nil then
|
|
__hss_dan_intro = nil
|
|
if req == "full" and not M.armed_open then
|
|
M.armed_open = true
|
|
M.open_t0 = nil
|
|
M.open_held = false
|
|
M.open_from = INTRO_FROM
|
|
-- the cabinet's PlayIntroduction fires TSound "se_daniodai_v12e"/"intro" at
|
|
-- the top of IntroductionMain; on the SONG_SELECT route M.draw() already fired
|
|
-- it during the close leg and `M.sfx_done` swallows this call.
|
|
fire_sfx()
|
|
elseif req == "open" then
|
|
M.open_from = OPEN_FROM
|
|
end
|
|
end
|
|
if not M.armed_open then return false end
|
|
local now = tex.get_current_ms()
|
|
if not M.open_t0 then M.open_t0 = now end
|
|
local from = M.open_from or OPEN_FROM
|
|
local f = from + (now - M.open_t0) / MS_PER_FRAME
|
|
|
|
-- ROUND 95: clamp at f182 while the courses are still loading, re-anchor on
|
|
-- f182 the frame they land. See the block above.
|
|
local ready = (__hss_dan_ready ~= false)
|
|
if not ready then
|
|
if f > REVEAL_F then
|
|
f = REVEAL_F
|
|
M.open_held = true
|
|
end
|
|
elseif M.open_held then
|
|
M.open_held = false
|
|
M.open_t0 = now - (REVEAL_F - from) * MS_PER_FRAME
|
|
f = REVEAL_F
|
|
end
|
|
M.open_f = f -- for the r95 trace (YATAIDON_R95_TRACE)
|
|
|
|
if f >= OPEN_TO then
|
|
M.armed_open = false
|
|
M.open_t0 = nil
|
|
M.open_held = false
|
|
M.open_from = nil
|
|
M.sfx_done = false
|
|
M.engine_u = nil
|
|
M.t0 = nil
|
|
return false
|
|
end
|
|
return draw_frame(f, nil)
|
|
end
|
|
|
|
return M
|