-- YataiDON-HSS child-skin addition (no parent counterpart). -- -- 演奏スキップ — the cabinet's on-screen cue, drawn from Background:draw_fore() -- so it lands in front of the whole lane/HUD exactly as the arcade's Lumen layer -- does. The engine owns the RULE (GameScreen::poll_skip: rim-only, alternating, -- unused drum, ten hits, no timeout) and pushes the live counter here through -- Background:handle_skip(player, { count, total, remaining, skipped }). -- -- ARCADE SOURCE — lumen/000_default/enso_normal/enso/information/skip.nulm. -- Root sprite 60 places mc `skip_end` (sprite 35) at (960, 804); `skip_end` -- places mc `skip_end_panel` (sprite 34) at (0, 0). Nothing below is guessed: -- the two timelines ship as generated tables (ANIM_PIPELINE.md) in -- Scripts/anim/skip_card.lua (sprite 35) and Scripts/anim/skip_panel.lua -- (sprite 34), and every rectangle is the shape's own local bounds. -- -- depth 0 shape 1 the card, local (-504,-264)..(504,264) -> 456,540 1008x528 -- depth 1 shapes 12,14,..,30 the numeral, local (-172,-172)..(172,172) -- depth 2 shape 32 the dim, 8x8 solid #000000 scaled (240.002, 135.001) -- at ty -264 -> exactly 1920x1080 over (960,540) -- depth 3 mc 11 `lang_skip_text` jpn=shape3 / en_us=5 / kor=7 / cn_tw=9, -- settling at tx 0, ty -386 -> (960, 418) -- -- THE COUNT IS SHOWN AS "HITS REMAINING", NOT AS "HITS SO FAR". The panel's -- frame labels are count_0..count_10 and the display list they settle on is -- shape 12 at count_1 ... shape 30 at count_10; shape 12 is the glyph "9" and -- shape 30 is "0". So the first rim hit puts a 9 on the card and the tenth puts -- a 0 on it — which is what the cabinet does and what the reference shows. -- -- Per count label the panel plays 9 frames and stops: frames label+1..label+8 -- bounce the numeral that is ALREADY on the card (scale-y 1 -> 1.1 -> 1, with a -- matching upward ty), and only on label+9 is it replaced by the new glyph. -- count_1 has no bounce — its keyframe already holds the 9. -- -- At count_10 the panel keeps playing instead of stopping: frames 120..123 fade -- the full-screen dim in to alpha 192/256, and frames 123..135 grow the banner -- from scale-y 0.05 to 1 while it fades in. The dim sits at depth 2, i.e. OVER -- the card and the numeral and UNDER the banner — which is why the arcade's 0 -- reads as greyed out once the skip has fired. -- -- Fail-soft throughout: anim.safe() never raises, a missing table degrades to -- "no animation" (alpha 1, scale 1), and a missing texture simply does not draw. local anim = require("anim/sampler") local EnsoSkip = {} EnsoSkip.__index = EnsoSkip -- 1080p skin pixels; the child skin loads texture.json at scale 1.0. local CARD_CY = 804 -- root placement of `skip_end` (y) local NUM_HALF = 172 -- numeral local half-extent local TEXT_CY = 418 -- banner origin after the reveal tween local TEXT_TOP = -145 -- lang shape local min y local TEXT_H = 288 -- lang shape local height -- skip_card (sprite 35) labels: wait 0 / in 4 / on 9 / out 24, last frame 29. local IN_F, ON_HOLD_F, OUT_F, END_F = 4, 23, 24, 29 function EnsoSkip.new() local self = setmetatable({}, EnsoSkip) self.card_clip = anim.safe("anim/skip_card") self.panel_clip = anim.safe("anim/skip_panel") self.count = 0 -- last count the engine reported self.held = 0 -- kept across the reset fade-out self.total = 10 self.skipped = false self.phase = "off" -- off | in | out self.card_f = IN_F self.panel_f = 0 self.label = 0 -- frame of the current count_N label self.last_ms = nil self.tex = {} return self end local function id(self, name) local cached = self.tex[name] if cached == nil then cached = tex.load_texture("game/skip/" .. name) or false self.tex[name] = cached end return cached or nil end -- Engine push, once per frame while the feature is armed. function EnsoSkip:handle(state) if not state then return end local count = state.count or 0 self.total = state.total or 10 self.skipped = state.skipped or false if count ~= self.count then if count > 0 then -- gotoAndPlay("count_N"): the panel restarts at that label. self.phase = "in" self.label = self.panel_clip:label("count_" .. count) or 0 self.panel_f = self.label if self.count == 0 then self.card_f = IN_F end -- skip_end "in" else -- back to 0 = the arcade reset (a hit on the playing drum). self.phase = "out" self.card_f = OUT_F end self.count = count end end function EnsoSkip:update(current_ms) local prev = self.last_ms self.last_ms = current_ms if not prev then return end local df = anim.frames(current_ms - prev) if df <= 0 then return end if self.phase == "in" then self.card_f = math.min(self.card_f + df, ON_HOLD_F) -- Each count_N segment stops on label+9; count_10 keeps playing through -- the dim (120..123) and the banner reveal (123..135) and stops there. local stop = (self.count >= self.total) and (self.label + 25) or (self.label + 9) self.panel_f = math.min(self.panel_f + df, stop) elseif self.phase == "out" then self.card_f = self.card_f + df if self.card_f >= END_F then self.phase = "off" end end end function EnsoSkip:draw() if self.phase == "off" then return end -- card alpha envelope, straight off sprite 35's own track local a = self.card_clip:get("skip_end_panel", self.card_f, "a") if a == nil then a = 1 end if a <= 0 then return end local panel = id(self, "panel") if panel then tex.draw_texture(panel, { fade = a }) end -- the numeral: before label+9 the OLD glyph is still on the card. During the -- "out" fade the count is already back to 0, so the last glyph is held -- -- the arcade fades the whole panel out, numeral included. local off = self.panel_f - self.label local live = (self.count > 0) and self.count or self.held local shown = live if live > 1 and off < 9 then shown = live - 1 end self.held = live local digit = (shown > 0) and (self.total - shown) or -1 -- count_N shows (10 - N) if digit >= 0 and digit <= 9 then -- frame index i of number.png is the digit 9 - i (the strip is baked in -- shape order 12,14,..,30 == "9".."0"). local frame = 9 - digit local sy, ty = 1, 0 if live > 1 then -- Every count_N bounce is the same nine rows; #14@1 is the copy that -- carries it at count_3 (label 40), so sample it at label+off. local bty, bsy = self.panel_clip:get("#14@1", 40 + off, "ty"), self.panel_clip:get("#14@1", 40 + off, "sy") if bsy then sy, ty = bsy, bty or 0 end end local num = id(self, "number") if num then local h = 2 * NUM_HALF * sy tex.draw_texture(num, { frame = frame, fade = a, x = 0, y = CARD_CY + ty - NUM_HALF * sy - (CARD_CY - NUM_HALF), y2 = h - 2 * NUM_HALF, }) end end -- past this point only the fired skip draws anything if not self.skipped then return end local dim = id(self, "dim") if dim then local da = self.panel_clip:get("#32@2", self.panel_f, "a") if da and da > 0 then tex.draw_texture(dim, { fade = da }) end end local banner = id(self, "text") if banner then local ty, sy, ba = self.panel_clip:get("lang_skip_text", self.panel_f, "ty"), self.panel_clip:get("lang_skip_text", self.panel_f, "sy"), self.panel_clip:get("lang_skip_text", self.panel_f, "a") sy = sy or 1 ba = ba or 1 if ba > 0 then -- texture.json parks the banner at its resting rect; the reveal is a -- scale-y about the shape origin, so re-anchor on TEXT_CY. local cy = (ty and (CARD_CY + ty)) or TEXT_CY tex.draw_texture(banner, { fade = ba, y = (cy + TEXT_TOP * sy) - (TEXT_CY + TEXT_TOP), y2 = TEXT_H * sy - TEXT_H, }) end end end return EnsoSkip