code wiki / _hdl_build / nx_char_ai_gate.nx
nx_char_ai_gate.nx source
↩ module page · 82 lines · 4775 B
1// nx_char_ai_gate.nx -- proves G-R2 char-AI (behavior tree + FSM). NPC BT (priority selector):
2// SEL[ SEQ[COND health_low, ACT flee], SEQ[COND enemy_visible, ACT attack], ACT patrol ]
3// blackboard bits: 0=health_low, 1=enemy_visible. actions: flee=1, attack=2, patrol=3.
4// FSM: IDLE--see-->ATTACK--health_low-->FLEE--healed-->IDLE (no other transitions). GREEN only if every
5// decision + transition is correct, incl. priority and neg-controls. license_tier: ORIGINAL expect_exit: 0
6// 2026-08-28 D001 MIGRATION: the hand-rolled pass/total counter and the sys_exit tail are replaced by
7// gv_ctr/gv_check/gv_verdict, so /api/promote will accept it and /api/gate_run can READ the verdict from
8// the exit code instead of a printed string nothing parses. Every tooth now PRINTS THE VALUE IT JUDGED --
9// the incumbent printed a value only on failure, so a passing run said nothing about what it saw.
10import "nx_syscalls.nx"
11import "nx_char_ai.nx"
12import "nx_gate_verdict.nx"
13
14func cw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
15func cn(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1} sys_write(1,bb,k); return 0 }
16
17func bt_action(ty: *i64, a0: *i64, nk: *i64, kids: *i64, bb: i64) -> i64 {
18 let ao: *i64 = sys_mmap(8) as *i64
19 ao[0] = -1
20 bt_tick(ty, a0, nk, kids, 0, bb, ao)
21 return ao[0]
22}
23
24// one place that turns "the observed value" into a tooth condition, so the value is always PRINTED
25// beside the verdict and a tooth can never assert on a number the reader cannot see.
26func cai_is(label: *u8, got: i64, want: i64) -> i64 {
27 cw(" " as *u8); cw(label); cw(" got=" as *u8); cn(got); cw(" want=" as *u8); cn(want); cw("\n" as *u8)
28 if got == want { return 1 }
29 return 0
30}
31
32func main() -> i64 {
33 // ---- behavior tree (8 nodes) ----
34 let ty: *i64 = sys_mmap(8 * 8) as *i64
35 let a0: *i64 = sys_mmap(8 * 8) as *i64
36 let nk: *i64 = sys_mmap(8 * 8) as *i64
37 let kids: *i64 = sys_mmap(8 * 32) as *i64
38 var i: i64 = 0
39 while i < 32 { kids[i] = -1; i = i + 1 }
40 // node 0 SEL(flee-seq, attack-seq, patrol); 1 SEQ(cond hp, act flee); 2 COND hp; 3 ACT flee;
41 // 4 SEQ(cond vis, act atk); 5 COND vis; 6 ACT atk; 7 ACT patrol
42 ty[0]=NT_SEL; nk[0]=3; kids[0]=1; kids[1]=4; kids[2]=7
43 ty[1]=NT_SEQ; nk[1]=2; kids[4]=2; kids[5]=3
44 ty[2]=NT_COND; a0[2]=0
45 ty[3]=NT_ACT; a0[3]=1
46 ty[4]=NT_SEQ; nk[4]=2; kids[16]=5; kids[17]=6
47 ty[5]=NT_COND; a0[5]=1
48 ty[6]=NT_ACT; a0[6]=2
49 ty[7]=NT_ACT; a0[7]=3
50
51 let ctr: *i64 = gv_ctr()
52
53 let bt1: i64 = bt_action(ty,a0,nk,kids, 1)
54 gv_check("BT1-health_low-alone-selects-FLEE" as *u8, cai_is("bt[health_low]" as *u8, bt1, 1), ctr)
55 let bt2: i64 = bt_action(ty,a0,nk,kids, 2)
56 gv_check("BT2-enemy_visible-alone-selects-ATTACK" as *u8, cai_is("bt[enemy_visible]" as *u8, bt2, 2), ctr)
57 let bt3: i64 = bt_action(ty,a0,nk,kids, 3)
58 gv_check("BT3-PRIORITY-both-bits-set-still-selects-FLEE-not-ATTACK" as *u8, cai_is("bt[both]" as *u8, bt3, 1), ctr)
59 let bt4: i64 = bt_action(ty,a0,nk,kids, 0)
60 gv_check("BT4-neg-control-no-bit-set-falls-through-to-PATROL" as *u8, cai_is("bt[none]" as *u8, bt4, 3), ctr)
61
62 // ---- FSM (3 states x 4 events) ----
63 // states IDLE=0 ATTACK=1 FLEE=2 ; events SEE=0 LOSE=1 HPLOW=2 HEALED=3
64 let trans: *i64 = sys_mmap(8 * 12) as *i64
65 var j: i64 = 0
66 while j < 12 { trans[j] = -1; j = j + 1 }
67 trans[0*4 + 0] = 1 // IDLE + SEE -> ATTACK
68 trans[1*4 + 1] = 0 // ATTACK + LOSE -> IDLE
69 trans[1*4 + 2] = 2 // ATTACK + HPLOW -> FLEE
70 trans[2*4 + 3] = 0 // FLEE + HEALED -> IDLE
71
72 let f1: i64 = fsm_step(trans,4, 0,0)
73 gv_check("FSM1-IDLE-plus-SEE-goes-to-ATTACK" as *u8, cai_is("fsm[idle,see]" as *u8, f1, 1), ctr)
74 let f2: i64 = fsm_step(trans,4, 1,2)
75 gv_check("FSM2-ATTACK-plus-HPLOW-goes-to-FLEE" as *u8, cai_is("fsm[attack,hplow]" as *u8, f2, 2), ctr)
76 let f3: i64 = fsm_step(trans,4, 2,3)
77 gv_check("FSM3-FLEE-plus-HEALED-returns-to-IDLE" as *u8, cai_is("fsm[flee,healed]" as *u8, f3, 0), ctr)
78 let f4: i64 = fsm_step(trans,4, 0,1)
79 gv_check("FSM4-neg-control-IDLE-plus-LOSE-is-an-UNDECLARED-transition-and-must-STAY-idle" as *u8, cai_is("fsm[idle,lose]" as *u8, f4, 0), ctr)
80
81 return gv_verdict("CHAR-AI" as *u8, ctr, "behaviour tree AND finite-state machine on one subject: BT3 is the discriminating tooth because a selector that merely returns the first matching branch in declaration order passes BT1/BT2 and fails it, and FSM4 refuses an undeclared transition rather than inventing one" as *u8)
82}