From d9869cfde08af74300da26c1f9485f7d19f80e4e Mon Sep 17 00:00:00 2001 From: TaikoZhong <108016253+GanYiZhong@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:26:53 +0800 Subject: [PATCH 1/2] Roll effect: fly over the lower backdrop along the original paths The birds / shuriken / arrows spawned in the top-left corner and swept across the top band. The original spawns them over the lower backdrop (x 6..1100, y 540..1020), behind the dancers, and each movie has its own path: birds and arrows rise to the right, shuriken fly level and spin. Replayed at 120 Hz for 120 frames, same 25-slot pool as before. Co-Authored-By: Claude Fable 5.1 --- Scripts/background/bg_objects/renda.lua | 152 ++++++++---------------- 1 file changed, 52 insertions(+), 100 deletions(-) diff --git a/Scripts/background/bg_objects/renda.lua b/Scripts/background/bg_objects/renda.lua index 5cbf3b7..393e65b 100644 --- a/Scripts/background/bg_objects/renda.lua +++ b/Scripts/background/bg_objects/renda.lua @@ -1,122 +1,74 @@ -- YataiDON-HSS override of PyTaikoGreen/background/bg_objects/renda.lua: Renda1 origin from x2[1]/y2[1]; frame from frame_count +-- The three roll effects, replayed the way the original places them: a piece is +-- spawned somewhere over the LOWER backdrop (x 6..1100, y 540..1020 -- the region +-- the original's spawn box covers below the lane) and flies for 120 frames at +-- 120 Hz along its movie's own path: +-- renda_0 birds (chidori) +17.3 px/frame right, -10.9 px/frame up +-- renda_1 shuriken (shuriken) +17.3 px/frame right, level, spinning -6 deg/frame +-- renda_2 arrow (hamaya) +13.1 px/frame right, -13.8 px/frame up +-- They are drawn before the dancers (see background.lua), as the original does. +local FRAME_MS = 1000.0 / 120.0 +local LIFE = 120 +local SPAWN_X0, SPAWN_X1 = 6, 1100 +local SPAWN_Y0, SPAWN_Y1 = 540, 1020 + local BaseRenda = {} BaseRenda.__index = BaseRenda -function BaseRenda.new(index) +function BaseRenda.new(index, vx, vy, spin) local self = setmetatable({}, BaseRenda) self.name = "renda_" .. index - local screen_width = tex.get_screen_width() - local screen_scale = tex.get_screen_scale() - self.hori_move = anim.move(1500 * screen_scale, {total_distance = screen_width}) - self.hori_move:start() + self.vx, self.vy, self.spin = vx, vy, spin or 0 + self.x0 = math.random(SPAWN_X0, SPAWN_X1) + self.y0 = math.random(SPAWN_Y0, SPAWN_Y1) + self.frames = 0 + self.last_ms = nil + local info = tex.get_texture_info("renda", self.name) + self.frame = math.random(0, ((info and info.frame_count) or 1) - 1) + self.w = (info and info.x2 and info.x2[1]) or 0 + self.h = (info and info.y2 and info.y2[1]) or 0 + self._id = tex.get_id("renda", self.name) -- nil = missing (ROUND 60: id 0 is valid) return self end function BaseRenda:update(current_time_ms) - self.hori_move:update(current_time_ms) + if self.last_ms then + local dt = current_time_ms - self.last_ms + if dt > 0 and dt < 500 then self.frames = self.frames + dt / FRAME_MS end + end + self.last_ms = current_time_ms +end + +function BaseRenda:finished() + return self.frames >= LIFE +end + +function BaseRenda:draw(y_offset) + if not self._id or self:finished() then return end + local f = self.frames + local cx = self.x0 + self.vx * f + local cy = self.y0 + self.vy * f + (y_offset or 0) + if self.spin ~= 0 then + tex.draw_texture(self._id, { + frame = self.frame, x = cx, y = cy, + origin = { self.w * 0.5, self.h * 0.5 }, rotation = self.spin * f, + }) + else + tex.draw_texture(self._id, { frame = self.frame, x = cx - self.w * 0.5, y = cy - self.h * 0.5 }) + end end local Renda0 = setmetatable({}, {__index = BaseRenda}) Renda0.__index = Renda0 - -function Renda0.new(index) - local self = setmetatable({}, Renda0) - local base = BaseRenda.new(index) - for k, v in pairs(base) do self[k] = v end - local screen_scale = tex.get_screen_scale() - local screen_height = tex.get_screen_height() - self.vert_move = anim.move(1500 * screen_scale, {total_distance = screen_height + (80 * screen_scale)}) - self.vert_move:start() - local tex_info = tex.get_texture_info("renda", self.name) - local num_frames = (tex_info and tex_info.frame_count) or 1 - self.frame = math.random(0, num_frames - 1) - self.x = math.random(0, math.floor(500 * screen_scale)) - self.y = math.random(0, math.floor(20 * screen_scale)) - self._id = tex.get_id("renda", self.name) -- nil = missing (ROUND 60: id 0 is valid) - return self -end - -function Renda0:update(current_time_ms) - BaseRenda.update(self, current_time_ms) - self.vert_move:update(current_time_ms) -end - -function Renda0:draw(y_offset) - -- ROUND 60 (r60-collab-fix-all): fail soft when the collab ships no renda art - if not self._id then return end - tex.draw_texture(self._id, { - frame = self.frame, - x = self.hori_move.attribute + self.x, - y = -self.vert_move.attribute + self.y + (y_offset or 0) - }) -end +function Renda0.new(index) return setmetatable(BaseRenda.new(index, 17.3, -10.9), Renda0) end local Renda1 = setmetatable({}, {__index = BaseRenda}) Renda1.__index = Renda1 - -function Renda1.new(index) - local self = setmetatable({}, Renda1) - local base = BaseRenda.new(index) - for k, v in pairs(base) do self[k] = v end - local screen_scale = tex.get_screen_scale() - local screen_height = tex.get_screen_height() - local tex_info = tex.get_texture_info("renda", self.name) - self.frame = math.random(0, ((tex_info and tex_info.frame_count) or 6) - 1) - self.y = math.random(0, math.floor(200 * screen_scale)) - self.rotate = anim.move(800 * screen_scale, {total_distance = math.floor(screen_height / 2), loop = true}) - self.rotate:start() - self.origin = {(tex_info and tex_info.x2[1] or 0) / 2, (tex_info and tex_info.y2[1] or 0) / 2} - self._id = tex.get_id("renda", self.name) -- nil = missing (ROUND 60: id 0 is valid) - return self -end - -function Renda1:update(current_time_ms) - BaseRenda.update(self, current_time_ms) - self.rotate:update(current_time_ms) -end - -function Renda1:draw(y_offset) - -- ROUND 60 (r60-collab-fix-all): fail soft when the collab ships no renda art - if not self._id then return end - tex.draw_texture(self._id, { - frame = self.frame, - x = self.hori_move.attribute + self.origin[1], - y = self.y + self.origin[2] + (y_offset or 0), - origin = self.origin, - rotation = self.rotate.attribute - }) -end +function Renda1.new(index) return setmetatable(BaseRenda.new(index, 17.3, 0, -6), Renda1) end local Renda2 = setmetatable({}, {__index = BaseRenda}) Renda2.__index = Renda2 - -function Renda2.new(index) - local self = setmetatable({}, Renda2) - local base = BaseRenda.new(index) - for k, v in pairs(base) do self[k] = v end - local screen_scale = tex.get_screen_scale() - local screen_height = tex.get_screen_height() - self.vert_move = anim.move(1500, {total_distance = screen_height + (80 * screen_scale)}) - self.vert_move:start() - self.x = math.random(0, math.floor(500 * screen_scale)) - self.y = math.random(0, math.floor(20 * screen_scale)) - self._id = tex.get_id("renda", self.name) -- nil = missing (ROUND 60: id 0 is valid) - return self -end - -function Renda2:update(current_time_ms) - BaseRenda.update(self, current_time_ms) - self.vert_move:update(current_time_ms) -end - -function Renda2:draw(y_offset) - -- ROUND 60 (r60-collab-fix-all): fail soft when the collab ships no renda art - if not self._id then return end - tex.draw_texture(self._id, { - x = self.hori_move.attribute + self.x, - y = -self.vert_move.attribute + self.y + (y_offset or 0) - }) -end +function Renda2.new(index) return setmetatable(BaseRenda.new(index, 13.1, -13.8), Renda2) end -- ROUND 52 (r52-lua-divergence-fixes): the cabinet's renda effect is a FIXED -- round-robin pool, not an unbounded per-hit spawner. the reference build @@ -174,7 +126,7 @@ function RendaController:update(current_time_ms) local r = self.rendas[slot] if r then r:update(current_time_ms) - if r.hori_move.is_finished then + if r:finished() then self.rendas[slot] = nil end end -- 2.54.0 From 166174865d5895830abba4a10fc1783dacfcfc76 Mon Sep 17 00:00:00 2001 From: TaikoZhong <108016253+GanYiZhong@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:35:46 +0800 Subject: [PATCH 2/2] Roll effect: 60 Hz and a pool of ten Checked against the original: the pieces cross at half the speed the 120 Hz step gave them, and only about ten are ever live -- the oldest is replaced as soon as an eleventh starts. Co-Authored-By: Claude Fable 5.1 --- Scripts/background/bg_objects/renda.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Scripts/background/bg_objects/renda.lua b/Scripts/background/bg_objects/renda.lua index 393e65b..769b72c 100644 --- a/Scripts/background/bg_objects/renda.lua +++ b/Scripts/background/bg_objects/renda.lua @@ -2,12 +2,12 @@ -- The three roll effects, replayed the way the original places them: a piece is -- spawned somewhere over the LOWER backdrop (x 6..1100, y 540..1020 -- the region -- the original's spawn box covers below the lane) and flies for 120 frames at --- 120 Hz along its movie's own path: +-- 60 Hz along its movie's own path: -- renda_0 birds (chidori) +17.3 px/frame right, -10.9 px/frame up -- renda_1 shuriken (shuriken) +17.3 px/frame right, level, spinning -6 deg/frame -- renda_2 arrow (hamaya) +13.1 px/frame right, -13.8 px/frame up -- They are drawn before the dancers (see background.lua), as the original does. -local FRAME_MS = 1000.0 / 120.0 +local FRAME_MS = 1000.0 / 60.0 local LIFE = 120 local SPAWN_X0, SPAWN_X1 = 6, 1100 local SPAWN_Y0, SPAWN_Y1 = 540, 1020 @@ -88,7 +88,9 @@ function Renda2.new(index) return setmetatable(BaseRenda.new(index, 13.1, -13.8) -- is still per-hit -- there is no per-frame "roll active" Lua signal -- so a -- slow roll spawns fewer than the cabinet's one-per-frame; the cap and the -- steady-state density now match.) -local POOL_SIZE = 25 +-- Observed on the original: about ten pieces are ever live at once -- the oldest +-- is dropped as soon as an eleventh starts, before it leaves the screen. +local POOL_SIZE = 10 local RendaController = {} RendaController.__index = RendaController -- 2.54.0