nx_glprobe_lib.nx source
↩ module page · 122 lines · 7255 B
1// nx_glprobe_lib.nx -- THE GL-CONTEXT PROBE RULER: one emitted probe, one native classifier, N page emitters.
2//
3// WHY (2026-09-02, operator: "it says no webgl2"): /world/rigview refused with the bare string "NXA REFUSED:
4// no webgl2" and /world/beach fell to its sovereign tier with a bare "no-webgl2-context" -- neither said WHOSE
5// refusal it was. Measured on the operator's laptop the same hour: every process started AFTER a display-driver
6// swap (Intel UMD 32.0.101.6629 -> .8991) created WebGL2 (a fresh headless Edge AND a fresh headless Waterfox,
7// ANGLE on the new UMD; the served rigview rendered 14164 verts / 104 joints in that Edge), while the Waterfox
8// that had stayed open since before the swap still MAPPED THE DELETED 6629 UMD in its GPU process (the module
9// that faulted Windows Terminal five times that week) and refused. The pages were innocent and the refusal
10// text could not say so. A refusal that does not name its cause sends the reader at the wrong subject
11// (nx_refusal_shape_lib asks exactly this: does the refusal name its subject and its remedy?).
12//
13// ONE TABLE, TWO SURFACES. The browser must answer the question itself (no page can call the estate for a
14// remedy while its GPU is refusing), so the emitted JS carries the state and remedy table -- and the SAME
15// table is the native classifier below, so an MCP caller (nx_glprobe classify <gl2> <gl1> <gpu>) and the
16// served page cannot disagree. nx_glprobe_gate proves the two surfaces carry identical strings and that no
17// page emitter still ships a bare refusal, with a runtime-assembled bare refusal as the firing neg-control.
18//
19// WHAT THIS IS NOT: not a fallback renderer. It names a refusal; the sovereign tier that would make the
20// refusal irrelevant is the gameengine contract (wc_mob_tex, wc_render_tiles), not this lib.
21// license_tier: ORIGINAL No hw writes (Rule 26).
22import "nx_syscalls.nx"
23
24// the four named states. Raw facts: gl2 (0/1 granted on the target canvas), gl1 (0/1 granted on a FRESH
25// canvas), gpu (0/1 navigator.gpu advertised). NOT probed on purpose: a 2d context on the TARGET canvas --
26// a canvas holds ONE context type forever, and a probe that claimed it would poison the very fallback it
27// reports on (the beach page's own 2026-08-23 finding).
28const GLP_OK: i64 = 0
29const GLP_GL1_ONLY: i64 = 1
30const GLP_GPU_NO_GL: i64 = 2
31const GLP_NO_CONTEXT: i64 = 3
32const GLP_STATES: i64 = 4
33
34const GLP_S_OK: *u8 = "webgl2-granted"
35const GLP_S_GL1: *u8 = "webgl2-refused-webgl1-granted"
36const GLP_S_GPU: *u8 = "no-gl-context-webgpu-adapter-advertised"
37const GLP_S_NONE: *u8 = "no-gpu-context-of-any-kind"
38const GLP_R_OK: *u8 = "none"
39const GLP_R_GL1: *u8 = "this browser build stops at WebGL1: open the page in a WebGL2 browser"
40const GLP_R_NOGL: *u8 = "the browser is not creating GPU contexts. Measured cause 2026-09-02: a browser left open across a display-driver update keeps the deleted driver mapped while every fresh process gets the new one -- restart the browser (finish any pending reboot), then reload"
41
42func glp_classify(gl2: i64, gl1: i64, gpu: i64) -> i64 {
43 if gl2 == 1 { return GLP_OK }
44 if gl1 == 1 { return GLP_GL1_ONLY }
45 if gpu == 1 { return GLP_GPU_NO_GL }
46 return GLP_NO_CONTEXT
47}
48func glp_state_name(st: i64) -> *u8 {
49 if st == GLP_OK { return GLP_S_OK }
50 if st == GLP_GL1_ONLY { return GLP_S_GL1 }
51 if st == GLP_GPU_NO_GL { return GLP_S_GPU }
52 return GLP_S_NONE
53}
54func glp_remedy(st: i64) -> *u8 {
55 if st == GLP_OK { return GLP_R_OK }
56 if st == GLP_GL1_ONLY { return GLP_R_GL1 }
57 return GLP_R_NOGL
58}
59
60// THE JS TWIN. Consumers append this ONCE before their first getContext and call nxGlProbe(canvas, opts):
61// it returns the WebGL2 context or null, and ALWAYS leaves window.__nx_gl = {gl2,gl1,gpu,state,remedy} so a
62// refusal message, a tier badge or an instrument can read the diagnosis instead of a bare word. The state and
63// remedy strings below MUST equal the consts above; nx_glprobe_gate holds that equality as teeth.
64func glp_js() -> *u8 {
65 return "function nxGlProbe(cv,opts){\n// nx_glprobe_lib: the GL-context probe ruler -- one table, emitted here and held natively by nx_glprobe classify.\nvar g=null;try{g=cv.getContext(\"webgl2\",opts);}catch(e0){g=null;}\nif(g){window.__nx_gl={gl2:1,gl1:1,gpu:(navigator.gpu?1:0),state:\"webgl2-granted\",remedy:\"none\"};return g;}\nvar g1=0,gpu=(navigator.gpu?1:0);\ntry{g1=document.createElement(\"canvas\").getContext(\"webgl\")?1:0;}catch(e1){g1=0;}\nvar st=g1?\"webgl2-refused-webgl1-granted\":(gpu?\"no-gl-context-webgpu-adapter-advertised\":\"no-gpu-context-of-any-kind\");\nvar rem=g1?\"this browser build stops at WebGL1: open the page in a WebGL2 browser\":\"the browser is not creating GPU contexts. Measured cause 2026-09-02: a browser left open across a display-driver update keeps the deleted driver mapped while every fresh process gets the new one -- restart the browser (finish any pending reboot), then reload\";\nwindow.__nx_gl={gl2:0,gl1:g1,gpu:gpu,state:st,remedy:rem};\nreturn null;}\n" as *u8
66}
67
68// ---- scanning helpers (shared by the gate and the CLI so there is ONE detector) ----
69func glp_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
70func glp_find(buf: *u8, off: i64, n: i64, needle: *u8) -> i64 {
71 let m: i64 = glp_slen(needle)
72 if m == 0 { return 0 - 1 }
73 var i: i64 = off
74 while i + m <= n {
75 var k: i64 = 0
76 var hit: i64 = 1
77 while k < m {
78 if buf[i + k] != needle[k] { hit = 0; k = m } else { k = k + 1 }
79 }
80 if hit == 1 { return i }
81 i = i + 1
82 }
83 return 0 - 1
84}
85func glp_count(buf: *u8, n: i64, needle: *u8) -> i64 {
86 var c: i64 = 0
87 var at: i64 = 0
88 var run: i64 = 1
89 while run == 1 {
90 let h: i64 = glp_find(buf, at, n, needle)
91 if h < 0 { run = 0 } else { c = c + 1; at = h + 1 }
92 }
93 return c
94}
95// A BARE REFUSAL is the string no webgl2 followed IMMEDIATELY by a quote -- the shape `throw"no webgl2"` in
96// emitted HTML, `throw\"no webgl2\"` in an emitter's .nx source. The diagnosed form the probe emits reads
97// `no webgl2 (<state>)`, so a paren after the 2 is NOT bare. The needle is ASSEMBLED at runtime from two
98// literals so this lib's own source never contains it contiguously (a detector that scans source finds its
99// own fixture -- prose is source bytes too).
100func glp_bare_needle(esc: i64) -> *u8 {
101 let nd: *u8 = sys_mmap(32)
102 var p: i64 = 0
103 let a: *u8 = "no webgl" as *u8
104 var k: i64 = 0
105 while a[k] != (0 as u8) { nd[p] = a[k]; p = p + 1; k = k + 1 }
106 let b: *u8 = "2" as *u8
107 nd[p] = b[0]; p = p + 1
108 if esc == 1 { let bs: *u8 = "\\" as *u8; nd[p] = bs[0]; p = p + 1 }
109 let q: *u8 = "\"" as *u8
110 nd[p] = q[0]; p = p + 1
111 nd[p] = 0 as u8
112 return nd
113}
114// count bare refusals in a buffer: both the HTML shape and the .nx-source shape
115func glp_bare_count(buf: *u8, n: i64) -> i64 {
116 return glp_count(buf, n, glp_bare_needle(0)) + glp_count(buf, n, glp_bare_needle(1))
117}
118// does an emitter source COMPOSE the probe? (the call site the consumer must carry)
119func glp_composes(buf: *u8, n: i64) -> i64 {
120 if glp_find(buf, 0, n, "nxGlProbe(" as *u8) < 0 { return 0 }
121 return 1
122}