code wiki / _hdl_build / nx_batt_facility_test.nx
nx_batt_facility_test.nx source
↩ module page · 103 lines · 6242 B
1import "nx_gate_gn.nx"
2// nx_batt_facility_test.nx -- gate for R3. Proves every interlock refusal + admit + auto-isolation + the R1->R3
3// chain (an unsafe pack can never get a charging bay) + liar-kill (disarm thermal -> admit flips to refuse).
4// expect_exit: 0
5import "nx_syscalls.nx"
6import "nx_batt_intake.nx"
7import "nx_batt_facility.nx"
8
9func gp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
10func mk_fac() -> *FacilitySpec {
11 let s: *FacilitySpec = (sys_mmap(128)) as *FacilitySpec
12 s.n_bays = 6; s.bay_max_energy_wh = 1500; s.facility_max_energy_wh = 8000; s.cabinet_capacity_wh = 4000
13 s.station_max_charge_ma = 2000; s.storage_cap_lb = 220; s.thermal_isolation_armed = 1
14 return s
15}
16func mk_ispec() -> *IntakeSpec {
17 let s: *IntakeSpec = (sys_mmap(128)) as *IntakeSpec
18 s.unsafe_cell_floor_mv = 2000; s.healthy_cell_min_mv = 3000; s.healthy_cell_max_mv = 4250
19 s.soh_retire_permille = 700; s.max_imbalance_mv = 100; s.max_ir_mohm = 150
20 s.temp_min_mc = 0; s.temp_max_mc = 45000; s.recell_max_bad = 3
21 return s
22}
23func mk_pack() -> *PackState {
24 let p: *PackState = (sys_mmap(128)) as *PackState
25 p.n_cells = 10; p.min_cell_mv = 3700; p.max_cell_mv = 3720; p.n_bad_cells = 0
26 p.rated_capacity_mah = 2500; p.measured_capacity_mah = 2400; p.max_ir_mohm = 40
27 p.pack_temp_mc = 25000; p.terminal_mv = 37000; p.swollen = 0
28 return p
29}
30
31func main() -> i64 {
32 var pass: i64=0; var fail: i64=0
33 gp("=== nx_batt_facility_test: R3 fire-safe facility + self-service interlock ===\n" as *u8)
34
35 // T1 ADMIT a safe session within all caps
36 let a1: i64 = nx_fac_admit(mk_fac(), NX_BI_RECONDITION, 2, 3000, 800, 1500, 100)
37 gp(" T1 safe session -> " as *u8); gn(a1); gp(" (exp 0 ADMIT)\n" as *u8)
38 if a1==NX_FAC_ADMIT { pass=pass+1 } else { fail=fail+1 }
39
40 // T2 unsafe pack -> never admitted to a bay
41 let a2: i64 = nx_fac_admit(mk_fac(), NX_BI_UNSAFE_REJECT, 2, 3000, 800, 1500, 100)
42 gp(" T2 unsafe pack -> " as *u8); gn(a2); gp(" (exp 1 PACK_UNSAFE)\n" as *u8)
43 if a2==NX_FAC_REFUSE_PACK_UNSAFE { pass=pass+1 } else { fail=fail+1 }
44
45 // T3 thermal monitor disarmed -> NO charging (never-ignite), even for a safe pack
46 let fnt: *FacilitySpec = mk_fac(); fnt.thermal_isolation_armed = 0
47 let a3: i64 = nx_fac_admit(fnt, NX_BI_RECONDITION, 0, 0, 800, 1500, 100)
48 gp(" T3 thermal disarmed -> " as *u8); gn(a3); gp(" (exp 5 NO_THERMAL)\n" as *u8)
49 if a3==NX_FAC_REFUSE_NO_THERMAL { pass=pass+1 } else { fail=fail+1 }
50
51 // T4 all bays occupied
52 let a4: i64 = nx_fac_admit(mk_fac(), NX_BI_RECONDITION, 6, 3000, 800, 1500, 100)
53 gp(" T4 bays full -> " as *u8); gn(a4); gp(" (exp 2 BAY_FULL)\n" as *u8)
54 if a4==NX_FAC_REFUSE_BAY_FULL { pass=pass+1 } else { fail=fail+1 }
55
56 // T5 requested charge rate over station cap
57 let a5: i64 = nx_fac_admit(mk_fac(), NX_BI_RECONDITION, 2, 3000, 800, 2500, 100)
58 gp(" T5 over station rate -> " as *u8); gn(a5); gp(" (exp 4 STATION_RATE)\n" as *u8)
59 if a5==NX_FAC_REFUSE_STATION_RATE { pass=pass+1 } else { fail=fail+1 }
60
61 // T6 per-bay energy / fire-load cap
62 let a6: i64 = nx_fac_admit(mk_fac(), NX_BI_RECONDITION, 2, 3000, 1600, 1500, 100)
63 gp(" T6 over bay energy -> " as *u8); gn(a6); gp(" (exp 3 ENERGY_CAP)\n" as *u8)
64 if a6==NX_FAC_REFUSE_ENERGY_CAP { pass=pass+1 } else { fail=fail+1 }
65
66 // T7 facility total energy / fire-load cap
67 let a7: i64 = nx_fac_admit(mk_fac(), NX_BI_RECONDITION, 3, 7500, 800, 1500, 100)
68 gp(" T7 over facility energy-> " as *u8); gn(a7); gp(" (exp 3 ENERGY_CAP)\n" as *u8)
69 if a7==NX_FAC_REFUSE_ENERGY_CAP { pass=pass+1 } else { fail=fail+1 }
70
71 // T8 regulatory Li-ion storage over VSQG (220 lb) limit
72 let a8: i64 = nx_fac_admit(mk_fac(), NX_BI_RECONDITION, 1, 1000, 500, 1500, 250)
73 gp(" T8 over VSQG storage -> " as *u8); gn(a8); gp(" (exp 6 STORAGE_CAP)\n" as *u8)
74 if a8==NX_FAC_REFUSE_STORAGE_CAP { pass=pass+1 } else { fail=fail+1 }
75
76 // T9 auto-isolation: over-temp bay cut from power; normal bay not
77 let i_hot: i64 = nx_fac_isolate(65000, 60000)
78 let i_ok: i64 = nx_fac_isolate(40000, 60000)
79 gp(" T9 isolate hot=" as *u8); gn(i_hot); gp(" ok=" as *u8); gn(i_ok); gp(" (exp 1 / 0)\n" as *u8)
80 if i_hot==1 { if i_ok==0 { pass=pass+1 } else { fail=fail+1 } } else { fail=fail+1 }
81
82 // T10 FULL R1->R3 CHAIN: a swollen pack triages UNSAFE -> facility refuses the bay; a healthy pack -> admitted
83 let isp: *IntakeSpec = mk_ispec()
84 let bad: *PackState = mk_pack(); bad.swollen = 1
85 let vbad: i64 = nx_batt_triage(bad, isp)
86 let cbad: i64 = nx_fac_admit(mk_fac(), vbad, 2, 3000, 800, 1500, 100)
87 let good: *PackState = mk_pack(); good.min_cell_mv = 3500; good.max_cell_mv = 3750
88 let vgood: i64 = nx_batt_triage(good, isp)
89 let cgood: i64 = nx_fac_admit(mk_fac(), vgood, 2, 3000, 800, 1500, 100)
90 gp(" T10 chain: swollen triage=" as *u8); gn(vbad); gp("->admit=" as *u8); gn(cbad); gp("(refuse) ; healthy triage=" as *u8); gn(vgood); gp("->admit=" as *u8); gn(cgood); gp("(admit)\n" as *u8)
91 if vbad==NX_BI_UNSAFE_REJECT { if cbad==NX_FAC_REFUSE_PACK_UNSAFE { if cgood==NX_FAC_ADMIT { pass=pass+1 } else { fail=fail+1 } } else { fail=fail+1 } } else { fail=fail+1 }
92
93 // LIAR-KILL: same safe session admits; disarm thermal -> MUST flip to refuse (interlock load-bearing)
94 let la: i64 = nx_fac_admit(mk_fac(), NX_BI_RECONDITION, 2, 3000, 800, 1500, 100)
95 let fld: *FacilitySpec = mk_fac(); fld.thermal_isolation_armed = 0
96 let lb: i64 = nx_fac_admit(fld, NX_BI_RECONDITION, 2, 3000, 800, 1500, 100)
97 gp(" LIAR armed=" as *u8); gn(la); gp(" disarmed=" as *u8); gn(lb); gp(" (must be 0 then 5)\n" as *u8)
98 if la==NX_FAC_ADMIT { if lb==NX_FAC_REFUSE_NO_THERMAL { pass=pass+1 } else { fail=fail+1; gp(" FAIL liar\n" as *u8) } } else { fail=fail+1; gp(" FAIL liar-base\n" as *u8) }
99
100 gp("BATT-FACILITY pass=" as *u8); gn(pass); gp(" fail=" as *u8); gn(fail)
101 if fail==0 { gp(" verdict=GREEN (every interlock + auto-isolation + R1->R3 chain + liar-kill; never-ignite at the facility level)\n" as *u8); sys_exit(0); return 0 }
102 gp(" verdict=RED\n" as *u8); sys_exit(1); return 1
103}