fix(block-cache): harden fill_tris_low access against unpaired wires

emit_wire reconstructed fill verts via a raw `fill_tris_low[idx]` index,
guarded only by a debug_assert. Slice bounds checks are not gated by
debug-assertions, so a Leader with a filled arrowhead nested in a block
(populated fill_tris, empty fill_tris_low via the low-level tessellate
early-return) panicked in release as well as debug.

Use the panic-safe `.get(idx).copied().unwrap_or([0.0; 3])` — the same
"empty = all-zero low half" contract the other fill consumers
(face3d_gpu, xclip) already honour. The debug_assert stays as a tripwire.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-28 21:42:16 +03:00
1 parent d2cd728313
commit a84da122f4
1 file changed
+7 -1
+7 -1
View File
@@ -1413,7 +1413,13 @@ fn emit_wire(
}
debug_assert_eq!(lw.fill_tris.len(), lw.fill_tris_low.len());
for (idx, p) in lw.fill_tris.iter().enumerate() {
let pl = lw.fill_tris_low[idx];
// Empty/short fill_tris_low means "no low half" (all-zero), per the
// WireModel contract — same panic-safe access the other fill consumers
// use (face3d_gpu, xclip). A Leader with a filled arrowhead nested in a
// block reaches here with populated fill_tris but empty fill_tris_low;
// a raw `[idx]` would panic in release (bounds checks are not gated by
// debug-assertions). The debug_assert above stays as a tripwire.
let pl = lw.fill_tris_low.get(idx).copied().unwrap_or([0.0; 3]);
let v = accum_xform.apply(Vector3::new(
p[0] as f64 + pl[0] as f64,
p[1] as f64 + pl[1] as f64,