MarbleMagnificence

Dev log

The value bracket that was never on screen

Rendering commit 1ec2064

The design doc said the lit wall was brighter than the floor. On screen it never had been.

docs/visual-design.md had a sound rule in it. Because the camera spins through a full circle under an orthographic projection, hue has to carry what you can roll on. The lit wall was supposed to be brighter than the floor, so “bright means walkable” would stop being a shortcut the eye could take.

Those numbers were albedo. They described Palette.swift, not the screen.

Bar chart of four surface colours ordered by luminance: wall dark 0.299, floor 0.470, ramp 0.509, wall lit 0.794, with the floor row outlined to show it falls between the two wall values.
Read out of Palette.swift. The floor lands between the two wall brightnesses on purpose, so no brightness threshold anywhere separates walkable from wall. These are albedo — the gap between them and the screen is what the rest of this entry is about.

What the screenshots said

The sun is steep, so a vertical face catches little of it, and the compensating albedo bump was short by roughly a factor of two. On a device screenshot the lit wall measured 140 against the top’s 191 — the old hierarchy the rule existed to remove.

Two more things came out of the same pass:

  • 63–69% of the playfield was one flat pale grey. Luma 191, a 25% dip where the grid line lands, no other variation. Graph paper over a blockout.
  • Two perpendicular sunlit walls barely broke at the corner — under 10%, against 24% for a grid line. The fill light travels (0.55, -0.38, 0.42) and separates the −x/−z pair cleanly, while the sun has almost no horizontal component and does nothing for +x/+z. All the corner separation was coming from the fill light, on the half of the walls that did not need it.

Three fixes

Walls split by world axis as well as sun side. Six index buffers instead of four, and corners now break at 19.7%. A fourth light lost on maintenance rather than looks: the sun-side classification already existed one line above, so the axis split is three lines and a switch, where a light’s direction and category mask would need keeping in sync with sunDirection forever.

Per-cell value jitter at 0.05, hashed from the world-space UV. Tuned on screenshots: 0.03 vanishes at frame scale, 0.06 reads as a faint second grid. The jitter is per-cell rather than per-block, which does not show — the grid line already draws a boundary at every cell edge, so the shade step lands where the eye expects one.

Tops dropped from 0.68 to 0.47. The floor now sits between the two sunlit wall values, so no brightness threshold anywhere separates walkable from wall. Hue carries it alone, which is what the doc claimed all along. Checked at four camera yaws.

Costs

The marble used to read dark-against-bright one way and bright-against-dark the other; contrast is now similar in both, so the eyes and wisp carry more of the job of finding it. The floor is no longer brighter than the sky, and the skyline reads on saturation instead.

Frame time is unmeasured: six draw calls instead of four, about six ALU added to a modifier covering most of the screen, no numbers. Recorded rather than assumed away.

Every measurement above needed the same frame twice, and the demo driver spins the camera — so the commit also adds -yaw <degrees> to pin it. Without a way to hold the frame still you are comparing memories of renders.