This commit is contained in:
2026-05-05 20:38:40 -04:00
parent 76ee7306bb
commit fd4096b98a
2 changed files with 158 additions and 145 deletions

View File

@@ -76,24 +76,23 @@ local COL_BALL_SHD = 0x444444
local BALL_RADIUS = 8 -- px (screen drawing radius)
local BALL_WORLD_R = 5 -- physics sphere radius in world units
-- Initial tangential speed (world units / s)
local BALL_SPEED_MIN = 700
local BALL_SPEED_MAX = 1000
-- Initial tangential speed (world units / s) — very fast launch
local BALL_SPEED_MIN = 1800
local BALL_SPEED_MAX = 2800
-- Gravity (world units / s²)
local GRAVITY = 1800
-- Bowl cone half-angle from horizontal (radians) — steeper = faster slide
-- Bowl cone half-angle from horizontal (radians)
local BOWL_SLOPE = math.pi / 9 -- 20 degrees
-- Pocket well is deeper — steeper slope
local POCKET_SLOPE = math.pi / 5 -- 36 degrees
-- Restitution on bowl surface normal bounce
-- Restitution on wall bounce
local RESTITUTION_WALL = 0.82
local RESTITUTION_POCKET = 0.68
-- Rolling friction: velocity multiplier per second on the bowl surface
local FRICTION_ROLL = 0.988 -- per frame on track
local FRICTION_POCKET = 0.970 -- per frame in pocket
-- Ball drops from track into pocket ring when its radial position
-- crosses the inner deflector radius
local BOUNCE_KICK_MAX = 0.08 -- rad random angular kick on rim bounce
-- Rolling friction: multiplier per frame — strong enough to stop cleanly
local FRICTION_ROLL = 0.975 -- per frame (~0.43/s at 33fps — decays in ~5s)
local FRICTION_POCKET = 0.955 -- pocket damps faster
-- Stop when horizontal speed drops below this — no wiggle
local STOP_SPEED = 18 -- px/s
local BOUNCE_KICK_MAX = 0.08 -- rad
----------------------------------------------------------------------
-- GPU / pixel primitives
@@ -242,74 +241,62 @@ end
local ballX, ballY = 0, 0
-- Repair the wheel pixels inside a circle of radius r centred on (cx,cy).
-- Re-rasterises all wedges clipped to that bounding box, then chrome on top.
-- This replaces the old flat-colour eraseBall which left coloured smears.
-- Single pass: for every pixel in the bounding box, compute the correct
-- wheel colour and paint it. No flat-colour approximation.
local function repairWheelPatch(cx, cy, r)
cx = math.floor(cx); cy = math.floor(cy)
-- Bounding box for the patch
local px0 = cx - r; local px1 = cx + r
local py0 = cy - r; local py1 = cy + r
-- For each wedge, re-rasterise only within this bounding box
-- Pre-compute per-wedge geometry (angle extents) once
local halfArc = math.pi / NUM_POCKETS
local wedges = {}
for slotIdx = 1, NUM_POCKETS do
local halfArc = math.pi / NUM_POCKETS
local midAngle = FIXED_ROTOR + (slotIdx - 1) * TWO_PI / NUM_POCKETS
local a0 = midAngle - halfArc
local a1 = midAngle + halfArc
local arc = (a1 - a0) % TWO_PI
local num = WHEEL_ORDER[slotIdx]
local col = pocketColor(num)
local ri, ro = R_POCKET_IN, R_POCKET_OUT
for sy = py0, py1 do
local runStart = nil
for sx = px0, px1 do
local dx = sx - CX; local dy = sy - CY
local dist = math.sqrt(dx*dx + dy*dy)
local inRing = dist >= ri and dist <= ro
local rel = (math.atan2(dy, dx) - a0) % TWO_PI
local inWedge = rel <= arc
if inRing and inWedge then
if not runStart then runStart = sx end
else
if runStart then
px_rect(runStart, sy, sx - runStart, 1, col)
runStart = nil
end
end
end
if runStart then
px_rect(runStart, sy, px1 - runStart + 1, 1, col)
end
end
local mid = FIXED_ROTOR + (slotIdx - 1) * TWO_PI / NUM_POCKETS
wedges[slotIdx] = {
a0 = mid - halfArc,
arc = TWO_PI / NUM_POCKETS,
col = pocketColor(WHEEL_ORDER[slotIdx]),
}
end
-- Repaint chrome rings and background that overlap the patch,
-- in inside-out priority order using elseif so each pixel is set once.
for sy = py0, py1 do
for sx = px0, px1 do
local dx = sx - CX; local dy = sy - CY
local d = math.sqrt(dx*dx + dy*dy)
local dx = sx - CX
local dy = sy - CY
local d = math.sqrt(dx*dx + dy*dy)
local col
-- Determine correct colour for this pixel, inside-out priority
if d <= R_HUB - 4 then
px_rect(sx, sy, 1, 1, COL_HUB)
col = COL_HUB
elseif d <= R_HUB then
px_rect(sx, sy, 1, 1, COL_HUB_RING)
col = COL_HUB_RING
elseif d <= R_POCKET_IN - 2 then
col = COL_HUB -- gap between hub ring and inner rim
elseif d <= R_POCKET_IN then
-- gap between hub ring and inner rim — plain hub bg
px_rect(sx, sy, 1, 1, COL_HUB)
elseif d <= R_POCKET_IN + 2 then
px_rect(sx, sy, 1, 1, COL_RIM)
col = COL_RIM
elseif d <= R_POCKET_OUT then
-- inside pocket ring — wedge already painted above
-- Pocket ring: find which wedge this pixel belongs to
local angle = math.atan2(dy, dx)
for _, w in ipairs(wedges) do
local rel = (angle - w.a0) % TWO_PI
if rel <= w.arc then
col = w.col
break
end
end
if not col then col = COL_HUB end -- fallback
elseif d <= R_POCKET_OUT + 2 then
px_rect(sx, sy, 1, 1, COL_RIM)
col = COL_RIM
elseif d <= R_OUTER - 6 then
px_rect(sx, sy, 1, 1, COL_TRACK)
col = COL_TRACK
elseif d <= R_OUTER then
px_rect(sx, sy, 1, 1, COL_RIM)
col = COL_RIM
end
-- d > R_OUTER: outside wheel, leave as-is (ball never goes there)
if col then px_rect(sx, sy, 1, 1, col) end
end
end
end
@@ -545,7 +532,7 @@ local function spin()
-- Settled when horizontal speed is very low
local hspd = math.sqrt(vx*vx + vy*vy)
if hspd < 5 then break end
if hspd < STOP_SPEED then break end
end
eraseBall3()