feat(core): game engine — swept physics, paddle english, state machine #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/core-engine"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Game core for Breakout. Headless — never touches a canvas, an audio context, or the DOM. Emits the state object the renderer reads and the audio layer drains.
Herald's two non-negotiables, both done and test-proven
1. Paddle-angle control. Outgoing angle is set by where the ball lands on the paddle (±60°), preserving speed rather than flipping
vy. Test pins direction-from-impact AND that an edge hit is genuinely wider than a centre hit — if the mechanic were cosmetic, the test fails.2. No tunnelling at the 700px/s cap. The core sweeps the ball's whole path against each box (Minkowski-expanded AABB + slab raycast) instead of sampling position once per step. Speed-independent by construction, which is what makes the speed curve safe to push. A test fires a ball at the cap straight at a brick and proves it cannot skip.
Also in scope
alpha. Variabledtmakes collision response monitor-dependent and lets one long frame teleport the ball through a wall.ready -> playing -> levelclear / gameover. Pause is a flag, not a phase, so the renderer'sswitch (state.phase)stays total over Herald's four.Three bugs the tests caught that a code-read would not
1.
brick-hitwas silently mislabelled. The event literal carried bothtype: 'brick-hit'andtype: b.type— the later key wins in JS, so every brick-hit arrived as'wall'. Anything filtering forbrick-hit(Shipwright's particles, Lookout's crack) would have matched nothing, and it would have looked like their bug. Brick kind now travels asbrickType; regression test pins it.2. Events would have been destroyed under load. A fixed-timestep loop runs several sub-steps per frame; clearing
eventsinsidestep()wipes everything raised in any sub-step but the last. Bricks would break silently and intermittently — the worst kind of failure. Events now accumulate across the frame and are cleared once, by the loop, afteronRender.3. Carpenter's levels would have silently loaded as the wrong game. His
LEVELSentries are bare 8x12 numeric arrays, not objects with a.gridkey. Looking only for the key foundundefinedand fell back to my placeholder grid — no error, no console output, just the wrong level. The integration test loads his real exported module and asserts the bricks match his ownbuildBrickGriddescriptors.Seams
CONTRACT.mddocuments module system, file ownership, units, state shape, events, and level-data format. I ownsrc/{engine,collision,constants,loop,input}.jsand the singlerequestAnimationFrame;index.htmlis Shipwright's and untouched here.The loader accepts both level alphabets (
0/1/2and./N/H) and bothballSpeedconventions (px/s or multiplier) — Carpenter's format and Herald's brief crossed on the wire mid-jam, and reading a1.0multiplier as px/s yields a ball moving one pixel per second, which presents as a hung game.Not in scope
Rendering (Shipwright), audio (Lookout), level content (Carpenter), deploy (QM). Steel is cut per Herald — the loader still reads
3harmlessly, but nothing emits it.26/26 tests green — this PR's 14, plus Carpenter's, Lookout's audio, and high-scores, all passing together.
3baafdd116tofd24126ee4