code wiki / (root) / nx_cast_aperture_artifact_expanded_t207.nx

nx_cast_aperture_artifact_expanded_t207.nx source

↩ module page · 3771 lines · 262144 B

1// nx_cast_shader_src.nx -- THE CHARACTER CAST VERTEX STAGE, WRITTEN ONCE (gameengine S12c, 2026-09-05). 2// The cast vertex shader that nx_game_page_emit ships as the hand-written GLSL literal MVS (extracted to the board as 3// buildroot/knowledge/compare/cast_mvs_hand.glsl, 2,485 raw / 2,521 expanded / 3,097 canonical tokens) is transcribed here 4// SLICE BY SLICE as shader-IR builder calls, so that BOTH dialects are emitted from one source: GLSL for the WebGL2 door 5// (proven against the hand text by nx_glsl_tokdiff -- an emitted slice is accepted when its canonical token stream is a PREFIX 6// of the hand's up to main's closing brace, slice_prefix=1, and the whole stage when the verdict reads IDENTICAL) and WGSL for 7// the WebGPU door (the S12 cast pipeline). The vocabulary is nx_world_shader_src's ws_* shorthands over nx_shader_ir; nothing 8// here re-implements a builder. RULES THAT KEEP THE CANONICAL FORM REACHABLE: a negative literal is sir_neg over the positive 9// literal ((-999.0), never a "-999.0" literal); every literal is spelled with the hand's own digits, canonically (0.5 not .5, 10// 0.930 not 0.93 -- the ruler canonicalises spelling, never value); a compound assignment is x=(x op (y)); left-associative 11// chains are built left-nested exactly as the grammar parses them; the hand's grouping parentheses are simply the tree's shape. 12// The f32 uniform arrays the hand declares (uOF uGB uGT uGN, float[12]) are declared EXACTLY as the hand does -- the 13// uniform-space stride rule is a WGSL-dialect fact and belongs in the WGSL backend's lowering (S12c-2b), never in this source. 14// SLICES (each measured, journaled on gameengine.plan under GE59): 15// 1 declarations + hsh + the instance/lock prologue (through vII=ii;) -- MEASURED 602/3097 canonical, slice_prefix 16// 2 dual-quaternion skinning off the joint table, garment displacement, the PASS-1 face cut, the y-up rotation and world 17// placement (through vec3 wp=...;) 18// 3 soft-body fields (hb2, sbo over the DYNA anchors, hair wind, dress inheritance, the body branch), wp+=sbo 19// 4 garment degeneration, the cast-shadow projection, vW, the clip transform (gl_Position) 20// license_tier: ORIGINAL No hw writes (Rule 26). 21// nx_world_shader_src.nx -- THE WORLD SHADER, WRITTEN ONCE (gameengine GE44 R-G, 2026-09-04). 22// The voxel world pass that nx_game_page_emit ships hand-written twice (a GLSL FSH for the WebGL2 door and a WGSL 23// module for the WebGPU door) is authored here ONCE as shader-IR builder calls and emitted to BOTH dialects by 24// nx_wgsl. Every function below is the hand WGSL, statement for statement (vx, h21, h31, pal9, sky, struct Hit, 25// march, shade, the fragment entry); the vertex entry is the backend's own fullscreen triangle (shsrc_fullscreen_tri). 26// Deliberate deltas from the hand text, all semantic no-ops: compound assignments are spelled x=(x+y); unsigned 27// constants are casts (u32(0) / uint(0)) because the GLSL backend spells a u32 literal without its suffix and a bare 0 28// beside a uint is a GLSL type error; the WebGPU top-left fragment origin is carried as the uniform `yflip` (1 on the 29// WebGPU door, 0 on WebGL2) instead of a per-dialect edit, so ONE source serves both doors. The uniform block is 30// declared ONCE here (ws_world_uniforms) and `nx_wgsl layout` derives its memory layout from it. 31// GE55 (2026-09-05): the world module now takes the sim's RAW state words as its uniform block (ws_world_uniforms_raw, 32// the same 40-word table as nx_wasm_craft UR_*) and derives every float in upk() into module-scope private state, so 33// the page packs nothing and computes nothing; ws_world_uniforms (the f32 block) stays as the R-F layout ruler fixture. 34// license_tier: ORIGINAL 35// nx_shader_ir.nx -- THE SHADER IR: ONE source of truth, N dialect backends. 36// 37// WHY THIS EXISTS (measured 2026-08-23). The estate's shaders are hand-written GLSL string 38// literals inside nx_game_page_emit. Drawing characters on the WebGPU tier therefore demanded a 39// SECOND HAND COPY in WGSL -- ~600 lines (dual-quaternion skinning off a joint texture, instance 40// uniform arrays, the voxel occlusion march, the procedural anatomy/eye/hair/skin paint) of the 41// most actively-edited shader in the estate: three separate feature edits landed in it on the day 42// this was written. Two artifacts that must agree, kept in agreement by discipline, is the exact 43// shape the banked law forbids. This module is the construction that makes disagreement 44// IMPOSSIBLE: neither dialect is authored, both are EMITTED from this representation. 45// 46// HOUSE SHAPE. This mirrors the NishiLang compiler itself -- lex_source -> parse_module -> 47// opt_run -> {nx_compile_x86, nx_compile_wat}: one front-end representation, many backends. 48// nx_glsl.nx and nx_wgsl.nx are backends over this IR, not transpilers bolted to a call site. 49// 50// THE SOURCE OF TRUTH IS NISHILANG (operator, 2026-08-23: "we dont want python or glsl or 51// javascript or all that it should be nishi lang and nishi ecosystem from the first byte up each 52// rung gaining all these capabilities"). GLSL IS A TARGET, NOT AN INPUT. This is NOT a GLSL->WGSL 53// transpiler: translating would enshrine GLSL as the canonical language and make a foreign dialect 54// the artifact we maintain. A shader is authored ONCE in NishiLang; GLSL and WGSL are both OUTPUTS, 55// exactly as x86_64 and WAT are outputs of the same NishiLang front-end. The ~600 lines of GLSL in 56// MVS/MFS are therefore a MIGRATION TARGET, not the source: each rung expresses more of them in 57// NishiLang and PROVES the emitted GLSL equivalent to the hand-written GLSL that ships today. 58// 59// LADDER (each rung widens the covered subset; rung 1 is what this lane ships): 60// rung 1 the fullscreen-triangle vertex stage -- authored in NishiLang, emitting BOTH the GLSL 61// VSH and the WGSL vs that are hand-written and SHIPPING today, proven against them. 62// rung 2 lower the real NishiLang AST (nx_tokenizer/nx_parse) into this IR, so a shader is an 63// ordinary NishiLang function rather than builder calls -- the full 'target, not 64// translator' shape, sharing the front-end with nx_compile_x86 and the WAT lane. 65// rung 3 widen the subset to the MFS paint set; rung 4 the MVS skinning set; then the character 66// pass becomes a compile target and the WebGPU abstention flips from owed to shipped. 67// 68// THE SOURCE OF TRUTH IS NISHILANG (operator, 2026-08-23: "we dont want python or glsl or 69// javascript or all that it should be nishi lang and nishi ecosystem from the first byte up each 70// rung gaining all these capabilities"). GLSL IS A TARGET, NOT AN INPUT. This is NOT a GLSL->WGSL 71// transpiler: translating would enshrine GLSL as the canonical language and make a foreign dialect 72// the artifact we maintain. A shader is authored ONCE in NishiLang; GLSL and WGSL are both OUTPUTS, 73// exactly as x86_64 and WAT are outputs of the same NishiLang front-end. The ~600 lines of GLSL in 74// MVS/MFS are therefore a MIGRATION TARGET, not the source: each rung expresses more of them in 75// NishiLang and PROVES the emitted GLSL equivalent to the hand-written GLSL that ships today. 76// 77// LADDER (each rung widens the covered subset; rung 1 is what this lane ships): 78// rung 1 the fullscreen-triangle vertex stage -- authored in NishiLang, emitting BOTH the GLSL 79// VSH and the WGSL vs that are hand-written and SHIPPING today, proven against them. 80// rung 2 lower the real NishiLang AST (nx_tokenizer/nx_parse) into this IR, so a shader is an 81// ordinary NishiLang function rather than builder calls -- the full "target, not 82// translator" shape, sharing the front-end with nx_compile_x86 and the WAT lane. 83// rung 3 widen the subset to the MFS paint set; rung 4 the MVS skinning set; then the pass 84// itself becomes a compile target and the WebGPU character abstention flips to shipped. 85// 86// SCOPE IS DECLARED AND ENFORCED, NEVER SILENT. The node kinds below cover what MVS/MFS actually 87// use. A backend meeting anything outside the subset must call sir_refuse and return -1. 88// A NAMED REFUSAL IS A CONTRACT; A SILENT MISTRANSLATION IS THE WORST POSSIBLE FAILURE IN A 89// SHADER BACKEND -- it compiles, it runs, and it draws the wrong thing. 90// 91// NO FLOATING POINT IS USED OR NEEDED: the NishiLang toolchain carries no f32, and a shader 92// emitter never computes with literals -- it TRANSCRIBES them. Numeric literals live in the 93// string pool in canonical form ("0.0", never "0."), which is also what WGSL requires. 94 95// syscalls.nx -- thin __syscall wrappers used across modules. 96// 97// Sovereign path: no libc. Every memory allocation, file op, and 98// clock read in the rest of the runtime routes through one of these 99// helpers. Numbers match Linux RV64; NishiOS uses the same set. 100// 101// Extracted from runtime.nx and ir.nx's copy-pasted helpers so the 102// module-import build doesn't produce duplicate symbols. 103 104// Tier aliases (nx_size / nx_idx / nx_fd / ...) ride along with the 105// syscall shelf: 141 runtime files use `as nx_size` etc. and only 106// compiled historically because the old parser silently void-cast 107// unknown type names (T#nx-int-alias-size-0 closed that hole LOUDLY, 108// which exposed the missing import). nx_tier.nx is pure type 109// aliases (0 funcs); prepass_register_aliases skips duplicates, so 110// modules that also import it directly stay fine. 111// nx_tier.nx -- substrate-wide tier configuration. 112// 113// Single point of edit for scale-agnostic substrate. Per user 114// directive 2026-05-13: "with the i64 it looks hardcoded everywhere 115// if we really want this dynamic dont we want that to be a changeable 116// value everywhere so it can switch to i128 and i256 etc." 117// 118// Per cardinals: 119// - feedback-numeric-tier-ladder.md (N0..N9 swap) 120// - feedback-scale-agnostic-substrate.md (MCU..HPC swap) 121// - feedback-substrate-additive-not-restrictive.md (declare cost) 122// 123// SEMANTIC ALIASES (not all should swap simultaneously): 124// 125// nx_int -- DEFAULT ARITHMETIC integer. Swappable across the 126// numeric tier ladder. Swap this to i128 to make the 127// entire substrate compute in 128-bit integers. 128// 129// nx_size -- MEMORY-SIZE integer. Always platform-pointer-width. 130// Used for buffer sizes, mmap byte counts, struct 131// sizes. Does NOT swap with nx_int -- changing this 132// would break pointer arithmetic. Stays i64 on RV64. 133// 134// nx_idx -- ARRAY-INDEX integer. Same width as nx_size on 135// flat-memory targets. Distinct alias so future 136// GPU/distributed targets can change indexing without 137// touching arithmetic. 138// 139// nx_byte -- The byte type. Stays u8. Distinct alias so MCU 140// targets that emulate u16-byte memory could rebind. 141// 142// HARDWARE-TIER BUFFER SIZES (declare cost, don't restrict): 143// 144// NX_BUF_TINY -- 64 B (MCU-friendly; stack-safe) 145// NX_BUF_SMALL -- 256 B (MCU heap-friendly) 146// NX_BUF_MEDIUM -- 4096 B (page-size; workstation default) 147// NX_BUF_LARGE -- 64 KiB (server-friendly) 148// NX_BUF_HUGE -- 1 MiB (HPC; assumes virtual memory) 149// 150// Use these instead of `sys_mmap(4096)` etc. so the substrate 151// announces its memory footprint and tier-incompatible code can 152// be flagged by audit. 153// 154// HARDWARE TIER (informational; downstream code may branch): 155// 156// NX_TIER_MCU = 0 -- microcontroller, kilobytes RAM 157// NX_TIER_SOVEREIGN_CHIP = 1 -- custom silicon, ~MB RAM 158// NX_TIER_FAMILY_DEVICE = 2 -- phone/router, ~GB RAM 159// NX_TIER_WORKSTATION = 3 -- laptop/desktop, ~10-100 GB RAM 160// NX_TIER_SERVER = 4 -- server-class, ~TB RAM 161// NX_TIER_HPC = 5 -- cluster, distributed 162// 163// COMPILE-TIME SWAP for nx_int (uncomment exactly one line): 164 165// THIS FILE IS THE SINGLE DEFINITION SITE for substrate-wide types. 166// Per user directive 2026-05-13: only this file (and platform-ABI 167// definition files like nx_syscalls.nx) should declare bare i64. 168// Every other substrate module uses the aliases below. 169 170// ===== arithmetic-tier aliases (swappable per nx_int tier ladder) ===== 171 172type nx_int = i64 // N1 -- default; 9 quintillion, fits all physical scales 173// type nx_int = i32 // N0 -- MCU / embedded 174// type nx_int = i128 // N2 -- queued; needs nx_i128 backend ops 175// type nx_int = i256 // N3 -- shipped (nx_i256.nx); cosmology / crypto 176 177// ===== platform-width aliases (stay at pointer width) ================= 178 179type nx_size = i64 // memory-size / byte-count 180type nx_idx = i64 // array-index 181type nx_byte = u8 // single-byte unit 182 183// ===== POSIX/Linux platform-ABI aliases (mandated 64-bit on RV64) ==== 184// 185// Each is a 64-bit integer by Linux RV64 ABI. Renamed here so substrate 186// code never writes bare `i64` for these semantic types. 187 188type nx_fd = i64 // file descriptor (kernel-mandated width) 189type nx_exit = i64 // exit / status code (main() return) 190type nx_pid = i64 // process id 191type nx_uid = i64 // user id 192type nx_gid = i64 // group id 193type nx_syscall_num = i64 // Linux syscall number 194type nx_off = i64 // file offset (off_t) 195type nx_errno = i64 // errno (negative on syscall failure) 196 197// ===== SEMANTIC TYPE GENEALOGY (added 2026-05-20) ====================== 198// 199// Per cardinal [[feedback-type-genealogy-math-cardinal-not-script]] 200// AND its immediate refinement (same session): every alias collapsing 201// to i64 is "y2k incestuous" -- relabeling, not genealogy. Real 202// semantic types pick the APPROPRIATE underlying width based on 203// the physics of the values they represent: 204// 205// - Small sealed enums (15 outcomes, 18 probe kinds) -> u8 206// - Display pixel coords (~32M max realistic) -> i32 207// - Q10 / Q14 fixed-point (values * 1024 / 16384) -> i32 208// - 32-bit color packs (RGBA8888) -> u32 209// - Q20 fixed-point (values * 1048576) -> i64 210// - Wide color packs (RGBA16161616, PRESERVE_ALL) -> u64 211// - Timestamps (ns / us / ms / cycles) -> i64 (2038 Y2K38) 212// - 64-bit hash digests -> u64 213// - Cryptographic hashes (SHA-256, SHA-512) -> STRUCT (multi-word; queued) 214// - Virtual addresses on 64-bit ISA -> u64 215// 216// Each type is a child of its PHYSICALLY-APPROPRIATE parent 217// (i8/u8/i32/u32/i64/u64), not blanket-i64. This breaks the 218// y2k-incestuous trap where renaming i64 N ways pretends to be 219// type discipline while every value silently shares one width. 220 221// ----- TIME family (all i64; ns/us/ms/cycles legitimately need it) ----- 222// 2038 Y2K38 lurks for 32-bit time_t; i64 is the substrate-honest 223// choice. ms/us/ns + cycles all i64. s_q14 needs only i32 range 224// (val*16384 fits comfortably in i32 for typical second scales) but 225// we stay at i64 to compose cleanly with the i64 time arithmetic 226// across the substrate. 227type nx_ns = i64 // nanoseconds (since boot, monotonic) 228type nx_us = i64 // microseconds (since boot, monotonic) 229type nx_ms = i64 // milliseconds (since epoch, wall) 230type nx_s_q14 = i64 // seconds in Q14 fixed-point 231type nx_cycles = i64 // CPU cycle count 232 233// ----- HASH family (non-cryptographic 64-bit; crypto = STRUCT) ----- 234// FNV-1a / xxhash digest is u64 by spec. SHA-256 / SHA-512 / BLAKE 235// hashes are MULTI-WORD; they're declared as structs in 236// nx_sha256.nx / nx_sha512.nx / nx_blake2b.nx (each carries its own 237// fixed-size byte array; NOT i64). 238type nx_hash64 = u64 // FNV-1a / xxhash / truncated SHA -- 64-bit digest 239 240// ----- ETG family (sealed enums; small value space -> u8) ----- 241// nx_outcome_id sealed enum has 11 values; u8 fits 256 242// nx_probe_kind sealed enum has 18 values; u8 fits 256 243// nx_claim_source sealed enum has 13 values; u8 fits 256 244// nx_silicon_serial is a content-addressed identity HASH; u64. 245type nx_outcome_id = u8 // NX_ETG_OUTCOME_* (11 values; u8 fits) 246type nx_probe_kind = u8 // NX_ETG_PROBE_* (18 values; u8 fits) 247type nx_claim_source = u8 // NX_ETG_CLAIM_* (13 values; u8 fits) 248type nx_silicon_serial = u64 // per-die identity hash (cryptographic-strength width) 249 250// ----- PERF family (sealed enums) ----- 251type nx_pathology_id = u8 // NX_PERF_PATH_* (15 values; u8 fits) 252type nx_flow_state_id = u8 // NX_FLOW_STATE_* (6 values; u8 fits) 253 254// ----- FIXED-POINT family (width chosen by precision*range) ----- 255// Q10: value * 1024. Typical seed values are 0..255 so q10 max is 256// ~261K; i32 holds up to ~2.1B -> plenty of headroom. 257// Q14: value * 16384. Typical max around 16K of seed -> q14 ~ 2.6e8; 258// i32 holds up to 2.1e9 -> headroom for a few decimal seconds. 259// Q20: value * 1048576. Wider precision; needs i64 to avoid wrap. 260type nx_q10 = i32 // val * 1024; ~0.001 precision 261type nx_q14 = i32 // val * 16384; ~6e-5 precision 262type nx_q20 = i64 // val * 1048576; ~1e-6 precision 263 264// ----- GRAPHICS family (display coords + color packs at real widths) ----- 265// Modern displays are well within 32-bit pixel addressing. 266// 8K display = 7680x4320 pixels. i32 holds 2.1B -> plenty. 267// nx_color_rgba8 = 32-bit packed RGBA (the common case) 268// nx_color_rgba16 = 64-bit packed RGBA16161616 (HDR / wide gamut) 269type nx_pixel_x = i32 // screen X in pixels 270type nx_pixel_y = i32 // screen Y in pixels 271type nx_color_rgba8 = u32 // RGBA8888 packed 272type nx_color_rgba16 = u64 // RGBA16161616 packed (HDR / preserve-all) 273 274// ----- PERCEPTUAL family (sealed enum; small value space) ----- 275// nx_perceptual_profile has ~40 declared values up through 276// NX_PERCEPT_PRESERVE_ALL = 9999. Sentinel value 9999 needs i16, 277// not u8. i16 fits -32768..32767 with room for sentinels. 278type nx_perceptual_profile = i16 // NX_PERCEPT_* (~40 values + 9999 sentinel) 279 280// ----- ADDRESS family (virtual addresses on 64-bit ISA) ----- 281// Pointer-width is u64 on all our supported 64-bit targets 282// (RV64 / x86_64 / AArch64 / ppc64le / loongarch64 / mips64 / 283// s390x / RV32 uses u32 -- TODO: tier-conditional). 284type nx_addr = u64 // raw virtual address (caller casts to *u8) 285 286// nx_capability_manifest: 287// variant_class: tier_config 288// variant_id: tier_config_v1_global 289// requires_isa: [rv32i, rv32imac, rv64imac, rv64imacv, x86_64, aarch64, armv7a, cortex_m, avr, xtensa, wasm32] 290// requires_syscalls: [] 291// requires_ram_min_b: 0 // pure-const + typedef module, no runtime cost 292// tier_floor: NX_TIER_MCU 293// tier_ceiling: NX_TIER_HPC 294// cost_model: 295// flops_per_n: 0.0 296// bytes_per_n: 0.0 297// syscalls_per_n: 0.0 298// adversary_class: THREAT_OPPORTUNISTIC 299// 300// Note: This file is the substrate's TIER ENUM SOURCE OF TRUTH. It 301// has no variants by design (it IS the variant_class taxonomy that 302// other primitives' tier_floor / tier_ceiling reference). Manifest 303// declared for hygiene completeness; selector will skip it. 304 305// ---- buffer-size constants (use instead of bare numbers) ------- 306 307const NX_BUF_TINY: nx_size = 64 308const NX_BUF_SMALL: nx_size = 256 309const NX_BUF_MEDIUM: nx_size = 4096 310const NX_BUF_LARGE: nx_size = 65536 311const NX_BUF_HUGE: nx_size = 1048576 312 313// ---- hardware tier sentinels ----------------------------------- 314 315const NX_TIER_MCU: nx_int = 0 316const NX_TIER_SOVEREIGN_CHIP: nx_int = 1 317const NX_TIER_FAMILY_DEVICE: nx_int = 2 318const NX_TIER_WORKSTATION: nx_int = 3 319const NX_TIER_SERVER: nx_int = 4 320const NX_TIER_HPC: nx_int = 5 321 322// ---- numeric tier sentinels (informational) -------------------- 323 324const NX_NUM_N0_I32: nx_int = 0 325const NX_NUM_N1_I64: nx_int = 1 326const NX_NUM_N2_I128: nx_int = 2 327const NX_NUM_N3_I256: nx_int = 3 328const NX_NUM_N4_I512: nx_int = 4 329const NX_NUM_N5_BIGINT: nx_int = 5 330 331// ---- byte-width of substrate types (replace bare `8` / `4`) ---- 332// 333// Use these wherever you need the byte count of a substrate type -- 334// e.g., sys_mmap(N * NX_SIZEOF_NX_SIZE) to allocate N nx_size slots. 335// Swap nx_int's underlying type and ONLY this constant changes. 336 337const NX_SIZEOF_NX_INT: nx_size = 8 // nx_int currently i64 -> 8 bytes 338const NX_SIZEOF_NX_SIZE: nx_size = 8 // nx_size always pointer-width 339const NX_SIZEOF_NX_IDX: nx_size = 8 // nx_idx alias of nx_size 340 341// ---- POSIX stdio file descriptors (replace bare 0/1/2) --------- 342 343const NX_FD_STDIN: nx_fd = 0 344const NX_FD_STDOUT: nx_fd = 1 345const NX_FD_STDERR: nx_fd = 2 346 347const SYS_MAGIC_1024: i64 = 1024 348const SYS_MAGIC_1000000: i64 = 1000000 349const SYS_MAGIC_4294967296: i64 = 4294967296 350// first read window for a size-UNKNOWABLE file (lseek END <= 0); doubles while it fills -- see sys_read_file 351const SYS_READ_GROW_INIT: i64 = 65536 352const SYS_MAGIC_100000: i64 = 100000 353 354// ---- syscall numbers (per-target) ---- 355// 356// Cross-target via the macro processor (cardinal landed 2026-05-20: 357// feedback-hardware-agnostic-is-robustness -- the substrate must 358// compile + run on every silicon we point it at). Default path 359// (TARGET_X86_64 not defined) carries Linux RV64 numbers used by 360// qemu-RV64 + NishiOS. When nxc2 is invoked with --target x86_64 361// main.c pre-defines @macro TARGET_X86_64 1 so this file resolves 362// to x86_64 Linux ABI numbers. 363// 364// nx_syscalls_x86_64.nx remains the dedicated x86_64-only mirror 365// for files that want explicit single-target imports (e.g., bench 366// smokes built only for x86_64). This block makes nx_syscalls.nx 367// itself dual-target so substrate primitives compile portably. 368 369@ifdef TARGET_X86_64 370const SYS_READ: i64 = 0 371const SYS_WRITE: i64 = 1 372const SYS_CLOSE: i64 = 3 373const SYS_LSEEK: i64 = 8 374const SYS_OPENAT: i64 = 257 375const SYS_EXIT: i64 = 60 376const SYS_MMAP: i64 = 9 377const SYS_CLOCK_GETTIME: i64 = 228 378const SYS_IOCTL: i64 = 16 379const SYS_CLOCK_NANOSLEEP: i64 = 230 380// Namespace/container family, x86 branch (debt 1785528831). Moved here from 381// nx_syscalls_x86_64.nx so ONE module owns the wrapper set -- a TU reaching both 382// modules used to hold every wrapper TWICE, resolved silently by definition ORDER. 383const SYS_CHROOT: i64 = 161 384const SYS_MOUNT: i64 = 165 385const SYS_UNSHARE: i64 = 272 386const SYS_GETUID: i64 = 102 387const SYS_GETGID: i64 = 104 388const SYS_POLL: i64 = 7 389@endif 390 391@ifndef TARGET_X86_64 392const SYS_READ: i64 = 63 393const SYS_WRITE: i64 = 64 394const SYS_CLOSE: i64 = 57 395const SYS_LSEEK: i64 = 62 396const SYS_OPENAT: i64 = 56 397const SYS_EXIT: i64 = 93 398const SYS_MMAP: i64 = 222 399const SYS_CLOCK_GETTIME: i64 = 113 400const SYS_IOCTL: i64 = 29 401const SYS_CLOCK_NANOSLEEP: i64 = 115 402// Namespace/container family, RV64 branch (debt 1785528831). This is the branch actually 403// KEPT (TARGET_X86_64 is hard-pinned undefined), so these are the numbers the x86 backend 404// translates at emit: 51->161 chroot, 40->165 mount, 97->272 unshare, 174->102 getuid, 405// 176->104 getgid. The 40 and 51 rows were added to x86ctx_rv64_to_x86_64_syscall and 406// shipped FIRST -- without them both would pass through to the WRONG x86 syscall 407// (sendfile / getsockname), silently, because that translator's default is `return num`. 408const SYS_CHROOT: i64 = 51 409const SYS_MOUNT: i64 = 40 410const SYS_UNSHARE: i64 = 97 411const SYS_GETUID: i64 = 174 412const SYS_GETGID: i64 = 176 413const SYS_POLL: i64 = 73 414@endif 415 416func sys_ioctl(fd: i64, request: i64, arg: i64) -> i64 { 417 return __syscall(SYS_IOCTL, fd, request, arg, 0, 0, 0) 418} 419 420// poll(2): wait for events on fds. fds points to an array of `nfds` 421// struct pollfd { i32 fd; i16 events; i16 revents } (8 bytes each). 422// timeout_ms < 0 = block forever, 0 = return immediately. Returns the 423// count of ready fds (>0), 0 on timeout, or -errno. Used by the 424// substrate's own network diagnostics (bounded non-blocking connect) 425// instead of reaching for external tools. (rv64 const = ppoll; this 426// wrapper only runs on the x86_64 target.) 427func sys_poll(fds: *u8, nfds: i64, timeout_ms: i64) -> i64 { 428 return __syscall(SYS_POLL, fds, nfds, timeout_ms, 0, 0, 0) 429} 430 431// ---- core wrappers ---- 432 433func sys_write(fd: i64, buf: *u8, count: i64) -> i64 { 434 return __syscall(SYS_WRITE, fd, buf, count, 0, 0, 0) 435} 436 437func sys_read(fd: i64, buf: *u8, count: i64) -> i64 { 438 return __syscall(SYS_READ, fd, buf, count, 0, 0, 0) 439} 440 441func sys_close(fd: i64) -> i64 { 442 return __syscall(SYS_CLOSE, fd, 0, 0, 0, 0, 0) 443} 444 445// chdir. The compiler only rv64->x86 translates CONSTANT syscall numbers (x86ctx_emit_syscall: 446// VK_CONST_INT); chdir is absent from that table, so a constant 49 falls through to x86_64 bind and a 447// constant 80 is mapped to fstat -- BOTH gave EBADF (PROBE-PROVEN by test_chdir). The documented escape 448// (nx_x86_64_ctx.nx:1004 "Runtime-computed syscall number -- load as-is") is to make op0 RUNTIME: a memory 449// load can't be folded to VK_CONST_INT, so the raw x86_64 number 80 passes through untranslated = real 450// chdir. Used by the supervisor to set a spawned daemon's CWD before execve. 0 on success, -errno on fail. 451func sys_chdir(path: *u8) -> i64 { 452 let nbox: *i64 = sys_mmap(16) as *i64 453 nbox[0] = 80 // x86_64 chdir, forced runtime so the rv64->x86 xlate is skipped 454 return __syscall(nbox[0], path as i64, 0, 0, 0, 0, 0) 455} 456 457// getcwd -- SAME runtime-number escape as sys_chdir directly above, for the same documented reason: the 458// rv64->x86 translator only rewrites CONSTANT syscall numbers, and getcwd is absent from that table, so a 459// constant would be mangled exactly as chdir's was. A memory load cannot be folded to VK_CONST_INT, so the 460// raw x86_64 number passes through untranslated. 461// WHY THIS EXISTS (2026-08-14): the shim had sys_chdir but NOTHING to ask where we are. Every organ that 462// resolves a path against the CWD could therefore only print a RELATIVE path -- a claim whose truth depends 463// on invisible state. Three separate working-directory faults in one session stayed invisible until they 464// bit, and in each the reader could not tell "the file is missing" from "I am standing somewhere else". 465// β˜…β˜…β˜…AN ORGAN THAT CANNOT REPORT WHERE IT IS CANNOT WRITE AN HONEST PATH. 466// Returns the byte length written INCLUDING the terminator, or -errno (notably -ERANGE if cap is short). 467// SYS_PATH_MAX is exported so a caller never hand-writes the size: the FIRST consumer of sys_getcwd (this 468// author, minutes after adding it) wrote `sys_mmap(4096)` and `sys_getcwd(buf, 4096)` on consecutive 469// lines -- a bare literal AND a duplicate-authored pair, the exact shape being removed elsewhere the same 470// day. β˜…β˜…A NEW PRIMITIVE THAT DOES NOT EXPORT ITS OWN SIZE INVITES EVERY CALLER TO INVENT ONE. 471const SYS_PATH_MAX: i64 = 4096 // Linux PATH_MAX; getcwd returns -ERANGE below it 472// The DIRECTORY sibling of MODE_0644, added on the same evidence: `0x1ed` appears at 569 sites in 473// buildroot/runtime (nx_shelltool, corpus_complete=1), i.e. the estate scatters TWO file-mode constants, 474// not one. Named here so the pair lives together and a reader meets both at the same place. 475const MODE_0755: i64 = 0x1ed // rwxr-xr-x : default mode for a created directory 476func sys_getcwd(buf: *u8, cap: i64) -> i64 { 477 let nbox: *i64 = sys_mmap(16) as *i64 478 nbox[0] = 79 // x86_64 getcwd, forced runtime so the rv64->x86 xlate is skipped 479 return __syscall(nbox[0], buf as i64, cap, 0, 0, 0, 0) 480} 481 482// ⚠AT_FDCWD MOVED UP 2026-07-20 -- IT WAS A LIVE MISCOMPILE. This const was declared ~60 lines BELOW 483// (in the openat block) while sys_unlinkat and sys_fchmodat immediately below REFERENCE it. A module 484// const referenced ABOVE its declaration does not resolve, and nx_cc silently substituted CONSTANT 0 485// -- so both wrappers passed dirfd=0 (stdin) instead of -100. Absolute paths survive that (openat 486// ignores dirfd when the path is absolute), RELATIVE paths do not, which is exactly why unlinkat was 487// long recorded as flaky and "passing only by luck". Surfaced by the new unknown-identifier 488// diagnostic, which turned a silent 0 into a compile error. LAW (already banked, now enforced): 489// module-wide consts/statics go ABOVE every possible reader. 490const AT_FDCWD: i64 = -100 491 492// unlinkat(AT_FDCWD, path, 0) -- delete a file. x86_64 263 is a PROVEN pass-through (not an rv64 key), 493// but this is THE canonical home: 5+ organs hand-rolled `__syscall(263,...)` before this landed (DRY, 494// 2026-07-20). 0 on success, -errno on fail. 495func sys_unlinkat(path: *u8) -> i64 { 496 return __syscall(263, AT_FDCWD, path as i64, 0, 0, 0, 0) 497} 498 499// fchmodat(AT_FDCWD, path, mode) -- chmod by path. ⚠a CONSTANT 268 gets rv64->x86 TRANSLATED to the 500// wrong syscall (silent no-op chmod -- cost a vacuous-permission-test debug cycle, 2026-07-20), so the 501// number is forced RUNTIME via the sys_chdir nbox pattern. 0 on success, -errno on fail. 502func sys_fchmodat(path: *u8, mode: i64) -> i64 { 503 let nbox: *i64 = sys_mmap(16) as *i64 504 nbox[0] = 268 // x86_64 fchmodat, forced runtime so the xlate is skipped 505 return __syscall(nbox[0], AT_FDCWD, path as i64, mode, 0, 0, 0) 506} 507 508// exit_group(2) -- terminate ALL tasks in the thread group. Raw x86_64 231 509// (231 is NOT an rv64 key in the compiler's swap table, so it passes through 510// untranslated -- the munmap-11 precedent). THE explicit program-exit call 511// once a process holds live nx_thread_pool workers: CLONE_VM tasks are 512// separate PIDs, so plain sys_exit (93 -> x86 60, single task) leaves them 513// running, holding stdout open and wedging any pipeline that waits for EOF 514// (found 2026-07-07: the shared-pool matmul dispatcher hung the build lane 515// this way). Return-from-main already exit_groups via the _start trampoline; 516// use THIS for explicit early program exit. Per-THREAD exit stays sys_exit 517// (see nx_thread_exit). 518func sys_exit_group(code: i64) -> i64 { 519 return __syscall(231, code, 0, 0, 0, 0, 0) 520} 521 522// setpriority(PRIO_PROCESS=0, who=0 -> SELF, prio) -- x86_64 syscall 141. 523// Lower priority = larger nice value; 19 is the maximum yield. 524// WHY A WRAPPER AND NOT AN OPERATOR STEP (measured 2026-07-30): a bulk media 525// migration walk saturated the NAS; every forked organ queued behind its I/O so 526// EVERY agent MCP call 503'd for minutes -- the control plane went blind while a 527// background job did exactly what it was told. `renice 19` on the running pid 528// restored interactive service at once. 529// LAW: a long-running BULK job must yield to the interactive control plane BY 530// CONSTRUCTION at its own launch, not when an operator notices. Bind it to the 531// one act every bulk job performs (its startup) and nothing has to remember it. 532// WARN: `ionice` does NOT exist on the Synology busybox, so the I/O-class lever 533// is unavailable; CPU nice sufficed because the walk is SHA-256-bound over 534// cached reads (state R, not D, once niced). 535func sys_setpriority(prio: i64) -> i64 { 536 return __syscall(141, 0, 0, prio, 0, 0, 0) 537} 538 539// ADDITIVE TWIN 2026-08-04 (nx_resgov): re-nice ANOTHER process by pid. The incumbent above pins 540// who=0 = "me", so it cannot deprioritise a runaway -- and a governor that can only slow ITSELF has 541// no graceful rung between "observe" and "kill". PRIO_PROCESS=0, who=pid. Existing callers untouched 542// (rule 19: add the new entry point, never re-shape the one in service). 543func sys_setpriority_of(pid: i64, prio: i64) -> i64 { 544 return __syscall(141, 0, pid, prio, 0, 0, 0) 545} 546 547// munmap -- free a region from sys_mmap. x86_64 munmap = 11; 11 is NOT an rv64 number in the compiler's 548// swap table, so the literal passes through untranslated = real munmap (unlike chdir, where rv64 80=fstat 549// intercepted it). CRITICAL for long-running loops: the supervisor's per-poll proc_* scans mmap 64KB+ each; 550// unfreed, the leak hits DSM's RLIMIT_AS -> mmap returns -12 -> the code writes through it -> SEGFAULT 551// (dmesg-proven: nx_hostctl segfault at 0xfffffffffffffff4). Free scan buffers to keep the supervisor alive. 552// ===== SMALL-ALLOCATION BUMP ARENA (2026-08-06, debt 1785516350 / 1786055008) ===================== 553// MEASURED FIRST, THEN BUILT. nx_arena_probe: 20,000 x sys_mmap(32) -> VmSize 80,172 kB, 554// VmRSS 80,024 kB. 640 KB of requested data cost 78 MB of RESIDENT memory -- 4096 bytes per 32-byte 555// request, exactly one page and one kernel VMA each. Across the corpus nx_mmapbal deep counts 17,157 556// functions / 43,498 sites that allocate and never return, so this multiplier is the actual shape of 557// the leak: the call sites are not individually wrong so much as individually EXPENSIVE. 558// 559// One VMA per call is also a HARD CORRECTNESS CEILING, not just a memory cost: vm.max_map_count 560// defaults to 65530, after which mmap returns -ENOMEM and callers write through the failed pointer. 561// That is precisely the dmesg-proven nx_hostctl SEGFAULT at 0xfffffffffffffff4 described below. 562// 563// SO: requests <= NXA_SMALL_MAX are bump-allocated out of a 256 KiB chunk (one VMA per ~5,400 small 564// allocations instead of one per allocation). Larger requests take the ORIGINAL path untouched -- 565// they are the ones plausibly relying on page alignment, and they are not where the leak lives. 566// 567// THE ZEROING CONTRACT IS LOAD-BEARING AND IS PRESERVED BY NEVER RECYCLING. Callers rely on mmap 568// returning zeroed memory (nx_mmapbal: "mmap zeroes, so an untouched slot reads empty with no init 569// loop"). Bytes handed out here come from a freshly mmapped chunk and are NEVER handed out twice, so 570// every region is zero-filled exactly as before. LIFO give-back on munmap was deliberately REJECTED: 571// it would recover memory but hand back dirty bytes, silently breaking every caller that trusts the 572// zero -- a correctness regression traded for a memory win, which is the wrong trade. 573// 574// KNOWN TRADE-OFF, stated rather than hidden: small allocations are now ADJACENT within a chunk 575// instead of isolated in their own pages. An overrun that today walks off the end of a page and 576// SIGSEGVs loudly may instead corrupt a neighbouring allocation quietly. NXA_GAP puts slack between 577// allocations and NXA_SMALL_MAX is kept deliberately low to bound the exposure, but the risk is real 578// and is the reason this starts at 256 rather than a page. 579// ---- MEMORY ORDERING, THE ONE DEFINITION ------------------------------------------------------- 580// Moved here from nx_atom.nx on 2026-08-25 and DELETED from its two other copies 581// (nx_atomic_intrinsic_test, nx_simd_i32x8_test). Measured before the move, corpus_complete=1: 582// THREE files each declared NX_MO_SEQ_CST = 5 independently. A constant written in three places is 583// three rulers that agree until one of them does not. 584// 585// They live at THIS layer because the arena allocator below needs an ordering value for its own 586// lock, and this file cannot import nx_atom.nx -- nx_atom imports THIS file, so that direction is a 587// cycle. Everything that had these constants still has them: nx_atom.nx imports this file, and so 588// does every consumer of nx_atom. 589// 590// The __atomic_* forms these feed are COMPILER INTRINSICS, not library calls, so this file can use 591// them with no import at all. Verified in nx_x86_64_ctx rather than assumed: __atomic_cas_i64 emits 592// `lock cmpxchgq`, __atomic_faa_i64 emits `lock xaddq`, __atomic_fence emits `mfence`. On x86-64 the 593// ordering operand is not consulted by the emitter because those instructions are full barriers 594// regardless; it is carried for the RV64A backend, where it selects the aq/rl bits. 595const NX_MO_RELAXED: i64 = 0 596const NX_MO_CONSUME: i64 = 1 597const NX_MO_ACQUIRE: i64 = 2 598const NX_MO_RELEASE: i64 = 3 599const NX_MO_ACQ_REL: i64 = 4 600const NX_MO_SEQ_CST: i64 = 5 601 602const NXA_SMALL_MAX: i64 = 256 603const NXA_CHUNK: i64 = 262144 604const NXA_ALIGN: i64 = 16 605const NXA_GAP: i64 = 16 606const NXA_STATE: i64 = 4096 607// RING CANARY (temporary diagnostic): the single-slot canary checked only the immediately 608// previous allocation and reported ZERO overruns -- but the bisection proved the write is 609// DELAYED, landing after later allocations have been served. Track the last NXA_RING 610// allocations and re-verify every one of them on each call. Lives at i64 slot NXA_RBASE in 611// the state page; the reporter borrows bytes 64/128, so 512 is clear of it. 612const NXA_RING: i64 = 128 613const NXA_RBASE: i64 = 64 614// ---- ARENA MARK/RESET (2026-08-12, additive; the durable fix for bump-without-reset). The arena 615// abandons a full chunk on rollover, so a long-running accept loop accumulates chunks into one giant 616// coalesced VMA (hub_gw MEASURED 3.4GB over 64k requests). A daemon marks the arena AFTER startup and 617// resets at its accept-loop's quiescent point; reset munmaps every chunk allocated since the mark and 618// zeroes the marked chunk's reclaimed tail, so per-request small allocations reuse a bounded slab. 619// State slots (state page is 512 i64): [3]=chunk_count [4]=mark_valid [5]=mark_bump [6]=mark_chunk_end 620// [7]=mark_chunk_count; the chunk-base list lives at slots NXA_CHUNKBASE..+NXA_CHUNKMAX (clear of the 621// ring at 64..320 and the reporter scratch below 64). CONTRACT: the caller guarantees NO arena 622// allocation made after the mark is still referenced at reset (the accept-loop top, where the previous 623// request's frames have all returned -- the same quiescent point ss_cache_reap already uses). LARGE 624// (>NXA_SMALL_MAX) allocations take their own VMA and are NOT tracked here; a per-request large mmap 625// still needs its own munmap. Untracked-overflow (>NXA_CHUNKMAX chunks between resets) degrades to the 626// old leak for the excess, never corrupts. 627// ---- ARENA MUTUAL EXCLUSION (2026-08-25) ------------------------------------------------------- 628// THE DEFECT: the bump-pointer advance below was a plain read-modify-write -- 629// let p: i64 = nxa_st[0] 630// nxa_st[0] = p + need 631// -- so two threads that read nxa_st[0] before either wrote it BOTH RECEIVE THE SAME POINTER and 632// then write over each other. The chunk refill, the ring-canary scan and the nxa_st[2] counter have 633// the same shape. MEASURED while shipping structured concurrency: eight pool workers calling a 634// helper that allocates a 16-byte timespec raced this cursor and produced ARENA-OVERRUN 635// prev_alloc_size=16 followed by SIGSEGV. It generalises to EVERY small allocation from more than 636// one thread, which is why the scoped-spawn child body was written to allocate nothing at all. 637// 638// WHY A LOCK AND NOT A LOCK-FREE BUMP. A fetch-and-add on the cursor fixes only the fast path; two 639// threads can still both observe the chunk exhausted and both refill, and the canary ring and the 640// counter would still race. One lock over the whole mutable region is correct by inspection, which 641// on the allocator that every organ in the estate calls is worth more than a clever fast path. 642// THE COST IS NOT THE DOMINANT COST HERE: this function ALREADY walks all NXA_RING canary slots on 643// every allocation, so one uncontended `lock cmpxchgq` is far below the noise of work already done. 644// 645// SLOT 4 IS FREE BY THE LAYOUT ABOVE: [0] cursor, [1] limit, [2] ring counter, [3] chunk count, and 646// the ring starts at NXA_RBASE=64. It is also clear of the byte-64 and byte-128 scratch that 647// nxa_report_overrun formats digits into (slots 8 and 16), which slot 4 (bytes 32-39) does not touch. 648const NXA_LOCK: i64 = 4 649// A BOUND ON AN UNKNOWABLE WAIT, DERIVED RATHER THAN PICKED, AND ITS EXHAUSTION ANNOUNCES. The 650// longest thing the critical section can do is the NXA_RING canary scan plus one mmap, so a spin far 651// beyond that is not contention -- it is a holder that is never coming back. Eight times the ring 652// gives an order of magnitude of headroom over the longest legitimate hold; on reaching it the 653// allocator SAYS SO on stderr once and keeps waiting, because hanging visibly is recoverable and 654// corrupting silently is not, and dying inside the allocator would take down a process that may be 655// merely slow. 656const NXA_LOCK_WARN: i64 = NXA_RING * 8 657// Slot 5: "the contention hint has already been printed by this process". Also free by the layout 658// above and clear of every scratch region. It is a FLAG, not a counter, and it is set through a CAS 659// so the once-ness is itself race-free rather than depending on the lock it reports about. 660const NXA_LOCK_WARNED: i64 = 5 661 662const NXA_CHUNKBASE: i64 = 320 663const NXA_CHUNKMAX: i64 = 192 664 665// [0] = next free byte, [1] = one past the end of the current chunk. A static POINTER to a real 666// mmapped page rather than scalar statics, matching the idiom the corpus already proves; the state 667// page is taken through __syscall directly so this can never recurse into itself. 668static nxa_st: *i64 669 670// munmap -- free a region from sys_mmap. x86_64 munmap = 11; 11 is NOT an rv64 number in the compiler's 671// swap table, so the literal passes through untranslated = real munmap (unlike chdir, where rv64 80=fstat 672// intercepted it). CRITICAL for long-running loops: the supervisor's per-poll proc_* scans mmap 64KB+ each; 673// unfreed, the leak hits DSM's RLIMIT_AS -> mmap returns -12 -> the code writes through it -> SEGFAULT 674// (dmesg-proven: nx_hostctl segfault at 0xfffffffffffffff4). Free scan buffers to keep the supervisor alive. 675// 676// A small len means the region came from the bump arena above, because sys_mmap routes by the SAME 677// threshold. Unmapping an interior pointer would tear a hole in a chunk still holding other callers' 678// live allocations, so it is a no-op here. Balanced small callers therefore no longer return memory -- 679// but they now cost ~48 bytes instead of 4096, so the arena wins by two orders of magnitude even 680// against code that was already correct. 681// Matching release for sys_mmap_try and other whole kernel mappings. 682// Never pass an arena allocation from sys_mmap: its small pointers may be interior. 683// Preserve the requested mapping length; the kernel applies its page rounding. 684const NXA_MAP_INVALID:i64=0-22 // Linux EINVAL, a protocol value rather than a resource budget. 685func sys_munmap_direct(addr:*u8,len:i64)->i64{ 686 if (addr as i64)<=0||len<=0{return NXA_MAP_INVALID} 687 return __syscall(11,addr as i64,len,0,0,0,0) 688} 689 690func sys_munmap(addr: *u8, len: i64) -> i64 { 691 if len <= NXA_SMALL_MAX { return 0 } 692 return __syscall(11, addr as i64, len, 0, 0, 0, 0) 693} 694 695// Seek within a file. whence: 0=SEEK_SET, 1=SEEK_CUR, 2=SEEK_END. 696// Returns new file offset on success, -errno on failure. 697func sys_lseek(fd: i64, offset: i64, whence: i64) -> i64 { 698 return __syscall(SYS_LSEEK, fd, offset, whence, 0, 0, 0) 699} 700 701// ---- FILESYSTEM SPACE: THE AXIS THE ESTATE DID NOT HAVE (2026-08-28) ----------------------------- 702// WHY THIS IS HERE AND NOT LEFT WHERE IT WAS. On 2026-08-28 a 100%-FULL DISK truncated a sibling seat's 703// MEMORY.md to 0 bytes -- open(path,"w") truncates before it writes, so a full volume does not refuse a 704// write, it DESTROYS the file. Nothing in the estate saw it coming: nx_resmon is "the resource axis 705// nx_health lacks" for MEMORY and SWAP, and a search for the disk primitive returned matches=0 for BOTH 706// sys_statfs and statvfs with corpus_complete=1. nx_res_census records the same absence in its own header. 707// The capability was not missing, it was DARK: nx_system_triage.tr_free_gb has read filesystem space since 708// 2026-06-10, in an _hdl_build organ that is NOT REGISTERED (nx_job_run refuses it as "not an unpinned 709// GREEN tool"), so the one instrument that could have warned was unreachable by any caller. 710// A CAPABILITY THAT EXISTS IN ONE UNREACHABLE ORGAN IS INDISTINGUISHABLE FROM ONE NOBODY BUILT. 711// 712// WHY THE RAW 137 AND NOT A SYS_ CONST. This file's dual-arch blocks are gated on TARGET_X86_64, which is 713// HARD-PINNED UNDEFINED, so the RV64 branch is what compiles and the x86 backend translates each number at 714// emit through x86ctx_rv64_to_x86_64_syscall -- whose default is `return num`. There is NO row for RV64 43 715// (statfs), so a SYS_STATFS=43 const would pass through unmapped to x86_64 43 = ACCEPT: a different 716// syscall, silently, on a path pointer. That is not a hypothesis -- nx_system_triage PROBE-PROVED it on 717// 2026-06-10: "rv64 43 returns -9 through the translation table; 137 raw matches df exactly." So 137 is 718// the MEASURED-CORRECT number for the target we actually emit, and it is named here ONCE instead of 719// sitting as a bare literal at each call site. 720// ⚠NAMED FOLLOW-UP, conflict-checked and deliberately NOT taken here: adding `if num == 43 { return 137 }` 721// to x86ctx_rv64_to_x86_64_syscall would make the arch-correct const work too. Nothing passes 43 as an x86 722// number (43 appears only as a translation TARGET, from RV64 202 accept), so the row is safe -- but it is a 723// COMPILER change that activates only on the next nx_cc self-host rebuild, and the working path needs none. 724// 725// struct statfs (x86_64) as i64 slots: 0 f_type, 1 f_bsize, 2 f_blocks, 3 f_bfree, 4 f_bavail, 5 f_files. 726// f_bavail (not f_bfree) is the honest number for "will my write succeed": it excludes the root reserve, so 727// it reports FULLER than root would see. Wrong in the safe direction, and said out loud rather than implied. 728// ⚠THE IMPRECISION, MEASURED AND NAMED SO NOBODY LATER "FIXES" IT INTO AGREEING WITH df: this permil is 729// NOT df's Use%. df computes Used/(Used+Available), which EXCLUDES the root-reserved blocks from its 730// denominator; this computes (blocks-bavail)/blocks, which counts the reserve as used. VERIFIED against df 731// on 2026-08-28: avail_bytes came back 958449582080, which is EXACTLY df's Available of 935985920 KiB, while 732// the same volume read 113 permil here and 7% there -- both correct, measuring different things. Both reach 733// their maximum at the SAME event (bavail = 0), so a threshold calibrated against THIS metric alarms at the 734// same moment a writer actually hits the wall; it simply sits higher below that. Calibrate thresholds to 735// this definition, and do not import a df-derived number as if it were the same quantity. 736const SYS_STATFS_X86_MEASURED: i64 = 137 737const STATFS_BUF_BYTES: i64 = 144 738const STATFS_I_BSIZE: i64 = 1 739const STATFS_I_BLOCKS: i64 = 2 740const STATFS_I_BAVAIL: i64 = 4 741const STATFS_PERMIL: i64 = 1000 742const STATFS_ERR: i64 = 0 - 1 743 744// raw statfs into a caller-supplied 144-byte buffer. 0 = ok, non-zero = the kernel's negative errno. 745func sys_statfs(path: *u8, buf: *i64) -> i64 { 746 return __syscall(SYS_STATFS_X86_MEASURED, path, buf, 0, 0, 0, 0) 747} 748 749// bytes available to a non-root writer on the filesystem holding `path`; STATFS_ERR if statfs failed. 750func sys_fs_avail_bytes(path: *u8) -> i64 { 751 let buf: *i64 = sys_mmap(STATFS_BUF_BYTES) as *i64 752 if sys_statfs(path, buf) != 0 { return STATFS_ERR } 753 return buf[STATFS_I_BSIZE] * buf[STATFS_I_BAVAIL] 754} 755 756// USED per-mille of the filesystem holding `path`, counted against what a non-root writer can reach: 757// (blocks - bavail) * 1000 / blocks. STATFS_ERR if statfs failed or the volume reports zero blocks -- 758// an UNMEASURABLE volume must never read as 0 permil used, which is the most flattering possible lie. 759func sys_fs_used_permil(path: *u8) -> i64 { 760 let buf: *i64 = sys_mmap(STATFS_BUF_BYTES) as *i64 761 if sys_statfs(path, buf) != 0 { return STATFS_ERR } 762 let blocks: i64 = buf[STATFS_I_BLOCKS] 763 if blocks <= 0 { return STATFS_ERR } 764 let avail: i64 = buf[STATFS_I_BAVAIL] 765 return ((blocks - avail) * STATFS_PERMIL) / blocks 766} 767 768func sys_exit(code: i64) -> i64 { 769 return __syscall(SYS_EXIT, code, 0, 0, 0, 0, 0) 770} 771 772// mmap anonymous R/W memory; returns raw bytes. Fixed flags: 773// PROT_READ|PROT_WRITE = 3, MAP_PRIVATE|MAP_ANONYMOUS = 0x22, fd=-1. 774// FAIL-CLOSED ON A REFUSED MAPPING (2026-08-07). MEASURED: the corpus has 90,817 sys_mmap call sites 775// and SIX of them check the result -- all six in test probes whose response is sys_exit anyway. So 776// 90,811 sites take whatever this returns and write through it. When the kernel refuses, that value is 777// -errno, and the write lands at 0xfffffffffffffff4 (-12, ENOMEM). That is not a hypothetical: dmesg 778// on this host recorded it hourly in nx_web_shard_compact, and 18 times in nx_web_crawl_step. 779// Returning a poisoned pointer to 90,811 unguarded callers is the defect. Dying here is strictly safer 780// than dying there: the process ends either way, but this way there is no memory corruption first and 781// the failure is NAMED instead of arriving as a bare segfault address an operator has to decode. 782// This is the never-brick shape -- fail-safe BY CONSTRUCTION, not by every caller remembering. 783// KNOWN COST, stated: nx_mmap_probe / test_munmap deliberately provoke a refusal to observe it. They 784// now exit here with code 12 rather than printing their own verdict. Six probes lose a diagnostic; 785// 90,811 sites stop corrupting memory. 786// ===== TEMPORARY DIAGNOSTIC -- ARENA OVERRUN CANARY (2026-08-07) ===================================== 787// β›”DO NOT BLESS A COMPILER BUILT WITH THIS. The canary writes 0xC7 into the NXA_GAP slack that a 788// caller could otherwise legitimately read as zeros, so it changes observable behaviour for any code 789// that reads past its declared size -- which is precisely the code being hunted. 790// PURPOSE: at NXA_SMALL_MAX=256 the compiler produces 14 SPURIOUS type diagnostics (it reports 791// `arg 2 is an INTEGER but the parameter is a POINTER` against a parameter DECLARED `j: *u8`), i.e. 792// something writes past its allocation and corrupts the parser's type table. At threshold 64 the same 793// requests each get a 4096-byte page whose slack absorbs it. Reading the source found nothing: the 794// two obvious suspects (nx_ir.nx:70 sys_mmap(104), nx_parse.nx:868 sys_mmap(256)) are both correctly 795// sized and bounded. So stop reading and MEASURE: stamp each small allocation's gap, verify the 796// PREVIOUS one on the next call, and print the size of whichever allocation was overrun. 797// Writes to fd 2 without allocating -- it borrows scratch inside the arena state page, because a 798// reporter that called sys_mmap would recurse into the thing it is instrumenting. 799// Dump n bytes at src to fd 2, unprintables as '.', using scratch at state+256 (the ring starts at 800// state+512 and the decimal scratch sits at +64/+128, so this cannot collide with either). n is 801// capped by callers at 48 so the buffer stays clear of the ring. 802func nxa_dump_printable(src: i64, n: i64) -> i64 { 803 let o: *u8 = ((nxa_st as i64) + 256) as *u8 804 var i: i64 = 0 805 while i < n { 806 let sp: *u8 = (src + i) as *u8 807 var c: i64 = sp[0] as i64 808 if c < 32 { c = 46 } 809 if c > 126 { c = 46 } 810 o[i] = c as u8 811 i = i + 1 812 } 813 o[n] = 10 as u8 814 sys_write(2, o, n + 1) 815 return 0 816} 817 818// FINGERPRINT (2026-08-12): the size alone + all-zeros byte dump never named the site. The ring already 819// records each allocation's REQUESTED size in counter order, so the recent size SEQUENCE fingerprints the 820// code path that was running when the overrun landed (a distinctive run of sizes is near-unique to a 821// function). Writes to fd 2 borrowing state-page scratch at bytes 320/340 (clear of the ring at byte 512, 822// the reporter decimals at 64/128, and the byte-dump at 256). No allocation -- must not recurse into sys_mmap. 823func nxa_dump_sizes() -> i64 { 824 sys_write(2, " ring_sizes(old->recent): " as *u8, 27) 825 let scr: *u8 = ((nxa_st as i64) + 320) as *u8 826 let out2: *u8 = ((nxa_st as i64) + 340) as *u8 827 let cnt: i64 = nxa_st[2] 828 var start: i64 = cnt - 32 829 if start < 0 { start = 0 } 830 var idx: i64 = start 831 while idx < cnt { 832 let slot: i64 = idx % NXA_RING 833 let szv: i64 = nxa_st[NXA_RBASE + slot * 2 + 1] 834 var m: i64 = szv 835 var k: i64 = 0 836 if m == 0 { scr[0] = 48 as u8; k = 1 } 837 while m > 0 { scr[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 838 var j: i64 = 0 839 while j < k { out2[j] = scr[k - 1 - j]; j = j + 1 } 840 out2[k] = 44 as u8 841 sys_write(2, out2, k + 1) 842 idx = idx + 1 843 } 844 sys_write(2, "\n" as *u8, 1) 845 return 0 846} 847 848func nxa_report_overrun(sz: i64, gs: i64) -> i64 { 849 let msg: *u8 = "ARENA-OVERRUN prev_alloc_size=" as *u8 850 var n: i64 = 0 851 while msg[n] != (0 as u8) { n = n + 1 } 852 sys_write(2, msg, n) 853 let b: *u8 = ((nxa_st as i64) + 64) as *u8 854 let o: *u8 = ((nxa_st as i64) + 128) as *u8 855 var m: i64 = sz 856 var k: i64 = 0 857 if m == 0 { b[0] = 48 as u8; k = 1 } 858 while m > 0 { b[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 859 var i: i64 = 0 860 while i < k { o[i] = b[k - 1 - i]; i = i + 1 } 861 o[k] = 10 as u8 862 sys_write(2, o, k + 1) 863 // The SIZE alone did not name the site (four 80-byte victims, and the two unbounded 80-byte 864 // buffers in nx_parse.nx were sized from their inputs with no effect). So show the DATA: the 865 // victim's own bytes identify the buffer, and the bytes written past its end identify the WRITER. 866 let algn: i64 = (sz + NXA_ALIGN - 1) / NXA_ALIGN * NXA_ALIGN 867 let base: i64 = gs - algn 868 var dn: i64 = sz 869 if dn > 48 { dn = 48 } 870 sys_write(2, " own : " as *u8, 8) 871 nxa_dump_printable(base, dn) 872 sys_write(2, " over: " as *u8, 8) 873 nxa_dump_printable(gs, 16) 874 nxa_dump_sizes() 875 return 0 876} 877 878func nxa_die(msg: *u8) -> i64 { 879 var n: i64 = 0 880 while msg[n] != (0 as u8) { n = n + 1 } 881 sys_write(2, msg, n) 882 sys_exit(12) 883 return 0 884} 885 886// Address of the arena lock word. Valid only once nxa_st exists; every caller below has already 887// ensured that, and the state-page creation itself is discussed at the take site. 888func nxa_lock_addr() -> *i64 { 889 return ((nxa_st as i64) + NXA_LOCK * 8) as *i64 890} 891 892// __atomic_cas_i64 returns 1 when it wrote and 0 when it did not, so the spin condition is == 0. 893// It is a COMPILER INTRINSIC, not a call into nx_atom -- that module imports THIS file, so importing 894// it back would be a cycle. Verified in nx_x86_64_ctx rather than assumed: it lowers to a genuine 895// `lock cmpxchgq` followed by sete, which is a full barrier on x86-64 whatever ordering is passed. 896func nxa_lock_take() -> i64 { 897 var spins: i64 = 0 898 while __atomic_cas_i64(nxa_lock_addr(), 0, 1, NX_MO_ACQUIRE) == 0 { 899 spins = spins + 1 900 // Fires EXACTLY ONCE, on equality rather than on exceeding, so a genuinely long wait reports 901 // itself without turning the allocator into a log generator. 902 if spins == NXA_LOCK_WARN { 903 // ONCE PER PROCESS, not once per acquisition. MEASURED 2026-08-25 and this is a 904 // correction to the first cut of this very function: it fired on equality per CALL, and 905 // eight workers contending LEGITIMATELY produced hundreds of identical lines in a single 906 // gate run. A DIAGNOSTIC THAT FIRES CONSTANTLY IS ONE EVERY READER LEARNS TO IGNORE, and 907 // this one writes to the stderr of every organ in the estate. 908 // The threshold was derived from the longest the critical section can run, which bounds 909 // ONE hold and says nothing about QUEUE DEPTH: with N threads waiting, a legitimate wait 910 // is N holds and can exceed any per-section derivation. So this is a NOISE FLOOR for a 911 // hint, never a correctness bound -- it never fails, never delays, and never repeats. 912 // The flag is set through a CAS so the once-ness cannot itself race. 913 let wflag: *i64 = ((nxa_st as i64) + NXA_LOCK_WARNED * 8) as *i64 914 if __atomic_cas_i64(wflag, 0, 1, NX_MO_ACQ_REL) == 1 { 915 let m: *u8 = "ARENA-LOCK: sustained allocator contention seen (reported once per process; a hint, not an error -- allocation proceeds normally).\n" as *u8 916 var mn: i64 = 0 917 while m[mn] != (0 as u8) { mn = mn + 1 } 918 sys_write(2, m, mn) 919 } 920 } 921 } 922 return 0 923} 924 925func nxa_lock_give() -> i64 { 926 // nx_cc refuses a bare intrinsic statement ("computes a value and never uses it") and an atomic 927 // store has no result worth using, so it is bound and discarded -- the same shape nx_atom uses 928 // for exactly this reason. The contract is unchanged: this returns 0 either way. 929 let discarded: i64 = __atomic_store_i64(nxa_lock_addr(), 0, NX_MO_RELEASE) 930 if discarded != 0 { return 0 } 931 return 0 932} 933 934// Optional mapping for request boundaries that must report allocation refusal. 935// Unlike sys_mmap, this never aborts the process and never consumes arena storage. 936// Release successful mappings with sys_munmap_direct, not the arena-aware sys_munmap. 937// A successful reservation can still fail on later physical-memory pressure; callers 938// must not describe virtual address admission as guaranteed resident RAM. 939func sys_mmap_try(size:i64)->*u8 { 940 if size<=0 { return 0 as *u8 } 941 let mapped:i64=__syscall(SYS_MMAP,0,size,3,0x22,-1,0) 942 if mapped<=0 { return 0 as *u8 } 943 return mapped as *u8 944} 945 946func sys_mmap(size: i64) -> *u8 { 947 // Large requests keep the EXACT original behaviour, byte for byte: page-aligned, own VMA. Any 948 // caller that depends on page alignment is allocating at least a page, so the arena cannot reach 949 // it. Every failure path below also falls back to this same call, so an exhausted arena degrades 950 // to the old allocator rather than returning a bad pointer. 951 if size > NXA_SMALL_MAX { 952 let big: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0) 953 if big <= 0 { nxa_die("FATAL sys_mmap: kernel refused a large mapping (ENOMEM). Refusing to return a poisoned pointer -- a write through it would corrupt memory.\n" as *u8) } 954 return big as *u8 955 } 956 if (nxa_st as i64) == 0 { 957 let s: i64 = __syscall(SYS_MMAP, 0, NXA_STATE, 3, 0x22, -1, 0) 958 if s <= 0 { 959 // arena state page refused -- degrade to the plain allocator, and only die if THAT fails too 960 let f1: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0) 961 if f1 <= 0 { nxa_die("FATAL sys_mmap: kernel refused the arena state page AND the fallback mapping (ENOMEM).\n" as *u8) } 962 return f1 as *u8 963 } 964 nxa_st = s as *i64 965 } 966 // EVERYTHING FROM HERE TO THE RETURN TOUCHES SHARED STATE: the cursor, the limit, the chunk 967 // table, the canary ring and the ring counter. It is ONE critical section because the refill 968 // decision and the bump that depends on it cannot be separated without reintroducing the race. 969 // The state page itself is created ABOVE this point, unlocked: two threads arriving there 970 // together would each map a page and one would win the static, leaking the other's 4 KiB but 971 // corrupting nothing, and in practice the arena is warm long before any thread is spawned 972 // because spawning one allocates. That residual is NAMED here rather than papered over. 973 nxa_lock_take() 974 var need: i64 = size 975 if need <= 0 { need = 1 } 976 need = (need + NXA_ALIGN - 1) / NXA_ALIGN * NXA_ALIGN + NXA_GAP 977 if nxa_st[0] + need > nxa_st[1] { 978 let c: i64 = __syscall(SYS_MMAP, 0, NXA_CHUNK, 3, 0x22, -1, 0) 979 if c <= 0 { 980 // chunk refused -- degrade to the plain allocator, and only die if THAT fails too. 981 // RELEASE FIRST: this is the one path that leaves the critical section early, and a lock 982 // held across a degraded return would wedge every other allocator in the process. 983 nxa_lock_give() 984 let f2: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0) 985 if f2 <= 0 { nxa_die("FATAL sys_mmap: kernel refused an arena chunk AND the fallback mapping (ENOMEM).\n" as *u8) } 986 return f2 as *u8 987 } 988 nxa_st[0] = c 989 nxa_st[1] = c + NXA_CHUNK 990 // track the chunk base so arena_reset can munmap post-mark chunks (additive; guarded at cap). 991 if nxa_st[3] < NXA_CHUNKMAX { nxa_st[NXA_CHUNKBASE + nxa_st[3]] = c; nxa_st[3] = nxa_st[3] + 1 } 992 } 993 // ---- RING CANARY (temporary diagnostic) ---- 994 var rk: i64 = 0 995 while rk < NXA_RING { 996 let gs0: i64 = nxa_st[NXA_RBASE + rk * 2] 997 if gs0 != 0 { 998 var bi: i64 = 0 999 var bad: i64 = 0 1000 while bi < 8 { 1001 let bp: *u8 = (gs0 + bi) as *u8 1002 if bp[0] != (199 as u8) { bad = 1; bi = 8 } else { bi = bi + 1 } 1003 } 1004 if bad == 1 { 1005 nxa_report_overrun(nxa_st[NXA_RBASE + rk * 2 + 1], gs0) 1006 nxa_st[NXA_RBASE + rk * 2] = 0 1007 } 1008 } 1009 rk = rk + 1 1010 } 1011 let p: i64 = nxa_st[0] 1012 nxa_st[0] = p + need 1013 let gs: i64 = p + need - NXA_GAP 1014 var gj: i64 = 0 1015 while gj < NXA_GAP { let q: *u8 = (gs + gj) as *u8; q[0] = 199 as u8; gj = gj + 1 } 1016 let slot: i64 = nxa_st[2] % NXA_RING 1017 nxa_st[NXA_RBASE + slot * 2] = gs 1018 nxa_st[NXA_RBASE + slot * 2 + 1] = size 1019 nxa_st[2] = nxa_st[2] + 1 1020 // The ONLY other exit from the critical section is the degraded chunk-refill path above, which 1021 // releases before it returns. Every shared write is now behind this pair. 1022 nxa_lock_give() 1023 return p as *u8 1024} 1025 1026// arena_mark: force the arena warm (so a first chunk + state page exist), then record the current 1027// position as the reset barrier. Returns 1. A daemon calls this ONCE after startup, before its loop. 1028func sys_arena_mark() -> i64 { 1029 let warm: *u8 = sys_mmap(1) // ensures nxa_st + chunk[0] exist; the 1 byte is itself arena scratch 1030 if (warm as i64) == 0 { return 0 } 1031 nxa_st[4] = 1 1032 nxa_st[5] = nxa_st[0] 1033 nxa_st[6] = nxa_st[1] 1034 nxa_st[7] = nxa_st[3] 1035 return 1 1036} 1037 1038// arena_reset: reclaim everything allocated since the mark. munmap post-mark chunks, restore the bump 1039// to the mark, ZERO the marked chunk's reclaimed tail (preserves the mmap-returns-zeroed contract for 1040// recycled bytes), and CLEAR the ring canary (its stamps may point into a just-munmap'd chunk, and a 1041// stale deref on the next alloc would SEGV). Returns 1 on reset, 0 if no mark was set. 1042func sys_arena_reset() -> i64 { 1043 if (nxa_st as i64) == 0 { return 0 } 1044 if nxa_st[4] != 1 { return 0 } 1045 var i: i64 = nxa_st[7] 1046 while i < nxa_st[3] { 1047 let cb: i64 = nxa_st[NXA_CHUNKBASE + i] 1048 if cb != 0 { __syscall(11, cb, NXA_CHUNK, 0, 0, 0, 0); nxa_st[NXA_CHUNKBASE + i] = 0 } 1049 i = i + 1 1050 } 1051 nxa_st[3] = nxa_st[7] 1052 nxa_st[0] = nxa_st[5] 1053 nxa_st[1] = nxa_st[6] 1054 var z: i64 = nxa_st[0] 1055 while z < nxa_st[1] { let q: *u8 = z as *u8; q[0] = 0 as u8; z = z + 1 } 1056 var r: i64 = 0 1057 while r < NXA_RING * 2 { nxa_st[NXA_RBASE + r] = 0; r = r + 1 } 1058 nxa_st[2] = 0 1059 return 1 1060} 1061 1062// mmap anonymous SHARED R/W memory -- ONE region that survives fork() so all 1063// children see each other's writes (MAP_SHARED|MAP_ANONYMOUS = 0x21). Allocate 1064// in the PARENT before fork. Foundation for the fork-per-connection video relay 1065// (peers in separate children share the per-room frame table). 1066func sys_mmap_shared(size: i64) -> *u8 { 1067 let r: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x21, -1, 0) 1068 return r as *u8 1069} 1070 1071// madvise(2) -- prefetch/advice hints for mapped ranges. MADV_WILLNEED=3 batches page-ins so a 1072// serial fault loop over a cold file-backed mmap becomes parallel disk readahead (the dp-web-pub 1073// stage-2 p95 fix, 2026-08-12). RAW x86_64 NUMBER 28 ON PURPOSE (sys_exit_group's raw-231 pattern): 1074// the portable rv64/asm-generic number is 233 and x86ctx_rv64_to_x86_64_syscall has no 233 row in 1075// the DEPLOYED compiler, so a portable const would emit x86_64 233 = epoll_ctl (the wrong-syscall- 1076// not-an-error class; see the setpgid/flock rows). The 233->28 row is staged in nx_x86_64_ctx.nx and 1077// activates on the next nx_cc self-host rebuild; flip this to the portable const AFTER that lands. 1078// Signature bite-proven by nx_madvise_probe (0 / -12 ENOMEM / -22 EINVAL). Advisory contract: callers 1079// may ignore the return value -- a failed hint costs nothing but the cold-read behaviour it hints away. 1080func sys_madvise(addr: *u8, len: i64, advice: i64) -> i64 { 1081 return __syscall(28, addr, len, advice, 0, 0, 0) 1082} 1083 1084// openat flavors used by the compiler driver. AT_FDCWD = -100 (declared ABOVE, next to its first 1085// reader -- see the miscompile note there; do NOT move it back down). 1086// O_RDONLY = 0; O_CREAT|O_WRONLY|O_TRUNC = 0x241 on Linux RV64. 1087const O_RDONLY: i64 = 0 1088const O_WRONLY_CT: i64 = 0x241 // O_CREAT | O_WRONLY | O_TRUNC 1089const O_WRONLY_CA: i64 = 0x441 // O_CREAT | O_WRONLY | O_APPEND 1090 1091func sys_openat_rd(path: *u8) -> i64 { 1092 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDONLY, 0, 0, 0) 1093} 1094 1095// O_RDWR|O_CREAT (NO truncate) -- for offset-addressed persistent files like the metrics ring TSDB 1096// (create if missing, then lseek+read/write records in place, never truncating existing history). 1097const O_RDWR_CREATE: i64 = 0x42 1098func sys_openat_rdwr(path: *u8, mode: i64) -> i64 { 1099 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDWR_CREATE, mode, 0, 0) 1100} 1101 1102// β˜…β˜…β˜…THE FILE MODE IS THE HALF OF THIS INTERFACE THAT WAS NEVER NAMED. The O_ flags above are named 1103// consts in hex WITH a decoding comment; the mode passed beside them is a bare literal at every call 1104// site. MEASURED 2026-08-14 (coverage_complete=1 corpus_complete=1 over 23,053 files): 1105// - 29 organs passed the mode as a bare DECIMAL literal, which no reader decodes as rw-r--r-- 1106// without stopping to convert it. ⚠THE FIRST COUNT PUBLISHED HERE WAS 26: the scan was scoped to 1107// runtime/_hdl_build/ and the SUBDIRECTORY's count was published as the estate figure -- three 1108// more (nx_forge_rag, nx_gpu_export, nx_bvhfk) sat one level up in runtime/. 1109// β˜…A COUNT INHERITS THE SCOPE OF ITS SCAN, AND THE SCOPE IS THE PART NOBODY PRINTS BESIDE IT. 1110// ⚠The offending call is deliberately NOT spelled out literally in this comment: prose is source 1111// bytes, so writing the pattern here would make every future grep for it match this note; 1112// - 10 MORE each define their OWN private 0644 const (IP_ VR_ VP_ LIVE_ FD_ FP_ WL_ PUB_ REG_ HFF_), 1113// nine written 0x1a4 and one written 420 -- THE SAME CONSTANT IN TWO DIFFERENT BASES. 1114// Ten seats each solved this privately and none put the answer where the next one would look. That is 1115// the duplicate-ruler defect precisely: changing the estate's default artifact mode today means finding 1116// 39 sites in two notations and hoping none was missed. One name, in the shim every organ already 1117// imports, is the entire fix -- and it belongs HERE, beside the flags, not in a 40th private copy. 1118const MODE_0644: i64 = 0x1a4 // rw-r--r-- : default mode for a generated artifact 1119// rwxr-xr-x : default mode for a created DIRECTORY. A directory without the execute bit cannot be 1120// traversed, so MODE_0644 is not merely stricter here -- it is wrong, and the failure surfaces later 1121// as an unopenable path rather than as a refused mkdir. Named beside its sibling so the choice is a 1122// lookup rather than a recollection; the estate otherwise spells this as a raw 0x1ed at every site. 1123const MODE_0755: i64 = 0x1ed 1124// Seconds of ZERO PROGRESS on one socket operation before an accepted connection is abandoned. 1125// A single-threaded accept-loop daemon that loop-reads to Content-Length can be starved FOREVER by one 1126// peer that declares a body it never finishes sending -- a one-request DoS, hostile OR merely buggy. 1127// nx_dos_timeout_scan supervises the class and named 16 daemons carrying no timeout at all; the cure is 1128// sys_set_socket_timeout(cfd, ACCEPT_TMO_S) folded in right after accept. 1129// WHY 30 AND NOT THE 5 THE LOGIN DAEMONS USE: this bound must be wrong in the direction of SERVING, not 1130// of dropping. The attack is an UNBOUNDED wait, so ANY finite bound closes it; a short one additionally 1131// risks aborting a legitimate slow client. 30s of zero progress on a single recv/send is pathological 1132// for every daemon in the class -- including the streaming ones, where data is flowing and the timer 1133// never approaches its bound -- while still converting an infinite starvation into a bounded one. 1134// It is the calibration nx_galx_bridge already uses for an accepted cfd; named here rather than copied 1135// into a 16th private literal, exactly as MODE_0644 above. 1136const ACCEPT_TMO_S: i64 = 30 1137func sys_openat_wr(path: *u8, mode: i64) -> i64 { 1138 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CT, mode, 0, 0) 1139} 1140 1141// Linux O_WRONLY | O_CREAT | O_EXCL. An existing final component, including 1142// a symlink, is a conflict; callers acquire ownership only on success. 1143const O_WRONLY_CREATE_EXCLUSIVE: i64 = 0x1 | 0x40 | 0x80 1144func sys_openat_exclusive(path: *u8, mode: i64) -> i64 { 1145 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CREATE_EXCLUSIVE, mode, 0, 0) 1146} 1147 1148// Linux O_DIRECTORY: require a directory, rather than merely an openable node. 1149const O_DIRECTORY: i64 = 0x10000 1150func sys_openat_directory(path: *u8) -> i64 { 1151 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDONLY | O_DIRECTORY, 0, 0, 0) 1152} 1153 1154// Open path for append (create if missing). Used by append-only 1155// journals such as .race_telemetry.tsv. RV64 syscall numbers; the 1156// x86_64 mirror lives in nx_syscalls_x86_64.nx. 1157func sys_openat_append(path: *u8, mode: i64) -> i64 { 1158 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CA, mode, 0, 0) 1159} 1160 1161// Linux open ABI flags: acquire close-on-exec atomically and refuse a final 1162// symlink. Nonblocking also prevents an unexpected FIFO from stalling admission. 1163const O_CLOEXEC: i64 = 0x80000 1164const O_NOFOLLOW: i64 = 0x20000 1165const O_NONBLOCK: i64 = 0x800 1166const MODE_0600: i64 = 0x180 1167func sys_openat_lock(path: *u8) -> i64 { 1168 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CA | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK, MODE_0600, 0, 0) 1169} 1170 1171// symlinkat(target, AT_FDCWD, linkpath) -- raw x86_64 266 forced RUNTIME (the chdir escape, same as 1172// readlinkat below). THE atomic-repoint primitive for release management: create releases/current.new -> 1173// sys_renameat over releases/current = an atomic symlink swap (golive/rollback are instant + crash-safe). 1174// 0 on success, -errno (notably -EEXIST=-17 if linkpath exists -- create the .new name, then rename). 1175func sys_symlinkat(target: *u8, linkpath: *u8) -> i64 { 1176 let nbox: *i64 = sys_mmap(16) as *i64 1177 nbox[0] = 266 1178 let r: i64 = __syscall(nbox[0], target as i64, AT_FDCWD, linkpath as i64, 0, 0, 0) 1179 sys_munmap(nbox as *u8, 16) 1180 return r 1181} 1182 1183// readlinkat(AT_FDCWD, path, buf, cap) -- raw x86_64 267 forced RUNTIME (the chdir escape: keep the 1184// number out of the rv64->x86 constant-translate path). Returns link length (NO NUL appended), -errno 1185// on fail. nbox is munmap'd before return: the daemon supervisor calls this hundreds of times PER CYCLE 1186// (exe-identity sweeps), and a leaked page per call is exactly the VSZ-balloon class that broke fork. 1187func sys_readlinkat(path: *u8, buf: *u8, cap: i64) -> i64 { 1188 let nbox: *i64 = sys_mmap(16) as *i64 1189 nbox[0] = 267 1190 let r: i64 = __syscall(nbox[0], AT_FDCWD, path as i64, buf as i64, cap, 0, 0) 1191 sys_munmap(nbox as *u8, 16) 1192 return r 1193} 1194 1195// Atomically replace newpath with oldpath (rename(2) on one filesystem: a concurrent reader sees the 1196// whole old file or the whole new file, never a torn read). The S-class content-publish primitive: 1197// write the new page to a temp file, then sys_renameat(tmp, live) -> hot-swap, NO rm+ln race. 1198// renameat2: rv64=276, x86_64=316, flags=0. The known-good compiler translates most rv64 syscall 1199// numbers to the x86_64 target but its table MISSES 276 -- verified 2026-06-14 via nx_rename_probe: 1200// raw 276 -> -EINVAL (lands on x86_64 `tee`), raw 316 -> renames OK. That silently broke every 1201// cst_write_atomic publish (page.html.new written, never swapped in). Try the x86_64 number first 1202// (works on every x86_64 build incl. known-good); fall back to the rv64 number for native-rv64 or 1203// translating compilers that do map it. flags=0 so renameat2 == renameat semantics. 1204func sys_renameat(oldpath: *u8, newpath: *u8) -> i64 { 1205 let r: i64 = __syscall(316, AT_FDCWD, oldpath, AT_FDCWD, newpath, 0, 0) 1206 if r == 0 { return 0 } 1207 return __syscall(276, AT_FDCWD, oldpath, AT_FDCWD, newpath, 0, 0) 1208} 1209 1210// fsync(2): flush file (or directory) data+metadata to stable storage. 1211// PROBE-PROVEN 2026-06-10 (_fsync_probe): rv64 82 is NOT in the compiler's 1212// translation table (lands on x86 rename -> -EFAULT both ways); direct 1213// x86_64 74 passes through raw (the unlinkat-263 precedent) and behaves as 1214// fsync (0 on a valid fd, -9 EBADF on a bad one). Storage commit points 1215// fsync the data files AND their directory around rename(2) so a committed 1216// segment survives power loss, not just process death. 1217func sys_fsync(fd: i64) -> i64 { 1218 return __syscall(74, fd, 0, 0, 0, 0, 0) 1219} 1220 1221// flock(2): BSD-style whole-file ADVISORY lock. rv64 32 -> x86_64 73 via the compiler's 1222// x86ctx_rv64_to_x86_64_syscall table (nx_x86_64_ctx.nx:961, PROVEN LIVE in flock_deploy.log). 1223// op: SYS_LOCK_SH=1 / SYS_LOCK_EX=2 / SYS_LOCK_NB=4 (OR) / SYS_LOCK_UN=8. Returns 0 on success, 1224// -errno on failure. Used by the framed-append durability floor to serialize the write-until- 1225// complete loop so a partial/short write under contention can NEVER misalign a concurrent appender 1226// (O_APPEND single-write atomicity is necessary but not sufficient on every fs -- the lock makes 1227// the whole framed record write atomic against other lockers). Additive: no existing caller in 1228// this file changes. NOTE: nx_flock.nx is a separate organ importing the LEGACY "syscalls.nx" 1229// name; this wrapper lives HERE so organs already on nx_syscalls.nx (e.g. nx_framed_append) get 1230// flock without a second import (double-import rc=6 trap). 1231const SYS_LOCK_SH: i64 = 1 1232const SYS_LOCK_EX: i64 = 2 1233const SYS_LOCK_NB: i64 = 4 1234const SYS_LOCK_UN: i64 = 8 1235func sys_flock(fd: i64, op: i64) -> i64 { 1236 return __syscall(32, fd, op, 0, 0, 0, 0) 1237} 1238 1239// newfstatat(2): stat `path` into a 144-byte x86-64 struct stat at `statbuf`. x86_64 nr 262 is passed 1240// DIRECTLY (the unlinkat-263 / fsync-74 precedent: stat-family rv64 numbers aren't in the compiler's 1241// translation table, so a raw x86_64 number passes through untranslated). Returns 0 on success, <0 1242// (e.g. -2 ENOENT) on error. st_mtim.tv_sec @ offset 88, st_mtim.tv_nsec @ 96 (the freshness channel). 1243func sys_fstatat(path: *u8, statbuf: *u8) -> i64 { 1244 return __syscall(262, AT_FDCWD, path, statbuf, 0, 0, 0) 1245} 1246 1247// utimensat(2): set `path` atime+mtime from `times` (a struct timespec[2] = [atime.sec,atime.nsec, 1248// mtime.sec,mtime.nsec]). x86_64 nr 280 passed DIRECTLY. A sovereign `touch`; also makes freshness 1249// tests deterministic. Returns 0 on success, <0 on error. 1250func sys_utimensat(path: *u8, times: *i64) -> i64 { 1251 return __syscall(280, AT_FDCWD, path, times as i64, 0, 0, 0) 1252} 1253 1254// ---- sovereign host control-plane syscalls (x86_64; single unconditional consts, 1255// per the known-good-compiler @ifdef finding). The Nishi supervisor uses these to 1256// manage the daemon lifecycle WITHOUT any shell (no pkill / mkdir / chmod glue). ---- 1257 1258// COMPILER NOTE: the known-good compiler BAKES whole function bodies by NAME for some syscalls 1259// (proven via emitted .s: a function literally named sys_kill emits number 8, sys_chmod emits 155 1260// -- both wrong, regardless of the const referenced). So these wrappers use NON-baked names 1261// (nx_kill / nx_chmod). sys_mkdir / sys_renameat are not baked, so those keep the sys_ name. 1262 1263// DESIGN: __syscall takes the RV64/generic number; the compiler's x86ctx_rv64_to_x86_64_syscall table 1264// (nx_x86_64_ctx.nx) translates it to the build target. So pass the RV64 number. These four were added 1265// to that sovereign table 2026-06-06 (kill 129->62, mkdirat 34->258, fchmodat 53->268, renameat2 1266// 276->316); x86 kill(62) had collided with rv64 lseek(62), x86 fchmodat(268) with rv64 pivot_root(268). 1267 1268// kill(pid, sig) -- rv64 129 -> x86_64 62. SIGTERM=15 / SIGKILL=9. Host control plane. 1269func nx_kill(pid: i64, sig: i64) -> i64 { return __syscall(129, pid, sig, 0, 0, 0, 0) } 1270 1271// setpgid(pid, pgid) -- put a process in its own PROCESS GROUP so a killer can reach its whole 1272// subtree. nx_kill(0 - pgid, sig) signals every member, not just the one process you forked. 1273// A BOUND THAT ONLY REACHES THE PROCESS YOU FORKED IS NOT A BOUND ON THE WORK IT STARTED. 1274// Per-target const, NOT a bare generic number: x86ctx_rv64_to_x86_64_syscall translates only the 1275// numbers it knows and FALLS THROUGH for the rest. MEASURED on the laptop lane 2026-08-10: a bare 1276// generic 154 reached x86_64 as 154 and returned -38 (ENOSYS), silently -- and a fix built on it 1277// reproduced the original bug exactly. Callers must treat setpgid as BEST-EFFORT. 1278@ifdef TARGET_X86_64 1279const SYS_SETPGID: i64 = 109 1280@endif 1281@ifndef TARGET_X86_64 1282const SYS_SETPGID: i64 = 154 1283@endif 1284func sys_setpgid(pid: i64, pgid: i64) -> i64 { return __syscall(SYS_SETPGID, pid, pgid, 0, 0, 0, 0) } 1285 1286// prlimit64(pid, resource, new_limit, old_limit) -- the Linux RESOURCE-LIMIT primitive = 1287// the Job-Object ActiveProcessLimit / memory-limit analog for the sovereign supervisor (M5). 1288// x86_64 prlimit64 = 302 (PASSED DIRECTLY, the unlinkat-263 / fsync-74 / fstatat-262 1289// precedent: a raw x86_64 number not in the compiler's rv64->x86 swap table passes through 1290// untranslated). NOTE: rv64 prlimit64 IS 261 but x86_64 261 = futimesat -- so the naive 1291// "261 is the same on both" is WRONG (PROBE-PROVEN: 261 returned EFAULT/EINVAL because it 1292// hit futimesat); the build target here is x86_64, so we emit 302 directly. pid=0 => the 1293// calling process (a forked child caps ITSELF before running its payload). new_limit / 1294// old_limit each point at a struct rlimit64 { rlim_cur: i64, rlim_max: i64 } (16 bytes); 1295// pass 0 for old_limit to skip read-back. Returns 0 on success, -errno (e.g. -1 EPERM if 1296// raising a hard limit unprivileged) on failure. NON-baked name (the compiler bakes some 1297// sys_* bodies by name; the nx_ prefix avoids that trap). 1298func nx_prlimit(pid: i64, resource: i64, new_limit: *u8, old_limit: *u8) -> i64 { 1299 return __syscall(302, pid, resource, new_limit as i64, old_limit as i64, 0, 0) 1300} 1301 1302// RLIMIT resource ids (Linux generic; identical rv64/x86_64). RLIMIT_AS = address-space 1303// (virtual memory) cap -- the cleanest userspace-settable "memory budget" for a supervised 1304// job. RLIMIT_CPU = CPU-seconds cap. WNOHANG=1 = wait4 non-blocking liveness poll option. 1305const RLIMIT_CPU: i64 = 0 1306const RLIMIT_AS: i64 = 9 1307const WNOHANG: i64 = 1 1308 1309// mkdirat -- rv64 34 -> x86_64 258. Create a doc-root directory. mode e.g. 0x1ed (0755). 1310func sys_mkdir(path: *u8, mode: i64) -> i64 { return __syscall(34, AT_FDCWD, path, mode, 0, 0, 0) } 1311 1312// fchmodat -- rv64 53 -> x86_64 268. +x a freshly-deployed daemon binary (mode 0x1ed). flags=0. 1313func nx_chmod(path: *u8, mode: i64) -> i64 { return __syscall(53, AT_FDCWD, path, mode, 0, 0, 0) } 1314 1315// setsid -- x86_64 = 112 (not in the rv64->x86 table, so the literal passes through). Detach a forked 1316// process into a NEW session so it survives the SSH/parent close -- sovereign daemonization (no shell setsid). 1317func nx_setsid() -> i64 { return __syscall(112, 0, 0, 0, 0, 0, 0) } 1318 1319// CLOCK_MONOTONIC = 1. ts is 16 bytes {sec: i64, nsec: i64}. 1320// Returns 0 / -errno. 1321func sys_clock_gettime_mono(ts: *i64) -> i64 { 1322 return __syscall(SYS_CLOCK_GETTIME, 1, ts, 0, 0, 0, 0) 1323} 1324 1325// CLOCK_REALTIME = 0 -- wall-clock seconds since the Unix epoch. Use 1326// this (NOT monotonic) for anything that must match calendar time: 1327// X.509 notBefore/notAfter, logs, TLS timestamps. Monotonic returns 1328// time-since-boot, which encodes as ~1970 when (mis)used as an epoch. 1329func sys_clock_gettime_real(ts: *i64) -> i64 { 1330 return __syscall(SYS_CLOCK_GETTIME, 0, ts, 0, 0, 0, 0) 1331} 1332 1333// Wall-clock seconds since the Unix epoch. 1334func sys_now_realtime_sec() -> i64 { 1335 let ts: *i64 = sys_mmap(16) as *i64 1336 sys_clock_gettime_real(ts) 1337 return ts[0] 1338} 1339 1340// Wall-clock milliseconds since the Unix epoch. 1341func sys_now_realtime_ms() -> i64 { 1342 let ts: *i64 = sys_mmap(16) as *i64 1343 sys_clock_gettime_real(ts) 1344 return ts[0] * 1000 + ts[1] / SYS_MAGIC_1000000 1345} 1346 1347// Wall-clock MICROSECONDS since the Unix epoch -- the CROSS-MACHINE stamp. 1348// β˜… Use this, never sys_now_us(), for any value one machine writes and ANOTHER machine judges 1349// (fleet beats, lease expiry, telemetry rows). Monotonic counts from each machine's OWN boot, so 1350// subtracting one node's monotonic stamp from another's monotonic now yields the difference of two 1351// unrelated boot epochs -- the remote row then reads as ancient (or future-forged) and a freshness 1352// guard rejects every honest remote node while looking like it is working. 1353func sys_now_realtime_us() -> i64 { 1354 let ts: *i64 = sys_mmap(16) as *i64 1355 sys_clock_gettime_real(ts) 1356 return ts[0] * SYS_MAGIC_1000000 + ts[1] / 1000 1357} 1358 1359// Convenience: monotonic time in milliseconds. Caller does not own 1360// the timespec buffer -- it is mmap'd once per call (cheap; the 1361// underlying syscall already costs more than the page fault). 1362func sys_now_ms() -> i64 { 1363 let ts: *i64 = sys_mmap(16) as *i64 1364 sys_clock_gettime_mono(ts) 1365 let sec_part: i64 = ts[0] * 1000 1366 let nsec_part: i64 = ts[1] / SYS_MAGIC_1000000 1367 return sec_part + nsec_part 1368} 1369 1370// Convenience: monotonic time in microseconds. Used by per-request 1371// elapsed-time tracking in search engines + benches where ms is too 1372// coarse. Same caller-ownership rules as sys_now_ms. 1373func sys_now_us() -> i64 { 1374 let ts: *i64 = sys_mmap(16) as *i64 1375 sys_clock_gettime_mono(ts) 1376 let sec_part: i64 = ts[0] * SYS_MAGIC_1000000 1377 let nsec_part: i64 = ts[1] / 1000 1378 return sec_part + nsec_part 1379} 1380 1381// Alias used by nx_search_onsite_engine etc. Matches `_us` naming 1382// convention. Substrate-canonical name is sys_now_us; this alias 1383// preserves existing call sites without churn. 1384func sys_clock_now_us() -> i64 { 1385 return sys_now_us() 1386} 1387 1388// Read the entire file at `path` into a fresh mmap'd buffer. Returns 1389// a null-terminated *u8 plus writes the byte count to *out_len. On 1390// error (open failure, oversize) returns null and leaves out_len = 0. 1391// Uses a fixed 1 MiB buffer for the first pass; larger sources need a 1392// growth loop. 1393// ---- process control (Linux RV64) ---------------------------- 1394// 1395// Lets NishiLang programs spawn other processes -- prerequisite 1396// for replacing shell scripts (f6_gate.sh) with .nx equivalents. 1397// NishiOS will expose a different process model (capability-based); 1398// these wrappers are the Linux-host compatibility layer. 1399 1400@ifdef TARGET_X86_64 1401const SYS_CLONE: i64 = 56 1402const SYS_EXECVE: i64 = 59 1403const SYS_WAIT4: i64 = 61 1404const SYS_PIPE2: i64 = 293 1405const SYS_DUP3: i64 = 292 1406@endif 1407 1408@ifndef TARGET_X86_64 1409const SYS_CLONE: i64 = 220 1410const SYS_EXECVE: i64 = 221 1411const SYS_WAIT4: i64 = 260 1412const SYS_PIPE2: i64 = 59 1413const SYS_DUP3: i64 = 24 1414@endif 1415 1416// Clone flags (subset). CLONE_VFORK blocks parent until child 1417// exec's or exits, matching fork() semantics closely enough for 1418// our spawn-then-wait patterns. 1419const CLONE_VM: i64 = 0x00000100 1420const CLONE_VFORK: i64 = 0x00004000 1421const SIGCHLD: i64 = 17 1422 1423// Create a child process via Linux clone(). Returns: 1424// > 0 in the parent: child PID 1425// == 0 in the child: child should exec or exit 1426// < 0 on error: -errno 1427// Uses SIGCHLD as the signal that parent receives on child exit 1428// (the libc fork() default); no shared memory or thread flags. 1429// ---- namespace / container family (debt 1785528831) ---------------- 1430// Moved here from nx_syscalls_x86_64.nx so ONE module owns the wrapper set. Their 1431// absence here is why nx_container.nx had to import that module as a SECOND syscall 1432// layer, which put every wrapper in the TU twice and let definition ORDER pick the 1433// winner, silently, until the duplicate-definition guard made it fail closed. 1434func sys_unshare(flags: i64) -> i64 { 1435 return __syscall(SYS_UNSHARE, flags, 0, 0, 0, 0, 0) 1436} 1437func sys_mount(source: *u8, target: *u8, fs_type: *u8, mountflags: i64, data: *u8) -> i64 { 1438 return __syscall(SYS_MOUNT, source, target, fs_type, mountflags, data, 0) 1439} 1440func sys_chroot(path: *u8) -> i64 { 1441 return __syscall(SYS_CHROOT, path, 0, 0, 0, 0, 0) 1442} 1443func sys_getuid() -> i64 { 1444 return __syscall(SYS_GETUID, 0, 0, 0, 0, 0, 0) 1445} 1446func sys_getgid() -> i64 { 1447 return __syscall(SYS_GETGID, 0, 0, 0, 0, 0, 0) 1448} 1449 1450func sys_fork() -> i64 { 1451 return __syscall(SYS_CLONE, SIGCHLD, 0, 0, 0, 0, 0) 1452} 1453 1454// Replace the current process image. `path` is the executable 1455// (absolute or in $PATH if the child first does a fresh clone). 1456// `argv` is a null-terminated array of *u8 (already-marshalled). 1457// `envp` same shape, or null for "inherit parent's env". 1458// Only returns on failure (-errno). 1459// EXEC WITH A CLEAN FD TABLE (seq1785451144). A child inherits every fd its parent held, INCLUDING 1460// listen sockets, across fork AND execve. That is how nx_opaque_login came to hold mgmt s :18098 1461// alongside mgmt itself -- two listeners on one port, connections split between them, a VALID route 1462// answering 404 on some requests. There is no error anywhere in that state, which is why it was 1463// filed as a transport flake for months. 1464// ADDITIVE ON PURPOSE: sys_execve is left byte-identical (910 call sites across 719 files -- a 1465// global change there is unverifiable in one session). Spawners opt in by calling THIS instead. 1466// AUDIT THAT MAKES IT SAFE: zero call sites in the tree dup3 to a target fd above 2, so no exec d 1467// child is deliberately handed a high fd; 0/1/2 are preserved untouched. 1468// Linux child lifetime binding: call in the freshly forked child, before exec. 1469// The expected parent PID is captured before fork, closing the pre-arm death race. 1470// Kernel semantics bind to the creating thread; privileged exec can clear this. 1471const NX_SYS_PRCTL: i64 = 167 1472const NX_PR_SET_PDEATHSIG: i64 = 1 1473const NX_PR_SET_CHILD_SUBREAPER: i64 = 36 1474func sys_prctl(option: i64, arg: i64) -> i64 { 1475 return __syscall(NX_SYS_PRCTL,option,arg,0,0,0,0) 1476} 1477func sys_bind_parent_lifetime(expected_parent: i64, signal: i64) -> i64 { 1478 if expected_parent <= 0 || signal <= 0 { return 0-22 } 1479 let armed: i64=sys_prctl(NX_PR_SET_PDEATHSIG,signal) 1480 if armed < 0 { return armed } 1481 let parent: i64=__syscall(173,0,0,0,0,0,0) 1482 if parent != expected_parent { return 0-10 } 1483 return 0 1484} 1485 1486// Linux waitid observes termination without releasing the child's PID when WNOWAIT is set. 1487// Portable syscall 95 requires the matching x86 backend translation to 247. 1488const SYS_WAITID_PORTABLE: i64 = 95 1489const NX_WAIT_P_PID: i64 = 1 1490const NX_WAIT_EXITED: i64 = 4 1491const NX_WAIT_NOWAIT: i64 = 0x01000000 1492const NX_WAIT_SIGINFO_BYTES: i64 = 128 1493func sys_waitid(idtype: i64, id: i64, info: *u8, options: i64) -> i64 { 1494 return __syscall(SYS_WAITID_PORTABLE,idtype,id,info as i64,options,0,0) 1495} 1496 1497// Post-fork only: the child owns its descriptor table. The buffer bounds a 1498// getdents batch, never the descriptor numbers or number of open handles. 1499const NX_FD_DENT_BUFFER: i64 = 4096 1500const NX_SYS_CLOSE_RANGE: i64 = 436 // Linux x86_64 and asm-generic ABI 1501const NX_FD_UINT_MAX: i64 = 4294967295 1502func sys_close_inherited_proc(first: i64) -> i64 { 1503 let directory: i64=sys_openat_rd("/proc/self/fd") 1504 if directory < 0 { return directory } 1505 let buf: *u8=sys_mmap(NX_FD_DENT_BUFFER) 1506 var result: i64=0 1507 var running: i64=1 1508 while running == 1 { 1509 let n: i64=sys_getdents64(directory,buf,NX_FD_DENT_BUFFER) 1510 if n == (0-4) { continue } 1511 if n <= 0 { result=n; break } 1512 var off: i64=0 1513 while off < n { 1514 if n-off < 20 { result=0-5; running=0; break } 1515 let rec: *u8=buf+off 1516 let size: i64=dirent_reclen(rec) 1517 if size < 20 || size > n-off { result=0-5; running=0; break } 1518 var i: i64=19 1519 var fd: i64=0 1520 var valid: i64=1 1521 while i < size { 1522 let c: i64=rec[i] as i64 1523 if c == 0 { break } 1524 if c < 48 || c > 57 { valid=0; break } 1525 if fd > (2147483647-(c-48))/10 { valid=0; break } 1526 fd=fd*10+c-48; i=i+1 1527 } 1528 if i == 19 || i == size { valid=0 } 1529 if valid == 1 && fd >= first && fd != directory { 1530 // Linux releases the descriptor even when close reports a late 1531 // I/O error; never retry close and risk a reused descriptor. 1532 let closed: i64=sys_close(fd) 1533 if closed < 0 && closed != (0-9) { result=closed; running=0; break } 1534 } 1535 off=off+size 1536 } 1537 } 1538 let closedir: i64=sys_close(directory) 1539 sys_munmap(buf,NX_FD_DENT_BUFFER) 1540 if result == 0 && closedir < 0 { result=closedir } 1541 return result 1542} 1543func sys_close_inherited(first: i64) -> i64 { 1544 if first < 0 { return 0-22 } 1545 let rc: i64=__syscall(NX_SYS_CLOSE_RANGE,first,NX_FD_UINT_MAX,0,0,0,0) 1546 if rc == (0-38) { return sys_close_inherited_proc(first) } 1547 return rc 1548} 1549func sys_execve_clean(path: *u8, argv: *i64, envp: *i64) -> i64 { 1550 let rc: i64=sys_close_inherited(3) 1551 if rc < 0 { return rc } 1552 return sys_execve(path,argv,envp) 1553} 1554 1555func sys_execve(path: *u8, argv: *i64, envp: *i64) -> i64 { 1556 return __syscall(SYS_EXECVE, path, argv, envp, 0, 0, 0) 1557} 1558 1559// Wait for a child to exit. `pid` = -1 waits for ANY child, 1560// otherwise waits for that specific PID. `status` is a caller- 1561// mmapped i64 slot: on exit the low 16 bits carry Linux's w* status 1562// flags (WIFEXITED / WEXITSTATUS). Returns the reaped child's PID 1563// or -errno. 1564func sys_wait4(pid: i64, status: *i64, options: i64) -> i64 { 1565 return __syscall(SYS_WAIT4, pid, status, options, 0, 0, 0) 1566} 1567 1568// Extract exit code from a wait4 status word. Matches the glibc 1569// WEXITSTATUS macro: bits 8-15 of the low 16. 1570func wait_exit_code(status: i64) -> i64 { 1571 return (status >> 8) & 0xFF 1572} 1573 1574// Terminating signal from a wait4 status (0 when the child exited normally). Sibling of 1575// wait_exit_code; RESTORED 2026-07-30 after a stale whole-tree push erased both it and 1576// sys_ignore_sigpipe below, while three files still CALLED them (nx_http_server, nx_sigpipe_gate, 1577// nx_tools_api_serve) -- so the tree could not build until they came back. 1578func wait_term_signal(status: i64) -> i64 { 1579 return status & 0x7f 1580} 1581 1582// THE ONE RULER for "what result code did this process actually produce". Use this, not 1583// wait_exit_code, anywhere the answer becomes a VERDICT. 1584// 1585// WHY IT EXISTS, MEASURED 2026-08-25. wait_exit_code is WEXITSTATUS and is correctly named: 1586// bits 8-15 of the status word. But a child KILLED BY A SIGNAL has no exit status at all, and 1587// those bits are ZERO -- so a SEGFAULTING process is indistinguishable from a clean exit 0 to 1588// every caller that reads only wait_exit_code. Measured live: a gate that SIGSEGV'd mid-run was 1589// served by /api/gate_run as exit_code 0, verdict GREEN. A CRASHED GATE WORE A PASS. 1590// 1591// This is not a new discovery in this estate -- and that is the point. nx_gatekit_lib's 1592// gk_wait_code already carried exactly this rule, with its own measurement recorded (two gates 1593// the 60 s watchdog KILLED journaled `GREEN exit=0 ms=60443`). It was fixed THERE in August and 1594// left unfixed in nx_tool_run, which is the shared exec primitive sitting behind /api/gate_run, 1595// /api/build and 51 other consumers. A LAW APPLIED IN ONE ORGAN AND NOT ITS SIBLING IS HALF A 1596// LAW, AND THE HALF LEFT UNDONE IS THE ONE ON THE PRODUCTION PATH. So the rule now lives HERE, 1597// beside the two accessors it is composed of, and gk_wait_code delegates to it: one ruler. 1598// 1599// Shell convention 128+signal (137 SIGKILL, 139 SIGSEGV) is deliberate: it makes the death both 1600// VISIBLE and NON-ZERO, so every existing caller that branches on rc != 0 sees it with no change. 1601// wait_exit_code is left EXACTLY as it was -- 85 call sites across the corpus (corpus_complete=1) 1602// read it, and silently redefining WEXITSTATUS under them would be the cure being worse. 1603func wait_status_rc(status: i64) -> i64 { 1604 let sig: i64 = wait_term_signal(status) 1605 if sig != 0 { return 128 + sig } 1606 return wait_exit_code(status) 1607} 1608 1609// Ignore SIGPIPE process-wide, so writing to a socket the peer already closed returns -EPIPE 1610// instead of KILLING the process. SIGPIPE default action is TERMINATE, which for a daemon means 1611// every client that walks away mid-response is an outage -- this one call at the listen primitive 1612// is inherited by all 52 consumers of nx_http_server_listen. 1613// rt_sigaction(SIGPIPE, {handler=SIG_IGN}, NULL, 8): syscall 13 on x86-64, which happens to equal 1614// the signal number. SA_RESTORER is deliberately NOT set -- the kernel consults it only when it 1615// DELIVERS a handler frame, and SIG_IGN never delivers one. 1616// PROVEN, not asserted: nx_sigpipe_gate forks a child that writes to a closed pipe and demands 1617// death-by-signal-13 WITHOUT this call and a clean -EPIPE WITH it. 1618// Restore a signal to its DEFAULT disposition. THE INVERSE OF sys_ignore_sigpipe, and it exists 1619// because SIG_IGN is inherited across BOTH fork and execve: a daemon that ignores SIGPIPE hands 1620// that ignore to every child it spawns, FOREVER. That silently corrupted verification -- the 1621// sigpipe gate reported 4/5 RED under /api/gate_run and 5/5 GREEN under a shell, same binary, 1622// same minute, because its DISEASE control (writing to a closed peer must KILL) could not be 1623// observed inside an environment where the kill was already disabled (seq1463). A harness must 1624// not change the state it is verifying; where it must, it has to hand back a clean slate. 1625// ⚠the same inheritance can also produce a FALSE GREEN, which is the far more dangerous half. 1626func sys_default_signal(sig: i64) -> i64 { 1627 let act: *i64 = sys_mmap(64) as *i64 1628 act[0] = 0 1629 act[1] = 0 1630 act[2] = 0 1631 act[3] = 0 1632 return __syscall(13, sig, act as i64, 0, 8, 0, 0) 1633} 1634 1635func sys_ignore_sigpipe() -> i64 { 1636 let act: *i64 = sys_mmap(64) as *i64 1637 act[0] = 1 1638 act[1] = 0 1639 act[2] = 0 1640 act[3] = 0 1641 return __syscall(13, 13, act as i64, 0, 8, 0, 0) 1642} 1643 1644// Create a pipe. `fds` must point at 8+ writable bytes; the kernel 1645// packs BOTH int32 fds into fds[0]: read end = low 32 bits, write end 1646// = HIGH 32 bits (fds[1] is never written -- the old comment claiming 1647// fds[1]=write-end caused a false-pass KAT + a hung gate, 2026-07-16). 1648// Extract: rfd = fds[0] & 0xffffffff; wfd = (fds[0] / 4294967296) & 1649// 0xffffffff. Returns 0 on success, -errno on failure. 1650func sys_pipe2(fds: *i64, flags: i64) -> i64 { 1651 return __syscall(SYS_PIPE2, fds, flags, 0, 0, 0, 0) 1652} 1653 1654// Duplicate `oldfd` onto `newfd`, closing `newfd` first if open. 1655// Used to wire child stdout to a pipe: dup3(pipe_write_end, 1). 1656func sys_dup3(oldfd: i64, newfd: i64, flags: i64) -> i64 { 1657 return __syscall(SYS_DUP3, oldfd, newfd, flags, 0, 0, 0) 1658} 1659 1660// ---- directory listing (Linux RV64 getdents64) --------------- 1661// 1662// Foundation for ls / glob / dir-walk helpers. Linux returns 1663// linux_dirent64 records: 1664// u64 d_ino (inode, ignored here) 1665// s64 d_off (next-record offset) 1666// u16 d_reclen (this record's byte length) 1667// u8 d_type (file type; DT_DIR=4, DT_REG=8, DT_LNK=10) 1668// char d_name[] (null-terminated name, padded so d_reclen 1669// carries us to the next record boundary) 1670// Total struct header: 19 bytes, then name up to d_reclen - 19. 1671 1672@ifdef TARGET_X86_64 1673const SYS_GETDENTS64: i64 = 217 1674@endif 1675@ifndef TARGET_X86_64 1676const SYS_GETDENTS64: i64 = 61 1677@endif 1678 1679const DT_UNKNOWN: i64 = 0 1680const DT_FIFO: i64 = 1 1681const DT_CHR: i64 = 2 1682const DT_DIR: i64 = 4 1683const DT_BLK: i64 = 6 1684const DT_REG: i64 = 8 1685const DT_LNK: i64 = 10 1686const DT_SOCK: i64 = 12 1687 1688// Raw syscall. Returns bytes written on success (0 = end-of-dir), 1689// or -errno on failure. 1690func sys_getdents64(fd: i64, buf: *u8, buf_len: i64) -> i64 { 1691 return __syscall(SYS_GETDENTS64, fd, buf, buf_len, 0, 0, 0) 1692} 1693 1694// Extract fields from a linux_dirent64 record. `rec` points at 1695// the start of the record; fields are at fixed offsets. 1696func dirent_reclen(rec: *u8) -> i64 { 1697 // d_reclen is u16 at offset 16. Read as two bytes little-endian. 1698 let lo: i64 = rec[16] 1699 let hi: i64 = rec[17] 1700 return lo | (hi << 8) 1701} 1702 1703func dirent_type(rec: *u8) -> i64 { 1704 return rec[18] 1705} 1706 1707// Pointer to the null-terminated name inside the record. 1708func dirent_name(rec: *u8) -> *u8 { 1709 let base: i64 = rec as i64 1710 return (base + 19) as *u8 1711} 1712 1713// ---- content-addressed file reader --------------------------- 1714 1715func sys_read_file(path: *u8, out_len: *i64) -> *u8 { 1716 let fd: i64 = sys_openat_rd(path) 1717 if fd < 0 { 1718 *out_len = 0 1719 return 0 as *u8 1720 } 1721 // DEBT-EATEN 2026-07-15: the old fixed 4 GiB cap SILENTLY TRUNCATED bigger files (a 9 GB gguf would 1722 // short-read into plausible-garbage tensors -- the worst failure class). Now the buffer is sized from 1723 // the file itself (lseek END), so ANY size reads fully. Physical pages still allocate on-demand. For 1724 // zero-copy any-size READ-ONLY access prefer sys_map_file (below). 1725 // DEBT-EATEN 2026-08-19 (1787076780): when the size is UNKNOWABLE (lseek END <= 0: /proc files, pipes 1726 // -- AND every empty regular file, which reports 0 just the same) this used to reserve 1727 // SYS_MAGIC_4294967296 of address space per call. Untouched pages were never resident, but the 1728 // mapping WAS: a daemon that read an empty registry every sweep ballooned its VmSize by 4 GiB per 1729 // read (measured: smoke instances at a 4.2 GB base), the leak screens flagged it, and sys_free_file 1730 // could only release what was read. The size-unknowable path now GROWS: start at SYS_READ_GROW_INIT, 1731 // double while the window fills, and hand back an EXACT mapping (total + 16) so sys_free_file 1732 // releases all of it. An empty file costs one small read and a 16-byte arena cell; /proc/stat fits 1733 // the first window; a pipe of any length still reads whole. The known-size path is unchanged. 1734 let fsz: i64 = sys_lseek(fd, 0, 2) 1735 sys_lseek(fd, 0, 0) 1736 var cap: i64 = SYS_READ_GROW_INIT 1737 var grow: i64 = 1 1738 if fsz > 0 { cap = fsz; grow = 0 } 1739 var buf: *u8 = sys_mmap(cap + 16) 1740 var total: i64 = 0 1741 var go: i64 = 1 1742 while go == 1 { 1743 let base: i64 = buf as i64 1744 let tail: *u8 = (base + total) as *u8 1745 let n: i64 = sys_read(fd, tail, cap - total) 1746 if n <= 0 { go = 0 } 1747 if n > 0 { total = total + n } 1748 if total >= cap { 1749 if grow == 0 { go = 0 } else { 1750 // the window filled and the size is unknown: double it, copy, release the old mapping 1751 let ncap: i64 = cap * 2 1752 let nb: *u8 = sys_mmap(ncap + 16) 1753 var ci: i64 = 0 1754 let obase: i64 = buf as i64 1755 let nbase: i64 = nb as i64 1756 while ci < total { let src: *u8 = (obase + ci) as *u8; let dst: *u8 = (nbase + ci) as *u8; dst[0] = src[0]; ci = ci + 1 } 1757 sys_munmap(buf, cap + 16) 1758 buf = nb 1759 cap = ncap 1760 } 1761 } 1762 } 1763 sys_close(fd) 1764 if grow == 1 { 1765 // hand back an EXACT mapping so the paired free releases everything (the doubled window would 1766 // otherwise leave its slack mapped forever -- the address-space leak this change exists to end) 1767 let xb: *u8 = sys_mmap(total + 16) 1768 var xi: i64 = 0 1769 let gbase: i64 = buf as i64 1770 let xbase: i64 = xb as i64 1771 while xi < total { let gsrc: *u8 = (gbase + xi) as *u8; let xdst: *u8 = (xbase + xi) as *u8; xdst[0] = gsrc[0]; xi = xi + 1 } 1772 sys_munmap(buf, cap + 16) 1773 buf = xb 1774 } 1775 // Null-terminate for the lexer. 1776 let bbase: i64 = buf as i64 1777 let term: *u8 = (bbase + total) as *u8 1778 term[0] = 0 1779 *out_len = total 1780 return buf 1781} 1782 1783// PAIRED FREE FOR sys_read_file (2026-08-17). sys_read_file mmaps `cap + 16` where cap is the FILE SIZE 1784// and returns only the pointer -- so any caller that frees it must know the padding, and a caller that 1785// unmaps `len` alone leaks the tail page whenever the file size sits just under a page boundary. 1786// β˜…A CALLER FORCED TO KNOW ITS ALLOCATOR'S PADDING IS A COUPLING THAT WILL DRIFT -- so the +16 lives 1787// HERE, beside the +16 it mirrors, instead of being retyped at every call site. 1788// Pass the length sys_read_file reported through out_len; this re-derives the mapping from it. 1789// Null-safe by construction: sys_read_file returns 0 on failure, so callers need no extra guard -- 1790// β˜…A FREE THAT REFUSES NULL IS A FREE NOBODY HAS TO WRAP IN AN IF. 1791// EXACT for every path since 2026-08-19: the size-unknowable fallback (lseek <= 0: /proc, pipes, empty 1792// regular files) now returns a mapping of exactly total + 16, so this releases ALL of it. (It used to 1793// map SYS_MAGIC_4294967296 of address space and release only what was read -- stated then, ended now.) 1794// WHY IT EXISTS: nx_sites_daemon serves /wiki/roadmap by calling sys_read_file PER REQUEST inside a loop 1795// that runs up to NX_SD_MAX_REQ_PER_CONN (64) times per connection and never released it -- an 8,408 B 1796// file became 3 fresh pages and a fresh kernel VMA on every hit, held until the child exited. 1797func sys_free_file(buf: *u8, len: i64) -> i64 { 1798 if (buf as i64) == 0 { return 0 } 1799 if len < 0 { return 0 } 1800 return sys_munmap(buf, len + 16) 1801} 1802 1803// Read-only FILE-BACKED map of the whole file (PROT_READ=1, MAP_PRIVATE=2): any size, zero-copy -- only 1804// touched pages become resident (the lazy-MoE shape: a 9 GB model serves in ~active-set RSS, and load 1805// time is ~0 because nothing is copied). NO NUL pad (a file mapping cannot be extended) -- BINARY 1806// consumers only; text/lexer callers keep sys_read_file. Returns 0 on failure; *out_len = file size. 1807// Read-only by construction (PROT_READ; writes fault -- Rule 26-friendly). 1808func sys_map_file(path: *u8, out_len: *i64) -> *u8 { 1809 *out_len = 0 1810 let fd: i64 = sys_openat_rd(path) 1811 if fd < 0 { return 0 as *u8 } 1812 let fsz: i64 = sys_lseek(fd, 0, 2) 1813 if fsz <= 0 { sys_close(fd); return 0 as *u8 } 1814 let r: i64 = __syscall(SYS_MMAP, 0, fsz, 1, 2, fd, 0) 1815 sys_close(fd) 1816 if r <= 0 { return 0 as *u8 } 1817 *out_len = fsz 1818 return r as *u8 1819} 1820 1821// Sleep for `ms` milliseconds against CLOCK_MONOTONIC (relative). 1822// Returns 0 on success, negative errno on failure. Caller-supplied 1823// budget: ms <= 0 is a no-op; very large values are accepted as-is 1824// (the kernel will saturate to its own clamp). Defined at the bottom 1825// of this file so sys_mmap is in scope (single-pass parser). 1826func sys_sleep_ms(ms: i64) -> i64 { 1827 if ms <= 0 { return 0 } 1828 // struct timespec { sec: i64, nsec: i64 } -- 16 bytes RV64. 1829 let req: *u8 = sys_mmap(16) 1830 let rem: *u8 = sys_mmap(16) 1831 let secs: i64 = ms / 1000 1832 let nsec: i64 = (ms - secs * 1000) * SYS_MAGIC_1000000 // remainder ms -> ns 1833 let req_sec: *i64 = req as *i64 1834 let req_nsec: *i64 = ((req as i64) + 8) as *i64 1835 req_sec[0] = secs 1836 req_nsec[0] = nsec 1837 // clock_nanosleep(CLOCK_MONOTONIC=1, flags=0, req, rem). On EINTR (-4) a signal (e.g. SIGCHLD from a 1838 // reaped child) cut the sleep short and wrote the leftover into rem -- RESUME it, otherwise a caller 1839 // that uses the sleep as a timer (the torrent pool's 2s tick) gets spun into a busy loop by child 1840 // deaths and any tick-based budget collapses to milliseconds. A sleep must sleep its full duration. 1841 var r: i64 = __syscall(SYS_CLOCK_NANOSLEEP, 1, 0, req as i64, rem as i64, 0, 0) 1842 var guard: i64 = 0 1843 while r == (0 - 4) { 1844 if guard > SYS_MAGIC_100000 { r = 0 } else { 1845 let rs: *i64 = rem as *i64 1846 let rn: *i64 = ((rem as i64) + 8) as *i64 1847 req_sec[0] = rs[0] 1848 req_nsec[0] = rn[0] 1849 r = __syscall(SYS_CLOCK_NANOSLEEP, 1, 0, req as i64, rem as i64, 0, 0) 1850 guard = guard + 1 1851 } 1852 } 1853 sys_munmap(req, 16); sys_munmap(rem, 16) // FREE the timespec pages -- every call mmap'd 2 pages; in a 1854 // long-running poll loop (the supervisor's 15s tick) that leaked ~8KB/iter until mmap -> -12 -> SEGFAULT. 1855 return r 1856} 1857 1858// ---- sockets (RV64 generic syscall numbers) ---------------------- 1859// 1860// Source uses RV64 numbers; the x86_64 backend's 1861// x86ctx_rv64_to_x86_64_syscall table translates at codegen time. 1862// Numbers from arch/arm64/include/asm/unistd.h (RV64 inherits the 1863// generic ABI). 1864 1865// Socket-family syscall numbers via @ifdef macro -- mirrors the 1866// pattern already used for SYS_READ/WRITE/MMAP/etc. above. Without 1867// this gate, --target x86_64 compiled the RV64 numbers as literals 1868// into the `syscall` instruction (e.g. 198 = sched_setaffinity on 1869// x86_64, not socket) and any daemon using sys_socket() died with 1870// ENOSYS before printing its banner -- caught by the nx_signaling 1871// stone S2 deploy on 2026-05-20 (see [[project-cross-isa-syscall- 1872// unification-gap-2026-05-20]]). 1873@ifdef TARGET_X86_64 1874const SYS_SOCKET: i64 = 41 1875const SYS_BIND: i64 = 49 1876const SYS_LISTEN: i64 = 50 1877const SYS_ACCEPT: i64 = 43 1878const SYS_CONNECT: i64 = 42 1879const SYS_SETSOCKOPT: i64 = 54 1880const SYS_SENDTO: i64 = 44 1881const SYS_RECVFROM: i64 = 45 1882const SYS_SHUTDOWN: i64 = 48 1883@endif 1884 1885@ifndef TARGET_X86_64 1886const SYS_SOCKET: i64 = 198 1887const SYS_BIND: i64 = 200 1888const SYS_LISTEN: i64 = 201 1889const SYS_ACCEPT: i64 = 202 1890const SYS_CONNECT: i64 = 203 1891const SYS_SETSOCKOPT: i64 = 208 1892const SYS_SENDTO: i64 = 206 1893const SYS_RECVFROM: i64 = 207 1894const SYS_SHUTDOWN: i64 = 210 1895@endif 1896 1897// Socket-option constants used by nx_http_server / nx_https_server. 1898const SOL_SOCKET: i64 = 1 1899const SO_REUSEADDR: i64 = 2 1900// Receive/send timeouts (Linux x86_64). optval is a struct timeval 1901// {tv_sec: i64, tv_usec: i64} (16 bytes). Essential on PUBLIC sockets: 1902// without them, a single silent/slow client hangs a blocking read 1903// forever -> trivial DoS on a single-threaded accept loop. 1904const SO_SNDTIMEO: i64 = 21 1905const SO_RCVTIMEO: i64 = 20 1906 1907// setsockopt(2) -- set a socket option. Defined BEFORE its first caller 1908// (sys_set_socket_timeout, below): NishiLang forbids forward references, 1909// so the definition must precede every use. 1910func sys_setsockopt(fd: i64, level: i64, optname: i64, 1911 optval: *u8, optlen: i64) -> i64 { 1912 return __syscall(SYS_SETSOCKOPT, fd, level, optname, optval, optlen, 0) 1913} 1914 1915// Set a receive+send timeout (in whole seconds) on a socket fd. 1916// tv is munmap'd before return (LEAK FIXED 2026-07-16): this is called once per PROBE by the daemon 1917// supervisor (35/cycle forever -> ~800MB VSZ/day) and once per CONNECTION by fork-per-connection daemons. 1918// The unfreed page-per-call ballooned VSZ until heuristic overcommit made fork() return -ENOMEM (the 1919// proven pid=-12 failure class) -- likely the historical VSZ pressure behind the vsz_watchdog. 1920func sys_set_socket_timeout(fd: i64, secs: i64) -> i64 { 1921 let tv: *i64 = (sys_mmap(16)) as *i64 1922 tv[0] = secs // tv_sec 1923 tv[1] = 0 // tv_usec 1924 sys_setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, tv as *u8, 16) 1925 sys_setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, tv as *u8, 16) 1926 sys_munmap(tv as *u8, 16) 1927 return 0 1928} 1929 1930// alarm(2): deliver SIGALRM after `secs` seconds (0 cancels a pending alarm). No SIGALRM handler is installed, so 1931// the default action TERMINATES the process. Used as a per-request watchdog inside a forked request-child: a 1932// pathologically-slow page can then never hang the child forever (which would leak its buffers + pile up procs). 1933const SYS_ALARM: i64 = 37 1934func sys_alarm(secs: i64) -> i64 { return __syscall(SYS_ALARM, secs, 0, 0, 0, 0, 0) } 1935 1936const AF_INET: i64 = 2 1937const SOCK_STREAM: i64 = 1 1938const SOCK_DGRAM: i64 = 2 1939 1940func sys_socket(domain: i64, sock_type: i64, protocol: i64) -> i64 { 1941 return __syscall(SYS_SOCKET, domain, sock_type, protocol, 0, 0, 0) 1942} 1943// Pack an AF_INET any-address sockaddr_in (16 bytes) for `port` at `addr`. 1944// RESTORED INTO THE OWNER 2026-08-19: this lived in the old full nx_syscalls_x86_64.nx and was the 1945// one wrapper WITH LIVE CALLERS (nx_nishipages_serve, nx_udp) that the 2026-07-31 alias-stub 1946// consolidation dropped -- both lanes sat NAS-unbuildable ("I do not know the name") until the 1947// rebuild-drain surfaced them. Body verbatim from the old file, including its documented 1948// workaround: NO `as u8` casts on the byte stores -- the array-element-store already truncates 1949// when the lvalue is *u8, and casts on this path once tripped a codegen defect. 1950// (The old file's other two uncalled orphans, sys_pivot_root/sys_umount2, were left dead on a 1951// zero-caller full-tree grep -- restoring an uncalled wrapper is inventory, not capability.) 1952func sockaddr_in_init(addr: *u8, port: i64) -> i64 { 1953 addr[0] = 2 // AF_INET low byte 1954 addr[1] = 0 1955 // Port in network byte order (big-endian). 1956 let hi: i64 = (port >> 8) & 0xFF 1957 let lo: i64 = port & 0xFF 1958 addr[2] = hi 1959 addr[3] = lo 1960 addr[4] = 0 1961 addr[5] = 0 1962 addr[6] = 0 1963 addr[7] = 0 1964 addr[8] = 0 1965 addr[9] = 0 1966 addr[10] = 0 1967 addr[11] = 0 1968 addr[12] = 0 1969 addr[13] = 0 1970 addr[14] = 0 1971 addr[15] = 0 1972 return 0 1973} 1974 1975func sys_bind(fd: i64, addr: *u8, addr_len: i64) -> i64 { 1976 return __syscall(SYS_BIND, fd, addr, addr_len, 0, 0, 0) 1977} 1978func sys_listen(fd: i64, backlog: i64) -> i64 { 1979 return __syscall(SYS_LISTEN, fd, backlog, 0, 0, 0, 0) 1980} 1981// accept(2) -- accept the next pending connection on a listening socket. 1982// Single-arg form (kernel ignores NULL addr/addr_len writes). Existing 1983// nx_http_server callers use this signature; the 3-arg form is provided 1984// as sys_accept_with_addr for outliers needing peer address. 1985func sys_accept(fd: i64) -> i64 { 1986 return __syscall(SYS_ACCEPT, fd, 0, 0, 0, 0, 0) 1987} 1988func sys_accept_with_addr(fd: i64, addr: *u8, addr_len: *i64) -> i64 { 1989 return __syscall(SYS_ACCEPT, fd, addr, addr_len, 0, 0, 0) 1990} 1991// shutdown(2) -- half-close a socket. how: 0=RD, 1=WR, 2=RDWR. 1992func sys_shutdown(fd: i64, how: i64) -> i64 { 1993 return __syscall(SYS_SHUTDOWN, fd, how, 0, 0, 0, 0) 1994} 1995func sys_connect(fd: i64, addr: *u8, addr_len: i64) -> i64 { 1996 return __syscall(SYS_CONNECT, fd, addr, addr_len, 0, 0, 0) 1997} 1998func sys_sendto(fd: i64, buf: *u8, n: i64, flags: i64, 1999 dest_addr: *u8, addr_len: i64) -> i64 { 2000 return __syscall(SYS_SENDTO, fd, buf, n, flags, dest_addr, addr_len) 2001} 2002func sys_recvfrom(fd: i64, buf: *u8, n: i64, flags: i64, 2003 src_addr: *u8, addr_len: *i64) -> i64 { 2004 return __syscall(SYS_RECVFROM, fd, buf, n, flags, src_addr, addr_len) 2005} 2006 2007// ---- SCM_RIGHTS DESCRIPTOR PASSING (sendmsg/recvmsg over AF_UNIX) ----------------------------- 2008// ADDED 2026-08-21 for /compare/trafficsafety TS1. Until now sys_sendmsg was ABSENT-PROVEN from the 2009// whole tree (corpus_complete=1), so the mechanism nginx, HAProxy and Envoy all use for hitless 2010// replacement -- MOVING the listening descriptor rather than re-binding it -- could not be written 2011// at all. SO_REUSEPORT co-binding is an ACCEPT-DISTRIBUTION primitive, NOT a handoff primitive: 2012// LWN documents that changing the set of listening sockets on a port drops connections during the 2013// three-way handshake, so co-binding proves two binders and can never prove zero drops. 2014// 2015// EVERY OFFSET BELOW IS MEASURED, NOT RECALLED. They were read out of the platform's own headers 2016// with offsetof/sizeof/CMSG_LEN compiled for x86_64: 2017// msghdr 56 = name 0 | namelen 8 (u32) | iov 16 | iovlen 24 | control 32 | controllen 40 | flags 48 (u32) 2018// iovec 16 = base 0 | len 8 2019// cmsghdr 16 = len 0 (u64) | level 8 (u32) | type 12 (u32), data at 16 2020// CMSG_LEN(4)=20 CMSG_SPACE(4)=24 sendmsg=46 recvmsg=47 socketpair=53 2021// AF_UNIX=1 SOL_SOCKET=1 SCM_RIGHTS=1 MSG_CMSG_CLOEXEC=1073741824 2022// A WRONG LAYOUT HERE DOES NOT FAIL LOUD. The syscall still returns a positive byte count and 2023// simply transfers no descriptor, which is why the gate for this proves the property by passing a 2024// REAL descriptor between two REAL processes and then USING it, never by reading a return code. 2025// x86_64 Linux numbers, DELIBERATELY UNGUARDED, and the reason is a measurement rather than a 2026// preference. The first draft of this block wrapped these three in the same 2027// @ifdef TARGET_X86_64 / @ifndef pair every other syscall number in this file uses. On an x86 build 2028// that made every call ENOSYS, and the probe that caught it printed why: 2029// CONSTS SYS_SENDMSG=211 SYS_RECVMSG=212 SYS_SOCKETPAIR=199 SYS_WRITE=64 2030// N sendmsg PLAIN via the CONST rc=-38 (211 is unassigned on x86_64) 2031// N2 sendmsg PLAIN via the LITERAL rc=1 2032// SYS_WRITE reading 64 is the tell and it is NOT MINE: the file's own original guarded block 2033// resolves to its RV64 branch when the constant is referenced, on a build whose sys_write plainly 2034// works. So a constant inside these guards is not reliably the value the guard appears to select. 2035// !! A GUARD THAT SILENTLY SELECTS THE OTHER TARGET'S NUMBER IS WORSE THAN NO GUARD: the call still 2036// compiles, still returns, and dispatches a DIFFERENT SYSCALL. Syscall 199 on x86_64 is 2037// fremovexattr, which is why socketpair appeared to answer EFAULT for every input including a NULL 2038// vector and an unsupported domain -- varying the ARGUMENTS can never reveal that the NUMBER is 2039// wrong, because every variant was equally wrong. 2040// => RV64 support for these three is an OPEN, NAMED requirement, blocked on that toolchain 2041// behaviour. It is left undone and stated rather than papered over with a guard measured not to 2042// work. The estate already keeps nx_syscalls_x86_64.nx as the explicit single-target mirror for 2043// exactly this class of problem. 2044const SYS_SENDMSG: i64 = 46 2045const SYS_RECVMSG: i64 = 47 2046const SYS_SOCKETPAIR: i64 = 53 2047const SCM_AF_UNIX: i64 = 1 2048const SCM_SOL_SOCKET: i64 = 1 2049const SCM_RIGHTS_TYPE: i64 = 1 2050const SCM_MSG_CMSG_CLOEXEC: i64 = 1073741824 2051const SCM_MSGHDR_BYTES: i64 = 56 2052const SCM_MSGHDR_OFF_IOV: i64 = 16 2053const SCM_MSGHDR_OFF_IOVLEN: i64 = 24 2054const SCM_MSGHDR_OFF_CTRL: i64 = 32 2055const SCM_MSGHDR_OFF_CTRLLEN: i64 = 40 2056const SCM_IOVEC_BYTES: i64 = 16 2057const SCM_IOVEC_OFF_BASE: i64 = 0 2058const SCM_IOVEC_OFF_LEN: i64 = 8 2059const SCM_CMSG_OFF_LEN: i64 = 0 2060const SCM_CMSG_OFF_LEVEL: i64 = 8 2061const SCM_CMSG_OFF_TYPE: i64 = 12 2062const SCM_CMSG_OFF_DATA: i64 = 16 2063const SCM_CMSG_LEN_1FD: i64 = 20 2064const SCM_CMSG_SPACE_1FD: i64 = 24 2065const SCM_IOV_COUNT_ONE: i64 = 1 2066const SCM_U32_BYTES: i64 = 4 2067const SCM_BYTE_RADIX: i64 = 256 2068const SCM_FDPAIR_BYTES: i64 = 8 2069// One real data byte travels with the ancillary data ON PURPOSE: a sendmsg carrying SCM_RIGHTS and 2070// NO ordinary payload is the classic silent no-transfer, and it returns 0 rather than an error. 2071const SCM_PAYLOAD_BYTES: i64 = 1 2072const SCM_PAYLOAD_BYTE: i64 = 70 2073// Distinguishable refusals, each naming WHICH conjunct failed -- a compound assertion that will not 2074// name its failing conjunct is a false-alarm generator. All are negative and all sit far outside the 2075// errno range, so no caller can confuse one with a kernel error or with a valid descriptor. 2076const SCM_ERR_NO_CMSG: i64 = 0 - 901 2077const SCM_ERR_CMSG_LEN: i64 = 0 - 902 2078const SCM_ERR_CMSG_LEVEL: i64 = 0 - 903 2079const SCM_ERR_CMSG_TYPE: i64 = 0 - 904 2080 2081func scm_zero(base: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { base[i] = 0; i = i + 1 } return 0 } 2082func scm_put_i64(base: *u8, off: i64, v: i64) -> i64 { 2083 let p: *i64 = ((base as i64) + off) as *i64 2084 p[0] = v 2085 return 0 2086} 2087func scm_get_i64(base: *u8, off: i64) -> i64 { 2088 let p: *i64 = ((base as i64) + off) as *i64 2089 return p[0] 2090} 2091// The two cmsg header fields and the descriptor slot itself are 4-byte ints, so they are packed and 2092// unpacked byte by byte in little-endian order. Radix arithmetic rather than bit shifts, matching 2093// sockaddr_in_init's documented style on this exact path. 2094func scm_put_u32(base: *u8, off: i64, v: i64) -> i64 { 2095 var i: i64 = 0 2096 var m: i64 = v 2097 while i < SCM_U32_BYTES { 2098 base[off + i] = m % SCM_BYTE_RADIX 2099 m = m / SCM_BYTE_RADIX 2100 i = i + 1 2101 } 2102 return 0 2103} 2104func scm_get_u32(base: *u8, off: i64) -> i64 { 2105 var v: i64 = 0 2106 var mult: i64 = 1 2107 var i: i64 = 0 2108 while i < SCM_U32_BYTES { 2109 v = v + (base[off + i] as i64) * mult 2110 mult = mult * SCM_BYTE_RADIX 2111 i = i + 1 2112 } 2113 return v 2114} 2115 2116func sys_sendmsg(fd: i64, msg: *u8, flags: i64) -> i64 { 2117 return __syscall(SYS_SENDMSG, fd, msg, flags, 0, 0, 0) 2118} 2119func sys_recvmsg(fd: i64, msg: *u8, flags: i64) -> i64 { 2120 return __syscall(SYS_RECVMSG, fd, msg, flags, 0, 0, 0) 2121} 2122// socketpair(2). sv receives TWO 4-byte descriptors, so it is a *u8 read with scm_get_u32 -- a 2123// single *i64 read would splice both descriptors into one number and the second would vanish. 2124// !! THIS NUMBER IS NOT REACHING socketpair, AND THE FIRST DIAGNOSIS OF THAT WAS WRONG. 2125// Measured 2026-08-21: every call returns -14 (EFAULT) -- with a valid pointer, with a NULL vector, 2126// and with an UNSUPPORTED DOMAIN alike. The first reading of that evidence was "the host refuses 2127// this call for every input", and it was REFUTED by measuring the emitted constants instead of the 2128// arguments. TARGET_X86_64 is hard-pinned UNDEFINED in this toolchain (see nx_syscalls_x86_64.nx 2129// and nx_tokenizer.nx), so the @ifndef branch is what compiles and the x86 backend TRANSLATES RV64 2130// syscall numbers at emit time. Under that translation 53 is RV64 fchmodat, whose SECOND argument 2131// is a path pointer -- and SOCK_STREAM==1 as a path pointer is exactly EFAULT, every time, 2132// regardless of the other arguments. 2133// * VARYING THE ARGUMENTS CAN NEVER REVEAL THAT THE SYSCALL NUMBER IS WRONG: every variant is 2134// equally wrong, so a set of controls that all agree reads as a confident finding about the host. 2135// The control that actually discriminated was PRINTING THE CONSTANT the binary emits. 2136// => The likely correct value here is the RV64 number 199, exactly as sendmsg/recvmsg above needed 2137// their own numbers rather than the guarded pair. That is NOT asserted: it is UNTESTED, and this 2138// comment says so rather than shipping a plausible number with a confident sentence. 2139// => NOTHING DEPENDS ON IT. The descriptor-passing lane uses a NAMED AF_UNIX rendezvous 2140// (sys_unix_listen + sys_unix_connect_fd below), which is proven end to end by nx_scm_rights_gate 2141// and is also what nginx, HAProxy and systemd actually use to move a listener between processes. 2142// socketpair was only ever the convenience. 2143func sys_socketpair(domain: i64, sock_type: i64, protocol: i64, sv: *u8) -> i64 { 2144 return __syscall(SYS_SOCKETPAIR, domain, sock_type, protocol, sv, 0, 0) 2145} 2146 2147// Bind+listen a NAMED AF_UNIX stream socket -- the accepting half of the rendezvous whose 2148// connecting half is nx_unix_connect. Returns the listening fd, or a negative errno. 2149// The caller owns the path: unlink it first (a stale node makes bind return EADDRINUSE) and unlink 2150// it after, because an AF_UNIX bind leaves a filesystem entry that outlives the process. 2151const SCM_SUN_PATH_OFF: i64 = 2 // sockaddr_un = [sa_family: u16][sun_path: 108] 2152const SCM_SUN_BYTES: i64 = 110 2153const SCM_SUN_PATH_MAX: i64 = 107 2154func sys_unix_listen(path: *u8, backlog: i64) -> i64 { 2155 let fd: i64 = sys_socket(SCM_AF_UNIX, SOCK_STREAM, 0) 2156 if fd < 0 { return fd } 2157 let sa: *u8 = sys_mmap(SCM_SUN_BYTES) 2158 var i: i64 = 0 2159 while i < SCM_SUN_BYTES { sa[i] = 0; i = i + 1 } 2160 sa[0] = SCM_AF_UNIX 2161 sa[1] = 0 2162 var p: i64 = 0 2163 while path[p] != (0 as u8) { 2164 if p >= SCM_SUN_PATH_MAX { sys_close(fd); return 0 - 36 } 2165 sa[SCM_SUN_PATH_OFF + p] = path[p] 2166 p = p + 1 2167 } 2168 let br: i64 = sys_bind(fd, sa, SCM_SUN_PATH_OFF + p + 1) 2169 if br < 0 { sys_close(fd); return br } 2170 let lr: i64 = sys_listen(fd, backlog) 2171 if lr < 0 { sys_close(fd); return lr } 2172 return fd 2173} 2174 2175// The CONNECTING half of the same rendezvous. Returns the connected fd or a negative errno. 2176// RESIDUAL NAMED RATHER THAN LEFT SILENT: nx_unix_socket.nx already carries an nx_unix_connect with 2177// this exact body. It is not composed here because that file also defines a main(), so importing it 2178// would inject a second main into every one of the 52 daemons that reach nx_http_server -- a 2179// resolution-by-definition-order hazard this tree has already been bitten by. The primitive belongs 2180// in the shim; the older standalone file should be reduced to a caller of this one, and that is a 2181// separate change to a file with its own consumers rather than something to fold in silently here. 2182func sys_unix_connect_fd(path: *u8) -> i64 { 2183 let fd: i64 = sys_socket(SCM_AF_UNIX, SOCK_STREAM, 0) 2184 if fd < 0 { return fd } 2185 let sa: *u8 = sys_mmap(SCM_SUN_BYTES) 2186 var i: i64 = 0 2187 while i < SCM_SUN_BYTES { sa[i] = 0; i = i + 1 } 2188 sa[0] = SCM_AF_UNIX 2189 sa[1] = 0 2190 var p: i64 = 0 2191 while path[p] != (0 as u8) { 2192 if p >= SCM_SUN_PATH_MAX { sys_close(fd); return 0 - 36 } 2193 sa[SCM_SUN_PATH_OFF + p] = path[p] 2194 p = p + 1 2195 } 2196 let cr: i64 = sys_connect(fd, sa, SCM_SUN_PATH_OFF + p + 1) 2197 if cr < 0 { sys_close(fd); return cr } 2198 return fd 2199} 2200 2201// Send ONE open descriptor over a connected AF_UNIX socket. Returns the sendmsg result: the number 2202// of ordinary data bytes sent (SCM_PAYLOAD_BYTES on success) or a negative errno. The descriptor 2203// itself is NOT closed here -- both ends legitimately hold it until the sender chooses to let go, 2204// and that overlap is the entire point: there must be no instant at which zero processes hold the 2205// listening socket. 2206func sys_send_fd(sock: i64, fd: i64) -> i64 { 2207 let msg: *u8 = sys_mmap(SCM_MSGHDR_BYTES) 2208 let iov: *u8 = sys_mmap(SCM_IOVEC_BYTES) 2209 let cbuf: *u8 = sys_mmap(SCM_CMSG_SPACE_1FD) 2210 let data: *u8 = sys_mmap(SCM_PAYLOAD_BYTES) 2211 scm_zero(msg, SCM_MSGHDR_BYTES) 2212 scm_zero(cbuf, SCM_CMSG_SPACE_1FD) 2213 data[0] = SCM_PAYLOAD_BYTE 2214 scm_put_i64(iov, SCM_IOVEC_OFF_BASE, data as i64) 2215 scm_put_i64(iov, SCM_IOVEC_OFF_LEN, SCM_PAYLOAD_BYTES) 2216 scm_put_i64(msg, SCM_MSGHDR_OFF_IOV, iov as i64) 2217 scm_put_i64(msg, SCM_MSGHDR_OFF_IOVLEN, SCM_IOV_COUNT_ONE) 2218 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRL, cbuf as i64) 2219 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRLLEN, SCM_CMSG_SPACE_1FD) 2220 scm_put_i64(cbuf, SCM_CMSG_OFF_LEN, SCM_CMSG_LEN_1FD) 2221 scm_put_u32(cbuf, SCM_CMSG_OFF_LEVEL, SCM_SOL_SOCKET) 2222 scm_put_u32(cbuf, SCM_CMSG_OFF_TYPE, SCM_RIGHTS_TYPE) 2223 scm_put_u32(cbuf, SCM_CMSG_OFF_DATA, fd) 2224 let r: i64 = sys_sendmsg(sock, msg, 0) 2225 sys_munmap(msg, SCM_MSGHDR_BYTES) 2226 sys_munmap(iov, SCM_IOVEC_BYTES) 2227 sys_munmap(cbuf, SCM_CMSG_SPACE_1FD) 2228 sys_munmap(data, SCM_PAYLOAD_BYTES) 2229 return r 2230} 2231 2232// Receive ONE descriptor from a connected AF_UNIX socket. Returns the NEW descriptor number in this 2233// process (>= 0), a negative errno from recvmsg, or one of the SCM_ERR_* codes above. 2234// flags: 0, or SCM_MSG_CMSG_CLOEXEC so the arriving descriptor is not leaked into grandchildren -- 2235// the estate has already lost a port for six days to exactly that inheritance (nx_cloexec_gate). 2236// THE VALIDATION IS THE WHOLE POINT. recvmsg happily returns a positive byte count having delivered 2237// no ancillary data at all, so the kernel's REWRITTEN msg_controllen is read back rather than the 2238// value we asked for, and each of the three cmsg header fields is checked separately so a failure 2239// says which one. 2240func sys_recv_fd(sock: i64, flags: i64) -> i64 { 2241 let msg: *u8 = sys_mmap(SCM_MSGHDR_BYTES) 2242 let iov: *u8 = sys_mmap(SCM_IOVEC_BYTES) 2243 let cbuf: *u8 = sys_mmap(SCM_CMSG_SPACE_1FD) 2244 let data: *u8 = sys_mmap(SCM_PAYLOAD_BYTES) 2245 scm_zero(msg, SCM_MSGHDR_BYTES) 2246 scm_zero(cbuf, SCM_CMSG_SPACE_1FD) 2247 scm_put_i64(iov, SCM_IOVEC_OFF_BASE, data as i64) 2248 scm_put_i64(iov, SCM_IOVEC_OFF_LEN, SCM_PAYLOAD_BYTES) 2249 scm_put_i64(msg, SCM_MSGHDR_OFF_IOV, iov as i64) 2250 scm_put_i64(msg, SCM_MSGHDR_OFF_IOVLEN, SCM_IOV_COUNT_ONE) 2251 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRL, cbuf as i64) 2252 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRLLEN, SCM_CMSG_SPACE_1FD) 2253 let r: i64 = sys_recvmsg(sock, msg, flags) 2254 var out: i64 = r 2255 if r >= 0 { 2256 out = SCM_ERR_NO_CMSG 2257 if scm_get_i64(msg, SCM_MSGHDR_OFF_CTRLLEN) >= SCM_CMSG_LEN_1FD { 2258 out = SCM_ERR_CMSG_LEN 2259 if scm_get_i64(cbuf, SCM_CMSG_OFF_LEN) == SCM_CMSG_LEN_1FD { 2260 out = SCM_ERR_CMSG_LEVEL 2261 if scm_get_u32(cbuf, SCM_CMSG_OFF_LEVEL) == SCM_SOL_SOCKET { 2262 out = SCM_ERR_CMSG_TYPE 2263 if scm_get_u32(cbuf, SCM_CMSG_OFF_TYPE) == SCM_RIGHTS_TYPE { 2264 out = scm_get_u32(cbuf, SCM_CMSG_OFF_DATA) 2265 } 2266 } 2267 } 2268 } 2269 } 2270 sys_munmap(msg, SCM_MSGHDR_BYTES) 2271 sys_munmap(iov, SCM_IOVEC_BYTES) 2272 sys_munmap(cbuf, SCM_CMSG_SPACE_1FD) 2273 sys_munmap(data, SCM_PAYLOAD_BYTES) 2274 return out 2275} 2276 2277// Ordinary permission bits only. Special privilege bits are never copied by staging. 2278const NX_FILE_PERMISSION_MASK:i64=511 2279const NX_FILE_DESCRIPTOR_INVALID:i64=0-22 2280func sys_fchmod_fd(fd:i64,mode:i64)->i64{ 2281 if fd<0 || mode<0 || mode>NX_FILE_PERMISSION_MASK {return NX_FILE_DESCRIPTOR_INVALID} 2282 return __syscall(52,fd,mode,0,0,0,0) 2283} 2284// Portable descriptor syscall; consumers below use the x86-64 stat ABI layout. 2285const NX_STAT_X64_BYTES:i64=144 2286const NX_STAT_X64_MODE_OFFSET:i64=24 2287const NX_STAT_X64_BLOCK_OFFSET:i64=56 2288const NX_STAT_X64_SIZE_OFFSET:i64=48 2289const NX_STAT_X64_DEVICE_OFFSET:i64=0 2290const NX_STAT_X64_INODE_OFFSET:i64=8 2291const NX_STAT_TYPE_MASK:i64=61440 2292const NX_STAT_REGULAR_FILE:i64=32768 2293func sys_fstat_fd(fd:i64,stat:*u8)->i64{ 2294 if fd<0 || (stat as i64)==0{return NX_FILE_DESCRIPTOR_INVALID} 2295 return __syscall(80,fd,stat,0,0,0,0) 2296} 2297func sys_stat_permissions(stat:*u8)->i64{ 2298 let mode:i64=(stat[NX_STAT_X64_MODE_OFFSET] as i64)+((stat[NX_STAT_X64_MODE_OFFSET+1] as i64)<<8) 2299 return mode & NX_FILE_PERMISSION_MASK 2300} 2301 2302 2303// ---- fixed-width node arena ------------------------------------------------------------- 2304// Flat i64 slots rather than structs: the walker stays trivial and the encoding is auditable 2305// by eye. Slot 7 is the intrusive NEXT link, so every list is a chain of node ids. 2306const SIR_SLOTS: i64 = 8 2307// S12c-6 (2026-09-05): the node ARENA. Measured need: the cast vertex stage is 1,394 nodes and its fragment stage is 2.35x 2308// its canonical-token count, i.e. ~3,300 nodes -- under 4,096 by a margin no future slice could keep. sir_node OVERFLOW IS 2309// SILENT (returns 0, sets M_OVER, and every builder ignores a 0 child), so the arena is sized at 4x the measured vertex+fragment 2310// sum and every verb that builds a real module PRINTS M_OVER beside its node count; the flag, not the size, is the guard. 2311const SIR_MAXNODE: i64 = 16384 2312const SIR_POOLCAP: i64 = 65536 2313 2314const SIR_KIND: i64 = 0 2315const SIR_TY: i64 = 1 2316const SIR_A: i64 = 2 2317const SIR_B: i64 = 3 2318const SIR_C: i64 = 4 2319const SIR_NAME: i64 = 5 2320const SIR_D: i64 = 6 2321const SIR_NEXT: i64 = 7 2322 2323// module header words 2324const M_NODES: i64 = 0 2325const M_NCOUNT: i64 = 1 2326const M_POOL: i64 = 2 2327const M_POOLUSED: i64 = 3 2328const M_DECLH: i64 = 4 2329const M_DECLT: i64 = 5 2330const M_FUNCH: i64 = 6 2331const M_FUNCT: i64 = 7 2332const M_REFUSE: i64 = 8 2333const M_OVER: i64 = 9 2334// S2 (2026-09-04): THE EMISSION CONTEXT. The K_FUNC whose body a backend is emitting right now (0 between 2335// functions). An E_IDENT carries only a name, and what that name resolves to -- a parameter or local of 2336// THIS function, or a module-scope declaration -- decides how a dialect spells it (WGSL reads a uniform as 2337// `u.<name>`). Both backends set it; the resolver reads it. Word 10 of the 16-word header sir_new maps. 2338const M_CURFN: i64 = 10 2339// S5 (2026-09-04): THE FRAGMENT OUTPUT BEING EMITTED. The K_FRAGOUT declaration whose value the entry 2340// point currently returns (0 everywhere else -- between functions, inside a helper, outside a fragment 2341// stage). GLSL has no value-returning fragment entry: `void main()` ASSIGNS a declared `out` variable, 2342// while WGSL RETURNS one. Both backends walk ONE S_RETURN node, so the shape that differs has to be 2343// readable from somewhere; this word is that somewhere, and it is set ONLY around the entry body so a 2344// plain helper in the same module still emits an ordinary `return`. Word 11 of the 16 sir_new maps. 2345const M_FRAGOUT: i64 = 11 2346// S7 (2026-09-05): THE INTERSTAGE-STRUCT MODE. 0 = no varyings in play; 1 = inside the vertex entry that 2347// RETURNS struct VIO (varying writes and the position assignment land in vo.<name>); 2 = inside the fragment 2348// entry that TAKES vin:VIO (varying and position reads come from vin.<name>). Set and cleared by the WGSL 2349// entry emitter; wg_expr / wg_stmts read it. GLSL never sets it. Word 12 of the 16 sir_new maps. 2350const M_VIOMODE: i64 = 12 2351// S12a (2026-09-05): DOES THIS MODULE READ THE INSTANCE INDEX? Set by sir_builtin the moment an instance_index node is 2352// built, read by the WGSL vertex-entry emitter, which binds @builtin(instance_index) ii:u32 ONLY then -- so every module 2353// that never asks for it (the fullscreen triangle, every S0..S9 fixture) keeps a byte-identical signature. GLSL never 2354// reads it: gl_InstanceID needs no declaration. Word 13 of the 16 sir_new maps. 2355const M_USESII: i64 = 13 2356// S12c-5 (2026-09-05): THE WGSL FRAGMENT OUTPUT VARIABLE. The hand cast fragment ASSIGNS its output (`fc=...;`) and exits 2357// early with a void `return;` -- valid GLSL (fc is an `out` variable) and the shape the token ruler must see. WGSL RETURNS 2358// its output, so the WGSL backend opens such an entry with `var fc:vec4f;`, lowers every void return inside it to 2359// `return fc;` and appends one after the body when the last statement is not a return. This word holds the K_FRAGOUT 2360// declaration ONLY while the WGSL backend is inside a fragment entry whose body assigns it (0 everywhere else) -- the 2361// WGSL twin of M_FRAGOUT, which the GLSL backend uses for the opposite lowering (value return -> assignment). Word 14. 2362const M_WGFRAGOUT: i64 = 14 2363// S12 step 2 (2026-09-06): THE BIND GROUP a module's WGSL resources are spelled in (@group(N)). 0 for every module until 2364// now; the cast FRAGMENT module is emitted at 1, because its struct U and its textures differ from the vertex module's and 2365// WebGPU binds ONE resource per (group,binding) across both stages of a pipeline -- two structs named U at group 0 binding 0 2366// would read one buffer through two layouts, and vertex uJT (2d f32) against fragment uVm (3d u32) at binding 1 is a 2367// validation refusal. GLSL never reads this word. Word 15 of the 16 sir_new maps (the last free slot). 2368const M_BINDGROUP: i64 = 15 2369// Source clip depth is explicit: backends translate it to their device convention. 2370// Existing modules retain their native convention until they declare otherwise. 2371const M_CLIP_DEPTH: i64 = 16 2372const SIR_MODULE_WORDS: i64 = 17 2373const SIR_CLIP_NATIVE: i64 = 0 2374const SIR_CLIP_NEGATIVE_ONE_TO_ONE: i64 = 1 2375// S12c-5: an E_LIT flag (SIR_A) -- the unsigned literal keeps its `u` suffix in GLSL too (see sir_lit_u). 2376const SIR_LIT_USUFFIX: i64 = 1 2377 2378// ---- node kinds (the DECLARED SUBSET) ---------------------------------------------------- 2379const K_UNIFORM: i64 = 1 2380const K_ATTRIB: i64 = 2 2381const K_VARY: i64 = 3 2382const K_FUNC: i64 = 4 2383const K_PARAM: i64 = 5 2384// RUNG GE43 -- COMPUTE (gameengine board, 2026-09-02). A storage buffer is the one declaration a 2385// rasterizer KERNEL cannot do without: the depth buffer, the tile bins and the framebuffer all live 2386// in read_write storage and are resolved with atomics. GLSL ES 3.00 (WebGL2) has NEITHER storage 2387// buffers nor atomics nor a compute stage, so the GLSL backend REFUSES this kind BY NAME and the 2388// WGSL door is the only third-party-browser path for it -- exactly the split GE42-GE45 declare: 2389// ONE kernel authored here, one door per host (WebGPU as a submission pipe; dxg on NishiOS). 2390// SIR_TY = element type SIR_A = binding index SIR_B = 1 read_write / 0 read SIR_C = 1 atomic<> 2391const K_STORAGE: i64 = 6 2392// S3 (2026-09-04): a TEXTURE is its own declaration kind. It is a BOUND RESOURCE in both dialects -- a 2393// sampler uniform (`uniform highp usampler3D`) in GLSL, a `@group/@binding var` OUTSIDE struct U in WGSL -- 2394// and it was never a uniform: a texture-typed K_UNIFORM passed the WGSL type check and landed INSIDE 2395// struct U (WGSL that does not compile, no refusal) until S1 made that a named refusal in both backends. 2396// SIR_TY = the texture type (T_TEX3U is the covered one; every other texture type refuses BY NAME) 2397// No binding slot is stored: GLSL has no bindings and the WGSL backend DERIVES one from declaration order. 2398const K_TEXTURE: i64 = 7 2399// S5 (2026-09-04): THE FRAGMENT OUTPUT is its own declaration kind, for the same reason K_TEXTURE is. 2400// It was not modelled at all, and the cost was silent: a fragment stage emitted `void main(){ return 2401// vec4(...); }` -- a value returned from a void function, against no declared output -- which is invalid 2402// GLSL ES 3.00 on BOTH counts and which no gate could see, because nothing pinned the GLSL fragment 2403// bytes. The WGSL side had the mirror defect wearing the opposite face: it spelled the whole signature 2404// tail as the LITERAL ")->@location(0) vec4f{", so the location and the type were unreadable constants 2405// rather than data -- exactly the defect S4 removed from the parameter half of the same line. 2406// ONE declaration now feeds both: GLSL emits `out <ty> <name>;` and lowers the entry's return to 2407// `<name>=<expr>;`, WGSL emits no declaration and DERIVES `->@location(<loc>) <ty>`. 2408// SIR_TY = the output type SIR_A = the @location index 2409const K_FRAGOUT: i64 = 8 2410// R-D (2026-09-04, GE44, B1 of the hand-shader diff): A STRUCT is a module-scope declaration whose members are 2411// K_PARAM nodes chained under it EXACTLY as a function's parameters are (sir_member IS sir_param), so member 2412// spelling shares the parameter machinery and cannot drift from it. A struct TYPE is named by its declaration: 2413// sir_struct_ty(st) = SIR_MAXNODE + st, unique by construction and above every T_ scalar, so a type speller that 2414// meets one looks the declaration up (sir_struct_of) instead of refusing. Member ACCESS needs no new expression 2415// kind -- both dialects spell it base.member, the bytes E_SWZ already emits -- and CONSTRUCTION is an E_CALL 2416// named after the struct (both dialects spell Hit(a,b)). SIR_A = member chain head, SIR_C = member tail. 2417const K_STRUCT: i64 = 9 2418const S_RETURN: i64 = 10 2419const S_ASSIGN: i64 = 11 2420const S_VAR: i64 = 12 2421const S_IF: i64 = 13 2422const S_DISCARD: i64 = 14 2423// RUNG 2b-GENERAL: structured LOOP + BREAK. A back edge cannot be emitted as a goto -- neither 2424// GLSL ES 3.00 nor WGSL has one -- so the recovered form is an infinite loop whose exit is an 2425// explicit break. That is exactly the Relooper's Loop block, whose Inner block "will appear 2426// inside the loop, i.e., when execution reaches the end of that block, flow will return to the 2427// beginning" and which "will contain a conditional break defining where it is exited" 2428// (Zakai, Emscripten, OOPSLA 2011 -- READ from the mirrored PDF banked as graphics.refs 2429// key relooper-zakai11, not recalled from memory). 2430const S_LOOP: i64 = 15 2431const S_BREAK: i64 = 16 2432// GE43: an EXPRESSION STATEMENT -- an atomic read-modify-write whose old value is discarded is a 2433// statement in both dialects (`atomicMin(&d[i],z);`), and neither S_ASSIGN nor S_VAR can carry it 2434// without inventing a dead local. SIR_A = the expression. 2435const S_EXPR: i64 = 17 2436const E_LIT: i64 = 20 2437const E_IDENT: i64 = 21 2438const E_BIN: i64 = 22 2439const E_CALL: i64 = 23 2440const E_SWZ: i64 = 24 2441const E_INDEX: i64 = 25 2442const E_TEXLOAD: i64 = 26 2443const E_TEXSAMPLE: i64 = 27 2444const E_CTOR: i64 = 28 2445// rung-1 additions: a numeric CAST (GLSL float(x) / WGSL f32(x)) and a stage BUILTIN 2446// (GLSL gl_VertexID / WGSL @builtin(vertex_index)). Both are pure dialect-SPELLING differences 2447// over one IR node -- the thesis of this module in miniature. 2448const E_CAST: i64 = 29 2449const E_BUILTIN: i64 = 30 2450// GE43: an ATOMIC read-modify-write. SIR_NAME = the op spelling (atomicMin / atomicMax / atomicAdd, 2451// identical in WGSL and in the dxg-lane HLSL family so the IR carries the WGSL spelling), SIR_A = 2452// the TARGET lvalue (an E_INDEX into a K_STORAGE), SIR_B = the value. WGSL takes the ADDRESS of the 2453// target (`&d[i]`); that `&` is dialect knowledge the backend owns, the author never writes it. 2454const E_ATOMIC: i64 = 31 2455// R-A (2026-09-04, the GE44 world-shader contract): a SELECT. SIR_A = the condition, SIR_B = the value when 2456// true, SIR_C = the value when false. GLSL spells it as the ternary (cond?then:else), WGSL as select(f,t,cond) 2457// -- note WGSL's argument order puts the FALSE value first; that ordering is dialect knowledge the backend 2458// owns. The condition is a scalar bool by construction of this IR (there is no vector-bool type), so the 2459// ternary's scalar-condition rule holds wherever this node appears. 2460const E_SELECT: i64 = 32 2461// R-G0 (2026-09-04): unary negation. SIR_A = the operand; both dialects spell (-x). The world shader writes 2462// -vec3f(...), -u.yp.x and -1.0 as expressions; an E_BIN(0 - x) would be semantically equal but would spell a 2463// subtraction the hand shader never wrote, so negation is its own node with one arm per dialect. 2464const E_NEG: i64 = 33 2465// K_PRIVATE (GE55, 2026-09-05): MODULE-SCOPE PRIVATE STATE -- `var<private> name:T;` in WGSL, a bare global `T name;` 2466// in GLSL. Written by one function and read bare by another, never qualified as u.<name>, invisible to the uniform 2467// layout. It exists so a shader can derive every float it needs from RAW uniform words in ONE function (upk) while 2468// every other function body stays byte-identical: the sim hands the GPU integers, the kernel does the math. Numbered 2469// after the last expression kind because node kinds share one space and nothing range-tests them (measured). 2470const K_PRIVATE: i64 = 34 2471// S12c-4 (2026-09-05): LOGICAL NOT. SIR_A = the operand (a bool); both dialects spell (!x). The cast vertex stage gates a 2472// garment instance on `!(gbm==4||gbm==5||gbm==8||gbm==9)` and `!wr9`; the world shader never needed it (its exits are 2473// spelled as the inverted comparison, see sir_for_until), so it lands here as its own node kind rather than a rewrite the 2474// hand text does not contain -- the token ruler would name that rewrite as a divergence. Numbered after K_PRIVATE for the 2475// same reason K_PRIVATE is numbered after E_NEG: one kind space, nothing range-tests it. 2476const E_NOT: i64 = 35 2477 2478// ---- types ------------------------------------------------------------------------------- 2479const T_VOID: i64 = 0 2480const T_F32: i64 = 1 2481const T_I32: i64 = 2 2482const T_U32: i64 = 3 2483const T_BOOL: i64 = 4 2484const T_V2F: i64 = 5 2485const T_V3F: i64 = 6 2486const T_V4F: i64 = 7 2487const T_V3I: i64 = 8 2488const T_TEX3U: i64 = 9 2489const T_TEX2F: i64 = 10 2490// GE43: vec3<u32> -- the type of @builtin(global_invocation_id); GLSL ES 3.10 would spell it uvec3. 2491const T_V3U: i64 = 11 2492// S8 (2026-09-05): vec2<i32> -- the INTEGER texel coordinate of a 2D fetch (the cast reads its joint table and 2493// garment table with texelFetch(uJT, ivec2(...), 0)); WGSL spells it vec2i, GLSL ivec2. 2494const T_V2I: i64 = 12 2495// S9 (2026-09-05): mat3x3<f32> -- the cast's TBN / joint-rotation matrix. WGSL spells it mat3x3f, GLSL mat3; the 2496// constructor takes three column vectors and mat*vec is plain E_BIN '*' in both dialects, so the type NAME is the only 2497// dialect fact and it lives in wg_ty/gl_ty like every other type. 2498const T_M3F: i64 = 13 2499const T_MAX: i64 = 13 2500 2501func sir_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 2502 2503func sir_new() -> *i64 { 2504 let m: *i64 = sys_mmap(SIR_MODULE_WORDS*8) as *i64 2505 m[M_NODES] = sys_mmap(SIR_MAXNODE*SIR_SLOTS*8) as i64 2506 m[M_NCOUNT] = 1 2507 m[M_POOL] = sys_mmap(SIR_POOLCAP) as i64 2508 m[M_POOLUSED] = 1 2509 m[M_DECLH] = 0 2510 m[M_DECLT] = 0 2511 m[M_FUNCH] = 0 2512 m[M_FUNCT] = 0 2513 m[M_REFUSE] = 0 2514 m[M_OVER] = 0 2515 m[M_CURFN] = 0 2516 m[M_FRAGOUT] = 0 2517 return m 2518} 2519 2520func sir_get(m: *i64, id: i64, k: i64) -> i64 { 2521 let base: *i64 = m[M_NODES] as *i64 2522 return base[id*SIR_SLOTS + k] 2523} 2524 2525func sir_set(m: *i64, id: i64, k: i64, v: i64) -> i64 { 2526 let base: *i64 = m[M_NODES] as *i64 2527 base[id*SIR_SLOTS + k] = v 2528 return 0 2529} 2530 2531// intern a string; returns its pool offset (0 means none). Offsets start at 1 so 0 is a 2532// usable NULL for every name slot. 2533func sir_str(m: *i64, s: *u8) -> i64 { 2534 let pool: *u8 = m[M_POOL] as *u8 2535 let n: i64 = sir_slen(s) 2536 var u: i64 = m[M_POOLUSED] 2537 if u + n + 1 >= SIR_POOLCAP { m[M_OVER] = 1; return 0 } 2538 let at: i64 = u 2539 var i: i64 = 0 2540 while i < n { pool[u] = s[i]; u = u + 1; i = i + 1 } 2541 pool[u] = 0 as u8 2542 u = u + 1 2543 m[M_POOLUSED] = u 2544 return at 2545} 2546 2547func sir_cstr(m: *i64, off: i64) -> *u8 { 2548 return ((m[M_POOL] + off) as *u8) 2549} 2550 2551func sir_node(m: *i64, kind: i64, ty: i64, a: i64, b: i64, c: i64, nameoff: i64) -> i64 { 2552 var id: i64 = m[M_NCOUNT] 2553 if id >= SIR_MAXNODE { m[M_OVER] = 1; return 0 } 2554 m[M_NCOUNT] = id + 1 2555 sir_set(m, id, SIR_KIND, kind) 2556 sir_set(m, id, SIR_TY, ty) 2557 sir_set(m, id, SIR_A, a) 2558 sir_set(m, id, SIR_B, b) 2559 sir_set(m, id, SIR_C, c) 2560 sir_set(m, id, SIR_NAME, nameoff) 2561 sir_set(m, id, SIR_D, 0) 2562 sir_set(m, id, SIR_NEXT, 0) 2563 return id 2564} 2565 2566// append onto an intrusive chain whose head/tail live in two module words 2567func sir_chain(m: *i64, hw: i64, tw: i64, id: i64) -> i64 { 2568 if id == 0 { return 0 } 2569 if m[hw] == 0 { m[hw] = id; m[tw] = id; return id } 2570 sir_set(m, m[tw], SIR_NEXT, id) 2571 m[tw] = id 2572 return id 2573} 2574 2575// ---- declaration builders ---------------------------------------------------------------- 2576// arraylen 0 = scalar. location -1 = unlocated. flat 1 = no interpolation. 2577func sir_uniform(m: *i64, name: *u8, ty: i64, arraylen: i64) -> i64 { 2578 let id: i64 = sir_node(m, K_UNIFORM, ty, arraylen, 0, 0, sir_str(m, name)) 2579 return sir_chain(m, M_DECLH, M_DECLT, id) 2580} 2581 2582// module-scope private state (K_PRIVATE): arraylen 0 = scalar, N = array<ty,N> / ty name[N] 2583func sir_private(m: *i64, name: *u8, ty: i64, arraylen: i64) -> i64 { 2584 let id: i64 = sir_node(m, K_PRIVATE, ty, arraylen, 0, 0, sir_str(m, name)) 2585 return sir_chain(m, M_DECLH, M_DECLT, id) 2586} 2587 2588func sir_attrib(m: *i64, name: *u8, ty: i64, location: i64) -> i64 { 2589 let id: i64 = sir_node(m, K_ATTRIB, ty, location, 0, 0, sir_str(m, name)) 2590 return sir_chain(m, M_DECLH, M_DECLT, id) 2591} 2592 2593func sir_vary(m: *i64, name: *u8, ty: i64, location: i64, flat: i64) -> i64 { 2594 let id: i64 = sir_node(m, K_VARY, ty, location, flat, 0, sir_str(m, name)) 2595 return sir_chain(m, M_DECLH, M_DECLT, id) 2596} 2597 2598// GE43: a storage buffer `name: array<elemty>` at @group(0)@binding(binding). readwrite 1 = var<storage,read_write>, 2599// 0 = var<storage,read>. atomic 1 wraps the element as atomic<elemty> so E_ATOMIC may target it. 2600func sir_storage(m: *i64, name: *u8, elemty: i64, binding: i64, readwrite: i64, atomic: i64) -> i64 { 2601 let id: i64 = sir_node(m, K_STORAGE, elemty, binding, readwrite, atomic, sir_str(m, name)) 2602 return sir_chain(m, M_DECLH, M_DECLT, id) 2603} 2604 2605// S3: a texture declaration `name: <ty>` (see K_TEXTURE). Read with sir_texload / sir_texsample. 2606func sir_texture(m: *i64, name: *u8, ty: i64) -> i64 { 2607 let id: i64 = sir_node(m, K_TEXTURE, ty, 0, 0, 0, sir_str(m, name)) 2608 return sir_chain(m, M_DECLH, M_DECLT, id) 2609} 2610 2611// S5: the fragment stage's colour output (see K_FRAGOUT). Declared ONCE; each backend owns how its 2612// dialect carries it. A shader that only discards declares none, and neither backend requires one. 2613func sir_fragout(m: *i64, name: *u8, ty: i64, location: i64) -> i64 { 2614 let id: i64 = sir_node(m, K_FRAGOUT, ty, location, 0, 0, sir_str(m, name)) 2615 return sir_chain(m, M_DECLH, M_DECLT, id) 2616} 2617 2618// R-D (2026-09-04): struct builders. sir_member IS sir_param -- the same K_PARAM node under the same A/C chain 2619// -- so there is exactly one member/parameter shape in the IR. sir_struct_ty derives the type id from the 2620// declaration (never picked, never colliding with a T_ scalar); sir_struct_of is the ONE inverse both backends 2621// spell a struct type through. sir_member_of delegates to E_SWZ because no dialect distinguishes a member 2622// read from a swizzle in its bytes -- a second kind would be two arms that could disagree about `.`. 2623func sir_struct(m: *i64, name: *u8) -> i64 { 2624 let id: i64 = sir_node(m, K_STRUCT, T_VOID, 0, 0, 0, sir_str(m, name)) 2625 return sir_chain(m, M_DECLH, M_DECLT, id) 2626} 2627func sir_member(m: *i64, st: i64, name: *u8, ty: i64) -> i64 { return sir_param(m, st, name, ty) } 2628func sir_struct_ty(st: i64) -> i64 { return SIR_MAXNODE + st } 2629// the K_STRUCT declaration a type id names, or 0 for every scalar, vector and resource type 2630func sir_struct_of(m: *i64, ty: i64) -> i64 { 2631 if ty <= SIR_MAXNODE { return 0 } 2632 let st: i64 = ty - SIR_MAXNODE 2633 if st >= SIR_MAXNODE { return 0 } 2634 if sir_get(m, st, SIR_KIND) != K_STRUCT { return 0 } 2635 return st 2636} 2637func sir_member_of(m: *i64, base: i64, name: *u8, ty: i64) -> i64 { return sir_swz(m, base, name, ty) } 2638 2639// a function: A = param chain head, B = body chain head, C = param tail (build scratch) 2640func sir_func(m: *i64, name: *u8, rettype: i64) -> i64 { 2641 let id: i64 = sir_node(m, K_FUNC, rettype, 0, 0, 0, sir_str(m, name)) 2642 return sir_chain(m, M_FUNCH, M_FUNCT, id) 2643} 2644 2645// S4 (2026-09-04): HOW A PARAMETER IS BOUND. SIR_A of a K_PARAM names its binding: P_PLAIN (an ordinary 2646// function parameter -- the only kind before S4, and sir_node's zero default) or P_BUILTIN_POSITION (the 2647// fragment stage's position input: `@builtin(position) name:vec4f` in WGSL, `gl_FragCoord` in GLSL). The IR 2648// says WHAT the input is; each backend owns how its dialect spells it, and an entry-point parameter with a 2649// binding a backend cannot derive REFUSES by name rather than being dropped from the signature. 2650const P_PLAIN: i64 = 0 2651const P_BUILTIN_POSITION: i64 = 1 2652 2653func sir_param(m: *i64, fn: i64, name: *u8, ty: i64) -> i64 { 2654 let id: i64 = sir_node(m, K_PARAM, ty, P_PLAIN, 0, 0, sir_str(m, name)) 2655 if sir_get(m, fn, SIR_A) == 0 { sir_set(m, fn, SIR_A, id); sir_set(m, fn, SIR_C, id); return id } 2656 sir_set(m, sir_get(m, fn, SIR_C), SIR_NEXT, id) 2657 sir_set(m, fn, SIR_C, id) 2658 return id 2659} 2660 2661// S4: a parameter bound to the fragment stage's @builtin(position). Typed vec4f by the builtin itself. 2662func sir_param_position(m: *i64, fn: i64, name: *u8) -> i64 { 2663 let id: i64 = sir_param(m, fn, name, T_V4F) 2664 sir_set(m, id, SIR_A, P_BUILTIN_POSITION) 2665 return id 2666} 2667 2668// append a statement to a function body (D holds the body tail while building) 2669func sir_stmt(m: *i64, fn: i64, st: i64) -> i64 { 2670 if st == 0 { return 0 } 2671 if sir_get(m, fn, SIR_B) == 0 { sir_set(m, fn, SIR_B, st); sir_set(m, fn, SIR_D, st); return st } 2672 sir_set(m, sir_get(m, fn, SIR_D), SIR_NEXT, st) 2673 sir_set(m, fn, SIR_D, st) 2674 return st 2675} 2676 2677// ---- statement builders ------------------------------------------------------------------ 2678func sir_return(m: *i64, e: i64) -> i64 { return sir_node(m, S_RETURN, T_VOID, e, 0, 0, 0) } 2679// Fragment depth is window depth [0,1] in both emitted dialects. S_RETURN.B 2680// carries it alongside the color expression; ordinary returns leave B zero. 2681func sir_return_depth(m: *i64, color: i64, depth: i64) -> i64 { 2682 return sir_node(m, S_RETURN, T_VOID, color, depth, 0, 0) 2683} 2684 2685func sir_assign(m: *i64, lhs: i64, rhs: i64) -> i64 { return sir_node(m, S_ASSIGN, T_VOID, lhs, rhs, 0, 0) } 2686func sir_var(m: *i64, name: *u8, ty: i64, init: i64) -> i64 { return sir_node(m, S_VAR, ty, init, 0, 0, sir_str(m, name)) } 2687func sir_if(m: *i64, cond: i64, thenh: i64, elseh: i64) -> i64 { return sir_node(m, S_IF, T_VOID, cond, thenh, elseh, 0) } 2688func sir_discard(m: *i64) -> i64 { return sir_node(m, S_DISCARD, T_VOID, 0, 0, 0, 0) } 2689// SIR_A of an S_LOOP is the body chain. The loop is UNCONDITIONAL by construction; every exit is 2690// an explicit S_BREAK inside the body, so no dialect needs a loop-condition slot and the two 2691// backends cannot disagree about where the test lives. 2692func sir_loop(m: *i64, body: i64) -> i64 { return sir_node(m, S_LOOP, T_VOID, body, 0, 0, 0) } 2693func sir_break(m: *i64) -> i64 { return sir_node(m, S_BREAK, T_VOID, 0, 0, 0, 0) } 2694// GE43: an expression evaluated for its effect (an atomic whose old value is not kept) 2695func sir_expr(m: *i64, e: i64) -> i64 { return sir_node(m, S_EXPR, T_VOID, e, 0, 0, 0) } 2696// R-B (2026-09-04, GE44): A COUNTED LOOP IS BUILDER SUGAR, NOT A NODE KIND. The whole-population diff of the 2697// two hand world shaders named the counted for-loop as a missing IR kind; it lands as a composition of the 2698// incumbent S_LOOP/S_BREAK shape -- init; loop { if until { break } body; step } -- appended to fn in that 2699// order. Both dialects therefore lower it through arms they already share, so they cannot disagree about 2700// where the test lives (the argument sir_loop makes for itself) and the node trace is dialect-independent by 2701// construction -- no second visitation order to get wrong. `until` is the EXIT test, spelled by the caller: 2702// the IR carries no unary not, and inverting a comparison on the author's behalf would silently change its 2703// NaN semantics. Returns the S_LOOP node; init and the loop are already appended to fn. 2704func sir_for_until(m: *i64, fn: i64, init: i64, until: i64, step: i64, body: i64) -> i64 { 2705 sir_stmt(m, fn, init) 2706 let ex: i64 = sir_if(m, until, sir_break(m), 0) 2707 let lp: i64 = sir_loop(m, sir_seq(m, sir_seq(m, ex, body), step)) 2708 sir_stmt(m, fn, lp) 2709 return lp 2710} 2711 2712// chain two statements (for if-branch bodies) 2713func sir_seq(m: *i64, a: i64, b: i64) -> i64 { 2714 if a == 0 { return b } 2715 var t: i64 = a 2716 while sir_get(m, t, SIR_NEXT) != 0 { t = sir_get(m, t, SIR_NEXT) } 2717 sir_set(m, t, SIR_NEXT, b) 2718 return a 2719} 2720 2721// ---- expression builders ----------------------------------------------------------------- 2722func sir_lit(m: *i64, text: *u8, ty: i64) -> i64 { return sir_node(m, E_LIT, ty, 0, 0, 0, sir_str(m, text)) } 2723// S12c-5: an UNSIGNED literal spelled with its u suffix in BOTH dialects (`0u`). A plain sir_lit(.., T_U32) keeps GLSL bare 2724// (the fullscreen triangle's shift amount ships that way); a uint COMPARE in GLSL ES 3.00 needs the suffix, so the cast 2725// fragment builds its block ids with this. 2726func sir_lit_u(m: *i64, text: *u8) -> i64 { 2727 let id: i64 = sir_lit(m, text, T_U32) 2728 sir_set(m, id, SIR_A, SIR_LIT_USUFFIX) 2729 return id 2730} 2731// S12c-5: a CONSTANT local (`const float K=1.2;` in GLSL, `const K:f32=1.2;` in WGSL) -- SIR_D = SIR_VAR_CONST on a plain 2732// sir_var node, one flavour beside sir_let, so both backends spell it from the same word. 2733const SIR_VAR_CONST: i64 = 2 2734func sir_const(m: *i64, name: *u8, ty: i64, init: i64) -> i64 { 2735 let id: i64 = sir_var(m, name, ty, init) 2736 sir_set(m, id, SIR_D, SIR_VAR_CONST) 2737 return id 2738} 2739func sir_ident(m: *i64, name: *u8, ty: i64) -> i64 { return sir_node(m, E_IDENT, ty, 0, 0, 0, sir_str(m, name)) } 2740func sir_bin(m: *i64, op: *u8, l: i64, r: i64, ty: i64) -> i64 { return sir_node(m, E_BIN, ty, l, r, 0, sir_str(m, op)) } 2741func sir_swz(m: *i64, base: i64, sel: *u8, ty: i64) -> i64 { return sir_node(m, E_SWZ, ty, base, 0, 0, sir_str(m, sel)) } 2742func sir_index(m: *i64, base: i64, idx: i64, ty: i64) -> i64 { return sir_node(m, E_INDEX, ty, base, idx, 0, 0) } 2743// S8 (2026-09-05): the load's result type FOLLOWS THE TEXTURE -- texture_2d<f32> returns vec4f (the cast uses the 2744// fetched joint-table row WHOLE); texture_3d<u32> keeps the T_U32 the vox fixture swizzles .x from, so every 2745// existing 3D read is byte-identical. `tex` is the texture's E_IDENT and its SIR_TY names the texture type. 2746func sir_texload(m: *i64, tex: i64, coord: i64, lod: i64) -> i64 { 2747 var rt: i64 = T_U32 2748 if sir_get(m, tex, SIR_TY) == T_TEX2F { rt = T_V4F } 2749 return sir_node(m, E_TEXLOAD, rt, tex, coord, lod, 0) 2750} 2751func sir_texsample(m: *i64, tex: i64, coord: i64) -> i64 { return sir_node(m, E_TEXSAMPLE, T_V4F, tex, coord, 0, 0) } 2752 2753// call/ctor argument chains are built with sir_arg 2754func sir_call(m: *i64, name: *u8, ty: i64) -> i64 { return sir_node(m, E_CALL, ty, 0, 0, 0, sir_str(m, name)) } 2755func sir_ctor(m: *i64, ty: i64) -> i64 { return sir_node(m, E_CTOR, ty, 0, 0, 0, 0) } 2756func sir_cast(m: *i64, e: i64, ty: i64) -> i64 { return sir_node(m, E_CAST, ty, e, 0, 0, 0) } 2757func sir_builtin(m: *i64, which: *u8, ty: i64) -> i64 { 2758 // S12a: an instance_index read marks the module so the WGSL vertex entry binds the builtin (M_USESII). 2759 if which[0] == (105 as u8) { m[M_USESII] = 1 } 2760 return sir_node(m, E_BUILTIN, ty, 0, 0, 0, sir_str(m, which)) 2761} 2762// GE43: op is the WGSL spelling (atomicMin/atomicMax/atomicAdd); target is an E_INDEX into a storage buffer 2763func sir_atomic(m: *i64, op: *u8, target: i64, val: i64, ty: i64) -> i64 { return sir_node(m, E_ATOMIC, ty, target, val, 0, sir_str(m, op)) } 2764// R-A: select -- (cond, then, else) in IR order; each backend spells its own argument order. 2765func sir_select(m: *i64, cond: i64, tval: i64, fval: i64, ty: i64) -> i64 { return sir_node(m, E_SELECT, ty, cond, tval, fval, 0) } 2766// R-G0: unary negation of e, typed ty. 2767func sir_neg(m: *i64, e: i64, ty: i64) -> i64 { return sir_node(m, E_NEG, ty, e, 0, 0, 0) } 2768// S12c-4: logical not of a bool e (see E_NOT). 2769func sir_not(m: *i64, e: i64) -> i64 { return sir_node(m, E_NOT, T_BOOL, e, 0, 0, 0) } 2770// R-C: an IMMUTABLE local. Same node as sir_var with SIR_D = 1; WGSL spells it `let`, GLSL has no such 2771// spelling for a non-constant initialiser and emits the plain local -- one IR node, two shapes. 2772const SIR_VAR_LET: i64 = 1 2773func sir_let(m: *i64, name: *u8, ty: i64, init: i64) -> i64 { 2774 let id: i64 = sir_var(m, name, ty, init) 2775 sir_set(m, id, SIR_D, SIR_VAR_LET) 2776 return id 2777} 2778 2779func sir_arg(m: *i64, callid: i64, e: i64) -> i64 { 2780 if e == 0 { return 0 } 2781 // List linkage belongs to an argument occurrence, never to its reusable expression. 2782 // Child expressions remain shared; only the intrusive sibling link needs ownership. 2783 let arg:i64=sir_node(m,sir_get(m,e,SIR_KIND),sir_get(m,e,SIR_TY), 2784 sir_get(m,e,SIR_A),sir_get(m,e,SIR_B),sir_get(m,e,SIR_C),sir_get(m,e,SIR_NAME)) 2785 if arg==0 { return 0 } 2786 sir_set(m,arg,SIR_D,sir_get(m,e,SIR_D)) 2787 if sir_get(m, callid, SIR_A) == 0 { sir_set(m, callid, SIR_A, arg); sir_set(m, callid, SIR_C, arg); return e } 2788 sir_set(m, sir_get(m, callid, SIR_C), SIR_NEXT, arg) 2789 sir_set(m, callid, SIR_C, arg) 2790 return e 2791} 2792 2793// ---- refusal ----------------------------------------------------------------------------- 2794// A backend that meets a construct outside its declared subset records the construct BY NAME 2795// and returns -1. Never emit a guess. 2796func sir_refuse(m: *i64, what: *u8) -> i64 { 2797 if m[M_REFUSE] == 0 { m[M_REFUSE] = sir_str(m, what) } 2798 return 0 - 1 2799} 2800func sir_refused(m: *i64) -> i64 { return m[M_REFUSE] } 2801 2802// ---- shared emit buffer ------------------------------------------------------------------ 2803func eb_put(out: *u8, pos: i64, cap: i64, s: *u8) -> i64 { 2804 var p: i64 = pos 2805 var i: i64 = 0 2806 let n: i64 = sir_slen(s) 2807 while i < n { if p + 1 < cap { out[p] = s[i]; p = p + 1 } i = i + 1 } 2808 return p 2809} 2810 2811func eb_num(out: *u8, pos: i64, cap: i64, v: i64) -> i64 { 2812 var p: i64 = pos 2813 var x: i64 = v 2814 if x < 0 { p = eb_put(out, p, cap, "-" as *u8); x = 0 - x } 2815 let tmp: *u8 = sys_mmap(32) 2816 var k: i64 = 0 2817 if x == 0 { tmp[0] = 48 as u8; k = 1 } 2818 while x > 0 { tmp[k] = ((48 + x - (x/10)*10) as u8); x = x/10; k = k + 1 } 2819 while k > 0 { k = k - 1; if p + 1 < cap { out[p] = tmp[k]; p = p + 1 } } 2820 return p 2821} 2822 2823// ---- backend trace ------------------------------------------------------------------------ 2824// EVERY backend appends each node id as it emits it. Two backends that consumed the SAME ir 2825// COMPLETELY produce IDENTICAL traces -- that identity is the equivalence oracle available on a 2826// box with no GPU, and it is not vacuous: a backend that silently skips a statement produces a 2827// SHORTER trace, and one that visits in a different order produces a DIFFERENT one. 2828func tr_add(trace: *i64, tcap: i64, tn: *i64, id: i64) -> i64 { 2829 if tn[0] < tcap { trace[tn[0]] = id; tn[0] = tn[0] + 1 } 2830 return 0 2831} 2832 2833 2834// ---- builder shorthands (one IR node per call; a node is never reused as two children) ---------------------- 2835func ws_f(m: *i64, s: *u8) -> i64 { return sir_lit(m, s, T_F32) } 2836func ws_i(m: *i64, s: *u8) -> i64 { return sir_lit(m, s, T_I32) } 2837// an unsigned constant as a cast of an int literal: `u32(0)` / `uint(0)`, the one spelling valid in both dialects 2838func ws_u(m: *i64, s: *u8) -> i64 { return sir_cast(m, sir_lit(m, s, T_I32), T_U32) } 2839func ws_id(m: *i64, n: *u8, ty: i64) -> i64 { return sir_ident(m, n, ty) } 2840func ws_bin(m: *i64, op: *u8, a: i64, b: i64, ty: i64) -> i64 { return sir_bin(m, op, a, b, ty) } 2841func ws_cmp(m: *i64, op: *u8, a: i64, b: i64) -> i64 { return sir_bin(m, op, a, b, T_BOOL) } 2842func ws_and(m: *i64, a: i64, b: i64) -> i64 { return sir_bin(m, "&&" as *u8, a, b, T_BOOL) } 2843func ws_or(m: *i64, a: i64, b: i64) -> i64 { return sir_bin(m, "||" as *u8, a, b, T_BOOL) } 2844func ws_sw(m: *i64, a: i64, sel: *u8, ty: i64) -> i64 { return sir_swz(m, a, sel, ty) } 2845// swizzle of a FRESH identifier (the common `name.x` read) 2846func ws_sx(m: *i64, n: *u8, nty: i64, sel: *u8, ty: i64) -> i64 { return sir_swz(m, sir_ident(m, n, nty), sel, ty) } 2847func ws_mem(m: *i64, a: i64, name: *u8, ty: i64) -> i64 { return sir_member_of(m, a, name, ty) } 2848func ws_c1(m: *i64, name: *u8, a: i64, ty: i64) -> i64 { let c: i64 = sir_call(m, name, ty); sir_arg(m, c, a); return c } 2849func ws_c2(m: *i64, name: *u8, a: i64, b: i64, ty: i64) -> i64 { let c: i64 = sir_call(m, name, ty); sir_arg(m, c, a); sir_arg(m, c, b); return c } 2850func ws_c3(m: *i64, name: *u8, a: i64, b: i64, c3: i64, ty: i64) -> i64 { let c: i64 = sir_call(m, name, ty); sir_arg(m, c, a); sir_arg(m, c, b); sir_arg(m, c, c3); return c } 2851func ws_c4(m: *i64, name: *u8, a: i64, b: i64, c3: i64, d: i64, ty: i64) -> i64 { let c: i64 = sir_call(m, name, ty); sir_arg(m, c, a); sir_arg(m, c, b); sir_arg(m, c, c3); sir_arg(m, c, d); return c } 2852func ws_c5(m: *i64, name: *u8, a: i64, b: i64, c3: i64, d: i64, e: i64, ty: i64) -> i64 { let c: i64 = sir_call(m, name, ty); sir_arg(m, c, a); sir_arg(m, c, b); sir_arg(m, c, c3); sir_arg(m, c, d); sir_arg(m, c, e); return c } 2853func ws_c6(m: *i64, name: *u8, a: i64, b: i64, c3: i64, d: i64, e: i64, g: i64, ty: i64) -> i64 { let c: i64 = sir_call(m, name, ty); sir_arg(m, c, a); sir_arg(m, c, b); sir_arg(m, c, c3); sir_arg(m, c, d); sir_arg(m, c, e); sir_arg(m, c, g); return c } 2854func ws_v1(m: *i64, ty: i64, a: i64) -> i64 { let c: i64 = sir_ctor(m, ty); sir_arg(m, c, a); return c } 2855func ws_vt2(m: *i64, ty: i64, a: i64, b: i64) -> i64 { let c: i64 = sir_ctor(m, ty); sir_arg(m, c, a); sir_arg(m, c, b); return c } 2856func ws_vt3(m: *i64, ty: i64, a: i64, b: i64, c3: i64) -> i64 { let c: i64 = sir_ctor(m, ty); sir_arg(m, c, a); sir_arg(m, c, b); sir_arg(m, c, c3); return c } 2857func ws_v2(m: *i64, a: i64, b: i64) -> i64 { return ws_vt2(m, T_V2F, a, b) } 2858func ws_v3(m: *i64, a: i64, b: i64, c3: i64) -> i64 { return ws_vt3(m, T_V3F, a, b, c3) } 2859func ws_v3i(m: *i64, a: i64, b: i64, c3: i64) -> i64 { return ws_vt3(m, T_V3I, a, b, c3) } 2860func ws_v4(m: *i64, a: i64, b: i64, c3: i64, d: i64) -> i64 { let c: i64 = sir_ctor(m, T_V4F); sir_arg(m, c, a); sir_arg(m, c, b); sir_arg(m, c, c3); sir_arg(m, c, d); return c } 2861// vec2f(x,y) / vec3f(x,y,z) of three f32 literals 2862func ws_v2f(m: *i64, a: *u8, b: *u8) -> i64 { return ws_v2(m, ws_f(m, a), ws_f(m, b)) } 2863func ws_v3f(m: *i64, a: *u8, b: *u8, c3: *u8) -> i64 { return ws_v3(m, ws_f(m, a), ws_f(m, b), ws_f(m, c3)) } 2864func ws_q2(m: *i64, a: i64, b: i64) -> i64 { return sir_seq(m, a, b) } 2865func ws_q3(m: *i64, a: i64, b: i64, c: i64) -> i64 { return sir_seq(m, sir_seq(m, a, b), c) } 2866func ws_q4(m: *i64, a: i64, b: i64, c: i64, d: i64) -> i64 { return sir_seq(m, ws_q3(m, a, b, c), d) } 2867func ws_q5(m: *i64, a: i64, b: i64, c: i64, d: i64, e: i64) -> i64 { return sir_seq(m, ws_q4(m, a, b, c, d), e) } 2868func ws_set(m: *i64, lhs: i64, rhs: i64) -> i64 { return sir_assign(m, lhs, rhs) } 2869// x = (x op y) -- the spelling of every compound assignment in the hand text 2870func ws_op(m: *i64, name: *u8, ty: i64, op: *u8, rhs: i64) -> i64 { return sir_assign(m, sir_ident(m, name, ty), sir_bin(m, op, sir_ident(m, name, ty), rhs, ty)) } 2871// name.sel = (name.sel op y) 2872func ws_opx(m: *i64, name: *u8, nty: i64, sel: *u8, ty: i64, op: *u8, rhs: i64) -> i64 { return sir_assign(m, ws_sx(m, name, nty, sel, ty), sir_bin(m, op, ws_sx(m, name, nty, sel, ty), rhs, ty)) } 2873func ws_if(m: *i64, c: i64, t: i64, e: i64) -> i64 { return sir_if(m, c, t, e) } 2874func ws_ret(m: *i64, e: i64) -> i64 { return sir_return(m, e) } 2875func ws_st(m: *i64, f: i64, s: i64) -> i64 { return sir_stmt(m, f, s) } 2876 2877// ---- the uniform block, declared ONCE (its layout is derived by `nx_wgsl layout`) ------------------------------ 2878// p0 of the hand packer is `yflip` here: the WebGPU door packs 1 (top-left origin), the WebGL2 door 0 (see fs). 2879func ws_world_uniforms(m: *i64) -> i64 { 2880 sir_uniform(m, "cam" as *u8, T_V3F, 0) 2881 sir_uniform(m, "t" as *u8, T_F32, 0) 2882 sir_uniform(m, "yp" as *u8, T_V4F, 0) 2883 sir_uniform(m, "sund" as *u8, T_V3F, 0) 2884 sir_uniform(m, "rain" as *u8, T_F32, 0) 2885 sir_uniform(m, "skt" as *u8, T_V3F, 0) 2886 sir_uniform(m, "sun" as *u8, T_F32, 0) 2887 sir_uniform(m, "skh" as *u8, T_V3F, 0) 2888 sir_uniform(m, "cld" as *u8, T_F32, 0) 2889 sir_uniform(m, "res" as *u8, T_V2F, 0) 2890 sir_uniform(m, "wmax" as *u8, T_F32, 0) 2891 sir_uniform(m, "selb" as *u8, T_F32, 0) 2892 sir_uniform(m, "hok" as *u8, T_F32, 0) 2893 sir_uniform(m, "npct" as *u8, T_F32, 0) 2894 sir_uniform(m, "yflip" as *u8, T_F32, 0) 2895 sir_uniform(m, "p1" as *u8, T_F32, 0) 2896 sir_uniform(m, "pal" as *u8, T_V4F, 12) 2897 sir_uniform(m, "palf" as *u8, T_V4F, 5) 2898 return 0 2899} 2900 2901// ---- GE55 (2026-09-05): THE RAW UNIFORM BLOCK -- the sim's own state words, verbatim (nx_wasm_craft pack_uniforms) ---- 2902// 40 little-endian 32-bit words, 160 B; word index = byte offset / 4, the SAME table as UR_* in nx_wasm_craft.nx, and 2903// `nx_wgsl layout` on this module is the one ruler both sides are pinned to. Signed state is i32, packed RGB words and 2904// flags are u32. NOTHING here is a float: the wasm lane carries no f32 lowering, so every float the world pass needs is 2905// derived in upk() below from these words -- the page packs no uniform and computes no arithmetic (the bridge is one 2906// writeBuffer from the sim's memory). The derived values live in MODULE-SCOPE PRIVATE STATE (K_PRIVATE) under the 2907// names every function body already reads (cam, t, yp, sund, rain, skt, sun, skh, cld, res, wmax, selb, hok, npct, 2908// yflip, pal[12], palf[5]), so every function below stays byte-identical to the hand text it was proven against. 2909func ws_ru(m: *i64, n: *u8, ty: i64) -> i64 { return sir_uniform(m, n, ty, 0) } 2910func ws_world_uniforms_raw(m: *i64) -> i64 { 2911 ws_ru(m, "r_camx" as *u8, T_I32); ws_ru(m, "r_camy" as *u8, T_I32); ws_ru(m, "r_camz" as *u8, T_I32); ws_ru(m, "r_tnow" as *u8, T_I32) 2912 ws_ru(m, "r_yaw" as *u8, T_I32); ws_ru(m, "r_pitch" as *u8, T_I32); ws_ru(m, "r_sunel" as *u8, T_I32); ws_ru(m, "r_dayt" as *u8, T_I32) 2913 ws_ru(m, "r_dlen" as *u8, T_I32); ws_ru(m, "r_rain" as *u8, T_I32); ws_ru(m, "r_skt" as *u8, T_U32); ws_ru(m, "r_skh" as *u8, T_U32) 2914 ws_ru(m, "r_sun" as *u8, T_I32); ws_ru(m, "r_cld" as *u8, T_I32); ws_ru(m, "r_wmax" as *u8, T_I32); ws_ru(m, "r_selb" as *u8, T_I32) 2915 ws_ru(m, "r_hok" as *u8, T_I32); ws_ru(m, "r_npct" as *u8, T_I32); ws_ru(m, "r_broken" as *u8, T_I32); ws_ru(m, "r_placed" as *u8, T_I32) 2916 ws_ru(m, "r_pal0" as *u8, T_U32); ws_ru(m, "r_pal1" as *u8, T_U32); ws_ru(m, "r_pal2" as *u8, T_U32); ws_ru(m, "r_pal3" as *u8, T_U32) 2917 ws_ru(m, "r_pal4" as *u8, T_U32); ws_ru(m, "r_pal5" as *u8, T_U32); ws_ru(m, "r_pal6" as *u8, T_U32); ws_ru(m, "r_pal7" as *u8, T_U32) 2918 ws_ru(m, "r_pal8" as *u8, T_U32); ws_ru(m, "r_pal9" as *u8, T_U32); ws_ru(m, "r_pal10" as *u8, T_U32); ws_ru(m, "r_pal11" as *u8, T_U32) 2919 ws_ru(m, "r_palf0" as *u8, T_U32); ws_ru(m, "r_palf1" as *u8, T_U32); ws_ru(m, "r_palf2" as *u8, T_U32); ws_ru(m, "r_palf3" as *u8, T_U32) 2920 ws_ru(m, "r_palf4" as *u8, T_U32); ws_ru(m, "r_resw" as *u8, T_I32); ws_ru(m, "r_resh" as *u8, T_I32); ws_ru(m, "r_yflip" as *u8, T_I32) 2921 ws_ru(m, "r_surf" as *u8, T_I32) // Continuous terrain and water; vegetation/objects still need separate geometry. 2922 ws_ru(m, "r_water_q8" as *u8, T_I32) // Native UR_WATER_Q8, previously padding; block size is unchanged. 2923 return 0 2924} 2925// the derived values, module-scope private state: `var<private>` in WGSL, a bare global in GLSL 2926func ws_world_privates(m: *i64) -> i64 { 2927 sir_private(m, "cam" as *u8, T_V3F, 0); sir_private(m, "t" as *u8, T_F32, 0); sir_private(m, "yp" as *u8, T_V4F, 0) 2928 sir_private(m, "sund" as *u8, T_V3F, 0); sir_private(m, "rain" as *u8, T_F32, 0); sir_private(m, "skt" as *u8, T_V3F, 0) 2929 sir_private(m, "sun" as *u8, T_F32, 0); sir_private(m, "skh" as *u8, T_V3F, 0); sir_private(m, "cld" as *u8, T_F32, 0) 2930 sir_private(m, "res" as *u8, T_V2F, 0); sir_private(m, "wmax" as *u8, T_F32, 0); sir_private(m, "selb" as *u8, T_F32, 0) 2931 sir_private(m, "hok" as *u8, T_F32, 0); sir_private(m, "npct" as *u8, T_F32, 0); sir_private(m, "yflip" as *u8, T_F32, 0) 2932 sir_private(m, "pal" as *u8, T_V4F, 12); sir_private(m, "palf" as *u8, T_V4F, 5) 2933 return 0 2934} 2935// f32(<raw i32 word>) 2936func ws_fi(m: *i64, n: *u8) -> i64 { return sir_cast(m, sir_ident(m, n, T_I32), T_F32) } 2937// fn rgb9(w:u32)->vec3f{return vec3f(f32(w&255u),f32((w>>8u)&255u),f32((w>>16u)&255u))/255.0;} -- the packed RGB word 2938func ws_fn_rgb9(m: *i64) -> i64 { 2939 let f: i64 = sir_func(m, "rgb9" as *u8, T_V3F) 2940 sir_param(m, f, "w" as *u8, T_U32) 2941 let r: i64 = sir_cast(m, ws_bin(m, "&" as *u8, ws_id(m, "w" as *u8, T_U32), ws_u(m, "255" as *u8), T_U32), T_F32) 2942 let g: i64 = sir_cast(m, ws_bin(m, "&" as *u8, ws_bin(m, ">>" as *u8, ws_id(m, "w" as *u8, T_U32), ws_u(m, "8" as *u8), T_U32), ws_u(m, "255" as *u8), T_U32), T_F32) 2943 let b: i64 = sir_cast(m, ws_bin(m, "&" as *u8, ws_bin(m, ">>" as *u8, ws_id(m, "w" as *u8, T_U32), ws_u(m, "16" as *u8), T_U32), ws_u(m, "255" as *u8), T_U32), T_F32) 2944 ws_st(m, f, ws_ret(m, ws_bin(m, "/" as *u8, ws_v3(m, r, g, b), ws_f(m, "255.0" as *u8), T_V3F))) 2945 return f 2946} 2947// <arr>[<k>]=vec4f(rgb9(<rn>)*dl2,0.0); 2948func ws_pal(m: *i64, f: i64, arr: *u8, k: *u8, rn: *u8) -> i64 { 2949 let c: i64 = ws_vt2(m, T_V4F, ws_bin(m, "*" as *u8, ws_c1(m, "rgb9" as *u8, ws_id(m, rn, T_U32), T_V3F), ws_id(m, "dl2" as *u8, T_F32), T_V3F), ws_f(m, "0.0" as *u8)) 2950 return ws_st(m, f, ws_set(m, sir_index(m, ws_id(m, arr, T_V4F), ws_i(m, k), T_V4F), c)) 2951} 2952// fn upk(){ ... } -- every float the world pass reads, derived ONCE per fragment from the raw words. The arithmetic is 2953// the retired JS packer's, line for line: cam/256, yaw and pitch Q12, df=max(0,sunel/4096), dl2=.22+.78*pow(df,.6), 2954// nn=1-dl2, the sky words unpacked times dl2 plus nn*k, the sun disc only when the sim says sun AND df>.04, the sun 2955// direction from the day fraction (az) and the elevation (el), and the 17 palette words times dl2. 2956func ws_fn_upk(m: *i64) -> i64 { 2957 let f: i64 = sir_func(m, "upk" as *u8, T_VOID) 2958 ws_st(m, f, ws_set(m, ws_id(m, "cam" as *u8, T_V3F), ws_bin(m, "/" as *u8, ws_v3(m, ws_fi(m, "r_camx" as *u8), ws_fi(m, "r_camy" as *u8), ws_fi(m, "r_camz" as *u8)), ws_f(m, "256.0" as *u8), T_V3F))) 2959 ws_st(m, f, ws_set(m, ws_id(m, "t" as *u8, T_F32), ws_fi(m, "r_tnow" as *u8))) 2960 ws_st(m, f, sir_let(m, "ya" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_fi(m, "r_yaw" as *u8), ws_f(m, "4096.0" as *u8), T_F32))) 2961 ws_st(m, f, sir_let(m, "pa" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_fi(m, "r_pitch" as *u8), ws_f(m, "4096.0" as *u8), T_F32))) 2962 ws_st(m, f, ws_set(m, ws_id(m, "yp" as *u8, T_V4F), ws_v4(m, ws_c1(m, "sin" as *u8, ws_id(m, "ya" as *u8, T_F32), T_F32), ws_c1(m, "cos" as *u8, ws_id(m, "ya" as *u8, T_F32), T_F32), ws_c1(m, "sin" as *u8, ws_id(m, "pa" as *u8, T_F32), T_F32), ws_c1(m, "cos" as *u8, ws_id(m, "pa" as *u8, T_F32), T_F32)))) 2963 ws_st(m, f, sir_let(m, "sel" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_fi(m, "r_sunel" as *u8), ws_f(m, "4096.0" as *u8), T_F32))) 2964 ws_st(m, f, sir_let(m, "df" as *u8, T_F32, ws_c2(m, "max" as *u8, ws_f(m, "0.0" as *u8), ws_id(m, "sel" as *u8, T_F32), T_F32))) 2965 ws_st(m, f, sir_let(m, "dl2" as *u8, T_F32, ws_bin(m, "+" as *u8, ws_f(m, "0.22" as *u8), ws_bin(m, "*" as *u8, ws_f(m, "0.78" as *u8), ws_c2(m, "pow" as *u8, ws_id(m, "df" as *u8, T_F32), ws_f(m, "0.6" as *u8), T_F32), T_F32), T_F32))) 2966 ws_st(m, f, sir_let(m, "nn" as *u8, T_F32, ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_id(m, "dl2" as *u8, T_F32), T_F32))) 2967 ws_st(m, f, sir_let(m, "dfr" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_fi(m, "r_dayt" as *u8), ws_c2(m, "max" as *u8, ws_f(m, "1.0" as *u8), ws_fi(m, "r_dlen" as *u8), T_F32), T_F32))) 2968 ws_st(m, f, sir_let(m, "az" as *u8, T_F32, ws_bin(m, "*" as *u8, ws_f(m, "3.14159265" as *u8), ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_bin(m, "*" as *u8, ws_id(m, "dfr" as *u8, T_F32), ws_f(m, "1.6" as *u8), T_F32), T_F32), T_F32))) 2969 ws_st(m, f, sir_let(m, "el" as *u8, T_F32, ws_c1(m, "asin" as *u8, ws_c3(m, "clamp" as *u8, ws_id(m, "sel" as *u8, T_F32), ws_f(m, "-0.99" as *u8), ws_f(m, "0.99" as *u8), T_F32), T_F32))) 2970 ws_st(m, f, sir_let(m, "sd" as *u8, T_V3F, ws_v3(m, ws_bin(m, "*" as *u8, ws_c1(m, "cos" as *u8, ws_id(m, "el" as *u8, T_F32), T_F32), ws_c1(m, "cos" as *u8, ws_id(m, "az" as *u8, T_F32), T_F32), T_F32), ws_c1(m, "sin" as *u8, ws_id(m, "el" as *u8, T_F32), T_F32), ws_bin(m, "*" as *u8, ws_c1(m, "cos" as *u8, ws_id(m, "el" as *u8, T_F32), T_F32), ws_f(m, "0.45" as *u8), T_F32)))) 2971 ws_st(m, f, sir_let(m, "sdl" as *u8, T_F32, ws_c1(m, "length" as *u8, ws_id(m, "sd" as *u8, T_V3F), T_F32))) 2972 ws_st(m, f, ws_set(m, ws_id(m, "sund" as *u8, T_V3F), ws_bin(m, "/" as *u8, ws_id(m, "sd" as *u8, T_V3F), sir_select(m, ws_cmp(m, "==" as *u8, ws_id(m, "sdl" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_f(m, "1.0" as *u8), ws_id(m, "sdl" as *u8, T_F32), T_F32), T_V3F))) 2973 ws_st(m, f, ws_set(m, ws_id(m, "rain" as *u8, T_F32), ws_fi(m, "r_rain" as *u8))) 2974 ws_st(m, f, ws_set(m, ws_id(m, "skt" as *u8, T_V3F), ws_bin(m, "+" as *u8, ws_bin(m, "*" as *u8, ws_c1(m, "rgb9" as *u8, ws_id(m, "r_skt" as *u8, T_U32), T_V3F), ws_id(m, "dl2" as *u8, T_F32), T_V3F), ws_bin(m, "*" as *u8, ws_v3f(m, "0.02" as *u8, "0.03" as *u8, "0.10" as *u8), ws_id(m, "nn" as *u8, T_F32), T_V3F), T_V3F))) 2975 ws_st(m, f, ws_set(m, ws_id(m, "skh" as *u8, T_V3F), ws_bin(m, "+" as *u8, ws_bin(m, "*" as *u8, ws_c1(m, "rgb9" as *u8, ws_id(m, "r_skh" as *u8, T_U32), T_V3F), ws_id(m, "dl2" as *u8, T_F32), T_V3F), ws_bin(m, "*" as *u8, ws_v3f(m, "0.04" as *u8, "0.05" as *u8, "0.12" as *u8), ws_id(m, "nn" as *u8, T_F32), T_V3F), T_V3F))) 2976 ws_st(m, f, ws_set(m, ws_id(m, "sun" as *u8, T_F32), sir_select(m, ws_and(m, ws_cmp(m, "==" as *u8, ws_id(m, "r_sun" as *u8, T_I32), ws_i(m, "1" as *u8)), ws_cmp(m, ">" as *u8, ws_id(m, "df" as *u8, T_F32), ws_f(m, "0.04" as *u8))), ws_f(m, "1.0" as *u8), ws_f(m, "0.0" as *u8), T_F32))) 2977 ws_st(m, f, ws_set(m, ws_id(m, "cld" as *u8, T_F32), ws_fi(m, "r_cld" as *u8))) 2978 ws_st(m, f, ws_set(m, ws_id(m, "res" as *u8, T_V2F), ws_v2(m, ws_fi(m, "r_resw" as *u8), ws_fi(m, "r_resh" as *u8)))) 2979 ws_st(m, f, ws_set(m, ws_id(m, "wmax" as *u8, T_F32), ws_fi(m, "r_wmax" as *u8))) 2980 ws_st(m, f, ws_set(m, ws_id(m, "selb" as *u8, T_F32), ws_fi(m, "r_selb" as *u8))) 2981 ws_st(m, f, ws_set(m, ws_id(m, "hok" as *u8, T_F32), ws_fi(m, "r_hok" as *u8))) 2982 ws_st(m, f, ws_set(m, ws_id(m, "npct" as *u8, T_F32), ws_fi(m, "r_npct" as *u8))) 2983 ws_st(m, f, ws_set(m, ws_id(m, "yflip" as *u8, T_F32), ws_fi(m, "r_yflip" as *u8))) 2984 ws_pal(m, f, "pal" as *u8, "0" as *u8, "r_pal0" as *u8); ws_pal(m, f, "pal" as *u8, "1" as *u8, "r_pal1" as *u8); ws_pal(m, f, "pal" as *u8, "2" as *u8, "r_pal2" as *u8) 2985 ws_pal(m, f, "pal" as *u8, "3" as *u8, "r_pal3" as *u8); ws_pal(m, f, "pal" as *u8, "4" as *u8, "r_pal4" as *u8); ws_pal(m, f, "pal" as *u8, "5" as *u8, "r_pal5" as *u8) 2986 ws_pal(m, f, "pal" as *u8, "6" as *u8, "r_pal6" as *u8); ws_pal(m, f, "pal" as *u8, "7" as *u8, "r_pal7" as *u8); ws_pal(m, f, "pal" as *u8, "8" as *u8, "r_pal8" as *u8) 2987 ws_pal(m, f, "pal" as *u8, "9" as *u8, "r_pal9" as *u8); ws_pal(m, f, "pal" as *u8, "10" as *u8, "r_pal10" as *u8); ws_pal(m, f, "pal" as *u8, "11" as *u8, "r_pal11" as *u8) 2988 ws_pal(m, f, "palf" as *u8, "0" as *u8, "r_palf0" as *u8); ws_pal(m, f, "palf" as *u8, "1" as *u8, "r_palf1" as *u8); ws_pal(m, f, "palf" as *u8, "2" as *u8, "r_palf2" as *u8) 2989 ws_pal(m, f, "palf" as *u8, "3" as *u8, "r_palf3" as *u8); ws_pal(m, f, "palf" as *u8, "4" as *u8, "r_palf4" as *u8) 2990 return f 2991} 2992 2993// fn vx(c:vec3i)->u32{if(c.x<0||c.y<0||c.z<0||c.x>127||c.y>47||c.z>127){return 0u;}return textureLoad(tV,vec3i(c.x,c.z,c.y),0).r;} 2994func ws_fn_vx(m: *i64) -> i64 { 2995 let f: i64 = sir_func(m, "vx" as *u8, T_U32) 2996 sir_param(m, f, "c" as *u8, T_V3I) 2997 let c0: i64 = ws_or(m, ws_or(m, ws_or(m, ws_or(m, ws_or(m, 2998 ws_cmp(m, "<" as *u8, ws_sx(m, "c" as *u8, T_V3I, "x" as *u8, T_I32), ws_i(m, "0" as *u8)), 2999 ws_cmp(m, "<" as *u8, ws_sx(m, "c" as *u8, T_V3I, "y" as *u8, T_I32), ws_i(m, "0" as *u8))), 3000 ws_cmp(m, "<" as *u8, ws_sx(m, "c" as *u8, T_V3I, "z" as *u8, T_I32), ws_i(m, "0" as *u8))), 3001 ws_cmp(m, ">" as *u8, ws_sx(m, "c" as *u8, T_V3I, "x" as *u8, T_I32), ws_i(m, "127" as *u8))), 3002 ws_cmp(m, ">" as *u8, ws_sx(m, "c" as *u8, T_V3I, "y" as *u8, T_I32), ws_i(m, "47" as *u8))), 3003 ws_cmp(m, ">" as *u8, ws_sx(m, "c" as *u8, T_V3I, "z" as *u8, T_I32), ws_i(m, "127" as *u8))) 3004 ws_st(m, f, ws_if(m, c0, ws_ret(m, ws_u(m, "0" as *u8)), 0)) 3005 let co: i64 = ws_v3i(m, ws_sx(m, "c" as *u8, T_V3I, "x" as *u8, T_I32), ws_sx(m, "c" as *u8, T_V3I, "z" as *u8, T_I32), ws_sx(m, "c" as *u8, T_V3I, "y" as *u8, T_I32)) 3006 let tl: i64 = sir_texload(m, ws_id(m, "tV" as *u8, T_TEX3U), co, ws_i(m, "0" as *u8)) 3007 ws_st(m, f, ws_ret(m, ws_sw(m, tl, "r" as *u8, T_U32))) 3008 return f 3009} 3010 3011// fn h21(p:vec2f)->f32{return fract(sin(dot(p,vec2f(127.1,311.7)))*43758.5453);} 3012func ws_fn_h21(m: *i64) -> i64 { 3013 let f: i64 = sir_func(m, "h21" as *u8, T_F32) 3014 sir_param(m, f, "p" as *u8, T_V2F) 3015 let d: i64 = ws_c2(m, "dot" as *u8, ws_id(m, "p" as *u8, T_V2F), ws_v2f(m, "127.1" as *u8, "311.7" as *u8), T_F32) 3016 ws_st(m, f, ws_ret(m, ws_c1(m, "fract" as *u8, ws_bin(m, "*" as *u8, ws_c1(m, "sin" as *u8, d, T_F32), ws_f(m, "43758.5453" as *u8), T_F32), T_F32))) 3017 return f 3018} 3019 3020// fn h31(p:vec3f)->f32{return fract(sin(dot(p,vec3f(127.1,311.7,74.7)))*43758.5453);} 3021func ws_fn_h31(m: *i64) -> i64 { 3022 let f: i64 = sir_func(m, "h31" as *u8, T_F32) 3023 sir_param(m, f, "p" as *u8, T_V3F) 3024 let d: i64 = ws_c2(m, "dot" as *u8, ws_id(m, "p" as *u8, T_V3F), ws_v3f(m, "127.1" as *u8, "311.7" as *u8, "74.7" as *u8), T_F32) 3025 ws_st(m, f, ws_ret(m, ws_c1(m, "fract" as *u8, ws_bin(m, "*" as *u8, ws_c1(m, "sin" as *u8, d, T_F32), ws_f(m, "43758.5453" as *u8), T_F32), T_F32))) 3026 return f 3027} 3028 3029// fn pal9(b:u32)->vec3f{var i:i32=i32(b);if(i<0){i=0;}if(i>16){i=16;}if(i<12){return u.pal[i].xyz;}return u.palf[i-12].xyz;} 3030func ws_fn_pal9(m: *i64) -> i64 { 3031 let f: i64 = sir_func(m, "pal9" as *u8, T_V3F) 3032 sir_param(m, f, "b" as *u8, T_U32) 3033 ws_st(m, f, sir_var(m, "i" as *u8, T_I32, sir_cast(m, ws_id(m, "b" as *u8, T_U32), T_I32))) 3034 ws_st(m, f, ws_if(m, ws_cmp(m, "<" as *u8, ws_id(m, "i" as *u8, T_I32), ws_i(m, "0" as *u8)), ws_set(m, ws_id(m, "i" as *u8, T_I32), ws_i(m, "0" as *u8)), 0)) 3035 ws_st(m, f, ws_if(m, ws_cmp(m, ">" as *u8, ws_id(m, "i" as *u8, T_I32), ws_i(m, "16" as *u8)), ws_set(m, ws_id(m, "i" as *u8, T_I32), ws_i(m, "16" as *u8)), 0)) 3036 let pal: i64 = ws_sw(m, sir_index(m, ws_id(m, "pal" as *u8, T_V4F), ws_id(m, "i" as *u8, T_I32), T_V4F), "xyz" as *u8, T_V3F) 3037 ws_st(m, f, ws_if(m, ws_cmp(m, "<" as *u8, ws_id(m, "i" as *u8, T_I32), ws_i(m, "12" as *u8)), ws_ret(m, pal), 0)) 3038 let palf: i64 = ws_sw(m, sir_index(m, ws_id(m, "palf" as *u8, T_V4F), ws_bin(m, "-" as *u8, ws_id(m, "i" as *u8, T_I32), ws_i(m, "12" as *u8), T_I32), T_V4F), "xyz" as *u8, T_V3F) 3039 ws_st(m, f, ws_ret(m, palf)) 3040 return f 3041} 3042 3043// the 2-octave hash noise used by sky: mix(mix(h21(i),h21(i+(1,0)),f.x),mix(h21(i+(0,1)),h21(i+(1,1)),f.x),f.y) 3044func ws_noise2(m: *i64, iname: *u8, fname: *u8) -> i64 { 3045 let a: i64 = ws_c1(m, "h21" as *u8, ws_id(m, iname, T_V2F), T_F32) 3046 let b: i64 = ws_c1(m, "h21" as *u8, ws_bin(m, "+" as *u8, ws_id(m, iname, T_V2F), ws_v2f(m, "1.0" as *u8, "0.0" as *u8), T_V2F), T_F32) 3047 let c: i64 = ws_c1(m, "h21" as *u8, ws_bin(m, "+" as *u8, ws_id(m, iname, T_V2F), ws_v2f(m, "0.0" as *u8, "1.0" as *u8), T_V2F), T_F32) 3048 let d: i64 = ws_c1(m, "h21" as *u8, ws_bin(m, "+" as *u8, ws_id(m, iname, T_V2F), ws_v2f(m, "1.0" as *u8, "1.0" as *u8), T_V2F), T_F32) 3049 let m1: i64 = ws_c3(m, "mix" as *u8, a, b, ws_sx(m, fname, T_V2F, "x" as *u8, T_F32), T_F32) 3050 let m2: i64 = ws_c3(m, "mix" as *u8, c, d, ws_sx(m, fname, T_V2F, "x" as *u8, T_F32), T_F32) 3051 return ws_c3(m, "mix" as *u8, m1, m2, ws_sx(m, fname, T_V2F, "y" as *u8, T_F32), T_F32) 3052} 3053 3054// ---- PG22 (2026-09-06): THE VOLUMETRIC WEATHER FIELD ON THE GPU DOOR -- the sky's cloud is a 3D density RAYMARCHED, 3055// never a sheet. This is nx_worldgen.wg_cloud_volume's law spoken in the shader dialect: 3D value noise on h31 (three 3056// octaves), a vertical envelope between CB_BASE and CB_TOP, and a coverage threshold driven by the weather's cloud 3057// param (cld); the CPU tier keeps drawing the sheet (nx_worldgen.wg_cloud_sheet) and nx_cloud_volume_gate proves the 3058// volume follows the weather field. The march is one slab, entry to exit in CB_STEPS steps, front-to-back transmittance, 3059// lit by a base-to-top gradient plus a silver term toward the sun. Every number is a named string const (a data hook 3060// until the weather table carries it). The hand text, statement for statement: 3061// fn vn3(q:vec3f)->f32{let i=floor(q);let f0=fract(q);let f=f0*f0*(3.0-f0*2.0);let a=h31(i);let b=h31(i+vec3f(1,0,0));...;let k=h31(i+vec3f(1,1,1));return mix(mix(mix(a,b,f.x),mix(c,d,f.x),f.y),mix(mix(e,g,f.x),mix(h,k,f.x),f.y),f.z);} 3062// fn cvol(p:vec3f)->f32{let q=vec3f(p.x/CELL+t/DX,p.y/CELLY,p.z/CELL+t/DZ);let n=vn3(q)*0.5+vn3(q*2.0)*0.3+vn3(q*4.0)*0.2;let env=min((p.y-BASE)/RLO,1.0)*min((TOP-p.y)/RHI,1.0);let thr=THR0-THRK*clamp(cld,0.0,1.0);return clamp((n-thr)*GAIN,0.0,1.0)*max(env,0.0);} 3063// fn cld3(rd:vec3f,s0:vec3f)->vec3f{if(cld<=0.5||rd.y<=0.04){return s0;}let t0=(BASE-cam.y)/rd.y;if(t0<=0.0){return s0;}let dt=((TOP-cam.y)/rd.y-t0)/STEPS;var tr=1.0;var acc=vec3f(0.0);for(var i=0;i<STEPS;i++){let p=cam+rd*(t0+(f32(i)+0.5)*dt);let dn=1.0-exp(0.0-cvol(p)*dt*SIGMA);let lit=LIT0+LIT1*(p.y-BASE)/(TOP-BASE)+SUNK*max(dot(rd,sund),0.0)*sun;acc=acc+vec3f(0.98,0.99,1.0)*(lit*dn*tr);tr=tr*(1.0-dn);}return s0*tr+acc;} 3064const CB_BASE: *u8 = "180.0" // the incumbent sheet's height in blocks, now the slab base 3065const CB_TOP: *u8 = "300.0" 3066const CB_RLO: *u8 = "24.0" // density ramps in over this many blocks above the base 3067const CB_RHI: *u8 = "48.0" // and out over this many below the top 3068const CB_CELL: *u8 = "180.0" // one noise cell in blocks, horizontal 3069const CB_CELLY: *u8 = "120.0" // and vertical (flatter cells read as stratus) 3070const CB_DRIFT_X: *u8 = "240.0" // the incumbent sheet's drift divisors, kept so the motion does not change 3071const CB_DRIFT_Z: *u8 = "540.0" 3072const CB_THR0: *u8 = "0.78" // coverage threshold at cld=0 3073const CB_THRK: *u8 = "0.30" // and how far cld=1 lowers it 3074const CB_GAIN: *u8 = "1.4" 3075const CB_STEPS: *u8 = "48" 3076const CB_STEPS_F: *u8 = "48.0" 3077const CB_SIGMA: *u8 = "0.04" // extinction per block of unit density 3078const CB_LIT0: *u8 = "0.70" // ambient at the base 3079const CB_LIT1: *u8 = "0.30" // brighter toward the top 3080const CB_SUNK: *u8 = "0.15" // silver toward the sun 3081 3082// PRIVATE cloud profile: world-unit artistic defaults, not measured weather. 3083// Six sun-ray samples follow the existing banked cloud contract; view/light budgets require device qualification. 3084const CB_EROSION: *u8 = "0.22" 3085const CB_SUN_Y_MIN: *u8 = "0.01" 3086const CB_LIGHT_DISTANCE: *u8 = "480.0" 3087const CB_LIGHT_STEPS: *u8 = "6" 3088const CB_LIGHT_STEPS_F: *u8 = "6.0" 3089const CB_VIEW_DISTANCE: *u8 = "1400.0" 3090const CB_PHASE_G: *u8 = "0.35" 3091const CB_AMBIENT: *u8 = "0.85" 3092const CB_DIRECT: *u8 = "0.72" 3093func ws_h31o(m: *i64, a: *u8, b: *u8, c: *u8) -> i64 { 3094 return ws_c1(m, "h31" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "i" as *u8, T_V3F), ws_v3f(m, a, b, c), T_V3F), T_F32) 3095} 3096func ws_fx(m: *i64, sel: *u8) -> i64 { return ws_sx(m, "f" as *u8, T_V3F, sel, T_F32) } 3097func ws_py(m: *i64) -> i64 { return ws_sx(m, "p" as *u8, T_V3F, "y" as *u8, T_F32) } 3098// fn vn3(q:vec3f)->f32 -- 3D value noise on h31, trilinear with a smoothstep fade (the shape of noise2 lifted one axis) 3099func ws_fn_vn3(m: *i64) -> i64 { 3100 let f: i64 = sir_func(m, "vn3" as *u8, T_F32) 3101 sir_param(m, f, "q" as *u8, T_V3F) 3102 ws_st(m, f, sir_let(m, "i" as *u8, T_V3F, ws_c1(m, "floor" as *u8, ws_id(m, "q" as *u8, T_V3F), T_V3F))) 3103 ws_st(m, f, sir_let(m, "f0" as *u8, T_V3F, ws_c1(m, "fract" as *u8, ws_id(m, "q" as *u8, T_V3F), T_V3F))) 3104 ws_st(m, f, sir_let(m, "f" as *u8, T_V3F, ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "f0" as *u8, T_V3F), ws_id(m, "f0" as *u8, T_V3F), T_V3F), ws_bin(m, "-" as *u8, ws_v1(m, T_V3F, ws_f(m, "3.0" as *u8)), ws_bin(m, "*" as *u8, ws_id(m, "f0" as *u8, T_V3F), ws_f(m, "2.0" as *u8), T_V3F), T_V3F), T_V3F))) 3105 ws_st(m, f, sir_let(m, "a" as *u8, T_F32, ws_c1(m, "h31" as *u8, ws_id(m, "i" as *u8, T_V3F), T_F32))) 3106 ws_st(m, f, sir_let(m, "b" as *u8, T_F32, ws_h31o(m, "1.0" as *u8, "0.0" as *u8, "0.0" as *u8))) 3107 ws_st(m, f, sir_let(m, "c" as *u8, T_F32, ws_h31o(m, "0.0" as *u8, "1.0" as *u8, "0.0" as *u8))) 3108 ws_st(m, f, sir_let(m, "d" as *u8, T_F32, ws_h31o(m, "1.0" as *u8, "1.0" as *u8, "0.0" as *u8))) 3109 ws_st(m, f, sir_let(m, "e" as *u8, T_F32, ws_h31o(m, "0.0" as *u8, "0.0" as *u8, "1.0" as *u8))) 3110 ws_st(m, f, sir_let(m, "g" as *u8, T_F32, ws_h31o(m, "1.0" as *u8, "0.0" as *u8, "1.0" as *u8))) 3111 ws_st(m, f, sir_let(m, "h" as *u8, T_F32, ws_h31o(m, "0.0" as *u8, "1.0" as *u8, "1.0" as *u8))) 3112 ws_st(m, f, sir_let(m, "k" as *u8, T_F32, ws_h31o(m, "1.0" as *u8, "1.0" as *u8, "1.0" as *u8))) 3113 let x0: i64 = ws_c3(m, "mix" as *u8, ws_c3(m, "mix" as *u8, ws_id(m, "a" as *u8, T_F32), ws_id(m, "b" as *u8, T_F32), ws_fx(m, "x" as *u8), T_F32), ws_c3(m, "mix" as *u8, ws_id(m, "c" as *u8, T_F32), ws_id(m, "d" as *u8, T_F32), ws_fx(m, "x" as *u8), T_F32), ws_fx(m, "y" as *u8), T_F32) 3114 let x1: i64 = ws_c3(m, "mix" as *u8, ws_c3(m, "mix" as *u8, ws_id(m, "e" as *u8, T_F32), ws_id(m, "g" as *u8, T_F32), ws_fx(m, "x" as *u8), T_F32), ws_c3(m, "mix" as *u8, ws_id(m, "h" as *u8, T_F32), ws_id(m, "k" as *u8, T_F32), ws_fx(m, "x" as *u8), T_F32), ws_fx(m, "y" as *u8), T_F32) 3115 ws_st(m, f, ws_ret(m, ws_c3(m, "mix" as *u8, x0, x1, ws_fx(m, "z" as *u8), T_F32))) 3116 return f 3117} 3118// fn cvol(p:vec3f)->f32 -- the cloud density at a world point: three octaves, the envelope, the coverage threshold 3119func ws_fn_cvol(m: *i64) -> i64 { 3120 let f: i64 = sir_func(m, "cvol" as *u8, T_F32) 3121 sir_param(m, f, "p" as *u8, T_V3F) 3122 ws_st(m, f, sir_let(m, "hf" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_sx(m, "p" as *u8, T_V3F, "y" as *u8, T_F32), ws_f(m, CB_BASE), T_F32), ws_bin(m, "-" as *u8, ws_f(m, CB_TOP), ws_f(m, CB_BASE), T_F32), T_F32))) 3123 ws_st(m, f, sir_let(m, "adv" as *u8, T_V3F, ws_v3(m, ws_bin(m, "/" as *u8, ws_id(m, "t" as *u8, T_F32), ws_f(m, CB_DRIFT_X), T_F32), ws_f(m, "0.0" as *u8), ws_bin(m, "/" as *u8, ws_id(m, "t" as *u8, T_F32), ws_f(m, CB_DRIFT_Z), T_F32)))) 3124 ws_st(m, f, sir_let(m, "q" as *u8, T_V3F, ws_bin(m, "+" as *u8, ws_bin(m, "/" as *u8, ws_id(m, "p" as *u8, T_V3F), ws_v3(m, ws_f(m, CB_CELL), ws_f(m, CB_CELLY), ws_f(m, CB_CELL)), T_V3F), ws_id(m, "adv" as *u8, T_V3F), T_V3F))) 3125 ws_st(m, f, sir_let(m, "shape" as *u8, T_F32, ws_c1(m, "vn3" as *u8, ws_id(m, "q" as *u8, T_V3F), T_F32))) 3126 ws_st(m, f, sir_let(m, "detail" as *u8, T_F32, ws_c1(m, "vn3" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "q" as *u8, T_V3F), ws_f(m, "4.0" as *u8), T_V3F), T_F32))) 3127 ws_st(m, f, sir_let(m, "env" as *u8, T_F32, ws_bin(m, "*" as *u8, ws_c3(m, "smoothstep" as *u8, ws_f(m, "0.0" as *u8), ws_bin(m, "/" as *u8, ws_f(m, CB_RLO), ws_bin(m, "-" as *u8, ws_f(m, CB_TOP), ws_f(m, CB_BASE), T_F32), T_F32), ws_id(m, "hf" as *u8, T_F32), T_F32), ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_c3(m, "smoothstep" as *u8, ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_bin(m, "/" as *u8, ws_f(m, CB_RHI), ws_bin(m, "-" as *u8, ws_f(m, CB_TOP), ws_f(m, CB_BASE), T_F32), T_F32), T_F32), ws_f(m, "1.0" as *u8), ws_id(m, "hf" as *u8, T_F32), T_F32), T_F32), T_F32))) 3128 ws_st(m, f, sir_let(m, "thr" as *u8, T_F32, ws_bin(m, "-" as *u8, ws_f(m, CB_THR0), ws_bin(m, "*" as *u8, ws_f(m, CB_THRK), ws_c3(m, "clamp" as *u8, ws_id(m, "cld" as *u8, T_F32), ws_f(m, "0.0" as *u8), ws_f(m, "1.0" as *u8), T_F32), T_F32), T_F32))) 3129 ws_st(m, f, sir_let(m, "body" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "shape" as *u8, T_F32), ws_id(m, "thr" as *u8, T_F32), T_F32), ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_id(m, "thr" as *u8, T_F32), T_F32), T_F32))) 3130 ws_st(m, f, sir_let(m, "eroded" as *u8, T_F32, ws_bin(m, "-" as *u8, ws_id(m, "body" as *u8, T_F32), ws_bin(m, "*" as *u8, ws_id(m, "detail" as *u8, T_F32), ws_f(m, CB_EROSION), T_F32), T_F32))) 3131 ws_st(m, f, ws_ret(m, ws_bin(m, "*" as *u8, ws_c3(m, "clamp" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "eroded" as *u8, T_F32), ws_f(m, CB_GAIN), T_F32), ws_f(m, "0.0" as *u8), ws_f(m, "1.0" as *u8), T_F32), ws_id(m, "env" as *u8, T_F32), T_F32))) 3132 return f 3133} 3134 3135func ws_fn_csun(m: *i64) -> i64 { 3136 let f: i64 = sir_func(m, "csun" as *u8, T_F32) 3137 sir_param(m, f, "p" as *u8, T_V3F) 3138 ws_st(m, f, sir_let(m, "dy" as *u8, T_F32, ws_c2(m, "max" as *u8, ws_sx(m, "sund" as *u8, T_V3F, "y" as *u8, T_F32), ws_f(m, CB_SUN_Y_MIN), T_F32))) 3139 ws_st(m, f, sir_let(m, "ds" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_c2(m, "min" as *u8, ws_bin(m, "/" as *u8, ws_c2(m, "max" as *u8, ws_bin(m, "-" as *u8, ws_f(m, CB_TOP), ws_sx(m, "p" as *u8, T_V3F, "y" as *u8, T_F32), T_F32), ws_f(m, "0.0" as *u8), T_F32), ws_id(m, "dy" as *u8, T_F32), T_F32), ws_f(m, CB_LIGHT_DISTANCE), T_F32), ws_f(m, CB_LIGHT_STEPS_F), T_F32))) 3140 ws_st(m, f, sir_var(m, "tau" as *u8, T_F32, ws_f(m, "0.0" as *u8))) 3141 let lp: i64 = sir_let(m, "lp" as *u8, T_V3F, ws_bin(m, "+" as *u8, ws_id(m, "p" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "sund" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_bin(m, "+" as *u8, ws_fi(m, "j" as *u8), ws_f(m, "0.5" as *u8), T_F32), ws_id(m, "ds" as *u8, T_F32), T_F32), T_V3F), T_V3F)) 3142 let lt: i64 = ws_op(m, "tau" as *u8, T_F32, "+" as *u8, ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_c1(m, "cvol" as *u8, ws_id(m, "lp" as *u8, T_V3F), T_F32), ws_id(m, "ds" as *u8, T_F32), T_F32), ws_f(m, CB_SIGMA), T_F32)) 3143 sir_for_until(m, f, sir_var(m, "j" as *u8, T_I32, ws_i(m, "0" as *u8)), ws_cmp(m, ">=" as *u8, ws_id(m, "j" as *u8, T_I32), ws_i(m, CB_LIGHT_STEPS)), ws_op(m, "j" as *u8, T_I32, "+" as *u8, ws_i(m, "1" as *u8)), ws_q2(m, lp, lt)) 3144 ws_st(m, f, ws_ret(m, ws_c1(m, "exp" as *u8, ws_bin(m, "-" as *u8, ws_f(m, "0.0" as *u8), ws_id(m, "tau" as *u8, T_F32), T_F32), T_F32))) 3145 return f 3146} 3147// fn cld3(rd:vec3f,s0:vec3f)->vec3f -- the march: the sky colour s0 seen through the slab, front to back 3148func ws_fn_cld3(m: *i64) -> i64 { 3149 let f: i64 = sir_func(m, "cld3" as *u8, T_V3F) 3150 sir_param(m, f, "rd" as *u8, T_V3F) 3151 sir_param(m, f, "s0" as *u8, T_V3F) 3152 ws_st(m, f, ws_if(m, ws_cmp(m, "<=" as *u8, ws_id(m, "cld" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_ret(m, ws_id(m, "s0" as *u8, T_V3F)), 0)) 3153 ws_st(m, f, sir_let(m, "dy" as *u8, T_F32, ws_sx(m, "rd" as *u8, T_V3F, "y" as *u8, T_F32))) 3154 ws_st(m, f, ws_if(m, ws_cmp(m, "==" as *u8, ws_id(m, "dy" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_ret(m, ws_id(m, "s0" as *u8, T_V3F)), 0)) 3155 ws_st(m, f, sir_let(m, "a" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_f(m, CB_BASE), ws_sx(m, "cam" as *u8, T_V3F, "y" as *u8, T_F32), T_F32), ws_id(m, "dy" as *u8, T_F32), T_F32))) 3156 ws_st(m, f, sir_let(m, "b" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_f(m, CB_TOP), ws_sx(m, "cam" as *u8, T_V3F, "y" as *u8, T_F32), T_F32), ws_id(m, "dy" as *u8, T_F32), T_F32))) 3157 ws_st(m, f, sir_let(m, "t0" as *u8, T_F32, ws_c2(m, "max" as *u8, ws_c2(m, "min" as *u8, ws_id(m, "a" as *u8, T_F32), ws_id(m, "b" as *u8, T_F32), T_F32), ws_f(m, "0.0" as *u8), T_F32))) 3158 ws_st(m, f, sir_let(m, "t1" as *u8, T_F32, ws_c2(m, "min" as *u8, ws_c2(m, "max" as *u8, ws_id(m, "a" as *u8, T_F32), ws_id(m, "b" as *u8, T_F32), T_F32), ws_f(m, CB_VIEW_DISTANCE), T_F32))) 3159 ws_st(m, f, ws_if(m, ws_cmp(m, "<=" as *u8, ws_id(m, "t1" as *u8, T_F32), ws_id(m, "t0" as *u8, T_F32)), ws_ret(m, ws_id(m, "s0" as *u8, T_V3F)), 0)) 3160 ws_st(m, f, sir_let(m, "dt" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "t1" as *u8, T_F32), ws_id(m, "t0" as *u8, T_F32), T_F32), ws_f(m, CB_STEPS_F), T_F32))) 3161 ws_st(m, f, sir_var(m, "tr" as *u8, T_F32, ws_f(m, "1.0" as *u8))) 3162 ws_st(m, f, sir_var(m, "acc" as *u8, T_V3F, ws_v1(m, T_V3F, ws_f(m, "0.0" as *u8)))) 3163 ws_st(m, f, sir_let(m, "phase" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_bin(m, "*" as *u8, ws_f(m, CB_PHASE_G), ws_f(m, CB_PHASE_G), T_F32), T_F32), ws_c2(m, "pow" as *u8, ws_bin(m, "-" as *u8, ws_bin(m, "+" as *u8, ws_f(m, "1.0" as *u8), ws_bin(m, "*" as *u8, ws_f(m, CB_PHASE_G), ws_f(m, CB_PHASE_G), T_F32), T_F32), ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_f(m, "2.0" as *u8), ws_f(m, CB_PHASE_G), T_F32), ws_c2(m, "dot" as *u8, ws_id(m, "rd" as *u8, T_V3F), ws_id(m, "sund" as *u8, T_V3F), T_F32), T_F32), T_F32), ws_f(m, "1.5" as *u8), T_F32), T_F32))) 3164 ws_st(m, f, sir_let(m, "ambient" as *u8, T_V3F, ws_bin(m, "*" as *u8, ws_id(m, "s0" as *u8, T_V3F), ws_f(m, CB_AMBIENT), T_V3F))) 3165 let p0: i64 = sir_let(m, "p" as *u8, T_V3F, ws_bin(m, "+" as *u8, ws_id(m, "cam" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "rd" as *u8, T_V3F), ws_bin(m, "+" as *u8, ws_id(m, "t0" as *u8, T_F32), ws_bin(m, "*" as *u8, ws_bin(m, "+" as *u8, ws_fi(m, "i" as *u8), ws_f(m, "0.5" as *u8), T_F32), ws_id(m, "dt" as *u8, T_F32), T_F32), T_F32), T_V3F), T_V3F)) 3166 let d0: i64 = sir_let(m, "density" as *u8, T_F32, ws_c1(m, "cvol" as *u8, ws_id(m, "p" as *u8, T_V3F), T_F32)) 3167 let a0: i64 = sir_let(m, "alpha" as *u8, T_F32, ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_c1(m, "exp" as *u8, ws_bin(m, "-" as *u8, ws_f(m, "0.0" as *u8), ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "density" as *u8, T_F32), ws_id(m, "dt" as *u8, T_F32), T_F32), ws_f(m, CB_SIGMA), T_F32), T_F32), T_F32), T_F32)) 3168 let l0: i64 = sir_let(m, "light" as *u8, T_V3F, ws_bin(m, "+" as *u8, ws_id(m, "ambient" as *u8, T_V3F), ws_v1(m, T_V3F, ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_c1(m, "csun" as *u8, ws_id(m, "p" as *u8, T_V3F), T_F32), ws_id(m, "phase" as *u8, T_F32), T_F32), ws_f(m, CB_DIRECT), T_F32), ws_id(m, "sun" as *u8, T_F32), T_F32)), T_V3F)) 3169 let c0: i64 = ws_op(m, "acc" as *u8, T_V3F, "+" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "light" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "tr" as *u8, T_F32), ws_id(m, "alpha" as *u8, T_F32), T_F32), T_V3F)) 3170 let t0s: i64 = ws_op(m, "tr" as *u8, T_F32, "*" as *u8, ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_id(m, "alpha" as *u8, T_F32), T_F32)) 3171 let body: i64 = ws_q2(m, ws_q3(m, p0, d0, a0), ws_if(m, ws_cmp(m, ">" as *u8, ws_id(m, "density" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_q3(m, l0, c0, t0s), 0)) 3172 sir_for_until(m, f, sir_var(m, "i" as *u8, T_I32, ws_i(m, "0" as *u8)), ws_cmp(m, ">=" as *u8, ws_id(m, "i" as *u8, T_I32), ws_i(m, CB_STEPS)), ws_op(m, "i" as *u8, T_I32, "+" as *u8, ws_i(m, "1" as *u8)), body) 3173 let fog: i64 = ws_c3(m, "smoothstep" as *u8, ws_f(m, "0.0" as *u8), ws_f(m, CB_VIEW_DISTANCE), ws_id(m, "t0" as *u8, T_F32), T_F32) 3174 let composite: i64 = ws_bin(m, "+" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "s0" as *u8, T_V3F), ws_id(m, "tr" as *u8, T_F32), T_V3F), ws_id(m, "acc" as *u8, T_V3F), T_V3F) 3175 ws_st(m, f, ws_ret(m, ws_c3(m, "mix" as *u8, composite, ws_id(m, "s0" as *u8, T_V3F), fog, T_V3F))) 3176 return f 3177} 3178 3179// fn sky(rd:vec3f)->vec3f{ ... } -- the hand text, statement for statement 3180func ws_fn_sky(m: *i64) -> i64 { 3181 let f: i64 = sir_func(m, "sky" as *u8, T_V3F) 3182 sir_param(m, f, "rd" as *u8, T_V3F) 3183 // var s:vec3f=mix(u.skh,u.skt,clamp(rd.y*1.6+0.35,0.0,1.0)); 3184 let cl: i64 = ws_c3(m, "clamp" as *u8, ws_bin(m, "+" as *u8, ws_bin(m, "*" as *u8, ws_sx(m, "rd" as *u8, T_V3F, "y" as *u8, T_F32), ws_f(m, "1.6" as *u8), T_F32), ws_f(m, "0.35" as *u8), T_F32), ws_f(m, "0.0" as *u8), ws_f(m, "1.0" as *u8), T_F32) 3185 ws_st(m, f, sir_var(m, "s" as *u8, T_V3F, ws_c3(m, "mix" as *u8, ws_id(m, "skh" as *u8, T_V3F), ws_id(m, "skt" as *u8, T_V3F), cl, T_V3F))) 3186 // if(u.sun>0.5){let d=dot(rd,u.sund);if(d>0.9993){s=vec3f(1.0,0.98,0.88);}else if(d>0.985){s=mix(s,vec3f(1.0,0.98,0.88),(d-0.985)/0.015*0.6);}} 3187 let dlet: i64 = sir_let(m, "d" as *u8, T_F32, ws_c2(m, "dot" as *u8, ws_id(m, "rd" as *u8, T_V3F), ws_id(m, "sund" as *u8, T_V3F), T_F32)) 3188 let sunc: i64 = ws_set(m, ws_id(m, "s" as *u8, T_V3F), ws_v3f(m, "1.0" as *u8, "0.98" as *u8, "0.88" as *u8)) 3189 let glow: i64 = ws_set(m, ws_id(m, "s" as *u8, T_V3F), ws_c3(m, "mix" as *u8, ws_id(m, "s" as *u8, T_V3F), ws_v3f(m, "1.0" as *u8, "0.98" as *u8, "0.88" as *u8), ws_bin(m, "*" as *u8, ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "d" as *u8, T_F32), ws_f(m, "0.985" as *u8), T_F32), ws_f(m, "0.015" as *u8), T_F32), ws_f(m, "0.6" as *u8), T_F32), T_V3F)) 3190 let inner: i64 = ws_if(m, ws_cmp(m, ">" as *u8, ws_id(m, "d" as *u8, T_F32), ws_f(m, "0.9993" as *u8)), sunc, ws_if(m, ws_cmp(m, ">" as *u8, ws_id(m, "d" as *u8, T_F32), ws_f(m, "0.985" as *u8)), glow, 0)) 3191 ws_st(m, f, ws_if(m, ws_cmp(m, ">" as *u8, ws_id(m, "sun" as *u8, T_F32), ws_f(m, "0.5" as *u8)), ws_q2(m, dlet, inner), 0)) 3192 // PG22: s = cld3(rd, s) -- the cloud is a raymarched 3D density (vn3 / cvol / cld3 above), never the projected 3193 // sheet this block used to draw; the cld>0.5 && rd.y>0.04 gate lives inside cld3 as its early return. 3194 ws_st(m, f, ws_set(m, ws_id(m, "s" as *u8, T_V3F), ws_c2(m, "cld3" as *u8, ws_id(m, "rd" as *u8, T_V3F), ws_id(m, "s" as *u8, T_V3F), T_V3F))) 3195 ws_st(m, f, ws_ret(m, ws_id(m, "s" as *u8, T_V3F))) 3196 return f 3197} 3198 3199// struct Hit{t:f32,c:vec3i,fce:i32,b:u32} 3200func ws_struct_hit(m: *i64) -> i64 { 3201 let st: i64 = sir_struct(m, "Hit" as *u8) 3202 sir_member(m, st, "t" as *u8, T_F32) 3203 sir_member(m, st, "c" as *u8, T_V3I) 3204 sir_member(m, st, "fce" as *u8, T_I32) 3205 sir_member(m, st, "b" as *u8, T_U32) 3206 return st 3207} 3208 3209// one DDA step: c.<a>=(c.<a>+st.<a>);t=tm.<a>;tm.<a>=(tm.<a>+dd.<a>);fce=select(<f>,<t>,st.<a>>0); 3210func ws_dda_step(m: *i64, ax: *u8, fneg: *u8, fpos: *u8) -> i64 { 3211 let s1: i64 = ws_opx(m, "c" as *u8, T_V3I, ax, T_I32, "+" as *u8, ws_sx(m, "st" as *u8, T_V3I, ax, T_I32)) 3212 let s2: i64 = ws_set(m, ws_id(m, "t" as *u8, T_F32), ws_sx(m, "tm" as *u8, T_V3F, ax, T_F32)) 3213 let s3: i64 = ws_opx(m, "tm" as *u8, T_V3F, ax, T_F32, "+" as *u8, ws_sx(m, "dd" as *u8, T_V3F, ax, T_F32)) 3214 let s4: i64 = ws_set(m, ws_id(m, "fce" as *u8, T_I32), sir_select(m, ws_cmp(m, ">" as *u8, ws_sx(m, "st" as *u8, T_V3I, ax, T_I32), ws_i(m, "0" as *u8)), ws_i(m, fpos), ws_i(m, fneg), T_I32)) 3215 return ws_q4(m, s1, s2, s3, s4) 3216} 3217 3218// ---- GE53 THE SURFACE FIELD (WebGPU, emitted from this one source) --------------------------------------- 3219// fn hq(x:i32,z:i32)->f32{ if(x<0||z<0||x>127||z>127){return -1.0;} return textureLoad(tH,vec2i(x,z),0).r; } 3220// the column's Q height in blocks (the base of its top block), -1 off the window 3221func ws_fn_hq(m: *i64) -> i64 { 3222 let f: i64 = sir_func(m, "hq" as *u8, T_F32) 3223 sir_param(m, f, "x" as *u8, T_I32) 3224 sir_param(m, f, "z" as *u8, T_I32) 3225 let oob: i64 = ws_or(m, ws_or(m, ws_or(m, 3226 ws_cmp(m, "<" as *u8, ws_id(m, "x" as *u8, T_I32), ws_i(m, "0" as *u8)), 3227 ws_cmp(m, "<" as *u8, ws_id(m, "z" as *u8, T_I32), ws_i(m, "0" as *u8))), 3228 ws_cmp(m, ">" as *u8, ws_id(m, "x" as *u8, T_I32), ws_i(m, "127" as *u8))), 3229 ws_cmp(m, ">" as *u8, ws_id(m, "z" as *u8, T_I32), ws_i(m, "127" as *u8))) 3230 ws_st(m, f, ws_if(m, oob, ws_ret(m, ws_f(m, "-1.0" as *u8)), 0)) 3231 let tl: i64 = sir_texload(m, ws_id(m, "tH" as *u8, T_TEX2F), ws_vt2(m, T_V2I, ws_id(m, "x" as *u8, T_I32), ws_id(m, "z" as *u8, T_I32)), ws_i(m, "0" as *u8)) 3232 ws_st(m, f, ws_ret(m, ws_sw(m, tl, "r" as *u8, T_F32))) 3233 return f 3234} 3235// fn surf(p:vec2f)->f32 -- the terrain SURFACE height at a world xz: bilinear between the four columns, 3236// one block above the column base (the top of the ground). -1 when any corner is off the field. 3237func ws_fn_surf(m: *i64) -> i64 { 3238 let f: i64 = sir_func(m, "surf" as *u8, T_F32) 3239 sir_param(m, f, "p" as *u8, T_V2F) 3240 ws_st(m, f, sir_let(m, "ix" as *u8, T_I32, sir_cast(m, ws_c1(m, "floor" as *u8, ws_sx(m, "p" as *u8, T_V2F, "x" as *u8, T_F32), T_F32), T_I32))) 3241 ws_st(m, f, sir_let(m, "iz" as *u8, T_I32, sir_cast(m, ws_c1(m, "floor" as *u8, ws_sx(m, "p" as *u8, T_V2F, "y" as *u8, T_F32), T_F32), T_I32))) 3242 ws_st(m, f, sir_let(m, "fr" as *u8, T_V2F, ws_c1(m, "fract" as *u8, ws_id(m, "p" as *u8, T_V2F), T_V2F))) 3243 ws_st(m, f, sir_let(m, "h00" as *u8, T_F32, ws_c2(m, "hq" as *u8, ws_id(m, "ix" as *u8, T_I32), ws_id(m, "iz" as *u8, T_I32), T_F32))) 3244 ws_st(m, f, sir_let(m, "h10" as *u8, T_F32, ws_c2(m, "hq" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "ix" as *u8, T_I32), ws_i(m, "1" as *u8), T_I32), ws_id(m, "iz" as *u8, T_I32), T_F32))) 3245 ws_st(m, f, sir_let(m, "h01" as *u8, T_F32, ws_c2(m, "hq" as *u8, ws_id(m, "ix" as *u8, T_I32), ws_bin(m, "+" as *u8, ws_id(m, "iz" as *u8, T_I32), ws_i(m, "1" as *u8), T_I32), T_F32))) 3246 ws_st(m, f, sir_let(m, "h11" as *u8, T_F32, ws_c2(m, "hq" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "ix" as *u8, T_I32), ws_i(m, "1" as *u8), T_I32), ws_bin(m, "+" as *u8, ws_id(m, "iz" as *u8, T_I32), ws_i(m, "1" as *u8), T_I32), T_F32))) 3247 let miss: i64 = ws_or(m, ws_or(m, ws_or(m, 3248 ws_cmp(m, "<" as *u8, ws_id(m, "h00" as *u8, T_F32), ws_f(m, "0.0" as *u8)), 3249 ws_cmp(m, "<" as *u8, ws_id(m, "h10" as *u8, T_F32), ws_f(m, "0.0" as *u8))), 3250 ws_cmp(m, "<" as *u8, ws_id(m, "h01" as *u8, T_F32), ws_f(m, "0.0" as *u8))), 3251 ws_cmp(m, "<" as *u8, ws_id(m, "h11" as *u8, T_F32), ws_f(m, "0.0" as *u8))) 3252 ws_st(m, f, ws_if(m, miss, ws_ret(m, ws_f(m, "-1.0" as *u8)), 0)) 3253 let mx0: i64 = ws_c3(m, "mix" as *u8, ws_id(m, "h00" as *u8, T_F32), ws_id(m, "h10" as *u8, T_F32), ws_sx(m, "fr" as *u8, T_V2F, "x" as *u8, T_F32), T_F32) 3254 let mx1: i64 = ws_c3(m, "mix" as *u8, ws_id(m, "h01" as *u8, T_F32), ws_id(m, "h11" as *u8, T_F32), ws_sx(m, "fr" as *u8, T_V2F, "x" as *u8, T_F32), T_F32) 3255 ws_st(m, f, ws_ret(m, ws_bin(m, "+" as *u8, ws_c3(m, "mix" as *u8, mx0, mx1, ws_sx(m, "fr" as *u8, T_V2F, "y" as *u8, T_F32), T_F32), ws_f(m, "1.0" as *u8), T_F32))) 3256 return f 3257} 3258// fn terrain(b:u32)->i32 -- the ground classes the field replaces; structures (wood, leaves, crops, water) stay cells 3259func ws_fn_terrain(m: *i64) -> i64 { 3260 let f: i64 = sir_func(m, "terrain" as *u8, T_I32) 3261 sir_param(m, f, "b" as *u8, T_U32) 3262 let tb: i64 = ws_or(m, ws_or(m, ws_or(m, ws_or(m, ws_or(m, ws_or(m, ws_or(m, 3263 ws_beq(m, "1" as *u8), ws_beq(m, "2" as *u8)), ws_beq(m, "3" as *u8)), ws_beq(m, "5" as *u8)), ws_beq(m, "8" as *u8)), ws_beq(m, "9" as *u8)), ws_beq(m, "10" as *u8)), ws_beq(m, "11" as *u8)) 3264 ws_st(m, f, ws_if(m, tb, ws_ret(m, ws_i(m, "1" as *u8)), 0)) 3265 ws_st(m, f, ws_ret(m, ws_i(m, "0" as *u8))) 3266 return f 3267} 3268// fn smarch(ro:vec3f,rd:vec3f,mx:i32)->f32 -- march the continuous surface: quarter-block steps (the field is 3269// bilinear between columns, so a quarter block can never skip one), exit above the tallest column when climbing, 3270// six bisections at the crossing. Returns t, or -1 for sky. The GPU twin of the sim's wray_surf. 3271func ws_fn_smarch(m: *i64) -> i64 { 3272 let f: i64 = sir_func(m, "smarch" as *u8, T_F32) 3273 sir_param(m, f, "ro" as *u8, T_V3F) 3274 sir_param(m, f, "rd" as *u8, T_V3F) 3275 sir_param(m, f, "mx" as *u8, T_I32) 3276 ws_st(m, f, sir_var(m, "t" as *u8, T_F32, ws_f(m, "0.0" as *u8))) 3277 ws_st(m, f, sir_var(m, "tp" as *u8, T_F32, ws_f(m, "0.0" as *u8))) 3278 ws_st(m, f, sir_var(m, "hit" as *u8, T_I32, ws_i(m, "0" as *u8))) 3279 let s1: i64 = ws_op(m, "t" as *u8, T_F32, "+" as *u8, ws_f(m, "0.25" as *u8)) 3280 let s2: i64 = sir_let(m, "p" as *u8, T_V3F, ws_bin(m, "+" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "rd" as *u8, T_V3F), ws_id(m, "t" as *u8, T_F32), T_V3F), T_V3F)) 3281 let s3: i64 = ws_if(m, ws_and(m, ws_cmp(m, ">" as *u8, ws_sx(m, "p" as *u8, T_V3F, "y" as *u8, T_F32), ws_bin(m, "+" as *u8, ws_id(m, "wmax" as *u8, T_F32), ws_f(m, "2.0" as *u8), T_F32)), ws_cmp(m, ">=" as *u8, ws_sx(m, "rd" as *u8, T_V3F, "y" as *u8, T_F32), ws_f(m, "0.0" as *u8))), ws_ret(m, ws_f(m, "-1.0" as *u8)), 0) 3282 let oob: i64 = ws_or(m, ws_or(m, ws_or(m, 3283 ws_cmp(m, "<" as *u8, ws_sx(m, "p" as *u8, T_V3F, "x" as *u8, T_F32), ws_f(m, "-8.0" as *u8)), 3284 ws_cmp(m, ">" as *u8, ws_sx(m, "p" as *u8, T_V3F, "x" as *u8, T_F32), ws_f(m, "136.0" as *u8))), 3285 ws_cmp(m, "<" as *u8, ws_sx(m, "p" as *u8, T_V3F, "z" as *u8, T_F32), ws_f(m, "-8.0" as *u8))), 3286 ws_cmp(m, ">" as *u8, ws_sx(m, "p" as *u8, T_V3F, "z" as *u8, T_F32), ws_f(m, "136.0" as *u8))) 3287 let s4: i64 = ws_if(m, oob, ws_ret(m, ws_f(m, "-1.0" as *u8)), 0) 3288 let s5: i64 = sir_let(m, "hs" as *u8, T_F32, ws_c1(m, "surf" as *u8, ws_sx(m, "p" as *u8, T_V3F, "xz" as *u8, T_V2F), T_F32)) 3289 let s6: i64 = ws_if(m, ws_and(m, ws_cmp(m, ">=" as *u8, ws_id(m, "hs" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_cmp(m, "<=" as *u8, ws_sx(m, "p" as *u8, T_V3F, "y" as *u8, T_F32), ws_id(m, "hs" as *u8, T_F32))), ws_q2(m, ws_set(m, ws_id(m, "hit" as *u8, T_I32), ws_i(m, "1" as *u8)), sir_break(m)), 0) 3290 let s7: i64 = ws_set(m, ws_id(m, "tp" as *u8, T_F32), ws_id(m, "t" as *u8, T_F32)) 3291 let body: i64 = ws_q4(m, ws_q3(m, s1, s2, s3), s4, s5, ws_q2(m, s6, s7)) 3292 sir_for_until(m, f, sir_var(m, "i" as *u8, T_I32, ws_i(m, "0" as *u8)), ws_cmp(m, ">=" as *u8, ws_id(m, "i" as *u8, T_I32), ws_bin(m, "*" as *u8, ws_id(m, "mx" as *u8, T_I32), ws_i(m, "4" as *u8), T_I32)), ws_op(m, "i" as *u8, T_I32, "+" as *u8, ws_i(m, "1" as *u8)), body) 3293 ws_st(m, f, ws_if(m, ws_cmp(m, "==" as *u8, ws_id(m, "hit" as *u8, T_I32), ws_i(m, "0" as *u8)), ws_ret(m, ws_f(m, "-1.0" as *u8)), 0)) 3294 ws_st(m, f, sir_var(m, "lo" as *u8, T_F32, ws_id(m, "tp" as *u8, T_F32))) 3295 ws_st(m, f, sir_var(m, "hi" as *u8, T_F32, ws_id(m, "t" as *u8, T_F32))) 3296 let k1: i64 = sir_let(m, "md" as *u8, T_F32, ws_bin(m, "*" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "lo" as *u8, T_F32), ws_id(m, "hi" as *u8, T_F32), T_F32), ws_f(m, "0.5" as *u8), T_F32)) 3297 let k2: i64 = sir_let(m, "q" as *u8, T_V3F, ws_bin(m, "+" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "rd" as *u8, T_V3F), ws_id(m, "md" as *u8, T_F32), T_V3F), T_V3F)) 3298 let k3: i64 = sir_let(m, "h2" as *u8, T_F32, ws_c1(m, "surf" as *u8, ws_sx(m, "q" as *u8, T_V3F, "xz" as *u8, T_V2F), T_F32)) 3299 let k4: i64 = ws_if(m, ws_and(m, ws_cmp(m, ">=" as *u8, ws_id(m, "h2" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_cmp(m, "<=" as *u8, ws_sx(m, "q" as *u8, T_V3F, "y" as *u8, T_F32), ws_id(m, "h2" as *u8, T_F32))), ws_set(m, ws_id(m, "hi" as *u8, T_F32), ws_id(m, "md" as *u8, T_F32)), ws_set(m, ws_id(m, "lo" as *u8, T_F32), ws_id(m, "md" as *u8, T_F32))) 3300 sir_for_until(m, f, sir_var(m, "k" as *u8, T_I32, ws_i(m, "0" as *u8)), ws_cmp(m, ">=" as *u8, ws_id(m, "k" as *u8, T_I32), ws_i(m, "6" as *u8)), ws_op(m, "k" as *u8, T_I32, "+" as *u8, ws_i(m, "1" as *u8)), ws_q4(m, k1, k2, k3, k4)) 3301 ws_st(m, f, ws_ret(m, ws_id(m, "hi" as *u8, T_F32))) 3302 return f 3303} 3304// fn gnd(p:vec2f)->f32 -- the ground light from the field's own gradient (central differences a quarter block 3305// apart), Lambert against the sun and NORMALISED TO LEVEL GROUND: level lights exactly as the shipped flat top 3306// face (1.0), the lee side floors at the shipped darkest face (0.42) -- the same two values shade's six-way 3307// face table carries, so a flat field is byte-identical in brightness to a flat block top. 3308func ws_fn_gnd(m: *i64) -> i64 { 3309 let f: i64 = sir_func(m, "gnd" as *u8, T_F32) 3310 sir_param(m, f, "p" as *u8, T_V2F) 3311 ws_st(m, f, sir_let(m, "hl" as *u8, T_F32, ws_c1(m, "surf" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "p" as *u8, T_V2F), ws_v2f(m, "0.25" as *u8, "0.0" as *u8), T_V2F), T_F32))) 3312 ws_st(m, f, sir_let(m, "hr" as *u8, T_F32, ws_c1(m, "surf" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "p" as *u8, T_V2F), ws_v2f(m, "0.25" as *u8, "0.0" as *u8), T_V2F), T_F32))) 3313 ws_st(m, f, sir_let(m, "hd" as *u8, T_F32, ws_c1(m, "surf" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "p" as *u8, T_V2F), ws_v2f(m, "0.0" as *u8, "0.25" as *u8), T_V2F), T_F32))) 3314 ws_st(m, f, sir_let(m, "hu" as *u8, T_F32, ws_c1(m, "surf" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "p" as *u8, T_V2F), ws_v2f(m, "0.0" as *u8, "0.25" as *u8), T_V2F), T_F32))) 3315 let miss: i64 = ws_or(m, ws_or(m, ws_or(m, 3316 ws_cmp(m, "<" as *u8, ws_id(m, "hl" as *u8, T_F32), ws_f(m, "0.0" as *u8)), 3317 ws_cmp(m, "<" as *u8, ws_id(m, "hr" as *u8, T_F32), ws_f(m, "0.0" as *u8))), 3318 ws_cmp(m, "<" as *u8, ws_id(m, "hd" as *u8, T_F32), ws_f(m, "0.0" as *u8))), 3319 ws_cmp(m, "<" as *u8, ws_id(m, "hu" as *u8, T_F32), ws_f(m, "0.0" as *u8))) 3320 ws_st(m, f, ws_if(m, miss, ws_ret(m, ws_f(m, "1.0" as *u8)), 0)) 3321 let nx: i64 = ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "hl" as *u8, T_F32), ws_id(m, "hr" as *u8, T_F32), T_F32), ws_f(m, "0.5" as *u8), T_F32) 3322 let nz: i64 = ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "hd" as *u8, T_F32), ws_id(m, "hu" as *u8, T_F32), T_F32), ws_f(m, "0.5" as *u8), T_F32) 3323 ws_st(m, f, sir_let(m, "n9" as *u8, T_V3F, ws_c1(m, "normalize" as *u8, ws_v3(m, nx, ws_f(m, "1.0" as *u8), nz), T_V3F))) 3324 ws_st(m, f, sir_let(m, "cp" as *u8, T_F32, ws_c2(m, "max" as *u8, ws_c2(m, "dot" as *u8, ws_id(m, "n9" as *u8, T_V3F), ws_id(m, "sund" as *u8, T_V3F), T_F32), ws_f(m, "0.0" as *u8), T_F32))) 3325 ws_st(m, f, sir_let(m, "cf" as *u8, T_F32, ws_c2(m, "max" as *u8, ws_sx(m, "sund" as *u8, T_V3F, "y" as *u8, T_F32), ws_f(m, "0.001" as *u8), T_F32))) 3326 let span: i64 = ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_f(m, "0.42" as *u8), T_F32) 3327 ws_st(m, f, ws_ret(m, ws_c2(m, "min" as *u8, ws_bin(m, "+" as *u8, ws_f(m, "0.42" as *u8), ws_bin(m, "*" as *u8, span, ws_bin(m, "/" as *u8, ws_id(m, "cp" as *u8, T_F32), ws_id(m, "cf" as *u8, T_F32), T_F32), T_F32), T_F32), ws_f(m, "1.0" as *u8), T_F32))) 3328 return f 3329} 3330 3331// fn march(ro:vec3f,rd:vec3f,skipw:i32,mx:i32)->Hit{ ... } 3332func ws_fn_march(m: *i64, hty: i64) -> i64 { 3333 let f: i64 = sir_func(m, "march" as *u8, hty) 3334 sir_param(m, f, "ro" as *u8, T_V3F) 3335 sir_param(m, f, "rd" as *u8, T_V3F) 3336 sir_param(m, f, "skipw" as *u8, T_I32) 3337 sir_param(m, f, "mx" as *u8, T_I32) 3338 sir_param(m, f, "skipt" as *u8, T_I32) // GE53: 1 = terrain cells are air (the field is the ground); callers pass u.r_surf 3339 // var h:Hit;h.t=-1.0;h.fce=0;h.b=0u;h.c=vec3i(0); 3340 ws_st(m, f, sir_var(m, "h" as *u8, hty, 0)) 3341 ws_st(m, f, ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "t" as *u8, T_F32), ws_f(m, "-1.0" as *u8))) 3342 ws_st(m, f, ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "fce" as *u8, T_I32), ws_i(m, "0" as *u8))) 3343 ws_st(m, f, ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "b" as *u8, T_U32), ws_u(m, "0" as *u8))) 3344 ws_st(m, f, ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "c" as *u8, T_V3I), ws_v1(m, T_V3I, ws_i(m, "0" as *u8)))) 3345 // var c:vec3i=vec3i(floor(ro));let st=vec3i(sign(rd)); 3346 ws_st(m, f, sir_var(m, "c" as *u8, T_V3I, ws_v1(m, T_V3I, ws_c1(m, "floor" as *u8, ws_id(m, "ro" as *u8, T_V3F), T_V3F)))) 3347 ws_st(m, f, sir_let(m, "st" as *u8, T_V3I, ws_v1(m, T_V3I, ws_c1(m, "sign" as *u8, ws_id(m, "rd" as *u8, T_V3F), T_V3F)))) 3348 // let dd=abs(vec3f(1.0)/max(abs(rd),vec3f(1e-6))); 3349 ws_st(m, f, sir_let(m, "dd" as *u8, T_V3F, ws_c1(m, "abs" as *u8, ws_bin(m, "/" as *u8, ws_v1(m, T_V3F, ws_f(m, "1.0" as *u8)), ws_c2(m, "max" as *u8, ws_c1(m, "abs" as *u8, ws_id(m, "rd" as *u8, T_V3F), T_V3F), ws_v1(m, T_V3F, ws_f(m, "1e-6" as *u8)), T_V3F), T_V3F), T_V3F))) 3350 // var tm=(vec3f(st)*(vec3f(c)-ro)+vec3f(st)*0.5+vec3f(0.5))*dd; 3351 let tm0: i64 = ws_bin(m, "*" as *u8, ws_v1(m, T_V3F, ws_id(m, "st" as *u8, T_V3I)), ws_bin(m, "-" as *u8, ws_v1(m, T_V3F, ws_id(m, "c" as *u8, T_V3I)), ws_id(m, "ro" as *u8, T_V3F), T_V3F), T_V3F) 3352 let tm1: i64 = ws_bin(m, "+" as *u8, tm0, ws_bin(m, "*" as *u8, ws_v1(m, T_V3F, ws_id(m, "st" as *u8, T_V3I)), ws_f(m, "0.5" as *u8), T_V3F), T_V3F) 3353 let tm2: i64 = ws_bin(m, "+" as *u8, tm1, ws_v1(m, T_V3F, ws_f(m, "0.5" as *u8)), T_V3F) 3354 ws_st(m, f, sir_var(m, "tm" as *u8, T_V3F, ws_bin(m, "*" as *u8, tm2, ws_id(m, "dd" as *u8, T_V3F), T_V3F))) 3355 ws_st(m, f, sir_var(m, "t" as *u8, T_F32, ws_f(m, "0.0" as *u8))) 3356 ws_st(m, f, sir_var(m, "fce" as *u8, T_I32, ws_i(m, "0" as *u8))) 3357 // for(var i:i32=0;i<200;i++){ ... } 3358 let b1: i64 = ws_if(m, ws_cmp(m, ">=" as *u8, ws_id(m, "i" as *u8, T_I32), ws_id(m, "mx" as *u8, T_I32)), sir_break(m), 0) 3359 let cx: i64 = ws_and(m, ws_cmp(m, "<" as *u8, ws_sx(m, "tm" as *u8, T_V3F, "x" as *u8, T_F32), ws_sx(m, "tm" as *u8, T_V3F, "y" as *u8, T_F32)), ws_cmp(m, "<" as *u8, ws_sx(m, "tm" as *u8, T_V3F, "x" as *u8, T_F32), ws_sx(m, "tm" as *u8, T_V3F, "z" as *u8, T_F32))) 3360 let cy: i64 = ws_cmp(m, "<" as *u8, ws_sx(m, "tm" as *u8, T_V3F, "y" as *u8, T_F32), ws_sx(m, "tm" as *u8, T_V3F, "z" as *u8, T_F32)) 3361 let b2: i64 = ws_if(m, cx, ws_dda_step(m, "x" as *u8, "1" as *u8, "2" as *u8), ws_if(m, cy, ws_dda_step(m, "y" as *u8, "3" as *u8, "4" as *u8), ws_dda_step(m, "z" as *u8, "5" as *u8, "6" as *u8))) 3362 // if(f32(c.y)>u.wmax&&rd.y>=0.0){return h;}if(c.y<0){return h;} 3363 let b3: i64 = ws_if(m, ws_and(m, ws_cmp(m, ">" as *u8, sir_cast(m, ws_sx(m, "c" as *u8, T_V3I, "y" as *u8, T_I32), T_F32), ws_id(m, "wmax" as *u8, T_F32)), ws_cmp(m, ">=" as *u8, ws_sx(m, "rd" as *u8, T_V3F, "y" as *u8, T_F32), ws_f(m, "0.0" as *u8))), ws_ret(m, ws_id(m, "h" as *u8, hty)), 0) 3364 let b4: i64 = ws_if(m, ws_cmp(m, "<" as *u8, ws_sx(m, "c" as *u8, T_V3I, "y" as *u8, T_I32), ws_i(m, "0" as *u8)), ws_ret(m, ws_id(m, "h" as *u8, hty)), 0) 3365 // if(c.x< -8||c.x>135||c.z< -8||c.z>135){return h;} 3366 let oob: i64 = ws_or(m, ws_or(m, ws_or(m, ws_cmp(m, "<" as *u8, ws_sx(m, "c" as *u8, T_V3I, "x" as *u8, T_I32), ws_i(m, "-8" as *u8)), ws_cmp(m, ">" as *u8, ws_sx(m, "c" as *u8, T_V3I, "x" as *u8, T_I32), ws_i(m, "135" as *u8))), ws_cmp(m, "<" as *u8, ws_sx(m, "c" as *u8, T_V3I, "z" as *u8, T_I32), ws_i(m, "-8" as *u8))), ws_cmp(m, ">" as *u8, ws_sx(m, "c" as *u8, T_V3I, "z" as *u8, T_I32), ws_i(m, "135" as *u8))) 3367 let b5: i64 = ws_if(m, oob, ws_ret(m, ws_id(m, "h" as *u8, hty)), 0) 3368 // var b=vx(c);if(skipw==1&&b==4u){b=0u;} 3369 let b6: i64 = sir_var(m, "b" as *u8, T_U32, ws_c1(m, "vx" as *u8, ws_id(m, "c" as *u8, T_V3I), T_U32)) 3370 // GE53: under skipt the terrain classes are air too -- the field is the ground, only structures stay cells 3371 let b7: i64 = ws_q2(m, 3372 ws_if(m, ws_and(m, ws_cmp(m, "==" as *u8, ws_id(m, "skipw" as *u8, T_I32), ws_i(m, "1" as *u8)), ws_cmp(m, "==" as *u8, ws_id(m, "b" as *u8, T_U32), ws_u(m, "4" as *u8))), ws_set(m, ws_id(m, "b" as *u8, T_U32), ws_u(m, "0" as *u8)), 0), 3373 ws_if(m, ws_and(m, ws_cmp(m, "==" as *u8, ws_id(m, "skipt" as *u8, T_I32), ws_i(m, "1" as *u8)), ws_cmp(m, "==" as *u8, ws_c1(m, "terrain" as *u8, ws_id(m, "b" as *u8, T_U32), T_I32), ws_i(m, "1" as *u8))), ws_set(m, ws_id(m, "b" as *u8, T_U32), ws_u(m, "0" as *u8)), 0)) 3374 // if(b!=0u){h.t=t;h.c=c;h.fce=fce;h.b=b;return h;} 3375 let hit: i64 = ws_q5(m, 3376 ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "t" as *u8, T_F32), ws_id(m, "t" as *u8, T_F32)), 3377 ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "c" as *u8, T_V3I), ws_id(m, "c" as *u8, T_V3I)), 3378 ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "fce" as *u8, T_I32), ws_id(m, "fce" as *u8, T_I32)), 3379 ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "b" as *u8, T_U32), ws_id(m, "b" as *u8, T_U32)), 3380 ws_ret(m, ws_id(m, "h" as *u8, hty))) 3381 let b8: i64 = ws_if(m, ws_cmp(m, "!=" as *u8, ws_id(m, "b" as *u8, T_U32), ws_u(m, "0" as *u8)), hit, 0) 3382 let body: i64 = ws_q4(m, ws_q4(m, b1, b2, b3, b4), b5, b6, ws_q2(m, b7, b8)) 3383 sir_for_until(m, f, sir_var(m, "i" as *u8, T_I32, ws_i(m, "0" as *u8)), ws_cmp(m, ">=" as *u8, ws_id(m, "i" as *u8, T_I32), ws_i(m, "200" as *u8)), ws_op(m, "i" as *u8, T_I32, "+" as *u8, ws_i(m, "1" as *u8)), body) 3384 ws_st(m, f, ws_ret(m, ws_id(m, "h" as *u8, hty))) 3385 return f 3386} 3387 3388// one face arm of shade: uv=hp.<sw>;oc.<ax>=(oc.<ax><op>1);n=vec3f(<nx>,<ny>,<nz>); 3389func ws_face(m: *i64, sw: *u8, ax: *u8, op: *u8, nx: *u8, ny: *u8, nz: *u8) -> i64 { 3390 return ws_q3(m, 3391 ws_set(m, ws_id(m, "uv" as *u8, T_V2F), ws_sx(m, "hp" as *u8, T_V3F, sw, T_V2F)), 3392 ws_opx(m, "oc" as *u8, T_V3I, ax, T_I32, op, ws_i(m, "1" as *u8)), 3393 ws_set(m, ws_id(m, "n" as *u8, T_V3F), ws_v3f(m, nx, ny, nz))) 3394} 3395// li=(li*<k>) 3396func ws_li(m: *i64, k: *u8) -> i64 { return ws_op(m, "li" as *u8, T_F32, "*" as *u8, ws_f(m, k)) } 3397// fce==<k> 3398func ws_fce(m: *i64, k: *u8) -> i64 { return ws_cmp(m, "==" as *u8, ws_id(m, "fce" as *u8, T_I32), ws_i(m, k)) } 3399// b==<k>u 3400func ws_beq(m: *i64, k: *u8) -> i64 { return ws_cmp(m, "==" as *u8, ws_id(m, "b" as *u8, T_U32), ws_u(m, k)) } 3401// one AO probe: if(f.<fa> <cmp> <edge>){var nb=oc; <nudge>; if(vx(nb)!=0u&&vx(nb)!=4u){ao=(ao+(<gain>)*2.0);}} 3402func ws_ao(m: *i64, fa: *u8, cmp: *u8, edge: *u8, nudge: i64, gain: i64) -> i64 { 3403 let nbv: i64 = sir_var(m, "nb" as *u8, T_V3I, ws_id(m, "oc" as *u8, T_V3I)) 3404 let solid: i64 = ws_and(m, ws_cmp(m, "!=" as *u8, ws_c1(m, "vx" as *u8, ws_id(m, "nb" as *u8, T_V3I), T_U32), ws_u(m, "0" as *u8)), ws_cmp(m, "!=" as *u8, ws_c1(m, "vx" as *u8, ws_id(m, "nb" as *u8, T_V3I), T_U32), ws_u(m, "4" as *u8))) 3405 let add: i64 = ws_op(m, "ao" as *u8, T_F32, "+" as *u8, ws_bin(m, "*" as *u8, gain, ws_f(m, "2.0" as *u8), T_F32)) 3406 return ws_if(m, ws_cmp(m, cmp, ws_sx(m, "f" as *u8, T_V2F, fa, T_F32), ws_f(m, edge)), ws_q3(m, nbv, nudge, ws_if(m, solid, add, 0)), 0) 3407} 3408// nb.<ax>=(nb.<ax><op>1) 3409func ws_nb(m: *i64, ax: *u8, op: *u8) -> i64 { return ws_opx(m, "nb" as *u8, T_V3I, ax, T_I32, op, ws_i(m, "1" as *u8)) } 3410// fce==3||fce==4 3411func ws_topbot(m: *i64) -> i64 { return ws_or(m, ws_fce(m, "3" as *u8), ws_fce(m, "4" as *u8)) } 3412 3413// fn shade(ro:vec3f,rd:vec3f,t:f32,c:vec3i,fce:i32,b:u32)->vec3f{ ... } 3414// GE53 helpers -- each call builds a FRESH node tree (a node is never two children). 3415// Terrain lighting belongs only to a field hit, never to water or an object top face. 3416func ws_smooth(m: *i64) -> i64 { 3417 let field: i64 = ws_and(m, ws_cmp(m, "==" as *u8, sir_ident(m, "r_surf" as *u8, T_I32), ws_i(m, "1" as *u8)), ws_fce(m, "3" as *u8)) 3418 return ws_and(m, field, ws_cmp(m, "==" as *u8, ws_c1(m, "terrain" as *u8, ws_id(m, "b" as *u8, T_U32), T_I32), ws_i(m, "1" as *u8))) 3419} 3420// The analytic plane has two sides; neither inherits the voxel material's cell decoration. 3421func ws_cont_water(m: *i64) -> i64 { 3422 return ws_and(m, ws_and(m, ws_cmp(m, "==" as *u8, sir_ident(m, "r_surf" as *u8, T_I32), ws_i(m, "1" as *u8)), ws_beq(m, "4" as *u8)), ws_topbot(m)) 3423} 3424func ws_cont_surface(m: *i64) -> i64 { return ws_or(m, ws_smooth(m), ws_cont_water(m)) } 3425// one step of the scan-down to the column's top solid cell: if(cy9>0&&vx(vec3i(cx9,cy9,cz9))==0u){cy9=cy9-1;} 3426func ws_step_down(m: *i64) -> i64 { 3427 return ws_if(m, ws_and(m, ws_cmp(m, ">" as *u8, ws_id(m, "cy9" as *u8, T_I32), ws_i(m, "0" as *u8)), ws_cmp(m, "==" as *u8, ws_c1(m, "vx" as *u8, ws_vt3(m, T_V3I, ws_id(m, "cx9" as *u8, T_I32), ws_id(m, "cy9" as *u8, T_I32), ws_id(m, "cz9" as *u8, T_I32)), T_U32), ws_u(m, "0" as *u8))), ws_op(m, "cy9" as *u8, T_I32, "-" as *u8, ws_i(m, "1" as *u8)), 0) 3428} 3429// ---- GR27 (2026-09-06): THE GROUND IS A MATERIAL, NOT A PAINT ----------------------------------------------- 3430// fn sand(hp:vec3f,c:vec3i,rd:vec3f,t:f32)->vec2f -- the field's ground material, all arithmetic on the surface 3431// field and the voxel volume, no texture. x = the light multiplier: the gnd() Lambert (level 1.0, lee floor 0.42) 3432// taken on the field normal TILTED by grain facets and wind ripples, then wet-darkened; y = the specular: a thin 3433// water film inside the wet band (a Blinn lobe plus a Fresnel sky sheen) and dry quartz glints. 3434// FOOTPRINT: one pixel spans 2/res.x world units per unit distance (sc = px/(res.x*0.5)), stretched by 1/|n.rd| 3435// at grazing angles. A hash cannot be mip-averaged, so every detail whose period falls under TWO footprints 3436// (the Nyquist bound) fades to zero instead of aliasing -- grains and ripples survive a grazing view by 3437// fading, never by sparkling; beyond that range sand reads as its albedo, which is what sand does. 3438// WET BAND: the volume is probed for water (class 4) on a ring of SD_RING_DIRS directions x SD_WET_R radii at 3439// the column's top solid cell and one below it; the nearest water radius sets the wetness (adjacent 1, three 3440// cells 1/3, beyond 0) and a water cell ABOVE the column means submerged (1). Wet sand darkens because pore 3441// water index-matches the grains (albedo x SD_WET_DARK) and gains a film sheen; the swash erases ripples. 3442// Every constant below is a data hook: the ripple heading is the wind row's heading and the grain scale the 3443// recipe's sand grade; both become raw words when the material table lands, and are NAMED here until then. 3444const SD_GRAIN_PER_BLOCK: *u8 = "32.0" // facets per block edge: 3 cm at a 1 m block, coarse beach sand 3445const SD_GRAIN_SLOPE: *u8 = "1.2" // facet steepness as a normal slope 3446const SD_RIPPLE_PER_BLOCK: *u8 = "9.0" // wind ripples per block: an 11 cm wavelength at a 1 m block 3447const SD_RIPPLE_SLOPE: *u8 = "0.45" // crest slope 3448const SD_RIPPLE_DX: *u8 = "0.8" // ripple normal heading (unit): the wind heading, a data hook 3449const SD_RIPPLE_DZ: *u8 = "0.6" 3450const SD_PATCH: *u8 = "0.25" // ripple phase jitter per 4-block patch so the field is not one sine 3451const SD_TAU: *u8 = "6.2831853" 3452const SD_QUARTER_TURN: *u8 = "1.5707963" 3453const SD_EIGHTH_TURN: *u8 = "0.7853982" 3454const SD_RING_DIRS: *u8 = "8" 3455const SD_RING_STEPS: *u8 = "24" // SD_RING_DIRS x SD_WET_R 3456const SD_WET_R: *u8 = "3.0" // wet band reach in cells 3457const SD_WET_DRY: *u8 = "4.0" // SD_WET_R + 1: no water within reach 3458const SD_WET_DARK: *u8 = "0.55" // wet albedo multiplier 3459const SD_FILM_POW: *u8 = "48.0" 3460const SD_FILM_GAIN: *u8 = "0.55" 3461const SD_SHEEN_GAIN: *u8 = "0.10" 3462const SD_GLINT_POW: *u8 = "96.0" 3463const SD_GLINT_GAIN: *u8 = "0.35" 3464const SD_GLINT_FRAC: *u8 = "0.9" // facets whose hash exceeds this are quartz glints 3465const SD_NDV_FLOOR: *u8 = "0.15" // the footprint stretch floor at grazing angles 3466const SD_SHADOW_SPEC: *u8 = "0.2" // in a block shadow the film keeps its sky sheen and loses the sun 3467const SD_TWO: *u8 = "2.0" 3468const SD_HALF: *u8 = "0.5" 3469// helpers -- every call builds a FRESH node tree (a node is never two children) 3470func ws_sd_pxz(m: *i64) -> i64 { return ws_sx(m, "hp" as *u8, T_V3F, "xz" as *u8, T_V2F) } 3471func ws_sd_f(m: *i64, n: *u8) -> i64 { return ws_id(m, n, T_F32) } 3472func ws_sd_v3(m: *i64, n: *u8) -> i64 { return ws_id(m, n, T_V3F) } 3473// surf(hp.xz <op> (dx,dz)) 3474func ws_sd_surf(m: *i64, op: *u8, dx: *u8, dz: *u8) -> i64 { return ws_c1(m, "surf" as *u8, ws_bin(m, op, ws_sd_pxz(m), ws_v2f(m, dx, dz), T_V2F), T_F32) } 3475// h21(gc + (dx,dz)) 3476func ws_sd_g(m: *i64, dx: *u8, dz: *u8) -> i64 { return ws_c1(m, "h21" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "gc" as *u8, T_V2F), ws_v2f(m, dx, dz), T_V2F), T_F32) } 3477func ws_sd_lt0(m: *i64, n: *u8) -> i64 { return ws_cmp(m, "<" as *u8, ws_id(m, n, T_F32), ws_f(m, "0.0" as *u8)) } 3478func ws_sd_dk(m: *i64, n: *u8) -> i64 { return ws_bin(m, "*" as *u8, ws_id(m, n, T_I32), ws_id(m, "k" as *u8, T_I32), T_I32) } 3479// vx(c + vec3i(dx*k, <y>, dz*k)) == 4u 3480func ws_sd_wat(m: *i64, y: *u8) -> i64 { return ws_cmp(m, "==" as *u8, ws_c1(m, "vx" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "c" as *u8, T_V3I), ws_vt3(m, T_V3I, ws_sd_dk(m, "dx" as *u8), ws_i(m, y), ws_sd_dk(m, "dz" as *u8)), T_V3I), T_U32), ws_u(m, "4" as *u8)) } 3481// i32(floor(sin(a + <shift>) + 0.5)) -- the ring direction from the angle, sin only (the covered subset) 3482func ws_sd_dir(m: *i64, shift: *u8) -> i64 { return sir_cast(m, ws_c1(m, "floor" as *u8, ws_bin(m, "+" as *u8, ws_c1(m, "sin" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "a" as *u8, T_F32), ws_f(m, shift), T_F32), T_F32), ws_f(m, SD_HALF), T_F32), T_F32), T_I32) } 3483// clamp(1 - 2*fp*<freq>, 0, 1) -- the Nyquist fade of a detail with <freq> periods per block 3484func ws_sd_fade(m: *i64, freq: *u8) -> i64 { return ws_c3(m, "clamp" as *u8, ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_f(m, SD_TWO), ws_sd_f(m, "fp" as *u8), T_F32), ws_f(m, freq), T_F32), T_F32), ws_f(m, "0.0" as *u8), ws_f(m, "1.0" as *u8), T_F32) } 3485// (a - b) * <k> 3486func ws_sd_dslope(m: *i64, a: *u8, b: *u8, k: *u8) -> i64 { return ws_bin(m, "*" as *u8, ws_bin(m, "-" as *u8, ws_sd_f(m, a), ws_sd_f(m, b), T_F32), ws_f(m, k), T_F32) } 3487// <s> + gam*<g> - dry*rs*<rdk> -- one axis of the tilted normal 3488func ws_sd_tilt(m: *i64, s: *u8, g: *u8, rdk: *u8) -> i64 { 3489 let a: i64 = ws_bin(m, "+" as *u8, ws_sd_f(m, s), ws_bin(m, "*" as *u8, ws_sd_f(m, "gam" as *u8), ws_sd_f(m, g), T_F32), T_F32) 3490 return ws_bin(m, "-" as *u8, a, ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_sd_f(m, "dry" as *u8), ws_sd_f(m, "rs" as *u8), T_F32), ws_f(m, rdk), T_F32), T_F32) 3491} 3492// the sand classes: 1 the shore slot (SHORE repaints the grass slot as shore sand), 2 packed sand, 5 sand at and 3493// below the waterline (nx_wasm_craft gencol) -- and ONLY under the field (ws_smooth), which today is the shore 3494// world's ground; a per-class material word is the data hook owed before another world's field lands 3495func ws_sandcls(m: *i64) -> i64 { return ws_or(m, ws_or(m, ws_beq(m, "1" as *u8), ws_beq(m, "2" as *u8)), ws_beq(m, "5" as *u8)) } 3496func ws_sandy(m: *i64) -> i64 { return ws_and(m, ws_smooth(m), ws_sandcls(m)) } 3497// the block-shadow arm: li*=0.55 as shipped, and the film specular loses the sun (keeps its sky sheen) 3498func ws_shd(m: *i64) -> i64 { return ws_q2(m, ws_li(m, "0.55" as *u8), ws_op(m, "sp" as *u8, T_F32, "*" as *u8, ws_f(m, SD_SHADOW_SPEC))) } 3499func ws_fn_sand(m: *i64) -> i64 { 3500 let f: i64 = sir_func(m, "sand" as *u8, T_V2F) 3501 sir_param(m, f, "hp" as *u8, T_V3F) 3502 sir_param(m, f, "c" as *u8, T_V3I) 3503 sir_param(m, f, "rd" as *u8, T_V3F) 3504 sir_param(m, f, "t" as *u8, T_F32) 3505 // the field normal from the same quarter-block central differences gnd uses; off the field = the flat answer 3506 ws_st(m, f, sir_let(m, "hl" as *u8, T_F32, ws_sd_surf(m, "-" as *u8, "0.25" as *u8, "0.0" as *u8))) 3507 ws_st(m, f, sir_let(m, "hr" as *u8, T_F32, ws_sd_surf(m, "+" as *u8, "0.25" as *u8, "0.0" as *u8))) 3508 ws_st(m, f, sir_let(m, "hd" as *u8, T_F32, ws_sd_surf(m, "-" as *u8, "0.0" as *u8, "0.25" as *u8))) 3509 ws_st(m, f, sir_let(m, "hu" as *u8, T_F32, ws_sd_surf(m, "+" as *u8, "0.0" as *u8, "0.25" as *u8))) 3510 let miss: i64 = ws_or(m, ws_or(m, ws_or(m, ws_sd_lt0(m, "hl" as *u8), ws_sd_lt0(m, "hr" as *u8)), ws_sd_lt0(m, "hd" as *u8)), ws_sd_lt0(m, "hu" as *u8)) 3511 ws_st(m, f, ws_if(m, miss, ws_ret(m, ws_v2f(m, "1.0" as *u8, "0.0" as *u8)), 0)) 3512 ws_st(m, f, sir_let(m, "sx" as *u8, T_F32, ws_sd_dslope(m, "hl" as *u8, "hr" as *u8, SD_TWO))) 3513 ws_st(m, f, sir_let(m, "sz" as *u8, T_F32, ws_sd_dslope(m, "hd" as *u8, "hu" as *u8, SD_TWO))) 3514 ws_st(m, f, sir_let(m, "nrm" as *u8, T_V3F, ws_c1(m, "normalize" as *u8, ws_v3(m, ws_sd_f(m, "sx" as *u8), ws_f(m, "1.0" as *u8), ws_sd_f(m, "sz" as *u8)), T_V3F))) 3515 // the pixel footprint at this hit, stretched by the grazing angle 3516 ws_st(m, f, sir_let(m, "ndv" as *u8, T_F32, ws_c2(m, "max" as *u8, ws_c1(m, "abs" as *u8, ws_c2(m, "dot" as *u8, ws_sd_v3(m, "nrm" as *u8), ws_sd_v3(m, "rd" as *u8), T_F32), T_F32), ws_f(m, SD_NDV_FLOOR), T_F32))) 3517 ws_st(m, f, sir_let(m, "fp" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_bin(m, "*" as *u8, ws_sd_f(m, "t" as *u8), ws_bin(m, "/" as *u8, ws_f(m, SD_TWO), ws_sx(m, "res" as *u8, T_V2F, "x" as *u8, T_F32), T_F32), T_F32), ws_sd_f(m, "ndv" as *u8), T_F32))) 3518 // grain facets: three hash taps a facet apart give the facet's two slopes; the amplitude fades at the Nyquist bound 3519 ws_st(m, f, sir_let(m, "gc" as *u8, T_V2F, ws_c1(m, "floor" as *u8, ws_bin(m, "*" as *u8, ws_sd_pxz(m), ws_f(m, SD_GRAIN_PER_BLOCK), T_V2F), T_V2F))) 3520 ws_st(m, f, sir_let(m, "g0" as *u8, T_F32, ws_sd_g(m, "0.0" as *u8, "0.0" as *u8))) 3521 ws_st(m, f, sir_let(m, "g1" as *u8, T_F32, ws_sd_g(m, "1.0" as *u8, "0.0" as *u8))) 3522 ws_st(m, f, sir_let(m, "g2" as *u8, T_F32, ws_sd_g(m, "0.0" as *u8, "1.0" as *u8))) 3523 ws_st(m, f, sir_let(m, "gam" as *u8, T_F32, ws_sd_fade(m, SD_GRAIN_PER_BLOCK))) 3524 ws_st(m, f, sir_let(m, "gx" as *u8, T_F32, ws_sd_dslope(m, "g0" as *u8, "g1" as *u8, SD_GRAIN_SLOPE))) 3525 ws_st(m, f, sir_let(m, "gz" as *u8, T_F32, ws_sd_dslope(m, "g0" as *u8, "g2" as *u8, SD_GRAIN_SLOPE))) 3526 // wind ripples: a crest slope along the wind heading, phase-jittered per patch, fading at its own Nyquist bound 3527 let ph0: i64 = ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_c2(m, "dot" as *u8, ws_sd_pxz(m), ws_v2f(m, SD_RIPPLE_DX, SD_RIPPLE_DZ), T_F32), ws_f(m, SD_RIPPLE_PER_BLOCK), T_F32), ws_f(m, SD_TAU), T_F32) 3528 let ph1: i64 = ws_bin(m, "*" as *u8, ws_c1(m, "h21" as *u8, ws_c1(m, "floor" as *u8, ws_bin(m, "*" as *u8, ws_sd_pxz(m), ws_f(m, SD_PATCH), T_V2F), T_V2F), T_F32), ws_f(m, SD_TAU), T_F32) 3529 ws_st(m, f, sir_let(m, "ph" as *u8, T_F32, ws_bin(m, "+" as *u8, ph0, ph1, T_F32))) 3530 ws_st(m, f, sir_let(m, "ram" as *u8, T_F32, ws_sd_fade(m, SD_RIPPLE_PER_BLOCK))) 3531 ws_st(m, f, sir_let(m, "rs" as *u8, T_F32, ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_c1(m, "sin" as *u8, ws_bin(m, "+" as *u8, ws_sd_f(m, "ph" as *u8), ws_f(m, SD_QUARTER_TURN), T_F32), T_F32), ws_f(m, SD_RIPPLE_SLOPE), T_F32), ws_sd_f(m, "ram" as *u8), T_F32))) 3532 // the wet band: the nearest water on the ring, at this cell's height and one below 3533 ws_st(m, f, sir_var(m, "wd" as *u8, T_F32, ws_f(m, SD_WET_DRY))) 3534 let kk: i64 = sir_let(m, "k" as *u8, T_I32, ws_bin(m, "+" as *u8, ws_bin(m, "/" as *u8, ws_id(m, "j" as *u8, T_I32), ws_i(m, SD_RING_DIRS), T_I32), ws_i(m, "1" as *u8), T_I32)) 3535 let dd: i64 = sir_let(m, "d" as *u8, T_I32, ws_bin(m, "-" as *u8, ws_id(m, "j" as *u8, T_I32), ws_bin(m, "*" as *u8, ws_bin(m, "/" as *u8, ws_id(m, "j" as *u8, T_I32), ws_i(m, SD_RING_DIRS), T_I32), ws_i(m, SD_RING_DIRS), T_I32), T_I32)) 3536 let aa: i64 = sir_let(m, "a" as *u8, T_F32, ws_bin(m, "*" as *u8, sir_cast(m, ws_id(m, "d" as *u8, T_I32), T_F32), ws_f(m, SD_EIGHTH_TURN), T_F32)) 3537 let dx: i64 = sir_let(m, "dx" as *u8, T_I32, ws_sd_dir(m, SD_QUARTER_TURN)) 3538 let dz: i64 = sir_let(m, "dz" as *u8, T_I32, ws_sd_dir(m, "0.0" as *u8)) 3539 let near: i64 = ws_if(m, ws_or(m, ws_sd_wat(m, "0" as *u8), ws_sd_wat(m, "-1" as *u8)), ws_set(m, ws_id(m, "wd" as *u8, T_F32), ws_c2(m, "min" as *u8, ws_id(m, "wd" as *u8, T_F32), sir_cast(m, ws_id(m, "k" as *u8, T_I32), T_F32), T_F32)), 0) 3540 sir_for_until(m, f, sir_var(m, "j" as *u8, T_I32, ws_i(m, "0" as *u8)), ws_cmp(m, ">=" as *u8, ws_id(m, "j" as *u8, T_I32), ws_i(m, SD_RING_STEPS)), ws_op(m, "j" as *u8, T_I32, "+" as *u8, ws_i(m, "1" as *u8)), ws_q5(m, kk, dd, aa, ws_q2(m, dx, dz), near)) 3541 let sub: i64 = ws_cmp(m, "==" as *u8, ws_c1(m, "vx" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "c" as *u8, T_V3I), ws_vt3(m, T_V3I, ws_i(m, "0" as *u8), ws_i(m, "1" as *u8), ws_i(m, "0" as *u8)), T_V3I), T_U32), ws_u(m, "4" as *u8)) 3542 ws_st(m, f, ws_if(m, sub, ws_set(m, ws_id(m, "wd" as *u8, T_F32), ws_f(m, "0.0" as *u8)), 0)) 3543 ws_st(m, f, sir_let(m, "wet" as *u8, T_F32, ws_c3(m, "clamp" as *u8, ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_f(m, SD_WET_DRY), ws_sd_f(m, "wd" as *u8), T_F32), ws_f(m, SD_WET_R), T_F32), ws_f(m, "0.0" as *u8), ws_f(m, "1.0" as *u8), T_F32))) 3544 ws_st(m, f, sir_let(m, "dry" as *u8, T_F32, ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_sd_f(m, "wet" as *u8), T_F32))) 3545 // the tilted normal: field slope + faded facets + the ripples the swash has not erased 3546 ws_st(m, f, sir_let(m, "pn" as *u8, T_V3F, ws_c1(m, "normalize" as *u8, ws_v3(m, ws_sd_tilt(m, "sx" as *u8, "gx" as *u8, SD_RIPPLE_DX), ws_f(m, "1.0" as *u8), ws_sd_tilt(m, "sz" as *u8, "gz" as *u8, SD_RIPPLE_DZ)), T_V3F))) 3547 // light: the gnd() Lambert on the tilted normal (level 1.0, lee floor 0.42), then wet darkening 3548 ws_st(m, f, sir_let(m, "cp" as *u8, T_F32, ws_c2(m, "max" as *u8, ws_c2(m, "dot" as *u8, ws_sd_v3(m, "pn" as *u8), ws_sd_v3(m, "sund" as *u8), T_F32), ws_f(m, "0.0" as *u8), T_F32))) 3549 ws_st(m, f, sir_let(m, "cf" as *u8, T_F32, ws_c2(m, "max" as *u8, ws_sx(m, "sund" as *u8, T_V3F, "y" as *u8, T_F32), ws_f(m, "0.001" as *u8), T_F32))) 3550 let span: i64 = ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_f(m, "0.42" as *u8), T_F32) 3551 ws_st(m, f, sir_let(m, "lp" as *u8, T_F32, ws_c2(m, "min" as *u8, ws_bin(m, "+" as *u8, ws_f(m, "0.42" as *u8), ws_bin(m, "*" as *u8, span, ws_bin(m, "/" as *u8, ws_sd_f(m, "cp" as *u8), ws_sd_f(m, "cf" as *u8), T_F32), T_F32), T_F32), ws_f(m, "1.0" as *u8), T_F32))) 3552 ws_st(m, f, sir_let(m, "lw" as *u8, T_F32, ws_bin(m, "*" as *u8, ws_sd_f(m, "lp" as *u8), ws_c3(m, "mix" as *u8, ws_f(m, "1.0" as *u8), ws_f(m, SD_WET_DARK), ws_sd_f(m, "wet" as *u8), T_F32), T_F32))) 3553 // specular: the film's Blinn lobe and Fresnel sky sheen inside the band, quartz glints on dry facets 3554 ws_st(m, f, sir_let(m, "hv" as *u8, T_V3F, ws_c1(m, "normalize" as *u8, ws_bin(m, "-" as *u8, ws_sd_v3(m, "sund" as *u8), ws_sd_v3(m, "rd" as *u8), T_V3F), T_V3F))) 3555 ws_st(m, f, sir_let(m, "fr" as *u8, T_F32, ws_c2(m, "pow" as *u8, ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_sd_f(m, "ndv" as *u8), T_F32), ws_f(m, "5.0" as *u8), T_F32))) 3556 ws_st(m, f, sir_let(m, "film" as *u8, T_F32, ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_sd_f(m, "wet" as *u8), ws_c2(m, "pow" as *u8, ws_c2(m, "max" as *u8, ws_c2(m, "dot" as *u8, ws_sd_v3(m, "pn" as *u8), ws_sd_v3(m, "hv" as *u8), T_F32), ws_f(m, "0.0" as *u8), T_F32), ws_f(m, SD_FILM_POW), T_F32), T_F32), ws_f(m, SD_FILM_GAIN), T_F32))) 3557 ws_st(m, f, sir_let(m, "sheen" as *u8, T_F32, ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_sd_f(m, "wet" as *u8), ws_sd_f(m, "fr" as *u8), T_F32), ws_f(m, SD_SHEEN_GAIN), T_F32))) 3558 ws_st(m, f, sir_let(m, "gn" as *u8, T_V3F, ws_c1(m, "normalize" as *u8, ws_v3(m, ws_sd_f(m, "gx" as *u8), ws_f(m, "1.0" as *u8), ws_sd_f(m, "gz" as *u8)), T_V3F))) 3559 ws_st(m, f, sir_var(m, "glint" as *u8, T_F32, ws_f(m, "0.0" as *u8))) 3560 let gl: i64 = ws_set(m, ws_id(m, "glint" as *u8, T_F32), ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_sd_f(m, "gam" as *u8), ws_c2(m, "pow" as *u8, ws_c2(m, "max" as *u8, ws_c2(m, "dot" as *u8, ws_sd_v3(m, "gn" as *u8), ws_sd_v3(m, "hv" as *u8), T_F32), ws_f(m, "0.0" as *u8), T_F32), ws_f(m, SD_GLINT_POW), T_F32), T_F32), ws_f(m, SD_GLINT_GAIN), T_F32)) 3561 ws_st(m, f, ws_if(m, ws_cmp(m, ">" as *u8, ws_sd_f(m, "g0" as *u8), ws_f(m, SD_GLINT_FRAC)), gl, 0)) 3562 // x = light, y = specular: the sun terms scaled by the sun flag, the sheen is the sky's and stays 3563 let sunspec: i64 = ws_bin(m, "*" as *u8, ws_sd_f(m, "sun" as *u8), ws_bin(m, "+" as *u8, ws_sd_f(m, "film" as *u8), ws_sd_f(m, "glint" as *u8), T_F32), T_F32) 3564 ws_st(m, f, ws_ret(m, ws_v2(m, ws_sd_f(m, "lw" as *u8), ws_bin(m, "+" as *u8, ws_sd_f(m, "sheen" as *u8), sunspec, T_F32)))) 3565 return f 3566} 3567func ws_fn_shade(m: *i64, hty: i64) -> i64 { 3568 let f: i64 = sir_func(m, "shade" as *u8, T_V3F) 3569 sir_param(m, f, "ro" as *u8, T_V3F) 3570 sir_param(m, f, "rd" as *u8, T_V3F) 3571 sir_param(m, f, "t" as *u8, T_F32) 3572 sir_param(m, f, "c" as *u8, T_V3I) 3573 sir_param(m, f, "fce" as *u8, T_I32) 3574 sir_param(m, f, "b" as *u8, T_U32) 3575 // let hp=ro+rd*t; var uv:vec2f; var oc:vec3i=c; var n:vec3f=vec3f(0.0); 3576 ws_st(m, f, sir_let(m, "hp" as *u8, T_V3F, ws_bin(m, "+" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "rd" as *u8, T_V3F), ws_id(m, "t" as *u8, T_F32), T_V3F), T_V3F))) 3577 ws_st(m, f, sir_var(m, "uv" as *u8, T_V2F, 0)) 3578 ws_st(m, f, sir_var(m, "oc" as *u8, T_V3I, ws_id(m, "c" as *u8, T_V3I))) 3579 ws_st(m, f, sir_var(m, "n" as *u8, T_V3F, ws_v1(m, T_V3F, ws_f(m, "0.0" as *u8)))) 3580 // the six faces 3581 let f6: i64 = ws_face(m, "xy" as *u8, "z" as *u8, "-" as *u8, "0.0" as *u8, "0.0" as *u8, "-1.0" as *u8) 3582 let f5: i64 = ws_if(m, ws_fce(m, "5" as *u8), ws_face(m, "xy" as *u8, "z" as *u8, "+" as *u8, "0.0" as *u8, "0.0" as *u8, "1.0" as *u8), f6) 3583 let f4: i64 = ws_if(m, ws_fce(m, "4" as *u8), ws_face(m, "xz" as *u8, "y" as *u8, "-" as *u8, "0.0" as *u8, "-1.0" as *u8, "0.0" as *u8), f5) 3584 let f3: i64 = ws_if(m, ws_fce(m, "3" as *u8), ws_face(m, "xz" as *u8, "y" as *u8, "+" as *u8, "0.0" as *u8, "1.0" as *u8, "0.0" as *u8), f4) 3585 let f2: i64 = ws_if(m, ws_fce(m, "2" as *u8), ws_face(m, "zy" as *u8, "x" as *u8, "-" as *u8, "-1.0" as *u8, "0.0" as *u8, "0.0" as *u8), f3) 3586 ws_st(m, f, ws_if(m, ws_fce(m, "1" as *u8), ws_face(m, "zy" as *u8, "x" as *u8, "+" as *u8, "1.0" as *u8, "0.0" as *u8, "0.0" as *u8), f2)) 3587 // let f=fract(uv); var bb:u32=b; if(b==1u&&fce!=3&&fce!=4){bb=2u;if(f.y>0.8){bb=1u;}} 3588 ws_st(m, f, sir_let(m, "f" as *u8, T_V2F, ws_c1(m, "fract" as *u8, ws_id(m, "uv" as *u8, T_V2F), T_V2F))) 3589 ws_st(m, f, sir_var(m, "bb" as *u8, T_U32, ws_id(m, "b" as *u8, T_U32))) 3590 let grass: i64 = ws_q2(m, ws_set(m, ws_id(m, "bb" as *u8, T_U32), ws_u(m, "2" as *u8)), ws_if(m, ws_cmp(m, ">" as *u8, ws_sx(m, "f" as *u8, T_V2F, "y" as *u8, T_F32), ws_f(m, "0.8" as *u8)), ws_set(m, ws_id(m, "bb" as *u8, T_U32), ws_u(m, "1" as *u8)), 0)) 3591 ws_st(m, f, ws_if(m, ws_and(m, ws_and(m, ws_beq(m, "1" as *u8), ws_cmp(m, "!=" as *u8, ws_id(m, "fce" as *u8, T_I32), ws_i(m, "3" as *u8))), ws_cmp(m, "!=" as *u8, ws_id(m, "fce" as *u8, T_I32), ws_i(m, "4" as *u8))), grass, 0)) 3592 // var li:f32; the six-way face light 3593 ws_st(m, f, sir_var(m, "li" as *u8, T_F32, 0)) 3594 let l6: i64 = ws_set(m, ws_id(m, "li" as *u8, T_F32), ws_f(m, "0.56" as *u8)) 3595 let l5: i64 = ws_if(m, ws_fce(m, "5" as *u8), ws_set(m, ws_id(m, "li" as *u8, T_F32), ws_f(m, "0.62" as *u8)), l6) 3596 let l2: i64 = ws_if(m, ws_fce(m, "2" as *u8), ws_set(m, ws_id(m, "li" as *u8, T_F32), ws_f(m, "0.7" as *u8)), l5) 3597 let l1: i64 = ws_if(m, ws_fce(m, "1" as *u8), ws_set(m, ws_id(m, "li" as *u8, T_F32), ws_f(m, "0.76" as *u8)), l2) 3598 let l4: i64 = ws_if(m, ws_fce(m, "4" as *u8), ws_set(m, ws_id(m, "li" as *u8, T_F32), ws_f(m, "0.42" as *u8)), l1) 3599 ws_st(m, f, ws_if(m, ws_fce(m, "3" as *u8), ws_set(m, ws_id(m, "li" as *u8, T_F32), ws_f(m, "1.0" as *u8)), l4)) 3600 // GE53: on the field the ground's own gradient lights the top face (level = the same 1.0, lee = the same 0.42 floor) 3601 // GR27: on the field the sand classes are a MATERIAL -- sand() lights the tilted normal and returns the film specular 3602 // in sp; every other class on the field keeps the bare gradient light 3603 ws_st(m, f, sir_var(m, "sp" as *u8, T_F32, ws_f(m, "0.0" as *u8))) 3604 let sm: i64 = ws_q3(m, sir_let(m, "sm" as *u8, T_V2F, ws_c4(m, "sand" as *u8, ws_id(m, "hp" as *u8, T_V3F), ws_id(m, "c" as *u8, T_V3I), ws_id(m, "rd" as *u8, T_V3F), ws_id(m, "t" as *u8, T_F32), T_V2F)), ws_set(m, ws_id(m, "li" as *u8, T_F32), ws_sx(m, "sm" as *u8, T_V2F, "x" as *u8, T_F32)), ws_set(m, ws_id(m, "sp" as *u8, T_F32), ws_sx(m, "sm" as *u8, T_V2F, "y" as *u8, T_F32))) 3605 ws_st(m, f, ws_if(m, ws_smooth(m), ws_if(m, ws_sandcls(m), sm, ws_set(m, ws_id(m, "li" as *u8, T_F32), ws_c1(m, "gnd" as *u8, ws_sx(m, "hp" as *u8, T_V3F, "xz" as *u8, T_V2F), T_F32))), 0)) 3606 // if(u.sun>0.5){if(fce==1){li*=1.12;}if(fce==2){li*=0.92;}if(fce==5){li*=1.04;}} 3607 ws_st(m, f, ws_if(m, ws_cmp(m, ">" as *u8, ws_id(m, "sun" as *u8, T_F32), ws_f(m, "0.5" as *u8)), ws_q3(m, ws_if(m, ws_fce(m, "1" as *u8), ws_li(m, "1.12" as *u8), 0), ws_if(m, ws_fce(m, "2" as *u8), ws_li(m, "0.92" as *u8), 0), ws_if(m, ws_fce(m, "5" as *u8), ws_li(m, "1.04" as *u8), 0)), 0)) 3608 // let sd3=floor(hp)*0.13; let bs=f32(b)*7.0; 3609 ws_st(m, f, sir_let(m, "sd3" as *u8, T_V3F, ws_bin(m, "*" as *u8, ws_c1(m, "floor" as *u8, ws_id(m, "hp" as *u8, T_V3F), T_V3F), ws_f(m, "0.13" as *u8), T_V3F))) 3610 ws_st(m, f, sir_let(m, "bs" as *u8, T_F32, ws_bin(m, "*" as *u8, sir_cast(m, ws_id(m, "b" as *u8, T_U32), T_F32), ws_f(m, "7.0" as *u8), T_F32))) 3611 // t00 / t10 / t01 = h31(vec3f(floor(f*24.0 [+ offset]),bs)+sd3) 3612 let f24a: i64 = ws_bin(m, "*" as *u8, ws_id(m, "f" as *u8, T_V2F), ws_f(m, "24.0" as *u8), T_V2F) 3613 ws_st(m, f, sir_let(m, "t00" as *u8, T_F32, ws_c1(m, "h31" as *u8, ws_bin(m, "+" as *u8, ws_vt2(m, T_V3F, ws_c1(m, "floor" as *u8, f24a, T_V2F), ws_id(m, "bs" as *u8, T_F32)), ws_id(m, "sd3" as *u8, T_V3F), T_V3F), T_F32))) 3614 let f24b: i64 = ws_bin(m, "+" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "f" as *u8, T_V2F), ws_f(m, "24.0" as *u8), T_V2F), ws_v2f(m, "1.0" as *u8, "0.0" as *u8), T_V2F) 3615 ws_st(m, f, sir_let(m, "t10" as *u8, T_F32, ws_c1(m, "h31" as *u8, ws_bin(m, "+" as *u8, ws_vt2(m, T_V3F, ws_c1(m, "floor" as *u8, f24b, T_V2F), ws_id(m, "bs" as *u8, T_F32)), ws_id(m, "sd3" as *u8, T_V3F), T_V3F), T_F32))) 3616 let f24c: i64 = ws_bin(m, "+" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "f" as *u8, T_V2F), ws_f(m, "24.0" as *u8), T_V2F), ws_v2f(m, "0.0" as *u8, "1.0" as *u8), T_V2F) 3617 ws_st(m, f, sir_let(m, "t01" as *u8, T_F32, ws_c1(m, "h31" as *u8, ws_bin(m, "+" as *u8, ws_vt2(m, T_V3F, ws_c1(m, "floor" as *u8, f24c, T_V2F), ws_id(m, "bs" as *u8, T_F32)), ws_id(m, "sd3" as *u8, T_V3F), T_V3F), T_F32))) 3618 // let bmp=(t10-t00)*0.6+(t01-t00)*0.45; 3619 ws_st(m, f, sir_let(m, "bmp" as *u8, T_F32, ws_bin(m, "+" as *u8, ws_bin(m, "*" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "t10" as *u8, T_F32), ws_id(m, "t00" as *u8, T_F32), T_F32), ws_f(m, "0.6" as *u8), T_F32), ws_bin(m, "*" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "t01" as *u8, T_F32), ws_id(m, "t00" as *u8, T_F32), T_F32), ws_f(m, "0.45" as *u8), T_F32), T_F32))) 3620 // var bamp:f32; the block-class relief amplitude 3621 ws_st(m, f, sir_var(m, "bamp" as *u8, T_F32, 0)) 3622 let a4: i64 = ws_if(m, ws_beq(m, "4" as *u8), ws_set(m, ws_id(m, "bamp" as *u8, T_F32), ws_f(m, "0.25" as *u8)), ws_set(m, ws_id(m, "bamp" as *u8, T_F32), ws_f(m, "0.7" as *u8))) 3623 let a7: i64 = ws_if(m, ws_beq(m, "7" as *u8), ws_set(m, ws_id(m, "bamp" as *u8, T_F32), ws_f(m, "1.3" as *u8)), a4) 3624 let rough: i64 = ws_or(m, ws_or(m, ws_or(m, ws_or(m, ws_beq(m, "3" as *u8), ws_beq(m, "2" as *u8)), ws_beq(m, "9" as *u8)), ws_beq(m, "10" as *u8)), ws_beq(m, "11" as *u8)) 3625 ws_st(m, f, ws_if(m, rough, ws_set(m, ws_id(m, "bamp" as *u8, T_F32), ws_f(m, "1.0" as *u8)), a7)) 3626 ws_st(m, f, ws_if(m, ws_or(m, ws_sandy(m), ws_cont_water(m)), ws_set(m, ws_id(m, "bamp" as *u8, T_F32), ws_f(m, "0.0" as *u8)), 0)) // GR27: the per-cell hash relief IS the paint the material replaces 3627 // let dfade=clamp(1.0-t/24.0,0.0,1.0); li*=clamp(1.0+bmp*bamp*dfade,0.55,1.45); li*=0.9+t00*0.2*dfade; 3628 ws_st(m, f, sir_let(m, "dfade" as *u8, T_F32, ws_c3(m, "clamp" as *u8, ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_bin(m, "/" as *u8, ws_id(m, "t" as *u8, T_F32), ws_f(m, "24.0" as *u8), T_F32), T_F32), ws_f(m, "0.0" as *u8), ws_f(m, "1.0" as *u8), T_F32))) 3629 ws_st(m, f, ws_op(m, "li" as *u8, T_F32, "*" as *u8, ws_c3(m, "clamp" as *u8, ws_bin(m, "+" as *u8, ws_f(m, "1.0" as *u8), ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "bmp" as *u8, T_F32), ws_id(m, "bamp" as *u8, T_F32), T_F32), ws_id(m, "dfade" as *u8, T_F32), T_F32), T_F32), ws_f(m, "0.55" as *u8), ws_f(m, "1.45" as *u8), T_F32))) 3630 // The sand material owns its detail response; legacy voxel paint belongs to other surfaces. 3631 ws_st(m, f, ws_if(m, sir_not(m, ws_or(m, ws_sandy(m), ws_cont_water(m))), ws_op(m, "li" as *u8, T_F32, "*" as *u8, ws_bin(m, "+" as *u8, ws_f(m, "0.9" as *u8), ws_bin(m, "*" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "t00" as *u8, T_F32), ws_f(m, "0.2" as *u8), T_F32), ws_id(m, "dfade" as *u8, T_F32), T_F32), T_F32)), 0)) 3632 // if(bb==1u&&fce==3&&t<24.0){let tf=h21(floor(f*38.0)+floor(hp.xz)*3.7);if(tf>0.82){li*=1.28;}else if(tf<0.14){li*=0.78;}} 3633 let tf: i64 = sir_let(m, "tf" as *u8, T_F32, ws_c1(m, "h21" as *u8, ws_bin(m, "+" as *u8, ws_c1(m, "floor" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "f" as *u8, T_V2F), ws_f(m, "38.0" as *u8), T_V2F), T_V2F), ws_bin(m, "*" as *u8, ws_c1(m, "floor" as *u8, ws_sx(m, "hp" as *u8, T_V3F, "xz" as *u8, T_V2F), T_V2F), ws_f(m, "3.7" as *u8), T_V2F), T_V2F), T_F32)) 3634 let tfi: i64 = ws_if(m, ws_cmp(m, ">" as *u8, ws_id(m, "tf" as *u8, T_F32), ws_f(m, "0.82" as *u8)), ws_li(m, "1.28" as *u8), ws_if(m, ws_cmp(m, "<" as *u8, ws_id(m, "tf" as *u8, T_F32), ws_f(m, "0.14" as *u8)), ws_li(m, "0.78" as *u8), 0)) 3635 ws_st(m, f, ws_if(m, ws_and(m, sir_not(m, ws_sandy(m)), ws_and(m, ws_and(m, ws_cmp(m, "==" as *u8, ws_id(m, "bb" as *u8, T_U32), ws_u(m, "1" as *u8)), ws_fce(m, "3" as *u8)), ws_cmp(m, "<" as *u8, ws_id(m, "t" as *u8, T_F32), ws_f(m, "24.0" as *u8)))), ws_q2(m, tf, tfi), 0)) 3636 // if(f.x<0.055||f.x>0.945||f.y<0.055||f.y>0.945){li*=0.82;} 3637 let edge: i64 = ws_or(m, ws_or(m, ws_or(m, ws_cmp(m, "<" as *u8, ws_sx(m, "f" as *u8, T_V2F, "x" as *u8, T_F32), ws_f(m, "0.055" as *u8)), ws_cmp(m, ">" as *u8, ws_sx(m, "f" as *u8, T_V2F, "x" as *u8, T_F32), ws_f(m, "0.945" as *u8))), ws_cmp(m, "<" as *u8, ws_sx(m, "f" as *u8, T_V2F, "y" as *u8, T_F32), ws_f(m, "0.055" as *u8))), ws_cmp(m, ">" as *u8, ws_sx(m, "f" as *u8, T_V2F, "y" as *u8, T_F32), ws_f(m, "0.945" as *u8))) 3638 ws_st(m, f, ws_if(m, ws_and(m, edge, sir_not(m, ws_cont_surface(m))), ws_li(m, "0.82" as *u8), 0)) // GE53: no cell-edge grid on the field 3639 // var ao:f32=0.0; the four edge-AO probes 3640 ws_st(m, f, sir_var(m, "ao" as *u8, T_F32, ws_f(m, "0.0" as *u8))) 3641 let n1: i64 = ws_if(m, ws_topbot(m), ws_nb(m, "x" as *u8, "-" as *u8), ws_if(m, ws_cmp(m, "<" as *u8, ws_id(m, "fce" as *u8, T_I32), ws_i(m, "3" as *u8)), ws_nb(m, "z" as *u8, "-" as *u8), ws_nb(m, "x" as *u8, "-" as *u8))) 3642 ws_st(m, f, ws_ao(m, "x" as *u8, "<" as *u8, "0.17" as *u8, n1, ws_bin(m, "-" as *u8, ws_f(m, "0.17" as *u8), ws_sx(m, "f" as *u8, T_V2F, "x" as *u8, T_F32), T_F32))) 3643 let n2: i64 = ws_if(m, ws_topbot(m), ws_nb(m, "x" as *u8, "+" as *u8), ws_if(m, ws_cmp(m, "<" as *u8, ws_id(m, "fce" as *u8, T_I32), ws_i(m, "3" as *u8)), ws_nb(m, "z" as *u8, "+" as *u8), ws_nb(m, "x" as *u8, "+" as *u8))) 3644 ws_st(m, f, ws_ao(m, "x" as *u8, ">" as *u8, "0.83" as *u8, n2, ws_bin(m, "-" as *u8, ws_sx(m, "f" as *u8, T_V2F, "x" as *u8, T_F32), ws_f(m, "0.83" as *u8), T_F32))) 3645 let n3: i64 = ws_if(m, ws_topbot(m), ws_nb(m, "z" as *u8, "-" as *u8), ws_nb(m, "y" as *u8, "-" as *u8)) 3646 ws_st(m, f, ws_ao(m, "y" as *u8, "<" as *u8, "0.17" as *u8, n3, ws_bin(m, "-" as *u8, ws_f(m, "0.17" as *u8), ws_sx(m, "f" as *u8, T_V2F, "y" as *u8, T_F32), T_F32))) 3647 let n4: i64 = ws_if(m, ws_topbot(m), ws_nb(m, "z" as *u8, "+" as *u8), ws_nb(m, "y" as *u8, "+" as *u8)) 3648 ws_st(m, f, ws_ao(m, "y" as *u8, ">" as *u8, "0.83" as *u8, n4, ws_bin(m, "-" as *u8, ws_sx(m, "f" as *u8, T_V2F, "y" as *u8, T_F32), ws_f(m, "0.83" as *u8), T_F32))) 3649 // li*=1.0-min(ao,0.45); 3650 ws_st(m, f, ws_if(m, sir_not(m, ws_cont_surface(m)), ws_op(m, "li" as *u8, T_F32, "*" as *u8, ws_bin(m, "-" as *u8, ws_f(m, "1.0" as *u8), ws_c2(m, "min" as *u8, ws_id(m, "ao" as *u8, T_F32), ws_f(m, "0.45" as *u8), T_F32), T_F32)), 0)) // GE53: no cell-corner AO on the field 3651 // if(u.sun>0.5){let sh=march(hp+n*0.02,u.sund,1,60);if(sh.t>0.0){li*=0.55;}} 3652 let sh: i64 = sir_let(m, "sh" as *u8, hty, ws_c5(m, "march" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "hp" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "n" as *u8, T_V3F), ws_f(m, "0.02" as *u8), T_V3F), T_V3F), ws_id(m, "sund" as *u8, T_V3F), ws_i(m, "1" as *u8), ws_i(m, "60" as *u8), sir_ident(m, "r_surf" as *u8, T_I32), hty)) 3653 // GE53: on the field a cell march cannot shadow the ground (terrain cells are air to it), so the surface march does 3654 let shs: i64 = ws_if(m, ws_and(m, ws_and(m, ws_cmp(m, "==" as *u8, sir_ident(m, "r_surf" as *u8, T_I32), ws_i(m, "1" as *u8)), ws_cmp(m, "<=" as *u8, ws_mem(m, ws_id(m, "sh" as *u8, hty), "t" as *u8, T_F32), ws_f(m, "0.0" as *u8))), ws_cmp(m, ">" as *u8, ws_c3(m, "smarch" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "hp" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "n" as *u8, T_V3F), ws_f(m, "0.02" as *u8), T_V3F), T_V3F), ws_id(m, "sund" as *u8, T_V3F), ws_i(m, "60" as *u8), T_F32), ws_f(m, "0.0" as *u8))), ws_shd(m), 0) 3655 ws_st(m, f, ws_if(m, ws_cmp(m, ">" as *u8, ws_id(m, "sun" as *u8, T_F32), ws_f(m, "0.5" as *u8)), ws_q3(m, sh, ws_if(m, ws_cmp(m, ">" as *u8, ws_mem(m, ws_id(m, "sh" as *u8, hty), "t" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_shd(m), 0), shs), 0)) 3656 // let col=pal9(bb)*li; let fw9=vec3f(1.0)-exp(-vec3f(0.123,0.287,0.700)*t/168.0); return mix(col,sky(rd),fw9); 3657 ws_st(m, f, sir_let(m, "col" as *u8, T_V3F, ws_bin(m, "+" as *u8, ws_bin(m, "*" as *u8, ws_c1(m, "pal9" as *u8, ws_id(m, "bb" as *u8, T_U32), T_V3F), ws_id(m, "li" as *u8, T_F32), T_V3F), ws_v1(m, T_V3F, ws_id(m, "sp" as *u8, T_F32)), T_V3F))) // GR27: + the film specular 3658 let tau: i64 = ws_bin(m, "/" as *u8, ws_bin(m, "*" as *u8, sir_neg(m, ws_v3f(m, "0.123" as *u8, "0.287" as *u8, "0.700" as *u8), T_V3F), ws_id(m, "t" as *u8, T_F32), T_V3F), ws_f(m, "168.0" as *u8), T_V3F) 3659 ws_st(m, f, sir_let(m, "fw9" as *u8, T_V3F, ws_bin(m, "-" as *u8, ws_v1(m, T_V3F, ws_f(m, "1.0" as *u8)), ws_c1(m, "exp" as *u8, tau, T_V3F), T_V3F))) 3660 ws_st(m, f, ws_ret(m, ws_c3(m, "mix" as *u8, ws_id(m, "col" as *u8, T_V3F), ws_c1(m, "sky" as *u8, ws_id(m, "rd" as *u8, T_V3F), T_V3F), ws_id(m, "fw9" as *u8, T_V3F), T_V3F))) 3661 return f 3662} 3663 3664// the hotbar slot's block id: if(s2==0){bt=3;}else if(s2==1){bt=2;}...else{bt=13;} 3665func ws_bt(m: *i64, k: *u8, v: *u8, els: i64) -> i64 { 3666 return ws_if(m, ws_cmp(m, "==" as *u8, ws_id(m, "s2" as *u8, T_I32), ws_i(m, k)), ws_set(m, ws_id(m, "bt" as *u8, T_I32), ws_i(m, v)), els) 3667} 3668// px.<a> <op> (o.<a> + <e>) / px.<a> <op> (o.<a> + hbS - <e>) 3669func ws_pxo(m: *i64, a: *u8, op: *u8, e: i64) -> i64 { return ws_cmp(m, op, ws_sx(m, "px" as *u8, T_V2F, a, T_F32), ws_bin(m, "+" as *u8, ws_sx(m, "o" as *u8, T_V2F, a, T_F32), e, T_F32)) } 3670 3671// the fragment entry: fs -- the world pass per pixel, the hand text statement for statement (yflip carries the 3672// WebGPU top-left origin as a uniform so ONE source serves both doors) 3673// One hit policy for visible geometry and geometry seen through water. 3674// Continuous still-water intersection. Level is simulation data; no water voxel supplies geometry. 3675func ws_fn_water_hit(m: *i64, hty: i64) -> i64 { 3676 let f: i64 = sir_func(m, "water_hit" as *u8, hty) 3677 sir_param(m, f, "ro" as *u8, T_V3F); sir_param(m, f, "rd" as *u8, T_V3F) 3678 sir_param(m, f, "prior" as *u8, hty); sir_param(m, f, "mx" as *u8, T_I32) 3679 ws_st(m, f, ws_if(m, ws_cmp(m, "==" as *u8, ws_sx(m, "rd" as *u8, T_V3F, "y" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_ret(m, ws_id(m, "prior" as *u8, hty)), 0)) 3680 ws_st(m, f, sir_let(m, "level" as *u8, T_F32, ws_bin(m, "/" as *u8, sir_cast(m, ws_id(m, "r_water_q8" as *u8, T_I32), T_F32), ws_f(m, "256.0" as *u8), T_F32))) 3681 ws_st(m, f, sir_let(m, "wt" as *u8, T_F32, ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "level" as *u8, T_F32), ws_sx(m, "ro" as *u8, T_V3F, "y" as *u8, T_F32), T_F32), ws_sx(m, "rd" as *u8, T_V3F, "y" as *u8, T_F32), T_F32))) 3682 let outside: i64 = ws_or(m, ws_cmp(m, "<" as *u8, ws_id(m, "wt" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_cmp(m, ">" as *u8, ws_id(m, "wt" as *u8, T_F32), sir_cast(m, ws_id(m, "mx" as *u8, T_I32), T_F32))) 3683 ws_st(m, f, ws_if(m, outside, ws_ret(m, ws_id(m, "prior" as *u8, hty)), 0)) 3684 let occluded: i64 = ws_and(m, ws_cmp(m, ">=" as *u8, ws_mem(m, ws_id(m, "prior" as *u8, hty), "t" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_cmp(m, ">=" as *u8, ws_id(m, "wt" as *u8, T_F32), ws_mem(m, ws_id(m, "prior" as *u8, hty), "t" as *u8, T_F32))) 3685 ws_st(m, f, ws_if(m, occluded, ws_ret(m, ws_id(m, "prior" as *u8, hty)), 0)) 3686 ws_st(m, f, sir_let(m, "wp" as *u8, T_V3F, ws_bin(m, "+" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "rd" as *u8, T_V3F), ws_id(m, "wt" as *u8, T_F32), T_V3F), T_V3F))) 3687 ws_st(m, f, sir_let(m, "bed" as *u8, T_F32, ws_c1(m, "surf" as *u8, ws_sx(m, "wp" as *u8, T_V3F, "xz" as *u8, T_V2F), T_F32))) 3688 let dry: i64 = ws_or(m, ws_cmp(m, "<" as *u8, ws_id(m, "bed" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_cmp(m, ">=" as *u8, ws_id(m, "bed" as *u8, T_F32), ws_id(m, "level" as *u8, T_F32))) 3689 ws_st(m, f, ws_if(m, dry, ws_ret(m, ws_id(m, "prior" as *u8, hty)), 0)) 3690 ws_st(m, f, sir_var(m, "result" as *u8, hty, ws_id(m, "prior" as *u8, hty))) 3691 ws_st(m, f, ws_set(m, ws_mem(m, ws_id(m, "result" as *u8, hty), "t" as *u8, T_F32), ws_id(m, "wt" as *u8, T_F32))) 3692 ws_st(m, f, ws_set(m, ws_mem(m, ws_id(m, "result" as *u8, hty), "b" as *u8, T_U32), ws_u(m, "4" as *u8))) 3693 ws_st(m, f, ws_set(m, ws_mem(m, ws_id(m, "result" as *u8, hty), "c" as *u8, T_V3I), ws_v1(m, T_V3I, ws_c1(m, "floor" as *u8, ws_id(m, "wp" as *u8, T_V3F), T_V3F)))) 3694 ws_st(m, f, ws_set(m, ws_mem(m, ws_id(m, "result" as *u8, hty), "fce" as *u8, T_I32), ws_i(m, "3" as *u8))) 3695 ws_st(m, f, ws_if(m, ws_cmp(m, ">" as *u8, ws_sx(m, "rd" as *u8, T_V3F, "y" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_set(m, ws_mem(m, ws_id(m, "result" as *u8, hty), "fce" as *u8, T_I32), ws_i(m, "4" as *u8)), 0)) 3696 ws_st(m, f, ws_ret(m, ws_id(m, "result" as *u8, hty))) 3697 return f 3698} 3699 3700func ws_fn_scene_hit(m: *i64, hty: i64) -> i64 { 3701 ws_fn_water_hit(m, hty) 3702 let f: i64 = sir_func(m, "scene_hit" as *u8, hty) 3703 sir_param(m, f, "ro" as *u8, T_V3F) 3704 sir_param(m, f, "rd" as *u8, T_V3F) 3705 sir_param(m, f, "skipw" as *u8, T_I32) 3706 sir_param(m, f, "mx" as *u8, T_I32) 3707 sir_param(m, f, "ts" as *u8, T_F32) 3708 ws_st(m, f, sir_var(m, "h" as *u8, hty, ws_c5(m, "march" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_id(m, "rd" as *u8, T_V3F), ws_c2(m, "max" as *u8, ws_id(m, "skipw" as *u8, T_I32), ws_id(m, "r_surf" as *u8, T_I32), T_I32), ws_id(m, "mx" as *u8, T_I32), sir_ident(m, "r_surf" as *u8, T_I32), hty))) 3709 // Both camera and transmitted-water rays resolve the same continuous ground. 3710 let hp9: i64 = sir_let(m, "hp9" as *u8, T_V3F, ws_bin(m, "+" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "rd" as *u8, T_V3F), ws_id(m, "ts" as *u8, T_F32), T_V3F), T_V3F)) 3711 let cx9: i64 = sir_let(m, "cx9" as *u8, T_I32, sir_cast(m, ws_c1(m, "floor" as *u8, ws_sx(m, "hp9" as *u8, T_V3F, "x" as *u8, T_F32), T_F32), T_I32)) 3712 let cz9: i64 = sir_let(m, "cz9" as *u8, T_I32, sir_cast(m, ws_c1(m, "floor" as *u8, ws_sx(m, "hp9" as *u8, T_V3F, "z" as *u8, T_F32), T_F32), T_I32)) 3713 let cy9: i64 = sir_var(m, "cy9" as *u8, T_I32, ws_c2(m, "min" as *u8, sir_cast(m, ws_c1(m, "floor" as *u8, ws_sx(m, "hp9" as *u8, T_V3F, "y" as *u8, T_F32), T_F32), T_I32), ws_i(m, "47" as *u8), T_I32)) 3714 // Search the actual column extent: water and air cannot supply the seabed material. 3715 let ground_class: i64 = ws_c1(m, "terrain" as *u8, ws_c1(m, "vx" as *u8, ws_vt3(m, T_V3I, ws_id(m, "cx9" as *u8, T_I32), ws_id(m, "cy9" as *u8, T_I32), ws_id(m, "cz9" as *u8, T_I32)), T_U32), T_I32) 3716 let stop_scan: i64 = ws_or(m, ws_cmp(m, "<=" as *u8, ws_id(m, "cy9" as *u8, T_I32), ws_i(m, "0" as *u8)), ws_cmp(m, "==" as *u8, ground_class, ws_i(m, "1" as *u8))) 3717 let scan: i64 = sir_loop(m, ws_q2(m, ws_if(m, stop_scan, sir_break(m), 0), ws_op(m, "cy9" as *u8, T_I32, "-" as *u8, ws_i(m, "1" as *u8)))) 3718 let sett: i64 = ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "t" as *u8, T_F32), ws_id(m, "ts" as *u8, T_F32)) 3719 let setf: i64 = ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "fce" as *u8, T_I32), ws_i(m, "3" as *u8)) 3720 let setc: i64 = ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "c" as *u8, T_V3I), ws_vt3(m, T_V3I, ws_id(m, "cx9" as *u8, T_I32), ws_id(m, "cy9" as *u8, T_I32), ws_id(m, "cz9" as *u8, T_I32))) 3721 let setb: i64 = ws_set(m, ws_mem(m, ws_id(m, "h" as *u8, hty), "b" as *u8, T_U32), ws_c1(m, "vx" as *u8, ws_mem(m, ws_id(m, "h" as *u8, hty), "c" as *u8, T_V3I), T_U32)) 3722 let nearer: i64 = ws_and(m, ws_cmp(m, ">" as *u8, ws_id(m, "ts" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_or(m, ws_cmp(m, "<" as *u8, ws_mem(m, ws_id(m, "h" as *u8, hty), "t" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_cmp(m, "<" as *u8, ws_id(m, "ts" as *u8, T_F32), ws_mem(m, ws_id(m, "h" as *u8, hty), "t" as *u8, T_F32)))) 3723 let inner: i64 = ws_if(m, nearer, ws_q5(m, hp9, ws_q4(m, cx9, cz9, cy9, scan), sett, setf, ws_q2(m, setc, setb)), 0) 3724 ws_st(m, f, ws_if(m, ws_cmp(m, "==" as *u8, sir_ident(m, "r_surf" as *u8, T_I32), ws_i(m, "1" as *u8)), inner, 0)) 3725 // Resolve water against the same field after ground/objects, preserving nearest-hit ordering. 3726 let water_call: i64 = ws_c4(m, "water_hit" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_id(m, "rd" as *u8, T_V3F), ws_id(m, "h" as *u8, hty), ws_id(m, "mx" as *u8, T_I32), hty) 3727 ws_st(m, f, ws_if(m, ws_and(m, ws_cmp(m, "==" as *u8, ws_id(m, "r_surf" as *u8, T_I32), ws_i(m, "1" as *u8)), ws_cmp(m, "==" as *u8, ws_id(m, "skipw" as *u8, T_I32), ws_i(m, "0" as *u8))), ws_set(m, ws_id(m, "h" as *u8, hty), water_call), 0)) 3728 ws_st(m, f, ws_ret(m, ws_id(m, "h" as *u8, hty))) 3729 return f 3730} 3731 3732func ws_fn_fs(m: *i64, hty: i64) -> i64 { 3733 sir_fragout(m, "fc" as *u8, T_V4F, 0) 3734 let f: i64 = sir_func(m, "main" as *u8, T_VOID) 3735 sir_param_position(m, f, "pos" as *u8) 3736 // upk(); -- GE55: derive every float from the raw words FIRST; every statement below reads the private state 3737 ws_st(m, f, sir_expr(m, sir_call(m, "upk" as *u8, T_VOID))) 3738 // let px=vec2f(pos.x,mix(pos.y,u.res.y-pos.y,u.yflip)); 3739 let flipped: i64 = ws_bin(m, "-" as *u8, ws_sx(m, "res" as *u8, T_V2F, "y" as *u8, T_F32), ws_sx(m, "pos" as *u8, T_V4F, "y" as *u8, T_F32), T_F32) 3740 ws_st(m, f, sir_let(m, "px" as *u8, T_V2F, ws_v2(m, ws_sx(m, "pos" as *u8, T_V4F, "x" as *u8, T_F32), ws_c3(m, "mix" as *u8, ws_sx(m, "pos" as *u8, T_V4F, "y" as *u8, T_F32), flipped, ws_id(m, "yflip" as *u8, T_F32), T_F32)))) 3741 // let sc=(px-u.res*0.5)/(u.res.x*0.5); 3742 ws_st(m, f, sir_let(m, "sc" as *u8, T_V2F, ws_bin(m, "/" as *u8, ws_bin(m, "-" as *u8, ws_id(m, "px" as *u8, T_V2F), ws_bin(m, "*" as *u8, ws_id(m, "res" as *u8, T_V2F), ws_f(m, "0.5" as *u8), T_V2F), T_V2F), ws_bin(m, "*" as *u8, ws_sx(m, "res" as *u8, T_V2F, "x" as *u8, T_F32), ws_f(m, "0.5" as *u8), T_F32), T_V2F))) 3743 // let fw=vec3f(u.yp.x*u.yp.w,u.yp.z,u.yp.y*u.yp.w); let rt=vec3f(u.yp.y,0.0,-u.yp.x); let up=vec3f(-u.yp.x*u.yp.z,u.yp.w,-u.yp.y*u.yp.z); 3744 ws_st(m, f, sir_let(m, "fw" as *u8, T_V3F, ws_v3(m, ws_bin(m, "*" as *u8, ws_sx(m, "yp" as *u8, T_V4F, "x" as *u8, T_F32), ws_sx(m, "yp" as *u8, T_V4F, "w" as *u8, T_F32), T_F32), ws_sx(m, "yp" as *u8, T_V4F, "z" as *u8, T_F32), ws_bin(m, "*" as *u8, ws_sx(m, "yp" as *u8, T_V4F, "y" as *u8, T_F32), ws_sx(m, "yp" as *u8, T_V4F, "w" as *u8, T_F32), T_F32)))) 3745 ws_st(m, f, sir_let(m, "rt" as *u8, T_V3F, ws_v3(m, ws_sx(m, "yp" as *u8, T_V4F, "y" as *u8, T_F32), ws_f(m, "0.0" as *u8), sir_neg(m, ws_sx(m, "yp" as *u8, T_V4F, "x" as *u8, T_F32), T_F32)))) 3746 ws_st(m, f, sir_let(m, "up" as *u8, T_V3F, ws_v3(m, ws_bin(m, "*" as *u8, sir_neg(m, ws_sx(m, "yp" as *u8, T_V4F, "x" as *u8, T_F32), T_F32), ws_sx(m, "yp" as *u8, T_V4F, "z" as *u8, T_F32), T_F32), ws_sx(m, "yp" as *u8, T_V4F, "w" as *u8, T_F32), ws_bin(m, "*" as *u8, sir_neg(m, ws_sx(m, "yp" as *u8, T_V4F, "y" as *u8, T_F32), T_F32), ws_sx(m, "yp" as *u8, T_V4F, "z" as *u8, T_F32), T_F32)))) 3747 // let rd=normalize(fw+rt*sc.x+up*sc.y); let ro=u.cam; let h=march(ro,rd,0,200); 3748 ws_st(m, f, sir_let(m, "rd" as *u8, T_V3F, ws_c1(m, "normalize" as *u8, ws_bin(m, "+" as *u8, ws_bin(m, "+" as *u8, ws_id(m, "fw" as *u8, T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "rt" as *u8, T_V3F), ws_sx(m, "sc" as *u8, T_V2F, "x" as *u8, T_F32), T_V3F), T_V3F), ws_bin(m, "*" as *u8, ws_id(m, "up" as *u8, T_V3F), ws_sx(m, "sc" as *u8, T_V2F, "y" as *u8, T_F32), T_V3F), T_V3F), T_V3F))) 3749 ws_st(m, f, sir_let(m, "ro" as *u8, T_V3F, ws_id(m, "cam" as *u8, T_V3F))) 3750 // Water and camera use this identical ray against the same immutable per-frame field. 3751 ws_st(m, f, sir_var(m, "ground_t" as *u8, T_F32, ws_f(m, "-1.0" as *u8))) 3752 ws_st(m, f, ws_if(m, ws_cmp(m, "==" as *u8, sir_ident(m, "r_surf" as *u8, T_I32), ws_i(m, "1" as *u8)), ws_set(m, ws_id(m, "ground_t" as *u8, T_F32), ws_c3(m, "smarch" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_id(m, "rd" as *u8, T_V3F), ws_i(m, "200" as *u8), T_F32)), 0)) 3753 ws_st(m, f, sir_let(m, "h" as *u8, hty, ws_c5(m, "scene_hit" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_id(m, "rd" as *u8, T_V3F), ws_i(m, "0" as *u8), ws_i(m, "200" as *u8), ws_id(m, "ground_t" as *u8, T_F32), hty))) 3754 // var col:vec3f; if(h.t<0.0){col=sky(rd);}else{col=shade(...);if(h.b==4u){let h2=march(ro,rd,1,200);if(h2.t>0.0){col=col*0.58+shade(...h2...)*0.42;}}} 3755 ws_st(m, f, sir_var(m, "col" as *u8, T_V3F, 0)) 3756 let sh1: i64 = ws_c6(m, "shade" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_id(m, "rd" as *u8, T_V3F), ws_mem(m, ws_id(m, "h" as *u8, hty), "t" as *u8, T_F32), ws_mem(m, ws_id(m, "h" as *u8, hty), "c" as *u8, T_V3I), ws_mem(m, ws_id(m, "h" as *u8, hty), "fce" as *u8, T_I32), ws_mem(m, ws_id(m, "h" as *u8, hty), "b" as *u8, T_U32), T_V3F) 3757 let sh2: i64 = ws_c6(m, "shade" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_id(m, "rd" as *u8, T_V3F), ws_mem(m, ws_id(m, "h2" as *u8, hty), "t" as *u8, T_F32), ws_mem(m, ws_id(m, "h2" as *u8, hty), "c" as *u8, T_V3I), ws_mem(m, ws_id(m, "h2" as *u8, hty), "fce" as *u8, T_I32), ws_mem(m, ws_id(m, "h2" as *u8, hty), "b" as *u8, T_U32), T_V3F) 3758 let blend: i64 = ws_set(m, ws_id(m, "col" as *u8, T_V3F), ws_bin(m, "+" as *u8, ws_bin(m, "*" as *u8, ws_id(m, "col" as *u8, T_V3F), ws_f(m, "0.58" as *u8), T_V3F), ws_bin(m, "*" as *u8, sh2, ws_f(m, "0.42" as *u8), T_V3F), T_V3F)) 3759 let h2: i64 = sir_let(m, "h2" as *u8, hty, ws_c5(m, "scene_hit" as *u8, ws_id(m, "ro" as *u8, T_V3F), ws_id(m, "rd" as *u8, T_V3F), ws_i(m, "1" as *u8), ws_i(m, "200" as *u8), ws_id(m, "ground_t" as *u8, T_F32), hty)) 3760 let water: i64 = ws_if(m, ws_cmp(m, "==" as *u8, ws_mem(m, ws_id(m, "h" as *u8, hty), "b" as *u8, T_U32), ws_u(m, "4" as *u8)), ws_q2(m, h2, ws_if(m, ws_cmp(m, ">" as *u8, ws_mem(m, ws_id(m, "h2" as *u8, hty), "t" as *u8, T_F32), ws_f(m, "0.0" as *u8)), blend, 0)), 0) 3761 ws_st(m, f, ws_if(m, ws_cmp(m, "<" as *u8, ws_mem(m, ws_id(m, "h" as *u8, hty), "t" as *u8, T_F32), ws_f(m, "0.0" as *u8)), ws_set(m, ws_id(m, "col" as *u8, T_V3F), ws_c1(m, "sky" as *u8, ws_id(m, "rd" as *u8, T_V3F), T_V3F)), ws_q2(m, ws_set(m, ws_id(m, "col" as *u8, T_V3F), sh1), water))) 3762 // let ctr=u.res*0.5; 3763 ws_st(m, f, sir_let(m, "ctr" as *u8, T_V2F, ws_bin(m, "*" as *u8, ws_id(m, "res" as *u8, T_V2F), ws_f(m, "0.5" as *u8), T_V2F))) 3764 // if(u.rain>0.5){let rr=h21(vec2f(floor((px.x+px.y*0.35)/2.0),floor((px.y+u.t*13.0)/26.0)));if(rr>0.945){col=mix(col,vec3f(0.75,0.8,0.92),0.45);}col*=0.93;} 3765 let rrx: i64 = ws_c1(m, "floor" as *u8, ws_bin(m, "/" as *u8, ws_bin(m, "+" as *u8, ws_sx(m, "px" as *u8, T_V2F, "x" as *u8, T_F32), ws_bin(m, "*" as *u8, ws_sx(m, "px" as *u8, T_V2F, "y" as *u8, T_F32), ws_f(m, "0.35" as *u8), T_F32), T_F32), ws_f(m, "2.0" as *u8), T_F32), T_F32) 3766 let rry: i64 = ws_c1(m, "floor" as *u8, ws_bin(m, "/" as *u8, ws_bin(m, "+" as *u8, ws_sx(m, "px" as *u8, T_V2F, "y" as *u8, T_F32), ws_bin(m, "*" as *u8, ws_id(m, "t" as *u8, T_F32), ws_f(m, "13.0" as *u8), T_F32), T_F32), ws_f(m, "26.0" as *u8), T_F32), T_F32) 3767 let rr: i64 = sir_let(m, "rr" as *u8, T_F32, ws_c1(m, "h21" as *u8, ws_v2(m, rrx, rry), T_F32)) 3768 let drops: i64 = ws_if(m, ws_cmp(m, ">" as *u8, ws_id(m, "rr" as *u8, T_F32), ws_f(m, "0.945" as *u8)), ws_set(m, ws_id(m, "col" as *u8, T_V3F), ws_c3(m, "mix" as *u8, ws_id(m, "col" as *u8, T_V3F), ws_v3f(m, "0.75" as *u8, "0.8" as *u8, "0.92" as *u8), ws_f(m, "0.45" as *u8), T_V3F)), 0) 3769 ws_st(m, f, ws_if(m, ws_cmp(m, ">" as *u8, ws_id(m, "rain" as *u8, T_F32), ws_f(m, "0.5" as *u8)), ws_q3(m, rr, drops, ws_op(m, "col" as *u8, T_V3F, "*" as *u8, ws_f(m, "0.93" as *u8))), 0)) 3770 // the crosshair 3771 let dx: i64 = ws_c1(m, "abs" as *u8, ws_bin(m, "-" as *u8, ws_sx(m, "px" as *u8, T_V2F, "x" as *u8, T_F32), ws_sx(m, "ctr" as *u8, T_V2F, "x" as *u8, T_F32), T_F32), T_F32) 3772 let dy: i64 = ws_c1(m, "abs" as *u8, ws_bin(m, "-" as *u8, ws_sx(m