nx_jsscope.nx
buildroot/runtime/nx_jsscope.nx
about
nx_jsscope.nx -- STATIC SCOPE RESOLUTION over nx_js_parse's AST: does every identifier a script
REFERENCES have a declaration in a scope that ENCLOSES the reference?
WHY THIS EXISTS. 2026-08-25 every /world page with a cast went black on the WebGL tier and reverted
to the CPU renderer, and the reason -- once the reload was made to carry it -- was
`ReferenceError: nxHairTick is not defined`: a function DECLARED inside loadNPC, CALLED from
drawNPCs at script scope. The page LEXED (nx_jsbalance passed it), the wasm PAINTED, every gate was
green. Nothing in the estate asks the one question a browser asks first: can this name be seen from
here? This organ asks it, statically, over the incumbent parser -- one AST, no second front-end.
THE RULE, STATED ONCE. A reference resolves if its name is declared in the SAME function scope or
any ENCLOSING one (function declarations and `var` hoist to their function; params, class names,
catch params and for-targets bind where they appear). A name declared ONLY in a function that does
NOT enclose the reference is the exact defect above and is reported as OUT-OF-SCOPE, with the
declaring scope named -- because "undefined" sends a reader to grep for a typo, while "declared in
loadNPC" sends them to the real fix. A name declared nowhere is UNRESOLVED and is checked against a
globals CONF (knowledge/js_globals.conf, one name per line): browser and language globals are DATA
the page may lean on, never a list compiled into this organ.
⚠ DECLARED IMPRECISION, because a checker whose blind spots are unstated gets trusted as exact.
1. `let`/`const` are BLOCK-scoped in JS; this pass binds them to their FUNCTION. A let in one block
referenced from a sibling block would be a real ReferenceError this organ does NOT see. That is
the conservative direction -- it misses an error class, it never invents one.
2. Template-literal interiors (`${expr}`) are not parsed by nx_js_parse (the token is opaque), so
references inside them are invisible here.
3. `with` statements and `eval` are not modelled; a page that used them could not be judged.
4. Property names (a.b), object keys, labels and destructuring keys are NOT references and are
never counted, by construction of the walk.
license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 3 imports · 17 importers
diagram shows first 10 each side; +0 more imports, +7 more importers in the complete lists below.
imports: nx_syscalls.nxnx_js_lex.nxnx_js_parse.nx
imported by: nx_craft_body_driver_t138.nxnx_craft_combined_emit_t28.nxnx_craft_combined_emit_t29.nxnx_craft_emit.nxnx_craft_feedback_emit_t28.nxnx_craft_garment_emit_t132.nxnx_craft_integrated_emit_t73.nxnx_craft_knight_emit_t25.nxnx_craft_material_body_emit_t61.nxnx_craft_material_emit_t55.nxnx_craft_move_emit_t72.nxnx_craft_pipeline_emit_t64.nxnx_craft_recovery_emit_t23.nxnx_craft_search_emit_t53.nxnx_craft_support_emit_t31.nxnx_craft_workspace_emit_t48.nxnx_jsscope_gate.nx
structs
| none |
consts
| 38 | const SC_MAX: i64 = 4096 // function scopes a page may hold -- a REFUSAL at the cap, never a silent stop |
| 39 | const DC_MAX: i64 = 65536 // declarations across all scopes |
| 40 | const SC_ROW: i64 = 4 // node, parent, decl_start, decl_count |
| 41 | const SC_NODE: i64 = 0 |
| 42 | const SC_PARENT: i64 = 1 |
| 43 | const SC_DSTART: i64 = 2 |
| 44 | const SC_DCOUNT: i64 = 3 |
| 47 | const RP_MAX: i64 = 4096 |
| 48 | const RP_ROW: i64 = 4 |
| 49 | const RP_UNRESOLVED: i64 = 1 |
| 50 | const RP_OUTOFSCOPE: i64 = 2 |
| 53 | const RP_REDECL: i64 = 3 |
| 56 | const JS_ST_CTX: i64 = 0 |
| 57 | const JS_ST_SC: i64 = 1 // scope rows |
| 58 | const JS_ST_NSC: i64 = 2 |
| 59 | const JS_ST_DC: i64 = 3 // decl pairs |
| 60 | const JS_ST_NDC: i64 = 4 |
| 61 | const JS_ST_RP: i64 = 5 // report rows |
| 62 | const JS_ST_NRP: i64 = 6 |
| 63 | const JS_ST_NREF: i64 = 7 // references examined -- the coverage figure |
| 64 | const JS_ST_OVER: i64 = 8 // 1 if any table hit its cap (the verdict is then a BOUND) |
| 65 | const JS_ST_GLOB: i64 = 9 // globals conf bytes |
| 66 | const JS_ST_GLOBN: i64 = 10 |
| 67 | const JS_ST_N: i64 = 12 |
functions
| 69 | func jss_tokidx(ctx: *i64, idx: i64) -> i64 { let n: *i64 = jp_nodes(ctx); return n[idx * NODE_SLOTS + 4] } |
| 70 | func jss_src(ctx: *i64) -> *u8 { return (ctx[CTX_SRC]) as *u8 } |
| 71 | func jss_tok_start(ctx: *i64, tokidx: i64) -> i64 { let t: *i64 = jp_toks(ctx); return t[tokidx*3 + 1] } |
| 72 | func jss_tok_len(ctx: *i64, tokidx: i64) -> i64 { let t: *i64 = jp_toks(ctx); return t[tokidx*3 + 2] } |
| 75 | func jss_tok_eq(ctx: *i64, ta: i64, tb: i64) -> i64 |
| 90 | func jss_declared_in(st: *i64, s: i64, tokidx: i64) -> i64 |
| 102 | func jss_report(st: *i64, kind: i64, tokidx: i64, scope: i64, declscope: i64) -> i64 |
| 115 | func jss_declare(st: *i64, scope: i64, tokidx: i64) -> i64 |
| 136 | func jss_new_scope(st: *i64, node: i64, parent: i64) -> i64 |
| 151 | func jss_resolve(st: *i64, s: i64, tokidx: i64) -> i64 |
| 162 | func jss_declared_anywhere(st: *i64, tokidx: i64) -> i64 |
| 173 | func jss_is_global(st: *i64, tokidx: i64) -> i64 |
| 202 | func jss_is_list(k: i64) -> i64 |
| 217 | func jss_collect(st: *i64, scope: i64, idx: i64) -> i64 called by 3: jss_collectjss_walkjss_check calls 11: jp_nkindjp_najss_declarejss_tokidxjp_nbjp_child_at+5 |
| 305 | func jss_ref(st: *i64, scope: i64, tokidx: i64) -> i64 |
| 314 | func jss_walk(st: *i64, scope: i64, idx: i64) -> i64 |
| 405 | func jss_check(src: *u8, srclen: i64, globals: *u8, globlen: i64) -> *i64 called by 18: ce_shipce_shipce_shipce_shipce_shipce_ship+12 calls 6: sys_mmapjp_parse_sourcejp_pstjss_new_scopejss_collectjss_walk |
| 427 | func jss_error_pos(src: *u8, srclen: i64) -> i64 |
| 436 | func jss_scope_name_tok(st: *i64, s: i64) -> i64 |