The user reported the lifestone crystal (AlphaBlend part 3 of the
4-part 0x020002EE setup) rendered with one side consistently missing
— looked like "a box with one side missing, you can see into it"
while the whole thing rotated.
Isolated via experiment: routing the crystal through the opaque pass
(no blending, depth write on) produced a whole solid shape. Routing
it through the Phase 9.1 translucent pass (blending on, depth write
off) produced the hole. Mesh build was eliminated as the cause.
Root cause: our translucent pass matched WorldBuilder's state
(SrcAlpha/OneMinusSrcAlpha, DepthMask(false)) but NOT its culling
state. WorldBuilder enables GL_CULL_FACE with per-batch CullMode
(references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/
BaseObjectRenderManager.cs:361-365). Without face culling, the 58
triangles of the closed crystal shell drew in dict-iteration order;
back faces that happened to draw AFTER front faces composited over
them because depth-write-off meant nothing recorded depth within the
translucent set. One face of the crystal ended up permanently
overwritten by its own backside.
Fix: in pass 2 (translucent) enable GL_CULL_FACE with GL_BACK and
CCW front-face winding. Our mesh builder emits pos-side triangles as
(0, i, i+1), which is CCW in standard OpenGL conventions, so GL_BACK
correctly drops the inward-facing side. Back-face culling is disabled
again after pass 2 so subsequent renderers (terrain etc.) see the
default state.
Known limitation: neg-side polys on translucent surfaces — which my
pos/neg mesh-build fix would have emitted with reversed winding —
now get culled in the translucent pass. AC rarely uses double-sided
polygons on translucent surfaces so this is acceptable, and the
opaque pass still renders them correctly. A future Phase 9.3 can
track CullMode per sub-mesh and draw double-sided translucents with
GL_NONE if it turns out to matter.
Also strips the Portal/Lifestone [DIAG] spawn dump that served as
one-shot evidence gathering during the investigation.
194 tests green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>