MarbleMagnificence

Dev log

Gravity was one rounding error from finishing

Levels commit 48934e7

All four levels were rebuilt as downhill mazes, each with a critic assigned to break it. The best thing a critic found was a Float that never quite arrived.

The levels had a pacing problem you could describe in one sentence from the source comments: on a fixed isometric camera, a plaza is dead time. Nothing to decide, nothing to read. The Mountain’s summit was a nineteen-unit disc, its sea road had two thirty-unit courts, and Prism Hall took an estimated ten minutes to walk through.

So all four got rebuilt in one commit: 86 files, about 15,400 lines added. Mostly tests.

Walls the camera can see over

A maze on this camera has one hard limit. It looks down at 35 degrees, so anything taller than two cubes hides the marble at half of all yaws. One cube is a wall the marble cannot climb, since its radius is half that, and the camera sees straight over it. Edges that face a drop use fence1x1, so a marble pressed against the rim stays visible through the rails.

Gameplay screenshot of the bell garden: a maze of low yellow-sided walls on grey tile, a red marble in the middle, a green muncher pipe with googly eyes beside it, a patch of glowing yellow-green acid to the left, and white arrows on the floor marking the route.
The bell garden, simulator capture from this commit. Marble, muncher, acid and the arrows all stay readable over one-cube walls. The main line runs through the muncher’s yard; the loops around it are longer and have nothing on them.

The mazes are authored as text, which means the source is the map:

static let summitCrown = WallMaze(
    name: "summit crown", west: -9, north: 8,
    floor: organicSummitTop, rows: [
    "  P            P  ",  // z=8
    "  PPQ........QPP  ",  // z=7  the stair arrives
    "   PH........HP   ",  // z=6
    "  PH..........HP  ",  // z=5
    " P#...######...#P ",  // z=4
    " #...##....##...# ",  // z=3
    " #..##......##..# ",  // z=2
    " ...#........#..# ",  // z=1  west: the trail's top
    " ...#...SS...#..# ",  // z=0
    " ...#...SS...#..# ",  // z=-1
    " ...#........#..# ",  // z=-2
    " ...##......##..# ",  // z=-3
    " #...##....##...# ",  // z=-4
    " P#...#AAA##...#P ",  // z=-5 the court's only gate
    // ...

That is the statue’s court at the summit, and AAA is the arch you charge the statue through.

One yardstick

“Is this level long enough” used to be a matter of opinion per level. LevelPacing makes it the same arithmetic for all four: critical path at 3.5 units per second, plus 25% for exploring, plus 8 seconds per tricky section, plus the minigame.

Its own doc comment calls it “deliberately crude” and says a device playtest is the real answer. But every level is measured with the same constants, so a level that estimates at two minutes beside three at five is a real signal. Each level’s tests now fail outside the 240–360 second band.

Horizontal bar chart of estimated level duration in seconds: The Mountain 341, The Long Thaw 327, Prism Hall 347, Escher 308, all inside a shaded target band from 240 to 360. A marker shows Prism Hall's old estimate of about 620 with eight halls.
Prism Hall lost two of its eight halls to get here. The figures come from the tests’ own output, re-run at this commit.
levelpathtrickyscriptedestimate
The Mountain603.9493.6341
The Long Thaw652.6738.0327
Prism Hall659.1664.0347
Escher537.0760.0308

Path is in world units, tricky counts sections, and the last two columns are seconds. Scripted means the minigame and, on The Mountain, the tunnel ride too.

A critic who did not build it

Each level had one agent lead its rebuild and a second, independent one try to break the result. Every confirmed defect had to become a physics test that drives the real simulation. The Mountain’s were left failing on purpose first, so each fix had a target.

The critics earned it. On the summit, 17 of 18 rolls along the arrival corridor went over a boing pad and left the crown entirely. Diagonal walls drawn in single cubes meet only at corners, and a marble could squeeze through the joint. A land bridge let you skip the summit, Chomp and the ride, so now there is a moat. On the Long Thaw they turned up a softlock in a cove crack, a death slot in the palace and a skipped milestone.

The float that never landed

The best find was not in any level. It was in GravityFrame, which swings gravity through Escher’s bends, and it was a fixed-fraction approach to a target:

let step = clamp((measured - elapsed) * (1 - exp(-h / 0.08)),
                 0, 2 * h)
var next = elapsed + step
// A fixed-fraction approach never arrives in Float: it stalls a few
// ulps short of `measured`...
if next < measured && measured - next < 1e-4 { next = measured }

Move a fixed fraction of the remaining distance each step and on paper you converge. In a Float you stop a few units in the last place short, and stay there. Before the fix the swing never finished, so isTipping stayed true on a dead flat floor, forever.

Everything that waits for gravity to be settled waited too. After the first bend, every Escher run had no milestones, no checkpoints and no fall damage, with no error anywhere, because nothing had failed. The world looked normal. It was just one rounding error from admitting the turn was over.

The cube that looks the same

A second engine bug hid behind symmetry. A cube looks identical at every rotation, so face culling assumed it could ignore rotation. But the faces are local and the placement turns them: at rotation 2, local +X is drawn at world −X. A row of rotated cubes lost both outer ends and kept its buried seams.

Prism Hall’s Skeeball cabinet has 1,256 rotated cubes, and its walls were ones a marble could drive straight through. It broke silently, for exactly the reason it looked safe to skip.

Two gameplay screenshots side by side. Left: The Mountain's ruin arena with low walls, a stone column, a red marble and a black steelie. Right: Prism Hall's laser plaza, a blue floor with purple walls, two raised blocks, a laser post and a red marble.
The walled ruin arena on The Mountain, and the laser plaza that opens Prism Hall’s stepped cascade. Simulator captures from this commit.

Two callbacks

The level editor saves a diff, and a diff needs something to apply to. About 1,600 lines of mountain.edits.json targeted a layout that no longer existed, so the rebuild emptied the file. The design held; hand edits just do not outlive the ground they were placed on.

The two-player entry shares one scene between both players and depends on nothing in the world being able to change. Two days before this commit, b455537 added split-screen racing on any release level, where everything can change, so a race gives each player their own simulation and their own renderer, and the rival is drawn as just a marble. Twice the level geometry in memory. Share the scene and the first gravity flip on Escher would turn the other player’s screen.

Unmeasured

The check script was green on 26 October: 791 tests in 83 suites, plus the XCTest bundle, an iOS simulator build and a tvOS typecheck.

As of that run, nobody had played any of this on a device. Every duration above is an estimate from a formula that says it is crude. The critics proved the routes can be rolled, and that says nothing about whether they are fun to roll. That is the next job.