code wiki / _hdl_build / nx_ale_grade.nx

nx_ale_grade.nx source

↩ module page · 149 lines · 6295 B

1// nx_ale_grade.nx -- sovereign ALE DETERMINISTIC grader-runner core (rung ALE-R1a). 2// THE scoring engine the whole ALE-R1 harness routes through: the smallest load-bearing 3// slice (deterministic, code-graded, [0,1], no leakage). PURE function of two files: 4// argv[1] = REFERENCE path (list of grading-unit lines "unit|<name>|<value>") 5// argv[2] = ARTIFACT path (the agent-produced deliverable, same line grammar) 6// argv[3] = scratch OUT path (optional): the milli-score is written here as decimal 7// text + newline, so the gate reads back a value in 0..1000 WITHOUT hitting 8// the 0..255 process-exit-code ceiling. The score also goes to stdout. 9// SCORE (milli-units, 1000 == 1.0), data-driven, NO magic numbers: 10// total = number of "unit|" grading-unit lines in the REFERENCE (the rubric owns it) 11// matched= number of reference grading-unit lines that appear VERBATIM (name+value) as a 12// complete line in the ARTIFACT 13// raw = (matched * 1000) / total # per-unit weight = 1000/total, DERIVED 14// score = clamp(raw, 0, 1000) # BOUNDED into [0,1] inclusive 15// total == 0 -> score 0 (degenerate empty reference: nothing to credit) 16// Deterministic by construction: no clock, no rand -- only the two files decide the score. 17// No-leakage: the grader reads argv[1] (reference) ONLY; the score is independent of when/ 18// where the reference existed during the (separate) agent phase. Landmines respected: 19// nested ifs (no &&/||), flat exprs, <=6 args/func, no empty-string literal, strings via 20// Write, openat_wr has no O_TRUNC (scratch file is fresh per gate run). license_tier: ORIGINAL 21import "nx_syscalls.nx" 22import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 23const K_MAGIC_262144: i64 = 262144 24 25// read whole file at path into buf (cap), return byte count (0 on open-fail) 26func ag_read(path: *u8, buf: *u8, cap: i64) -> i64 { 27 let fd: i64 = sys_openat_rd(path) 28 if fd < 0 { return 0 } 29 var n: i64 = 0 30 var go: i64 = 1 31 while go == 1 { 32 let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - 1 - n) 33 if r <= 0 { go = 0 } else { n = n + r } 34 if n >= cap - 1 { go = 0 } 35 } 36 sys_close(fd) 37 return n 38} 39 40// length of a C string 41func ag_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 42 43// does buf2[pos2 .. pos2+seglen) byte-equal buf1[seg .. seg+seglen)? 1/0 44func ag_seg_eq(buf1: *u8, seg: i64, seglen: i64, buf2: *u8, pos2: i64) -> i64 { 45 var k: i64 = 0 46 while k < seglen { 47 if buf1[seg + k] != buf2[pos2 + k] { return 0 } 48 k = k + 1 49 } 50 return 1 51} 52 53// is pos a line start in buf? (pos==0 or previous byte is newline) 54func ag_is_bol(buf: *u8, pos: i64) -> i64 { 55 if pos == 0 { return 1 } 56 if buf[pos - 1] == (10 as u8) { return 1 } 57 return 0 58} 59 60// index of the end of the line starting at start (the newline index, or n at EOF) 61func ag_line_end(buf: *u8, n: i64, start: i64) -> i64 { 62 var e: i64 = start 63 while e < n { 64 if buf[e] == (10 as u8) { return e } 65 e = e + 1 66 } 67 return n 68} 69 70// does the COMPLETE line buf1[seg .. segend) (segend exclusive, no newline) also appear 71// as a COMPLETE line somewhere in buf2 (length n2)? 1/0. A line is bounded by BOL and 72// by newline-or-EOF on both ends so "id" never spuriously matches inside "id,name". 73func ag_line_in(buf1: *u8, seg: i64, segend: i64, buf2: *u8, n2: i64) -> i64 { 74 let seglen: i64 = segend - seg 75 var j: i64 = 0 76 while j < n2 { 77 if ag_is_bol(buf2, j) == 1 { 78 var lineok: i64 = 0 79 if j + seglen <= n2 { 80 if ag_seg_eq(buf1, seg, seglen, buf2, j) == 1 { 81 let after: i64 = j + seglen 82 if after == n2 { lineok = 1 } else { if buf2[after] == (10 as u8) { lineok = 1 } } 83 } 84 } 85 if lineok == 1 { return 1 } 86 } 87 j = j + 1 88 } 89 return 0 90} 91 92// write decimal v (>=0) + newline to fd 93// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 94// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 95// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 96// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 97func ag_wn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 98 99func main(argc: i64, argv: *i64) -> i64 { 100 if argc < 3 { sys_exit(2); return 2 } 101 let refp: *u8 = argv[1] as *u8 102 let artp: *u8 = argv[2] as *u8 103 104 let rbuf: *u8 = sys_mmap(K_MAGIC_262144) 105 let abuf: *u8 = sys_mmap(K_MAGIC_262144) 106 let rn: i64 = ag_read(refp, rbuf, K_MAGIC_262144) 107 let an: i64 = ag_read(artp, abuf, K_MAGIC_262144) 108 109 // Count total grading-units in the REFERENCE (lines starting "unit|") and, for each, 110 // count whether that whole line appears verbatim as a complete line in the ARTIFACT. 111 let pfx: *u8 = "unit|" as *u8 112 let pl: i64 = ag_len(pfx) 113 var total: i64 = 0 114 var matched: i64 = 0 115 var i: i64 = 0 116 while i < rn { 117 if ag_is_bol(rbuf, i) == 1 { 118 var isunit: i64 = 0 119 if i + pl <= rn { 120 if ag_seg_eq(pfx, 0, pl, rbuf, i) == 1 { isunit = 1 } 121 } 122 if isunit == 1 { 123 let lineend: i64 = ag_line_end(rbuf, rn, i) 124 total = total + 1 125 if ag_line_in(rbuf, i, lineend, abuf, an) == 1 { matched = matched + 1 } 126 // advance i to the newline index; the loop's i=i+1 below steps past it 127 i = lineend 128 } 129 } 130 i = i + 1 131 } 132 133 // data-driven score: per-unit weight = 1000/total (derived from the reference rubric) 134 var raw: i64 = 0 135 if total > 0 { raw = (matched * 1000) / total } 136 // BOUNDED: clamp into [0,1000] inclusive (the [0,1] milli contract) 137 var score: i64 = raw 138 if score < 0 { score = 0 } 139 if score > 1000 { score = 1000 } 140 141 // machine-readable result: scratch file (argv[3]) if given, AND stdout 142 if argc >= 4 { 143 let out: i64 = sys_openat_wr(argv[3] as *u8, 0x1a4) 144 if out >= 0 { ag_wn(out, score); sys_close(out) } 145 } 146 ag_wn(1, score) 147 sys_exit(0) 148 return 0 149}