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 by1 = math.ceil(CY + ro) + 1
-- Normalise angle range to handle wrap-around
-- We scan row by row and fill runs for speed.
-- Yield every 32 rows so CC:Tweaked doesn't kill us.
local rowCount = 0
-- Scan row by row, yield every pixel row to stay within CC tick budget.
for sy = by0, by1 do
rowCount = rowCount + 1
if rowCount % 32 == 0 then sleep(0) end
sleep(0)
local runStart = nil
for sx = bx0, bx1 do
local dx = sx - CX
@@ -315,83 +311,108 @@ end
-- at settle time (in rotor-relative coordinates)
----------------------------------------------------------------------
local ROTOR_SPEED_MIN = 1.5 -- rad/s initial rotor speed
local ROTOR_SPEED_MAX = 2.5
local BALL_SPEED_MIN = 8.0 -- rad/s initial ball speed (opposite sign)
local BALL_SPEED_MAX = 12.0
local ROTOR_FRICTION = 0.18 -- rad/s^2 rotor deceleration
local BALL_FRICTION = 0.55 -- rad/s^2 ball deceleration
local SPIRAL_SPEED = 1.2 -- rad/s ball speed threshold to begin spiral
local SPIRAL_TIME = 1.4 -- seconds to spiral inward
----------------------------------------------------------------------
-- Spin — fully physics-based
--
-- Phases:
-- 1. TRACK : ball rolls on outer track, decelerating under friction.
-- Rotor also decelerates (much slower — heavy wheel).
-- 2. DROP : when ball tangential speed drops below DROP_SPEED, centripetal
-- 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 dt = FRAME_DELAY
local rotorSpeed = ROTOR_SPEED_MIN + math.random() * (ROTOR_SPEED_MAX - ROTOR_SPEED_MIN)
local ballSpeed = -(BALL_SPEED_MIN + math.random() * (BALL_SPEED_MAX - BALL_SPEED_MIN))
local ballAngle = math.random() * TWO_PI -- random start position
local ballR = (R_OUTER - 6 + R_POCKET_OUT + 2) / 2 -- mid track
-- Initial conditions (only speeds are random — outcome determined by physics)
rotorSpeed = ROTOR_SPEED_MIN + math.random() * (ROTOR_SPEED_MAX - ROTOR_SPEED_MIN)
local ballSpeed = -(BALL_SPEED_MIN + math.random() * (BALL_SPEED_MAX - BALL_SPEED_MIN))
local ballAngle = math.random() * TWO_PI
local spiraling = false
local spiralT = 0
local R_SETTLE = (R_POCKET_IN + R_POCKET_OUT) / 2
local R_TRACK_MID = (R_OUTER - 6 + R_POCKET_OUT + 2) / 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
-- Update rotor
local rSign = rotorSpeed > 0 and 1 or -1
rotorSpeed = rotorSpeed - ROTOR_FRICTION * dt * rSign
if rSign > 0 and rotorSpeed < 0 then rotorSpeed = 0 end
if rSign < 0 and rotorSpeed > 0 then rotorSpeed = 0 end
-- ── Rotor ─────────────────────────────────────────────────
if rotorSpeed > 0 then
rotorSpeed = math.max(0, rotorSpeed - ROTOR_FRICTION * dt)
end
rotorAngle = (rotorAngle + rotorSpeed * dt) % TWO_PI
-- Update ball
local bSign = ballSpeed < 0 and -1 or 1
ballSpeed = ballSpeed + BALL_FRICTION * dt * bSign -- decelerates toward 0
if bSign < 0 and ballSpeed > 0 then ballSpeed = 0 end
if bSign > 0 and ballSpeed < 0 then ballSpeed = 0 end
-- ── Ball angular motion ────────────────────────────────────
local friction = (phase == "POCKET") and POCKET_FRICTION or TRACK_FRICTION
if ballSpeed < 0 then
ballSpeed = math.min(0, ballSpeed + friction * dt)
else
ballSpeed = math.max(0, ballSpeed - friction * dt)
end
ballAngle = (ballAngle + ballSpeed * dt) % TWO_PI
-- Check spiral condition
if not spiraling and math.abs(ballSpeed) <= SPIRAL_SPEED then
spiraling = true
spiralT = 0
-- ── Phase transitions ──────────────────────────────────────
if phase == "TRACK" and math.abs(ballSpeed) <= DROP_SPEED then
phase = "DROP"
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
if spiraling then
spiralT = spiralT + dt
local t = math.min(spiralT / SPIRAL_TIME, 1)
-- ease-in spiral (accelerates inward)
ballR = (R_OUTER - 6 + R_POCKET_OUT + 2) / 2 * (1 - t) + R_SETTLE * t
if phase == "DROP" then
ballR = ballR + ballVr * dt
-- Slow the inward rush as ball approaches pocket radius (damped)
ballVr = ballVr * (1 - 4 * dt)
if ballR >= R_SETTLE then
ballR = R_SETTLE
ballVr = 0
phase = "POCKET"
end
end
-- Erase old ball, draw new ball.
-- Repaint a small circle at the old position with the track colour,
-- then draw the ball at the new position.
-- ── Render ────────────────────────────────────────────────
px_circle(ballX, ballY, BALL_RADIUS + 3, COL_TRACK)
local bx, by = ballPosAt(ballR, ballAngle)
drawBallAt(bx, by)
gpu.sync()
sleep(dt)
-- Stop condition: ball fully spiraled in AND both nearly stopped
if spiraling and spiralT >= SPIRAL_TIME and math.abs(rotorSpeed) < 0.05 then
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
-- ── Stop condition ─────────────────────────────────────────
if phase == "POCKET" and ballSpeed == 0 then break end
end
-- Determine winning slot: find slot whose centre angle (in world space)
-- is closest to ballAngle
-- Winning pocket: closest slot centre angle to ball's final angle,
-- measured in the rotor's frame of reference
local relAngle = (ballAngle - rotorAngle) % TWO_PI
local bestSlot = 1
local bestDist = math.huge
for i = 1, NUM_POCKETS do
local sa = slotAngle(i, rotorAngle) % TWO_PI
local diff = math.abs(sa - ballAngle % TWO_PI)
local sa = ((i - 1) * TWO_PI / NUM_POCKETS) % TWO_PI
local diff = math.abs(sa - relAngle)
if diff > math.pi then diff = TWO_PI - diff end
if diff < bestDist then
bestDist = diff
@@ -399,9 +420,10 @@ local function spin()
end
end
-- Snap ball to pocket centre
-- Snap ball to pocket centre (world angle)
local snapAngle = slotAngle(bestSlot, rotorAngle)
local sx, sy = ballPosAt(R_SETTLE, snapAngle)
px_circle(ballX, ballY, BALL_RADIUS + 3, COL_TRACK)
drawBallAt(sx, sy)
gpu.sync()