nx_tictactoe_render.nx
buildroot/runtime/nx_tictactoe_render.nx
about
nx_tictactoe_render.nx -- bits-up HTML rendering + event dispatch for /tictactoe.
STATUS 2026-05-20: ARCHITECTURE-COMPLETE BUT BLOCKED ON SUBSTRATE GAPS.
This file describes the intended bits-up rendering architecture; it
compiles to RISC-V asm (qemu green path) but **does NOT yet compile to
WAT** due to two substrate-side gaps:
1. nxc2 --target wat: "unhandled opcode 30 in emit_instr" -- the
WAT backend does not yet lower string-literal data into WASM
data segments. Same gap blocks nx_html_emit compilation to WAT.
Fix: extend nxc2/wasm.c emit_instr switch + add data-segment
synthesis from string-literal IR nodes.
2. @ifdef TARGET_X86_64 / @endif blocks in nx_syscalls.nx report
"2 unclosed @ifdef/@ifndef block(s)" under --target wat as of
2026-05-20 background changes. Macro expansion regression.
Until both close, the /tictactoe page uses minimal bespoke JS for DOM
rendering with NishiLang-compiled WASM for game logic + AI. When the
gaps close, this file gets compiled into the WASM module and the bespoke
JS gets replaced by dist/_nishi/nishi-host.js calling nx_ttt_render +
nx_ttt_action via the sentinel-fd-99 convention.
Per [[feedback-ui-rendering-bits-up-not-just-game-logic]]: UI emission +
click-to-state mapping live in NishiLang. Only the IRREDUCIBLE
dist/_nishi/nishi-host.js (~120 LOC: WASM-instantiate + syscall imports +
innerHTML setter + addEventListener) is JS.
HTML is emitted by writing bytes to fd 99 (sentinel); the JS host captures
those bytes into a buffer and sets innerHTML. Same fd-99 convention is
reusable across every Nishi game.
Exports (called by nishi-host.js):
nx_ttt_init() -> *i64 -- allocates + returns initial state
nx_ttt_render(s) -> i64 -- emits full HTML to fd 99
nx_ttt_action(s, act, a0, a1, a2, a3) -> i64
-- handles a click/change event;
returns 1 if state changed (host
re-renders), 0 otherwise.
dependencies 3 imports · 1 importers
imports: nx_syscalls.nxnx_tier.nxnx_tictactoe.nx
imported by: nx_ttt_bits_up_e2e.nx
structs
| none |
consts
| 49 | const NX_MAGIC_1000003: i64 = 1000003 |
| 51 | const NX_TTT_RENDER_FD: i64 = 99 |
| 53 | const NX_TTT_ACTION_CLICK_CELL: i64 = 1 |
| 54 | const NX_TTT_ACTION_RESET: i64 = 2 |
| 55 | const NX_TTT_ACTION_SET_OPPONENT: i64 = 3 |
| 56 | const NX_TTT_ACTION_SET_PLAYER: i64 = 4 |
functions
| 65 | func _ttt_strlen(s: *u8) -> i64 called by 1: _ttt_w |
| 71 | func _ttt_w(s: *u8) -> i64 |
| 77 | func _ttt_wd(d: i64) -> i64 |
| 85 | func nx_ttt_init() -> *i64 |
| 92 | func nx_ttt_reset_in_place(s: *i64) called by 1: nx_ttt_action |
| 102 | func _ttt_emit_status(s: *i64) -> i64 |
| 142 | func _ttt_emit_opt(value: i64, is_selected: i64, label: *u8) -> i64 |
| 158 | func _ttt_emit_controls(s: *i64) -> i64 |
| 190 | func _ttt_emit_cell(s: *i64, idx: i64) -> i64 |
| 214 | func _ttt_emit_version_banner() -> i64 |
| 221 | func nx_ttt_render(s: *i64) -> i64 called by 1: main calls 5: _ttt_w_ttt_emit_version_banner_ttt_emit_status_ttt_emit_cell_ttt_emit_controls |
| 242 | func _ttt_ai_pick(s: *i64, side: i64) -> i64 called by 1: nx_ttt_action calls 4: nx_ttt_pick_perfectnx_ttt_pick_mediumnx_ttt_state_hashnx_ttt_pick_easy |
| 261 | func nx_ttt_action(s: *i64, action: i64, arg0: i64, arg1: i64, called by 1: main calls 5: nx_ttt_try_applynx_ttt_outcomenx_ttt_other_ttt_ai_picknx_ttt_reset_in_place |