fix: stop ASCII Doughnut tiling one torus five times across the wall (#182) - #236
Merged
Conversation
…182) THE BUG The shader folded x with mod() into a 3.4-unit cell whenever the canvas was wider than that, so a 5:1 wall got the same doughnut stamped five times at identical phase -- five copies tumbling in lockstep. It read as a rendering fault, not a design, which is why this carried the bug label. The fold is gone. The width is divided into slots and each slot gets its OWN solid: shape, proportions, spin phase, scale, vertical offset and depth, all from a hash of the slot index. Cost is unchanged -- still one distance function per march step -- because a fragment only ever belongs to one slot. What changes is that no two slots look alike or move together. SLOT WIDTH FOLLOWS THE SOLID, NOT THE REVERSE The first attempt picked slots first and fitted solids into them, which produced five small solids adrift in a mostly black frame -- trading the tiling complaint for the emptiness complaint in the same issue. A solid's half-extent is ~1.34 world units against a vertical half-span of 1.55, so HEIGHT caps its size: the frame is 1200px tall however wide the wall is. Sizing the solid to the height first and then laying out as many slots as fit gives about nine across the wall, nearly touching. THE REST OF THE ISSUE - Cells 26px -> 48px, so ~53px at 6000x1200: about 113 columns by 22 rows, close to donut.c's own 80x22. The characters are readable across a room, which is the entire point of ASCII art and was measured as specks before. - Perceptually even ramp with dithering. The donut.c ramp is ordered by eye, not by area, so a linear index terraced the shading into bands. Each glyph's ink coverage is now MEASURED from the atlas the shader samples, and luminance is matched against real coverage; a 4x4 ordered dither picks between the two bracketing glyphs per cell, turning the residual step into noise the eye integrates. - Key plus fill plus specular, and ambient occlusion from the distance function, which is what finally darkens the inside of the hole -- a plain Lambert reports full light there. - Normals by central differences rather than the analytic torus gradient, so the shading is correct for every shape. - Colour carries depth: near solids warm amber, far ones cooler green, instead of one flat red. - Brightness raised for ambient light. Peak luminance under 12% washout measured 0.910 against 0.510 before. A WRONG DIAGNOSIS, RECORDED The rightmost solid looked clipped in a downscaled crop and I attributed it to an off-by-one in the slot index. Measuring ink in the last 40px was identical with and without the clamp (0.01% of pixels, peak 0.298), so there was no clipping and the diagnosis was wrong. The clamp is kept as a one-instruction guard, with the comment saying plainly that it is defensive rather than a fix. Frame cost at 6000x1200: 120.2 fps, vsync-capped, against main's 120.0 -- the extra shading and the AO taps cost nothing measurable. shadercheck ok=150 fail=0; edge density 0.0095 against a 0.0124 baseline, a 23% drop inside the 35% tolerance, so no baseline change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #182.
The bug
The shader folded
xwithmod()into a 3.4-unit cell whenever the canvas was widerthan that, so a 5:1 wall got the same doughnut stamped five times at identical phase
— five copies tumbling in lockstep. That reads as a rendering fault rather than a design,
which is why this carried the
buglabel.The fold is gone. The width is divided into slots and each slot gets its own solid:
shape, proportions, spin phase, scale, vertical offset and depth, all from a hash of the
slot index. Cost is unchanged — still one distance function per march step — because a
fragment only ever belongs to one slot. What changes is that no two slots look alike or
move together.
Slot width follows the solid, not the reverse
Worth recording because my first attempt got it backwards. I picked slot count first and
fitted solids into them, which produced five small solids adrift in a mostly black
frame — trading this issue's tiling complaint for its emptiness complaint.
A solid's half-extent is ~1.34 world units against a vertical half-span of 1.55, so
height caps its size: the frame is 1200px tall however wide the wall is. Sizing the
solid to the height first and then laying out as many slots as fit gives about nine
across the wall, nearly touching.
The rest of the issue
donut.c's own 80×22. The characters are readable across a room — the entire point ofASCII art, and measured as specks before.
donut.cramp is ordered by eye, not byarea, so a linear index terraced the shading into bands. Each glyph's ink coverage is
now measured from the atlas the shader samples, and luminance is matched against
real coverage; a 4×4 ordered dither picks between the two bracketing glyphs per cell.
function — which is what finally darkens the inside of the hole, where a plain Lambert
reports full light.
correct for every shape.
flat red.
A wrong diagnosis, recorded rather than buried
The rightmost solid looked clipped in a downscaled crop and I attributed it to an
off-by-one in the slot index. Measuring ink in the last 40px was identical with and
without the clamp (0.01% of pixels, peak 0.298) — there was no clipping and the
diagnosis was wrong. The clamp stays as a one-instruction guard, and its comment says
plainly that it is defensive rather than a fix.
Acceptance criteria
mod()-tiled repetition; the wide canvas is filled by a compositionVerification
main(76b8259).npm run lintclean.npm test— 407 passed.npm run shadercheck—ok=150 fail=0; edge density 0.0095 against a 0.0124 baseline,a 23% drop inside the 35% tolerance, so no baseline change and the new staleness
report stays empty.
Not verified / honest limits
.and,are inherently dim and sparse;no amount of gain fixes that without destroying the shading the ramp exists to express.
The bright end carries legibility, which is what the 0.910 peak measures.
fill it without either stretching them or shrinking the glyphs, both of which the issue
argues against.
octahedron branch is reachable by hash but was not observed in these two seeds.