code wiki / (root) / nx_genp_allow_lib.nx

nx_genp_allow_lib.nx source

↩ module page · 103 lines · 4942 B

1// nx_genp_allow_lib.nx -- THE ONE GEN-TIME RECIPE ALLOWLIST (2026-09-04). 2// 3// WHY THIS FILE EXISTS. Three organs each carried their own copy of "which recipe indices cross into the engine 4// BEFORE genworld reads the spec table": nx_wasm_craft (init_impl_v's gok9 chain), nx_world_snap (its g8 chain) 5// and nx_game_page_emit (the NXGP count pass and write pass). The three drifted three ways. MEASURED 2026-09-04: 6// the engine admitted 0..6, 59..64, 66..68; the snap was blind to 62..68 until 2026-09-01 (debt 1788294505); 7// the page emitter still admitted only 0..6, 59, 60, 61, 64 -- so a recipe's 66, 67 and 68 pairs VALIDATED (the 8// validator had been widened on 2026-08-28) and then reached nothing. WITNESS: sites/nishifamily/world/ridge.html 9// line 108 served const NXGP=[[64,0]] against a recipe of 67:12,68:32 -- the STAGED-PIPELINE world had served 10// legacy two-octave terrain since it shipped, while the capture instrument drew the staged one. 11// A LAW APPLIED IN ONE ORGAN AND NOT ITS SIBLINGS IS HALF A LAW, AND WITH THREE COPIES THE HALF LEFT UNDONE IS 12// THE ONE THAT SHIPS. This lib is the single ruler all three compose: an index added here reaches the engine, the 13// instrument and the page in ONE edit, and nx_wasm_craft_gate pins every engine P_* slot to it so the names and 14// the numbers cannot part. 15// WASM-SAFE BY CONSTRUCTION: pure arithmetic, no allocation, no syscalls (nx_wasm_craft compiles it to wasm). 16// The STYLE lane (7..58, poked post-boot) is deliberately NOT here: gen-time and style are different channels, 17// and blurring them is the silent no-op the engine's whitelist exists to prevent. 18// license_tier: ORIGINAL No hw writes (Rule 26). 19 20// The gen-time slots, named for what they are. The engine's P_* table declares the same numbers under its own 21// names; the gate asserts each pair agrees, so this file cannot silently mean a different slot than the engine. 22const GA_TERRAIN_FIRST: i64 = 0 // P_TBASE .. P_CAVETH: the terrain-shape block 23const GA_TERRAIN_LAST: i64 = 6 24const GA_TREEMAX: i64 = 59 25const GA_HISTAGE: i64 = 60 26const GA_TREESPEC: i64 = 61 27const GA_BLDG: i64 = 62 28const GA_BLDGSPEC: i64 = 63 29const GA_ERODE: i64 = 64 30const GA_OCEAN: i64 = 66 31const GA_WPIPE: i64 = 67 32const GA_WPSCALE: i64 = 68 33// a scan bound for the census, not a limit: past the widest spec table the engine owns (WC_SPECN = 80) 34const GA_CENSUS_SPAN: i64 = 128 35// the number of admitted indices, stated ONCE so the census below can be asserted against it: 7 terrain + 9 named 36const GA_DUNE: i64 = 79 37const GA_ADMITTED: i64 = 16 38 39// 1 = idx crosses into the engine BEFORE genworld reads the spec table; 0 = it does not (style lane or unowned). 40func genp_gen_ok(idx: i64) -> i64 { 41 if idx >= GA_TERRAIN_FIRST { if idx <= GA_TERRAIN_LAST { return 1 } } 42 if idx == GA_TREEMAX { return 1 } 43 if idx == GA_HISTAGE { return 1 } 44 if idx == GA_TREESPEC { return 1 } 45 if idx == GA_BLDG { return 1 } 46 if idx == GA_BLDGSPEC { return 1 } 47 if idx == GA_ERODE { return 1 } 48 if idx == GA_OCEAN { return 1 } 49 if idx == GA_WPIPE { return 1 } 50 if idx == GA_WPSCALE { return 1 } 51 return 0 52} 53// the census: how many indices the allowlist admits. A gate asserts it equals GA_ADMITTED, so an index added 54// above without its count (or a count moved without an index) is caught as a number that disagrees with itself. 55func genp_gen_count() -> i64 { 56 var n: i64 = 0 57 var i: i64 = 0 58 while i < GA_CENSUS_SPAN { 59 if genp_gen_ok(i) == 1 { n = n + 1 } 60 i = i + 1 61 } 62 return n 63} 64 65const GA_DUNE_AMP_MASK: i64 = 4095 66const GA_DUNE_WAVE_MASK: i64 = 255 67const GA_DUNE_WAVE_SHIFT: i64 = 12 68const GA_DUNE_HEADING_SHIFT: i64 = 20 69const GA_DUNE_PACK_MAX: i64 = 268435455 70const GA_DUNE_MIN_WAVE: i64 = 4 71func genp_dune_valid(packed: i64) -> i64 { 72 if packed < 0 { return 0 } 73 if packed > GA_DUNE_PACK_MAX { return 0 } 74 if packed == 0 { return 1 } 75 if (packed & GA_DUNE_AMP_MASK) == 0 { return 0 } 76 if ((packed >> GA_DUNE_WAVE_SHIFT) & GA_DUNE_WAVE_MASK) < GA_DUNE_MIN_WAVE { return 0 } 77 return 1 78} 79// The text door consumes a bounded decimal field without multiplying an overflow. 80// Return -1 for empty, malformed or out-of-range input; delimiter remains unread. 81func genp_dune_decimal(text: *u8, pos: *i64) -> i64 { 82 var p: i64 = pos[0] 83 var value: i64 = 0 84 var digits: i64 = 0 85 while text[p] != (0 as u8) { 86 let c: i64 = text[p] as i64 87 if c == 44 { break } 88 if c < 48 || c > 57 { return 0 - 1 } 89 let digit: i64 = c - 48 90 if value > (GA_DUNE_PACK_MAX - digit)/10 { return 0 - 1 } 91 value = value*10 + digit 92 digits = digits + 1; p = p + 1 93 } 94 if digits == 0 { return 0 - 1 } 95 pos[0] = p 96 return value 97} 98 99// Opt-in predicate: incumbent engines keep their original sixteen indices. 100func genp_dune_gen_ok(idx: i64) -> i64 { 101 if idx == GA_DUNE { return 1 } 102 return genp_gen_ok(idx) 103}