helix: 20260629b re-bake (vessel diameter + teeth→skeleton) + living-anatomy browser (search / x-ray / LOD)#66
Conversation
… + /cesium scope Adds a viewer-side per-concept extent clamp to /helix: robust median centre + p95 radius × margin, dropping triangles that touch an out-of-bounds vertex. Verified on the real 4.28M-vert bake — conservative: 154 stray tris dropped (0.00%), all genuine decimation orphans in eye parts (cornea/lens/sclera), worst 8× p95. Nothing aorta- or foot-related (those vessels sit within their concept extents). NOTE for the record: the reported "aorta under the feet" is NOT a misplaced aorta — all 7 aorta concepts are anatomically correct (torso, z+0.19..+0.61, zero feet verts). The red mass at the soles is the foot's own vasculature (plantar/dorsal digital veins + arteries), vessel-red like the aorta. Both that and the "too-thick aorta" share one cause: /helix draws vessels opaque at full thickness while /body draws them in a translucent pass. The true fix (translucent layer-5 pass) is deferred pending a design call. Also lands the /cesium HHTL-splat activation scope (claude-notes/plans), with the J1 result recorded: two place-relative Signed360 carry a full 3-DOF rotation (Σ rel-err 0.34% mean), confirming "2×2 DOF → 3D/4D". Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhpwkHGgia2TuDFvdnuQdE
…y, not discard) The Gouraud change cut per-fragment lighting but /helix was still geometry- bound: it drew all 6.8M triangles DoubleSide every frame, and disabled layers (skin+muscle, off by default) were still fully rasterised then thrown away by a fragment discard (which also kills early-Z). That is the freeze. Now visible layers are excluded as GEOMETRY: the index is rebuilt from the enabled layers (on toggle) and the fragment shader drops the discard, so the GPU never touches hidden triangles and early-Z survives. Material switches to FrontSide (backface cull). Measured on the real mesh: default view (skin+muscle off) draws 3.6M tris instead of 6.8M, and FrontSide removes DoubleSide's doubling → 13.6M → 3.6M rasterised triangles (~3.8×), before early-Z gains. tsc + vite build clean. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhpwkHGgia2TuDFvdnuQdE
… bends) The vessel slicer-fill swept a solid core along a single straight PCA axis per component, with each ring's radius = MEDIAN perpendicular distance clamped to a single global RMAX (~34 mm, aorta scale). At a strong bend the straight axis cannot follow the tube: both arms fall in one axial bin, the bin centroid lands between them (outside the limb), and the median perp-distance balloons — and the global RMAX let a finger artery swell to aorta caliber. Those are the "stray fat children branching off outside the limbs". Fix = per-vessel diameter boundary: - per-bin radius now reads a LOW percentile (PCTL=0.30, near-wall) instead of the median, so a bend bin sharing two arms no longer reports ~half-the-gap. - every ring is clamped to this vessel's OWN caliber (robust median of bin radii) × CAP=2.0, then RMAX. A capillary stays a capillary through its bends; the aorta still reaches RMAX. Validated on a synthetic 1 mm L-bent vessel: max ring 2.6× true radius (was up to 20× under the global cap). Bake-pipeline change — takes effect on next body re-bake. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhpwkHGgia2TuDFvdnuQdE
Teeth (all 28 + both gingivae) are in the dataset, but tissue_of had no dental category and teeth are not is_a bone in FMA (dentin/enamel, not skeletal element), so every tooth fell through to the "flesh" default → layer 1 (skin). With skin off (e.g. the skeleton view) the jaw bones showed but the teeth vanished — a toothless skull. Add tooth/molar/incisor/canine/premolar/cuspid to the bone name-matcher so teeth bucket with the skeleton (layer 4); gingiva stays flesh. Bake-pipeline change — takes effect on the body re-bake (in progress). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhpwkHGgia2TuDFvdnuQdE
Point the Dockerfile and the helix manifest at the freshly published
20260629b artifacts on the fma-body-soa-v3-v1 release:
- /body → body.20260629b.soa.gz (served same-origin AS body.soa.gz)
- /helix → body.20260629b.v6helix.soa.gz (BSO2 ver 6: F16 pos + Signed360
NORMAL column + HXFL floor trailer)
Both come from the same soa_v2 source in one bake run, so the 1658-concept
ordering is identical across the two — which is what lets /helix reuse the
server's per-concept body.blocks (also 1658) for its LOD cull. The re-bake
carries the two bake-pipeline fixes already committed (2a7ac4a per-vessel
slicer-fill diameter boundary, db7bce8 teeth→skeleton). Old artifacts stay
in the release untouched; a new bake is swapped in by bumping the manifest.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhpwkHGgia2TuDFvdnuQdE
…rver LOD The /helix viewer had only the layer toggles. Add the rest of the cockpit so it reads as a living, browsable database rather than a static mesh: - Left-side browser: the 1658 FMA structures grouped by layer (skin / muscle / organ / skeleton / vessel / nervous / connective / other), each group expandable (▾/▸), with a live per-group count. Concept names + display centroids are parsed from the BSO2 labels_json + centroid columns at decode. - Search: filters every group as you type; matching groups auto-expand. Click a result to glide the orbit camera + dolly onto that structure's centroid (revealing its layer if it was hidden). - x-ray: a uAlpha uniform makes the whole body translucent so deeper structures show through (depthWrite off; the fragment shader stays trivial). - LOD: opt-in server HHTL depth-cascade. Posts the live camera to the existing /api/body/lod endpoint (same protocol /body uses) and folds the per-concept reject byte into the SAME geometry index rebuild as the layer toggles — not a fragment discard — so early-Z survives and the GPU draws strictly fewer triangles when zoomed in. Absent endpoint (old deploy) silently keeps the full render; a near-total cull is treated as a camera-mapping miss and shows all. All additive: the decode/normal/Gouraud hot path is unchanged; the LOD cull and the layer mask share one linear index pass, so the mobile geometry-exclusion lever still holds. tsc + vite build clean. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhpwkHGgia2TuDFvdnuQdE
📝 WalkthroughWalkthroughRe-bakes body/helix SoA assets to version ChangesBake Pipeline Fixes and Asset Re-versioning
BodyHelix Viewer Decode and Render Upgrade
Cesium HHTL Splat Viewer Planning Doc
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
claude-notes/plans/2026-06-29-cesium-hhtl-splat-viewer.md (2)
74-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueClarify WebGL2 vs WebGPU target priority in P4.
P4 states "WebGL2/WebGPU" as the rasterization target. These have substantially different 3DGS performance characteristics — WebGPU's compute shaders enable proper tile-based sorting and splatting, while WebGL2 requires slower fragment-shader workarounds. Given that
/cesiumis explicitly the path where "ndarray→wasm pays off because 3DGS rasterization is heavy SoA compute," specifying the primary target (likely WebGPU with WebGL2 fallback) would help scope the wasm boundary and J3 precision gate correctly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@claude-notes/plans/2026-06-29-cesium-hhtl-splat-viewer.md` around lines 74 - 79, Clarify the rasterization target in the P4 section by explicitly naming the primary path and fallback, since “WebGL2/WebGPU” is ambiguous. Update the `/cesium` description to state whether `splat3d` is intended for WebGPU-first with WebGL2 fallback (or vice versa), and align the wasm scope wording so `splat3d`, `sse`, and the “ndarray→wasm” boundary reflect that chosen priority.
98-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd explicit J1 tolerance thresholds and probe place-anchor specification.
The J1 probe description is excellent in scope, but two details would make it immediately actionable:
- Tolerance thresholds: The normal-path benchmark cites "mean 0.26°" — the quaternion path should specify both a per-gaussian rotation error bound (e.g., mean/max degrees) and a covariance Σ relative error bound (e.g., Frobenius norm < 1e-3) for the "green" verdict.
- Place anchor:
start = CurveRuler::from_hhtl(tile_path, depth)requires a HHTL context. For the standalone probe, specify whetherstartis derived from a synthetic fixed tile (e.g.,HEEL/0/0/0/0), the centroid of the.plybounding box, or another deterministic reference — otherwise J1 results may vary by place and fail reproducibility.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@claude-notes/plans/2026-06-29-cesium-hhtl-splat-viewer.md` around lines 98 - 104, The J1 probe description is missing the exact pass/fail criteria and the deterministic place anchor needed to make it reproducible. Update the J1 probe spec to explicitly define the “green” thresholds for per-gaussian rotation error and covariance Σ relative error, and state the anchor source used to derive the place-relative `Signed360` reference for the standalone Rust probe. Reference the J1 probe, `Signed360`, `GaussianBatch`, and `CurveRuler::from_hhtl` when clarifying how the real `.ply` input is normalized and evaluated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@claude-notes/plans/2026-06-29-cesium-hhtl-splat-viewer.md`:
- Around line 12-23: The dimensionality description for Signed360 is overstated
and should match the actual codec used by BodyHelix.tsx. Update the prose in
this note so it clearly says the full 6-byte Signed360 payload (the rim endpoint
pair plus polar and azimuth fields) carries the 3-DOF orientation, and avoid
implying that the two Fisher-Z rim values alone provide the full
quaternion-equivalent information. Refer to Signed360, BodyHelix.tsx, and the
“rimu/end, polar, az16” decode terms to keep the wording aligned with the
implementation.
In `@cockpit/src/BodyHelix.tsx`:
- Around line 491-500: The layer header and concept row click targets in
BodyHelix are plain clickable divs, so they are not keyboard-operable. Update
the expandable header around setOpen/expanded and the concept row that calls
focusOn(c) to use semantic button elements where possible, or add tabIndex,
appropriate role, and keyboard handlers for Enter/Space so keyboard users can
expand groups and focus rows.
- Around line 179-196: The outlier pass in BodyHelix is doing heavy decode-time
allocation and sorting by building per-concept JS arrays, mapping positions
repeatedly, and sorting full distance lists. Refactor this block to use a
typed-array bucket pass and a cheaper percentile strategy (sampling or linear
selection) instead of allocating and sorting multiple arrays per concept. Keep
the logic centered around the outlier clustering section that uses byC, median,
dist, and p95, and consider applying the clamp only to concepts that actually
need it.
- Around line 326-333: Reset the LOD state back to full visibility when the LOD
fetch fails or returns unusable data. In the promise chain inside BodyHelix’s
LOD update logic, make the failure path and any incomplete action-set handling
restore lodAction to visible for all concepts and mark lodDirty/dirty.current so
the render refreshes, instead of only setting lodFail. Also guard the success
path in the .then handler so if j.actions is missing/short or the response is
otherwise incomplete, it falls back to full visibility rather than leaving stale
hidden entries.
In `@crates/osint-bake/tools/bake_torso_splat.py`:
- Around line 88-92: The dental substring list in bake_torso_splat.py is too
broad and can misclassify non-tooth concepts because tissue_of() uses raw
substring matching later on. Tighten the fix by limiting tooth bucketing to
safer, tooth-specific identifiers in the mapping logic around tissue_of() rather
than broad terms like incisor/canine/premolar, and ensure gingiva-related names
still resolve to flesh instead of bone.
In `@crates/osint-bake/tools/fill_body_soa.py`:
- Around line 38-43: The comments near CAP and PCTL use the Unicode
multiplication sign, which Ruff flags as ambiguous text. Update the affected
inline comments in fill_body_soa.py (including the later matching comment around
the same constant block) to use plain ASCII text instead of ×, keeping the
wording otherwise unchanged. Use the CAP and PCTL constant definitions as the
anchor points when locating the comments.
---
Nitpick comments:
In `@claude-notes/plans/2026-06-29-cesium-hhtl-splat-viewer.md`:
- Around line 74-79: Clarify the rasterization target in the P4 section by
explicitly naming the primary path and fallback, since “WebGL2/WebGPU” is
ambiguous. Update the `/cesium` description to state whether `splat3d` is
intended for WebGPU-first with WebGL2 fallback (or vice versa), and align the
wasm scope wording so `splat3d`, `sse`, and the “ndarray→wasm” boundary reflect
that chosen priority.
- Around line 98-104: The J1 probe description is missing the exact pass/fail
criteria and the deterministic place anchor needed to make it reproducible.
Update the J1 probe spec to explicitly define the “green” thresholds for
per-gaussian rotation error and covariance Σ relative error, and state the
anchor source used to derive the place-relative `Signed360` reference for the
standalone Rust probe. Reference the J1 probe, `Signed360`, `GaussianBatch`, and
`CurveRuler::from_hhtl` when clarifying how the real `.ply` input is normalized
and evaluated.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2d4fe08d-56e0-4080-ba6f-4a9689645d49
📒 Files selected for processing (6)
Dockerfileclaude-notes/plans/2026-06-29-cesium-hhtl-splat-viewer.mdcockpit/public/body.manifest.jsoncockpit/src/BodyHelix.tsxcrates/osint-bake/tools/bake_torso_splat.pycrates/osint-bake/tools/fill_body_soa.py
| helix `Signed360`'s rim is the **`(start, end)` endpoint pair**, each a Fisher-Z | ||
| point on the φ-spiral. Two sphere points = a great-circle **arc** = a **rotation** | ||
| (3-DOF, a 4D unit quaternion's worth). The crate frames it exactly so: `start` = | ||
| "where the arc begins" (the HHTL place anchor, `CurveRuler::from_hhtl(path,depth)`), | ||
| `end` = the residue. So the 2 Fisher-Z values do **3D/4D work** — a rotation | ||
| **relative to the HHTL tile frame**. | ||
|
|
||
| That is precisely a 3DGS gaussian's orientation: `GaussianBatch.quat_wxyz`, | ||
| consumed by `Spd3::from_scale_quat` → Σ = R·diag(s²)·Rᵀ. So **helix is the splat | ||
| orientation carrier** (place-relative), not sidelined by the quaternion | ||
| requirement — it satisfies it. The shading normal `/helix` decodes is just one | ||
| 2-DOF projection of this fuller frame. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Clarify the Signed360 encoding dimensionality claim.
The document states "2 Fisher-Z values do 3D/4D work" and "3-DOF, a 4D unit quaternion's worth." However, the downstream BodyHelix.tsx decode (lines 140-180) shows Signed360 as a 6-byte encoding: end (rim endpoint), polar (signed polar lift), and az16 (golden azimuth). Two Fisher-Z sphere points define a great-circle arc (2-DOF), but the full normal decode requires the additional polar+azimuth bytes to resolve hemisphere and direction. For a full 3-DOF quaternion, the encoding must preserve all three degrees of freedom — the J1 gate will validate this, but the prose understates the actual 6-byte payload and risks confusing readers about whether the "2 Fisher-Z" rim alone suffices. Consider rephrasing to "the 6-byte Signed360 (rim pair + polar/azimuth) carries 3-DOF orientation" to match the real codec.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@claude-notes/plans/2026-06-29-cesium-hhtl-splat-viewer.md` around lines 12 -
23, The dimensionality description for Signed360 is overstated and should match
the actual codec used by BodyHelix.tsx. Update the prose in this note so it
clearly says the full 6-byte Signed360 payload (the rim endpoint pair plus polar
and azimuth fields) carries the 3-DOF orientation, and avoid implying that the
two Fisher-Z rim values alone provide the full quaternion-equivalent
information. Refer to Signed360, BodyHelix.tsx, and the “rimu/end, polar, az16”
decode terms to keep the wording aligned with the implementation.
| const byC: number[][] = Array.from({ length: nC }, () => []); | ||
| for (let i = 0; i < nV; i++) { const c = rowArr[i]; if (c < nC) byC[c].push(i); } | ||
| const median = (a: number[]) => { const s = a.slice().sort((x, y) => x - y); return s[s.length >> 1]; }; | ||
| const outlier = new Uint8Array(nV); | ||
| let nOut = 0, worst = 0; | ||
| for (let c = 0; c < nC; c++) { | ||
| const vs = byC[c]; | ||
| if (vs.length < 8) continue; | ||
| const cx = median(vs.map((i) => positions[i * 3])); | ||
| const cy = median(vs.map((i) => positions[i * 3 + 1])); | ||
| const cz = median(vs.map((i) => positions[i * 3 + 2])); | ||
| const dist = vs.map((i) => Math.hypot(positions[i * 3] - cx, positions[i * 3 + 1] - cy, positions[i * 3 + 2] - cz)); | ||
| const ds = dist.slice().sort((a, b) => a - b); | ||
| const p95 = ds[Math.min(ds.length - 1, Math.floor(ds.length * 0.95))]; | ||
| const thr = Math.max(p95 * 1.8, 1e-3); // generous margin → only true far strays drop | ||
| for (let k = 0; k < vs.length; k++) { | ||
| if (dist[k] > thr) { outlier[vs[k]] = 1; nOut++; worst = Math.max(worst, dist[k] / Math.max(p95, 1e-4)); } | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift
Reduce decode-time allocation and sorting in the outlier pass.
This groups every vertex into JS arrays, then allocates and sorts multiple per-concept arrays. On the multi-million-triangle helix asset, that can reintroduce load-time freezes before rendering starts. Consider a typed-array bucket pass plus sampled/linear percentile selection, or limit the clamp to concepts/classes that need it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cockpit/src/BodyHelix.tsx` around lines 179 - 196, The outlier pass in
BodyHelix is doing heavy decode-time allocation and sorting by building
per-concept JS arrays, mapping positions repeatedly, and sorting full distance
lists. Refactor this block to use a typed-array bucket pass and a cheaper
percentile strategy (sampling or linear selection) instead of allocating and
sorting multiple arrays per concept. Keep the logic centered around the outlier
clustering section that uses byC, median, dist, and p95, and consider applying
the clamp only to concepts that actually need it.
| .then((j: { actions: number[]; n_concepts?: number; tally?: number[] }) => { | ||
| const a = j.actions; | ||
| const visible = (j.n_concepts ?? a.length) - (j.tally?.[0] ?? 0); | ||
| const degenerate = visible <= Math.max(1, a.length * 0.02); // cascade culled ~all ⇒ camera map suspect → show all | ||
| for (let i = 0; i < d.concepts && i < a.length; i++) lodAction[i] = degenerate ? 255 : a[i]; | ||
| lodDirty = true; dirty.current = true; | ||
| }) | ||
| .catch(() => { lodFail = true; }) // endpoint absent (old deploy) → keep full render |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Reset LOD gates when the endpoint fails or returns an incomplete action set.
After any successful LOD cull, a later error only sets lodFail; it does not restore lodAction to full visibility, so previously hidden concepts can stay hidden indefinitely while LOD appears enabled.
🐛 Proposed fix
- .then((j: { actions: number[]; n_concepts?: number; tally?: number[] }) => {
- const a = j.actions;
+ .then((j: { actions: number[]; n_concepts?: number; tally?: number[] }) => {
+ const a = Array.isArray(j.actions) ? j.actions : [];
+ if (a.length < d.concepts) {
+ lodAction.fill(255);
+ lodDirty = true; dirty.current = true;
+ return;
+ }
const visible = (j.n_concepts ?? a.length) - (j.tally?.[0] ?? 0);
const degenerate = visible <= Math.max(1, a.length * 0.02); // cascade culled ~all ⇒ camera map suspect → show all
for (let i = 0; i < d.concepts && i < a.length; i++) lodAction[i] = degenerate ? 255 : a[i];
lodDirty = true; dirty.current = true;
})
- .catch(() => { lodFail = true; }) // endpoint absent (old deploy) → keep full render
+ .catch(() => {
+ lodFail = true;
+ lodAction.fill(255);
+ lodDirty = true; dirty.current = true;
+ }) // endpoint absent (old deploy) → keep full render📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .then((j: { actions: number[]; n_concepts?: number; tally?: number[] }) => { | |
| const a = j.actions; | |
| const visible = (j.n_concepts ?? a.length) - (j.tally?.[0] ?? 0); | |
| const degenerate = visible <= Math.max(1, a.length * 0.02); // cascade culled ~all ⇒ camera map suspect → show all | |
| for (let i = 0; i < d.concepts && i < a.length; i++) lodAction[i] = degenerate ? 255 : a[i]; | |
| lodDirty = true; dirty.current = true; | |
| }) | |
| .catch(() => { lodFail = true; }) // endpoint absent (old deploy) → keep full render | |
| .then((j: { actions: number[]; n_concepts?: number; tally?: number[] }) => { | |
| const a = Array.isArray(j.actions) ? j.actions : []; | |
| if (a.length < d.concepts) { | |
| lodAction.fill(255); | |
| lodDirty = true; dirty.current = true; | |
| return; | |
| } | |
| const visible = (j.n_concepts ?? a.length) - (j.tally?.[0] ?? 0); | |
| const degenerate = visible <= Math.max(1, a.length * 0.02); // cascade culled ~all ⇒ camera map suspect → show all | |
| for (let i = 0; i < d.concepts && i < a.length; i++) lodAction[i] = degenerate ? 255 : a[i]; | |
| lodDirty = true; dirty.current = true; | |
| }) | |
| .catch(() => { | |
| lodFail = true; | |
| lodAction.fill(255); | |
| lodDirty = true; dirty.current = true; | |
| }) // endpoint absent (old deploy) → keep full render |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cockpit/src/BodyHelix.tsx` around lines 326 - 333, Reset the LOD state back
to full visibility when the LOD fetch fails or returns unusable data. In the
promise chain inside BodyHelix’s LOD update logic, make the failure path and any
incomplete action-set handling restore lodAction to visible for all concepts and
mark lodDirty/dirty.current so the render refreshes, instead of only setting
lodFail. Also guard the success path in the .then handler so if j.actions is
missing/short or the response is otherwise incomplete, it falls back to full
visibility rather than leaving stale hidden entries.
| <div onClick={() => setOpen((p) => ({ ...p, [l.id]: !expanded }))} | ||
| style={{ display: 'flex', alignItems: 'center', gap: 7, padding: '7px 8px', cursor: 'pointer', color: '#cdd9e5', font: '12px ui-monospace, monospace', userSelect: 'none' }}> | ||
| <span style={{ width: 8, opacity: 0.7 }}>{expanded ? '▾' : '▸'}</span> | ||
| <span style={{ display: 'inline-block', width: 9, height: 9, borderRadius: 5, background: l.color }} /> | ||
| <span style={{ flex: 1 }}>{l.name}</span> | ||
| <span style={{ opacity: 0.45 }}>{items.length}</span> | ||
| </div> | ||
| {expanded && items.slice(0, 500).map((c) => ( | ||
| <div key={c.row} onClick={() => focusOn(c)} title={c.name} | ||
| style={{ padding: '4px 8px 4px 30px', cursor: 'pointer', color: '#9fb0c2', font: '12px ui-monospace, monospace', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', borderRadius: 5 }} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Make browser rows keyboard-operable.
The expandable layer headers and concept rows are clickable <div> elements without tabIndex, role, or keyboard handlers, so keyboard users cannot expand groups or focus structures. Prefer <button> elements styled as rows, or add equivalent keyboard semantics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cockpit/src/BodyHelix.tsx` around lines 491 - 500, The layer header and
concept row click targets in BodyHelix are plain clickable divs, so they are not
keyboard-operable. Update the expandable header around setOpen/expanded and the
concept row that calls focusOn(c) to use semantic button elements where
possible, or add tabIndex, appropriate role, and keyboard handlers for
Enter/Space so keyboard users can expand groups and focus rows.
| "ischium", "pubis", "sacrum", "manubrium", "xiphoid", | ||
| # teeth are not is_a bone in FMA (dentin/enamel, not skeletal element), so | ||
| # they fell through to "flesh" → layer 1 (skin), hidden in the skeleton view | ||
| # (toothless skull). Bucket them with the skeleton; gingiva stays flesh. | ||
| "tooth", "molar", "incisor", "canine", "premolar", "cuspid"]), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
These dental substrings are broader than the intended fix.
tissue_of() falls back with raw substring checks at Lines 278-279, so adding "incisor", "canine", "premolar", etc. will also classify any non-tooth concept whose canonical name merely mentions a tooth as "bone". That means the "gingiva stays flesh" guarantee in this comment is not actually enforced.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/osint-bake/tools/bake_torso_splat.py` around lines 88 - 92, The dental
substring list in bake_torso_splat.py is too broad and can misclassify non-tooth
concepts because tissue_of() uses raw substring matching later on. Tighten the
fix by limiting tooth bucketing to safer, tooth-specific identifiers in the
mapping logic around tissue_of() rather than broad terms like
incisor/canine/premolar, and ensure gingiva-related names still resolve to flesh
instead of bone.
| CAP = 2.0 # PER-VESSEL diameter boundary: a ring may not exceed this vessel's own | ||
| # caliber × CAP. RMAX alone lets a finger artery balloon to aorta size at | ||
| # a bend; this keeps a capillary a capillary through its bends. | ||
| PCTL = 0.30 # per-bin radius percentile (NOT the median): at a strong bend two arms | ||
| # share one axial bin and the median perp-distance is ~half the gap (a | ||
| # balloon); the low percentile picks the near wall = the true radius. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Replace the Unicode multiplication sign in these comments.
Ruff is already flagging × at Lines 39 and 140 as ambiguous text, so this change will keep lint noise around until those are switched to plain ASCII.
Minimal cleanup
- # caliber × CAP. RMAX alone lets a finger artery balloon to aorta size at
+ # caliber x CAP. RMAX alone lets a finger artery balloon to aorta size at
...
- # (robust median of the bin radii) × CAP, then the absolute RMAX. A capillary stays a
+ # (robust median of the bin radii) x CAP, then the absolute RMAX. A capillary stays aAlso applies to: 139-140
🧰 Tools
🪛 Ruff (0.15.18)
[warning] 39-39: Comment contains ambiguous × (MULTIPLICATION SIGN). Did you mean x (LATIN SMALL LETTER X)?
(RUF003)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/osint-bake/tools/fill_body_soa.py` around lines 38 - 43, The comments
near CAP and PCTL use the Unicode multiplication sign, which Ruff flags as
ambiguous text. Update the affected inline comments in fill_body_soa.py
(including the later matching comment around the same constant block) to use
plain ASCII text instead of ×, keeping the wording otherwise unchanged. Use the
CAP and PCTL constant definitions as the anchor points when locating the
comments.
Source: Linters/SAST tools
What
Follow-up to #65 (merged). Six commits: two bake-quality fixes that drove a full re-bake, the deploy wiring for the new artifacts, and the
/helixviewer's missing UI. Branch is deployed for Railway verification before merge.Bake quality — drove the 20260629b re-bake
2a7ac4a): the slicer-fill swept a solid core along one straight PCA axis per component, each ring's radius = the median perpendicular distance clamped to a single global RMAX (~aorta scale). At a sharp bend the straight axis can't follow the tube — both arms land in one axial bin, the centroid falls between them (outside the limb), the median balloons, and the global cap let a finger artery swell to aorta caliber. Those were the "stray fat children branching off outside the limbs." Fix: per-bin radius now reads a low percentile (near-wall) instead of the median, and every ring is clamped to this vessel's own caliber × 2.0. Synthetic L-bend: worst ring 2.6× true radius, was up to 20×.db7bce8): teeth are in the dataset buttissue_ofhad no dental category and teeth aren'tis_a bonein FMA (dentin/enamel), so all 28 fell through to the "flesh" default and vanished in the skeleton view — a toothless skull. Added the dental terms to the bone name-matcher; gingiva stays flesh.Deploy wiring (
1c24630)body.20260629b.soa.gz(/body) andbody.20260629b.v6helix.soa.gz(/helix). Both come from one bake run off the same soa_v2 source, so the 1658-concept ordering is identical — which is what lets /helix reuse the server's per-conceptbody.blocks(also 1658) for its LOD cull. Old artifacts stay in the release; a bake is swapped in by bumping the manifest./helix freeze fix (
ef6916e) + max-diameter clamp (be7faec)FrontSidebackface cull — never a fragment discard (which still rasterises every triangle, killing early-Z). This is the mobile lever./cesium./helix living-anatomy browser (
7896f76) — the new UIThe viewer had only layer toggles. Added the rest so it reads as a living, browsable database:
labels_json+ centroid columns at decode.uAlphauniform makes the whole body translucent so deeper structures show through; the fragment shader stays trivial./api/body/lodHHTL depth-cascade (same protocol /body uses) and folds the per-concept reject byte into the same geometry index rebuild as the layer toggles — not a fragment discard — so early-Z survives and the GPU draws strictly fewer triangles when zoomed in. Absent endpoint (old deploy) silently keeps the full render; a near-total cull is treated as a camera-mapping miss and shows all.Verification
tsc --noEmit+vite buildclean./api/body/lodendpoint confirmed registered (crates/cockpit-server/src/main.rs) and itsactions[]are per-concept-row aligned to the 1658-conceptbody.blocksthe /helix LOD gate reads.fma-body-soa-v3-v1release.Scope / safety
/helixUI + bake tooling + deploy wiring./body(BodyV3) is untouched; the LOD endpoint is shared read-only.🤖 Generated with Claude Code
https://claude.ai/code/session_01RhpwkHGgia2TuDFvdnuQdE
Generated by Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Chores