fix: yield every row in wedge rasteriser to prevent too-long-without-yielding
This commit is contained in:
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user