fix: yield every row in wedge rasteriser to prevent too-long-without-yielding

This commit is contained in:
2026-05-05 19:24:55 -04:00
parent 7ac69d0ec6
commit 229e77e664

View File

@@ -180,13 +180,9 @@ local function drawWedge(slotIdx, rotorAngle, glowing)
local by0 = math.floor(CY - ro) - 1 local by0 = math.floor(CY - ro) - 1
local by1 = math.ceil(CY + ro) + 1 local by1 = math.ceil(CY + ro) + 1
-- Normalise angle range to handle wrap-around -- Scan row by row, yield every pixel row to stay within CC tick budget.
-- We scan row by row and fill runs for speed.
-- Yield every 32 rows so CC:Tweaked doesn't kill us.
local rowCount = 0
for sy = by0, by1 do for sy = by0, by1 do
rowCount = rowCount + 1 sleep(0)
if rowCount % 32 == 0 then sleep(0) end
local runStart = nil local runStart = nil
for sx = bx0, bx1 do for sx = bx0, bx1 do
local dx = sx - CX local dx = sx - CX
@@ -315,83 +311,108 @@ end
-- at settle time (in rotor-relative coordinates) -- at settle time (in rotor-relative coordinates)
---------------------------------------------------------------------- ----------------------------------------------------------------------
local ROTOR_SPEED_MIN = 1.5 -- rad/s initial rotor speed ----------------------------------------------------------------------
local ROTOR_SPEED_MAX = 2.5 -- Spin — fully physics-based
local BALL_SPEED_MIN = 8.0 -- rad/s initial ball speed (opposite sign) --
local BALL_SPEED_MAX = 12.0 -- Phases:
local ROTOR_FRICTION = 0.18 -- rad/s^2 rotor deceleration -- 1. TRACK : ball rolls on outer track, decelerating under friction.
local BALL_FRICTION = 0.55 -- rad/s^2 ball deceleration -- Rotor also decelerates (much slower — heavy wheel).
local SPIRAL_SPEED = 1.2 -- rad/s ball speed threshold to begin spiral -- 2. DROP : when ball tangential speed drops below DROP_SPEED, centripetal
local SPIRAL_TIME = 1.4 -- seconds to spiral inward -- force is insufficient; ball gains inward radial velocity.
-- A small random angular deflection simulates diamond/pin bounce.
-- 3. POCKET : ball is now at pocket-ring radius, still has angular momentum;
-- decelerates under higher friction until stopped.
-- Result = nearest pocket by angle relative to rotor.
----------------------------------------------------------------------
local rotorAngle = 0 -- persists between spins local ROTOR_SPEED_MIN = 1.2 -- rad/s rotor initial speed (CW, positive)
local ROTOR_SPEED_MAX = 2.0
local ROTOR_FRICTION = 0.08 -- rad/s² rotor deceleration (heavy wheel, slow)
local BALL_SPEED_MIN = 7.0 -- rad/s ball initial speed (CCW, negative)
local BALL_SPEED_MAX = 11.0
local TRACK_FRICTION = 0.40 -- rad/s² ball deceleration on outer track
local DROP_SPEED = 1.8 -- rad/s ball speed at which it leaves the track
local DROP_VEL = 80.0 -- px/s inward radial speed when drop triggers
local DEFLECT_MAX = 0.18 -- rad max angular kick from deflector pin
local POCKET_FRICTION = 1.2 -- rad/s² ball deceleration once in pocket ring
local rotorAngle = 0 -- persists between spins
local rotorSpeed = 0
local function spin() local function spin()
local dt = FRAME_DELAY local dt = FRAME_DELAY
local rotorSpeed = ROTOR_SPEED_MIN + math.random() * (ROTOR_SPEED_MAX - ROTOR_SPEED_MIN) -- Initial conditions (only speeds are random — outcome determined by physics)
local ballSpeed = -(BALL_SPEED_MIN + math.random() * (BALL_SPEED_MAX - BALL_SPEED_MIN)) rotorSpeed = ROTOR_SPEED_MIN + math.random() * (ROTOR_SPEED_MAX - ROTOR_SPEED_MIN)
local ballAngle = math.random() * TWO_PI -- random start position local ballSpeed = -(BALL_SPEED_MIN + math.random() * (BALL_SPEED_MAX - BALL_SPEED_MIN))
local ballR = (R_OUTER - 6 + R_POCKET_OUT + 2) / 2 -- mid track local ballAngle = math.random() * TWO_PI
local spiraling = false local R_TRACK_MID = (R_OUTER - 6 + R_POCKET_OUT + 2) / 2
local spiralT = 0 local R_SETTLE = (R_POCKET_IN + R_POCKET_OUT) / 2
local R_SETTLE = (R_POCKET_IN + R_POCKET_OUT) / 2 local ballR = R_TRACK_MID
local ballVr = 0 -- radial velocity (positive = inward)
local phase = "TRACK" -- "TRACK" | "DROP" | "POCKET"
while true do while true do
-- Update rotor -- ── Rotor ─────────────────────────────────────────────────
local rSign = rotorSpeed > 0 and 1 or -1 if rotorSpeed > 0 then
rotorSpeed = rotorSpeed - ROTOR_FRICTION * dt * rSign rotorSpeed = math.max(0, rotorSpeed - ROTOR_FRICTION * dt)
if rSign > 0 and rotorSpeed < 0 then rotorSpeed = 0 end end
if rSign < 0 and rotorSpeed > 0 then rotorSpeed = 0 end
rotorAngle = (rotorAngle + rotorSpeed * dt) % TWO_PI rotorAngle = (rotorAngle + rotorSpeed * dt) % TWO_PI
-- Update ball -- ── Ball angular motion ────────────────────────────────────
local bSign = ballSpeed < 0 and -1 or 1 local friction = (phase == "POCKET") and POCKET_FRICTION or TRACK_FRICTION
ballSpeed = ballSpeed + BALL_FRICTION * dt * bSign -- decelerates toward 0 if ballSpeed < 0 then
if bSign < 0 and ballSpeed > 0 then ballSpeed = 0 end ballSpeed = math.min(0, ballSpeed + friction * dt)
if bSign > 0 and ballSpeed < 0 then ballSpeed = 0 end else
ballSpeed = math.max(0, ballSpeed - friction * dt)
end
ballAngle = (ballAngle + ballSpeed * dt) % TWO_PI ballAngle = (ballAngle + ballSpeed * dt) % TWO_PI
-- Check spiral condition -- ── Phase transitions ──────────────────────────────────────
if not spiraling and math.abs(ballSpeed) <= SPIRAL_SPEED then if phase == "TRACK" and math.abs(ballSpeed) <= DROP_SPEED then
spiraling = true phase = "DROP"
spiralT = 0 ballVr = DROP_VEL
-- Deflector pin: small random angular kick
local kick = (math.random() * 2 - 1) * DEFLECT_MAX
ballAngle = (ballAngle + kick) % TWO_PI
ballSpeed = ballSpeed * 0.6 -- loses some speed on the pin
end end
if spiraling then if phase == "DROP" then
spiralT = spiralT + dt ballR = ballR + ballVr * dt
local t = math.min(spiralT / SPIRAL_TIME, 1) -- Slow the inward rush as ball approaches pocket radius (damped)
-- ease-in spiral (accelerates inward) ballVr = ballVr * (1 - 4 * dt)
ballR = (R_OUTER - 6 + R_POCKET_OUT + 2) / 2 * (1 - t) + R_SETTLE * t if ballR >= R_SETTLE then
ballR = R_SETTLE
ballVr = 0
phase = "POCKET"
end
end end
-- Erase old ball, draw new ball. -- ── Render ────────────────────────────────────────────────
-- Repaint a small circle at the old position with the track colour,
-- then draw the ball at the new position.
px_circle(ballX, ballY, BALL_RADIUS + 3, COL_TRACK) px_circle(ballX, ballY, BALL_RADIUS + 3, COL_TRACK)
local bx, by = ballPosAt(ballR, ballAngle) local bx, by = ballPosAt(ballR, ballAngle)
drawBallAt(bx, by) drawBallAt(bx, by)
gpu.sync() gpu.sync()
sleep(dt) sleep(dt)
-- Stop condition: ball fully spiraled in AND both nearly stopped -- ── Stop condition ─────────────────────────────────────────
if spiraling and spiralT >= SPIRAL_TIME and math.abs(rotorSpeed) < 0.05 then if phase == "POCKET" and ballSpeed == 0 then break end
break
end
-- Safety: if rotor stops and ball already stopped before spiral condition
if rotorSpeed == 0 and ballSpeed == 0 and not spiraling then
break
end
end end
-- Determine winning slot: find slot whose centre angle (in world space) -- Winning pocket: closest slot centre angle to ball's final angle,
-- is closest to ballAngle -- measured in the rotor's frame of reference
local relAngle = (ballAngle - rotorAngle) % TWO_PI
local bestSlot = 1 local bestSlot = 1
local bestDist = math.huge local bestDist = math.huge
for i = 1, NUM_POCKETS do for i = 1, NUM_POCKETS do
local sa = slotAngle(i, rotorAngle) % TWO_PI local sa = ((i - 1) * TWO_PI / NUM_POCKETS) % TWO_PI
local diff = math.abs(sa - ballAngle % TWO_PI) local diff = math.abs(sa - relAngle)
if diff > math.pi then diff = TWO_PI - diff end if diff > math.pi then diff = TWO_PI - diff end
if diff < bestDist then if diff < bestDist then
bestDist = diff bestDist = diff
@@ -399,9 +420,10 @@ local function spin()
end end
end end
-- Snap ball to pocket centre -- Snap ball to pocket centre (world angle)
local snapAngle = slotAngle(bestSlot, rotorAngle) local snapAngle = slotAngle(bestSlot, rotorAngle)
local sx, sy = ballPosAt(R_SETTLE, snapAngle) local sx, sy = ballPosAt(R_SETTLE, snapAngle)
px_circle(ballX, ballY, BALL_RADIUS + 3, COL_TRACK)
drawBallAt(sx, sy) drawBallAt(sx, sy)
gpu.sync() gpu.sync()