Summary
Under LÖVE 12 the sun pass stores a broken shadow map, producing large hard-edged dark regions detached from their casters. On iOS this is very visible: giant diagonal slabs across the overworld, and a lit parallelogram inside near-darkness in interiors.
This is not iOS-specific and not Metal-specific. It is a LÖVE 11 → 12 regression that iOS hit first because the iOS shell is the only place gen1recomp currently ships LÖVE 12. Desktop ships 11.5, which is why it never reproduced there — and why it is latent for desktop and Android the day those move to 12.
Controlled matrix
Same gamedir, same 430x932 portrait window, backend forced via t.renderers, everything else held still:
| LÖVE |
backend |
ShadowMap |
map coverage |
shadow placement |
| 11.5 |
OpenGL |
stock 1.5.1 |
full |
correct |
| 12 nightly |
OpenGL |
stock 1.5.1 |
sparse |
wrong |
| 12 nightly |
Metal |
stock 1.5.1 |
sparse |
wrong |
| 12 nightly |
Metal |
z remap only |
full |
wrong |
| 12 nightly |
Metal |
y probe only |
sparse |
correct |
| 12 nightly |
OpenGL + Metal |
both |
full |
correct |
The LÖVE 11.5 control ran with the same dpi-inflated canvases (px 4096 for a res 2048 rung) and stayed clean, so canvas dpiscale is not the cause of either artifact. It remains a real memory cost — see the PixelCanvas work in #48.
Cause
The sun pass bypasses transform_projection and builds its own clip-space matrices to LÖVE 11 conventions:
Mat4.ortho emits clip z in [-1, 1] — its own comment says "GL clip space (z in [-1, 1])"
fit() applies Mat4.scale(1, -1, 1) to compensate LÖVE 11's canvas-bound y-flip, commented "canvas coordinates run Y DOWN"
LÖVE 12 changed both, on every backend. love2d/love@7bfbd647 ("Make shader clip space consistent across APIs and projection matrices") states that projection matrices "no longer need to be flipped when rendering to a canvas versus the main screen", and closes with "custom projection matrices might need to be altered to account for the more consistent range".
The two symptoms map onto the two conventions, and the isolation runs above separate them cleanly:
- Coverage: under LÖVE 12 the map comes back with the near half of the light frustum missing — large regions holding only the clear value, cut along the straight lines where the z = 0 plane crosses the terrain mesh, survivors packing a high byte >= 128. Remapping the ortho's clip z onto [0, 1] restores it.
- Placement: the stale y-flip means the map is stored one way and read by uv math calibrated for the other, so every lookup samples it vertically mirrored — shadows of the far half stamped across the near field. Measuring the storage orientation restores it.
The camera pass never shows either wound: its projection is perspective, so everything visible sits past the z crossover at twice the near plane (near = dist * 0.05). The ortho here is linear, so it loses exactly half.
Environment
- iPhone 15 Pro Max / iOS 27.0, gen1recomp v0.1.56 and v0.1.59 official iOS IPA (LÖVE 12 / Metal shell)
- macOS, LÖVE 12.0 nightly (
love-macos CI artifact), both Metal and OpenGL
- macOS, LÖVE 11.5 (gen1recomp desktop build) as the control
- DRAMATIC_SHAPE 1.5.1, VOXEL 35, DAYTIME DAY, Viridian City
Also observed
The water shader fails to compile under LÖVE 12 + Metal ('mediump' : overloaded functions must have the same parameter precision qualifiers, then missing #endif), so lakes draw flat. Absent under LÖVE 11.5 and under LÖVE 12 + OpenGL. Filing separately.
Fix incoming as a PR.
Summary
Under LÖVE 12 the sun pass stores a broken shadow map, producing large hard-edged dark regions detached from their casters. On iOS this is very visible: giant diagonal slabs across the overworld, and a lit parallelogram inside near-darkness in interiors.
This is not iOS-specific and not Metal-specific. It is a LÖVE 11 → 12 regression that iOS hit first because the iOS shell is the only place gen1recomp currently ships LÖVE 12. Desktop ships 11.5, which is why it never reproduced there — and why it is latent for desktop and Android the day those move to 12.
Controlled matrix
Same gamedir, same 430x932 portrait window, backend forced via
t.renderers, everything else held still:The LÖVE 11.5 control ran with the same dpi-inflated canvases (
px 4096for ares 2048rung) and stayed clean, so canvas dpiscale is not the cause of either artifact. It remains a real memory cost — see the PixelCanvas work in #48.Cause
The sun pass bypasses
transform_projectionand builds its own clip-space matrices to LÖVE 11 conventions:Mat4.orthoemits clip z in [-1, 1] — its own comment says "GL clip space (z in [-1, 1])"fit()appliesMat4.scale(1, -1, 1)to compensate LÖVE 11's canvas-bound y-flip, commented "canvas coordinates run Y DOWN"LÖVE 12 changed both, on every backend. love2d/love@7bfbd647 ("Make shader clip space consistent across APIs and projection matrices") states that projection matrices "no longer need to be flipped when rendering to a canvas versus the main screen", and closes with "custom projection matrices might need to be altered to account for the more consistent range".
The two symptoms map onto the two conventions, and the isolation runs above separate them cleanly:
The camera pass never shows either wound: its projection is perspective, so everything visible sits past the z crossover at twice the near plane (
near = dist * 0.05). The ortho here is linear, so it loses exactly half.Environment
love-macosCI artifact), both Metal and OpenGLAlso observed
The water shader fails to compile under LÖVE 12 + Metal (
'mediump' : overloaded functions must have the same parameter precision qualifiers, thenmissing #endif), so lakes draw flat. Absent under LÖVE 11.5 and under LÖVE 12 + OpenGL. Filing separately.Fix incoming as a PR.