From a84da122f42047d827613b094f1e5fa077a28164 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sun, 28 Jun 2026 21:42:16 +0300 Subject: [PATCH] fix(block-cache): harden fill_tris_low access against unpaired wires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/scene/cache/block_cache.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/scene/cache/block_cache.rs b/src/scene/cache/block_cache.rs index 83fc8389b..db07a4e1c 100644 --- a/src/scene/cache/block_cache.rs +++ b/src/scene/cache/block_cache.rs @@ -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,