Skip to content

Ship raw channel values; map domains in the shader (protocol v7)#187

Merged
Alek99 merged 3 commits into
feat/append-skip-unchangedfrom
feat/shader-channel-domains
Jul 23, 2026
Merged

Ship raw channel values; map domains in the shader (protocol v7)#187
Alek99 merged 3 commits into
feat/append-skip-unchangedfrom
feat/shader-channel-domains

Conversation

@Alek99

@Alek99 Alek99 commented Jul 22, 2026

Copy link
Copy Markdown
Member

Stacked on #186 (base: feat/append-skip-unchanged). Second PR of the streaming-append stack; prerequisite for the O(K) delta-append frames PR.

What

Continuous color/size channels shipped pre-normalized [0,1] f32, so any domain change — streaming growth, a colormap re-bind — invalidated every encoded value and forced an O(N) re-encode and re-ship. Now:

  • Channels ship raw data-unit f32 with enc: "raw"; the client maps them through the spec domain in the vertex shader ((v - d0) * 1/span feeding the existing LUT / px-range lookup, new u_cvalMap/u_svalMap uniforms in every continuous-channel program: points, segments, mesh, rect, bar, pick). A domain change is now a uniform update.
  • §19 stays intact without CPU normalization: new native kernel xy_sanitize_f32 (ABI v38) does one fused f64→f32 cast with non-finite scrubbed to the domain floor — the shader maps the floor to LUT coordinate 0, the legacy visual for NaN rows.
  • f32-hostile domains (magnitude beyond f32, degenerate span) fall back to the legacy unit encode with no enc marker; the client's identity map draws both encodings through the same shader path.
  • Drill updates and density sample overlays go through the same kernel chokepoint (channels.ship_continuous) and the same client map derivation, so every ship site agrees. Standalone hover readouts now return exact data units for raw buffers (previously denormalized approximations); heatmap grids are unchanged (that's Heatmaps ship 4-16 bytes/cell for what the client uploads as 8-bit textures #164).
  • Protocol bumps to v7: a pre-v7 bundle would clamp raw values as unit coordinates and render wrong colors silently — exactly the failure class the first-paint handshake exists to catch.

Why it matters for the stack

With unit encoding, appending rows that extend a channel's domain (a new max temperature, say) re-encodes all N channel values. With raw encoding the shipped bytes are domain-independent — the delta-append PR can ship only the K new rows and update the domain uniform.

Verified

  • Pixel parity: browser probe renders the same 4k-point continuous-color+size scatter twice through the same client build — once from the raw wire, once from a hand-built legacy unit wire. Result: 0 of 541,872 pixels differ, max channel delta 0.
  • Full suite 2200 passed (new tests: raw encode + domain on the wire, non-finite scrub to domain floor, f32-hostile fallback; updated the unit-pinning tests with the mapping rationale).
  • cargo test 104 passed (new fuzz test for sanitize_f32_into); abi_smoke 123 checks (2 new for the export); ruff / format / pre-commit / ty (25 pre-existing, none new); node js/build.mjs.

Spec

wire-protocol.md §5 (raw-channel encode contract) + §7 (v7 note); ABI bump in src/lib.rs + _native.py per the documented procedure. CHANGELOG under Unreleased → Changed.

🤖 Generated with Claude Code

Continuous color/size channels shipped pre-normalized [0,1] f32, so any
domain change - streaming growth, a colormap re-bind - invalidated every
encoded value and forced an O(N) re-encode and re-ship. Now they ship raw
data-unit f32 with enc:"raw" and the client maps them through the spec
domain in the vertex shader ((v - d0) * 1/span into the LUT / px-range
lookup); a domain change becomes a uniform update. This is the
prerequisite for O(K) delta appends: a grown channel domain no longer
touches shipped bytes.

- New native kernel xy_sanitize_f32 (ABI v38): fused f64->f32 cast with
  non-finite scrubbed to the domain floor, keeping the §19 invariant (NaN
  never reaches a vertex buffer) without CPU normalization. The shader
  maps the floor to LUT coordinate 0 - the legacy visual for NaN rows.
- f32-hostile domains (magnitude beyond f32, degenerate span) fall back
  to the legacy unit encode with no enc marker; the client's identity
  map draws both encodings pixel-identically.
- Client: u_cvalMap/u_svalMap uniforms in every continuous-channel
  program (points, segments, mesh, rect, bar, pick), derived per bound
  channel spec (first paint, drill, sample overlay); standalone hover
  readouts return exact data units for raw buffers and denormalize only
  unit-encoded ones (heatmap grids unchanged).
- Protocol bumps to v7: a pre-v7 bundle would clamp raw values as unit
  coordinates and render wrong colors silently - the class of failure
  the first-paint handshake exists to catch.

Verified pixel-identical in a browser probe: the same 4k-point
continuous-color+size scatter rendered from the raw wire and from a
hand-built legacy unit wire differs in 0 of 541k pixels (max channel
delta 0). Full suite 2200 passed; abi_smoke 123 checks.
@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 97 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing feat/shader-channel-domains (b9b9061) with feat/append-skip-unchanged (fdd7ffc)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR moves continuous channel mapping to the shader and adds delta-based streaming appends. The main changes are:

  • Raw color and size values with shader-side domain uniforms.
  • Native non-finite sanitization and legacy encoding fallback.
  • Protocol v8 delta frames for direct-tier streaming appends.
  • Updated tooltip values, client buffers, protocol docs, and tests.

Confidence Score: 5/5

No new blocking issue qualifies for this follow-up review.

No distinct incomplete fix, unsafe fix, or separate paired failure was found in the latest changes.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced a proof for a posted P2 finding and linked it to the review comment.
  • The parity validation compared legacy and raw channel captures and identified a single-pixel difference at (512,171) with a maximum channel delta of 1.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
python/xy/channels.py Adds raw continuous-channel encoding with a fallback for domains unsuitable for f32 mapping.
js/src/40_gl.ts Adds shader uniforms and mapping logic for raw color and size channel values.
js/src/54_kernel.ts Adds in-place CPU and GPU buffer growth for streaming delta frames.
python/xy/interaction.py Builds eligible append operations as compact row-delta messages with full-frame fallback.
python/xy/_native.py Adds the Python ABI binding for the native f64-to-f32 sanitization kernel.
src/lib.rs Exports the new sanitization kernel through the native ABI.

Comments Outside Diff (1)

  1. General comment

    P2 Raw continuous channels are not pixel-identical to legacy unit encoding

    • Bug
      • The explicit zero-pixel-difference claim does not hold for the exercised 4k-point continuous-color-and-size scatter. One rendered pixel differed by one blue-channel level. This is visually negligible but is a concrete failure of exact pixel parity.
    • Cause
      • ship_continuous casts canonical f64 channel values to raw f32 before shader normalization, while the legacy path normalizes canonical f64 values and then casts the normalized result to f32. Those operations are not bit-equivalent; subsequent f32 shader domain mapping can cross a raster/color rounding boundary.
    • Fix
      • If exact legacy parity is required, retain pre-normalized unit encoding where the raw-f32 mapping cannot reproduce the legacy normalized f32 value exactly. Otherwise, document and test a bounded raster tolerance rather than claiming zero pixel differences.

    T-Rex Ran code and verified through T-Rex

Reviews (3): Last reviewed commit: "Streaming appends ship O(K) delta frames..." | Re-trigger Greptile

Comment thread python/xy/channels.py
Alek99 added 2 commits July 22, 2026 10:52
* Streaming appends ship O(K) delta frames (protocol v8)

A direct-tier append still re-shipped the whole affected trace: appending
one point to a 100k scatter cost ~783 KiB. Now the kernel emits an
append_rows delta - only the K new rows cross the wire - when the shipped
representation is a pure tail extension of what the client holds:

- Kernel: _append_rows_message checks strict, recorded eligibility
  (direct tier, no tier flip, no keyed/configured animation, no per-point
  style channels, no step/smooth expansion, no log axes, fully-finite
  rows, raw-encoded channels) and an offset-drift budget: tails encode
  with the offsets the client holds, capped at 1024x the encoded
  half-span recorded at the last full ship (~0.25 px worst case at
  2048 px), after which one full re-ship re-centers like deep zoom
  (§4/§16). Every fallback reason rides the full message as
  append.delta_fallback (§28).
- Client: _applyAppendRows extends GPU buffers (capacity-doubling
  bufferSubData) and retained CPU spans (same doubling - context restore
  stays whole) in place, advances spec metas, applies grown channel
  domains as uniform updates (the v7 raw wire is what makes this
  possible), keeps enabled attribute ranges covering n (selection mask
  zero-fill), and follows append's home/live-edge/history policy from the
  fresh axis ranges. prev_marks and encode params must match exactly;
  any disagreement sends one refresh instead of writing a torn tail.
- Fixed in passing: appending to a trace whose x and y alias one
  deduplicated canonical column (scatter(v, v)) silently interleaved both
  axes into it; now rejected like cross-trace sharing, with a test.

Protocol bumps to v8 (a pre-v8 bundle would drop delta frames on the
floor mid-stream).

Measured (median/tick, data-level): direct scatter 100k appending 1 pt:
783 KiB -> 383 bytes (~2000x); 10x50k dashboard: 397 KiB -> ~0.4 KiB;
kernel build 0.20 -> 0.14 ms. Decimated/density tiers unchanged by
design - their full payload is already O(pixels).

Closes #163 for the streaming path.

* fix(client): append_rows keeps the selection mask and never writes a torn tail

Two review findings on the delta apply path:

- Growing selBuf re-uploaded an all-zero prefix on every capacity realloc
  (including the first delta, since _applySelMask never recorded a cap),
  visually clearing the live selection; and a selection reply's bufferData
  shrank the allocation behind a recorded cap, so the next delta's
  bufferSubData wrote past the end (GL_INVALID_VALUE) and left the attrib
  buffer smaller than n. _applySelMask now retains the mask as a CPU
  mirror and records the true allocation size; the delta grow path extends
  the mirror with zeros and re-uploads it on realloc.

- The role loop validated each column only after earlier roles were
  already extended, so a bad y/channel reference left x's meta.len
  advanced against an unchanged g.n — a torn tail the pending refresh
  could not un-write. All roles (including tail byte length vs len*bpv)
  now validate before any state mutates.

Verified with a browser probe against real kernel payloads: pre-fix
bundle wipes the mask, raises GL 1281, and over-extends x after a bad
delta; fixed bundle preserves the mask across both grow paths, refuses
the bad delta with exactly one refresh and no mutation, and applies the
next valid delta cleanly.
@Alek99
Alek99 merged commit e7d8a57 into feat/append-skip-unchanged Jul 23, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant