nx_gsplat4d_lib.nx
buildroot/runtime/nx_gsplat4d_lib.nx
about
nx_gsplat4d_lib.nx -- ★4D SPATIOTEMPORAL GAUSSIAN SPLATTING: the temporal axis the estate did not have.
WHAT WAS ALREADY HERE (measured 2026-08-14 three independent ways: nx_capsearch over 1033 registered
tools, a coverage-complete grep of 23,023 sources, and reading the source): nx_gsplat is a real, gated,
sovereign 3D splatter -- isotropic blobs AND anisotropic surfels whose 2D covariance Sigma' is computed
EXACTLY by projecting the principal axes through the camera (the Jacobian, applied as a finite
projection rather than a symbolic J R S S^T R^T J^T), a conic power test against an integer exp-LUT, a
counting sort by depth, and front-to-back compositing C = sum c_i a_i prod(1-a_j). That is the
rasterizer. It is NOT re-implemented here.
WHAT WAS ABSENT: time. Every Gaussian in the estate was static. This organ adds the 4th axis and
nothing else -- it deforms state and hands the result to gs_render_aniso. One rasterizer, still.
THE KERNEL is the standard 4DGS temporal radial basis, integer-exact:
x_t = x_0 + v * (t - t_mu) * exp(-(t - t_mu)^2 / (2 * t_sigma^2))
★AND THE ENVELOPE COSTS NOTHING NEW: the incumbent's exp-LUT is explut[k] = GFXA*exp(-k/(2*GLUTU)), so
indexing it at k = GLUTU * dt^2/sigma^2 yields exp(-dt^2/(2 sigma^2)) EXACTLY -- the Gaussian envelope,
from the table the renderer already builds. A second exp table would have been a duplicate ruler.
★★NO RE-DECLARED CONSTANTS. The fixed-point scale, the LUT resolution and the record stride are FACTS
OF THE RASTERIZER and are read from it (gs_fxa / gs_lutu / gs_stride_aniso). An earlier revision of this
file hardcoded 256, 16 and 12; nx_magic at threshold 2 found them. They are not cosmetic: each was a
SECOND RULER, and the failure mode of a second ruler is that the two disagree silently, compile
cleanly, and render something plausible. There is no symptom to notice.
★★THE ARENA COUNTS ITSELF. Every allocation goes through g4_alloc, which accumulates the byte total as
a side effect of allocating -- so g4_bytes() cannot drift from reality. The previous revision multiplied
a HAND-WRITTEN COUNT of the field arrays; adding a field and forgetting to bump it would have silently
under-reported the resource envelope, which is a fabricated number wearing the shape of a measurement.
LAYOUT: Structure-of-Arrays, one array per field -- a field sweep touches contiguous memory instead of
striding over a record. Arrays are lazily allocated once against a declared capacity.
⚠DELIBERATE, DECLARED DIFFERENCES FROM A LITERAL FIELD-LIST TRANSCRIPTION -- each is a defect avoided:
- POSITION uses the signed ramp x envelope above (the kernel as specified).
- RADIUS uses the envelope ALONE, not the ramp. A signed ramp on a radius crosses zero and inverts
the splat, which the rasterizer cannot represent. A breathing surfel is the honest analogue.
- NORMAL uses ramp x envelope and is then RENORMALISED, so the surfel tips without its length
drifting. It is also the G-buffer normal the lighting rung will read.
- QUATERNION and 3-axis SCALE are NOT stored. The incumbent surfel is a DISK (normal + in-plane
dependencies 2 imports · 1 importers
imports: nx_syscalls.nxnx_gsplat.nx
imported by: nx_gsplat4d_gate.nx
structs
| none |
consts
| 53 | const G4_TSCALE: i64 = 1000 |
| 57 | const G4_RING_MIN: i64 = 2 |
| 58 | const G4_ERRFD: i64 = 2 |
| 59 | const G4_W64: i64 = 8 // the machine word this SoA is built from |
| 60 | const G4_E_CAP: i64 = 4 |
| 61 | const G4_E_SIGMA: i64 = 3 |
| 62 | const G4_E_RANGE: i64 = 5 |
| 63 | const G4_E_RING: i64 = 6 |
functions
| 96 | func g4_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 97 | func g4_err(s: *u8) -> i64 { sys_write(G4_ERRFD, s, g4_slen(s)); return 0 } |
| 102 | func g4_alloc(n: i64) -> i64 |
| 108 | func g4_init(cap: i64, ring: i64) -> i64 |
| 146 | func g4_cap() -> i64 { return G4_CAP } called by 1: main |
| 147 | func g4_n() -> i64 { return G4_N } called by 1: main |
| 148 | func g4_bytes() -> i64 { return G4_ALLOC } called by 1: main |
| 149 | func g4_ring_depth() -> i64 { return G4_RINGN } called by 1: main |
| 150 | func g4_tscale() -> i64 { return G4_TSCALE } called by 1: main |
| 151 | func g4_ring_min() -> i64 { return G4_RING_MIN } called by 1: main |
| 155 | func g4_set_rest(i: i64, x: i64, y: i64, z: i64, nx: i64, ny: i64, nz: i64, rt: i64, r: i64, g: i64, b: i64, op: i64) -> i64 |
| 188 | func g4_set_time(i: i64, tmu: i64, tsig: i64) -> i64 |
| 200 | func g4_set_motion(i: i64, vx: i64, vy: i64, vz: i64, drt: i64, dnx: i64, dny: i64, dnz: i64) -> i64 |
| 218 | func g4_envelope(i: i64, t: i64) -> i64 |
| 231 | func g4_wfull() -> i64 { let el: *i64 = G4_EXPLUT as *i64; return el[0] } called by 1: main |
| 235 | func g4_lut(k: i64) -> i64 called by 1: main |
| 243 | func g4_horizon_sigma() -> i64 { return gs_isqrt(G4_EXPN/gs_lutu()) } |
| 248 | func g4_bake(t: i64, out: *i64) -> i64 |
| 305 | func g4_ring_init() -> i64 called by 1: g4_ring_prime |
| 312 | func g4_ring_frame(s: i64) -> *i64 |
| 315 | func g4_ring_time(s: i64) -> i64 { let rt: *i64 = G4_RT_T as *i64; return rt[s] } |
| 316 | func g4_ring_valid(s: i64) -> i64 { let rv: *i64 = G4_RVALID as *i64; return rv[s] } called by 1: main |
| 317 | func g4_ring_head() -> i64 { return G4_RHEAD } |
| 319 | func g4_ring_fill(s: i64, t: i64) -> i64 |
| 327 | func g4_ring_prime(t0: i64, step: i64) -> i64 |
| 335 | func g4_ring_find(t: i64) -> i64 called by 1: main |
| 347 | func g4_ring_advance(step: i64) -> i64 |