nx_visual_novel.nx
buildroot/runtime/nx_visual_novel.nx
about
nx_visual_novel.nx -- VN0+VN1: the Nishi visual-novel ENGINE, bits-up
(operator 2026-06-10: "start with visual novels as an easy bottom up lift
to beat py visual novel and other engines" on the road to AAA-in-browser).
What a VN engine is: a deterministic script machine (say / choice /
variables / branches / endings) + a renderer. This module ships:
* the script op machine (SAY, CHOICE, SET, IFGOTO, JUMP, END),
* deterministic state (vars, state-hash, step count) -- replayable,
* the fd-99 HTML renderer for the nishi-host mount() pattern
(same browser delivery as /tictactoe: render emits scene HTML,
data-action clicks dispatch back -- zero per-app JS),
* a complete family demo story ("The Family Garden": 2 choice points,
3 endings, a tended-count variable) proving every op end-to-end.
BEAT-Ren'Py axes (G3 scorecard, measured not asserted):
bundle: Ren'Py ships a ~50MB Python runtime, web export ~30MB emscripten;
this engine compiles to a WASM measured in TENS OF KILOBYTES.
load: instant (one small wasm fetch). determinism: state-hash replay.
sovereignty: no Python, no engine runtime, user owns every bit.
Honest scope: VN0/1 = engine+browser text/choices. Sprites/backgrounds/
audio = VN3; authoring TEXT format (beat Ren'Py script) = VN4 (composes
the e-reader/format arc); scorecard vs Ren'Py = VN5.
license_tier: ORIGINAL
dependencies 1 imports · 1 importers
imports: nx_syscalls.nx
imported by: nx_visual_novel_test.nx
structs
| none |
consts
| 26 | const VN_MAGIC_1469598103934665603: i64 = 1469598103934665603 |
| 27 | const VN_MAGIC_1099511628211: i64 = 1099511628211 |
| 31 | const VN_OP_SAY: i64 = 0 // [1]=speaker_id [2]=text_ptr -> next = pc+1 |
| 32 | const VN_OP_CHOICE: i64 = 1 // [1]=n (2..3) [2]=t0_ptr [3]=tgt0 [4]=t1_ptr [5]=tgt1 [6]=t2_ptr [7]=tgt2 |
| 33 | const VN_OP_SET: i64 = 2 // [1]=var_idx [2]=value (silent) |
| 34 | const VN_OP_ADD: i64 = 3 // [1]=var_idx [2]=delta (silent) |
| 35 | const VN_OP_IFGOTO: i64 = 4 // [1]=var_idx [2]=min_val [3]=target (silent: jump if var >= min) |
| 36 | const VN_OP_JUMP: i64 = 5 // [1]=target (silent) |
| 37 | const VN_OP_END: i64 = 6 // [1]=epilogue_ptr |
| 39 | const VN_OP_STRIDE: i64 = 8 |
| 42 | const VN_S_SCRIPT: i64 = 0 // *i64 ops base |
| 43 | const VN_S_NOPS: i64 = 1 |
| 44 | const VN_S_PC: i64 = 2 |
| 45 | const VN_S_STEPS: i64 = 3 |
| 46 | const VN_S_ENDED: i64 = 4 |
| 47 | const VN_S_VARS: i64 = 8 // 8 vars: slots 8..15 |
| 48 | const VN_N_VARS: i64 = 8 |
| 49 | const VN_S_STRIDE: i64 = 16 |
| 52 | const VN_SPK_NARRATOR: i64 = 0 |
| 53 | const VN_SPK_MOM: i64 = 1 |
| 54 | const VN_SPK_KID: i64 = 2 |
| 119 | const VN_VAR_TEND: i64 = 0 |
| 231 | const VN_RENDER_FD: i64 = 99 |
functions
| 56 | func nx_vn_speaker_name(id: i64) -> *u8 called by 1: nx_vn_render |
| 63 | func vn_emit_say(ops: *i64, pc: i64, speaker: i64, text: *u8) -> i64 called by 1: nx_vn_build_demo_story |
| 70 | func vn_emit_choice2(ops: *i64, pc: i64, t0: *u8, tgt0: i64, t1: *u8, tgt1: i64) -> i64 called by 1: nx_vn_build_demo_story |
| 80 | func vn_emit_add(ops: *i64, pc: i64, v: i64, delta: i64) -> i64 called by 1: nx_vn_build_demo_story |
| 87 | func vn_emit_ifgoto(ops: *i64, pc: i64, v: i64, min: i64, target: i64) -> i64 called by 1: nx_vn_build_demo_story |
| 95 | func vn_emit_jump(ops: *i64, pc: i64, target: i64) -> i64 called by 1: nx_vn_build_demo_story |
| 101 | func vn_emit_end(ops: *i64, pc: i64, epilogue: *u8) -> i64 called by 1: nx_vn_build_demo_story |
| 121 | func nx_vn_build_demo_story(out_nops: *i64) -> *i64 called by 1: nx_vn_init calls 7: sys_mmapvn_emit_sayvn_emit_choice2vn_emit_addvn_emit_jumpvn_emit_ifgoto+1 |
| 151 | func vn_settle(s: *i64) -> i64 |
| 172 | func nx_vn_new(ops: *i64, nops: i64) -> *i64 |
| 185 | func nx_vn_kind(s: *i64) -> i64 |
| 189 | func nx_vn_op_field(s: *i64, f: i64) -> i64 |
| 193 | func nx_vn_ended(s: *i64) -> i64 { return s[VN_S_ENDED] } called by 1: main |
| 194 | func nx_vn_var(s: *i64, i: i64) -> i64 { return s[VN_S_VARS + i] } called by 1: main |
| 197 | func nx_vn_advance(s: *i64) -> i64 |
| 207 | func nx_vn_choose(s: *i64, i: i64) -> i64 |
| 220 | func nx_vn_state_hash(s: *i64) -> i64 called by 1: main |
| 233 | func vn_out(s: *u8) -> i64 |
| 240 | func nx_vn_init() -> *i64 |
| 247 | func nx_vn_render(s: *i64) -> i64 |
| 286 | func nx_vn_action(s: *i64, action: i64, a0: i64, a1: i64, a2: i64, a3: i64) -> i64 |