code wiki / (root) / nx_scene_contract_test.nx

nx_scene_contract_test.nx source

↩ module page · 199 lines · 7268 B

1// nx_scene_contract_test.nx -- KATs for the R0 style-spanning scene 2// contract. Exit 0 = all pass; the failing KAT's number is the exit code. 3// 4// KAT 1 identity determinism: same seed -> identical entity, twice. 5// KAT 2 style never touches identity: entity record byte-equal after 6// rendering under all four styles. 7// KAT 3 render determinism: same (world, human, style) -> byte-identical 8// framebuffers. 9// KAT 4 terrain anchoring: the figure's lowest outfit pixel sits on the 10// ground row at the anchor, in EVERY style. 11// KAT 5 styles genuinely differ: framebuffer hashes pairwise distinct. 12// KAT 6 cross-world reuse: the SAME human renders into two different 13// worlds; both visible, both deterministic. 14// KAT 7 proportion tables are style-distinct (head ratios all differ). 15// KAT 8 speed: full Tier-0 quickstart under NX_SCN_RENDER_BUDGET_MS 16// (the measured beat-them-on-speed number, printed every run). 17// license_tier: ORIGINAL 18 19import "nx_syscalls.nx" 20import "nx_tier.nx" 21import "nx_scene_contract.nx" 22import "nx_scene_render.nx" 23 24func t_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 25func t_putn(v: i64) -> i64 { 26 let b: *u8 = sys_mmap(32) 27 var m: i64 = v 28 var k: i64 = 0 29 if m == 0 { b[0] = 48; k = 1 } 30 while m > 0 { b[k] = 48 + (m % 10); m = m / 10; k = k + 1 } 31 var i: i64 = 0 32 while i < k / 2 { let t: u8 = b[i]; b[i] = b[k-1-i]; b[k-1-i] = t; i = i + 1 } 33 sys_write(1, b, k) 34 return 0 35} 36 37const FB_BYTES: nx_int = 147456 // NX_SCN_W * NX_SCN_H * 3 38 39func t_fnv(fb: *u8, n: nx_int) -> nx_int { 40 var h: nx_int = 1469598103934665603 41 var i: nx_int = 0 42 while i < n { 43 h = h ^ (fb[i] as i64) 44 h = h * 1099511628211 45 i = i + 1 46 } 47 return h 48} 49 50// lowest framebuffer row containing a pixel of color rgb in [x0,x1] 51func t_lowest_of_color(fb: *u8, x0: nx_int, x1: nx_int, rgb: nx_int) -> nx_int { 52 let rr: nx_int = (rgb / 65536) & 0xff 53 let gg: nx_int = (rgb / 256) & 0xff 54 let bb: nx_int = rgb & 0xff 55 var low: nx_int = 0 - 1 56 var y: nx_int = 0 57 while y < NX_SCN_H { 58 var x: nx_int = x0 59 while x <= x1 { 60 let o: nx_int = (y * NX_SCN_W + x) * 3 61 if (fb[o] as i64) == rr { 62 if (fb[o+1] as i64) == gg { 63 if (fb[o+2] as i64) == bb { low = y } 64 } 65 } 66 x = x + 1 67 } 68 y = y + 1 69 } 70 return low 71} 72 73func t_count_of_color(fb: *u8, rgb: nx_int) -> nx_int { 74 let rr: nx_int = (rgb / 65536) & 0xff 75 let gg: nx_int = (rgb / 256) & 0xff 76 let bb: nx_int = rgb & 0xff 77 var cnt: nx_int = 0 78 var i: nx_int = 0 79 while i < NX_SCN_W * NX_SCN_H { 80 let o: nx_int = i * 3 81 if (fb[o] as i64) == rr { 82 if (fb[o+1] as i64) == gg { 83 if (fb[o+2] as i64) == bb { cnt = cnt + 1 } 84 } 85 } 86 i = i + 1 87 } 88 return cnt 89} 90 91func t_render_to(world_seed: nx_int, human_seed: nx_int, style: nx_int, fb: *u8) -> *i64 { 92 let sw: *i64 = nx_scene_world(world_seed, NX_PROCGEN_PRESET_MOUNTAIN, style) 93 let e: *i64 = nx_entity_human(human_seed) 94 let land: *Landscape = sw[NX_SW_LAND] as *Landscape 95 nx_scene_place(e, land.width / 2, land.height / 2) 96 nx_scene_render(sw, e, fb) 97 return e 98} 99 100func main() -> i64 { 101 // ---- KAT 1: identity determinism ---- 102 let e1: *i64 = nx_entity_human(777) 103 let e2: *i64 = nx_entity_human(777) 104 var i: nx_int = 0 105 while i < NX_ENT_SLOTS { 106 if e1[i] != e2[i] { return 1 } 107 i = i + 1 108 } 109 110 // ---- KAT 2 + 3 + 4 + 5: render the same scene under all 4 styles ---- 111 let fb_a: *u8 = sys_mmap(FB_BYTES) 112 let fb_b: *u8 = sys_mmap(FB_BYTES) 113 let snap: *i64 = sys_mmap(NX_ENT_SLOTS * 8) as *i64 114 let hashes: *i64 = sys_mmap(NX_STYLE_COUNT * 8) as *i64 115 var style: nx_int = 0 116 while style < NX_STYLE_COUNT { 117 let e: *i64 = t_render_to(4242, 777, style, fb_a) 118 // KAT 2: identity untouched by the renderer 119 i = 0 120 while i < NX_ENT_SLOTS { snap[i] = e[i]; i = i + 1 } 121 let e_again: *i64 = nx_entity_human(777) 122 if snap[NX_ENT_SKIN_RGB] != e_again[NX_ENT_SKIN_RGB] { return 2 } 123 if snap[NX_ENT_HAIR_RGB] != e_again[NX_ENT_HAIR_RGB] { return 2 } 124 if snap[NX_ENT_OUTFIT_RGB] != e_again[NX_ENT_OUTFIT_RGB] { return 2 } 125 if snap[NX_ENT_EYE_RGB] != e_again[NX_ENT_EYE_RGB] { return 2 } 126 if snap[NX_ENT_BUILD_Q10] != e_again[NX_ENT_BUILD_Q10] { return 2 } 127 if snap[NX_ENT_HEIGHT_Q10] != e_again[NX_ENT_HEIGHT_Q10] { return 2 } 128 // KAT 3: byte determinism 129 t_render_to(4242, 777, style, fb_b) 130 i = 0 131 while i < FB_BYTES { 132 if (fb_a[i] as i64) != (fb_b[i] as i64) { return 3 } 133 i = i + 1 134 } 135 // KAT 4: anchored -- lowest outfit pixel = ground row - 1 (+-1) 136 let land_probe: *i64 = nx_scene_world(4242, NX_PROCGEN_PRESET_MOUNTAIN, style) 137 let lp: *Landscape = land_probe[NX_SW_LAND] as *Landscape 138 let gh: nx_int = nx_landscape_height_at(lp, lp.width / 2, lp.height / 2) 139 let feet: nx_int = _scn_ground_row(gh) 140 let cols: nx_int = NX_SCN_W / lp.width 141 let cx: nx_int = (lp.width / 2) * cols + cols / 2 142 let low: nx_int = t_lowest_of_color(fb_a, cx - 8, cx + 8, snap[NX_ENT_OUTFIT_RGB]) 143 if low < 0 { return 4 } 144 var dd: nx_int = low - (feet - 1) 145 if dd < 0 { dd = 0 - dd } 146 if dd > 1 { return 4 } 147 // KAT 5 prep: hash 148 hashes[style] = t_fnv(fb_a, FB_BYTES) 149 style = style + 1 150 } 151 // KAT 5: pairwise distinct 152 var a: nx_int = 0 153 while a < NX_STYLE_COUNT { 154 var b: nx_int = a + 1 155 while b < NX_STYLE_COUNT { 156 if hashes[a] == hashes[b] { return 5 } 157 b = b + 1 158 } 159 a = a + 1 160 } 161 162 // ---- KAT 6: same human, two different worlds ---- 163 t_render_to(1111, 777, NX_STYLE_ANIME, fb_a) 164 t_render_to(9999, 777, NX_STYLE_ANIME, fb_b) 165 let e6: *i64 = nx_entity_human(777) 166 let oc: nx_int = e6[NX_ENT_OUTFIT_RGB] 167 if t_count_of_color(fb_a, oc) <= 0 { return 6 } 168 if t_count_of_color(fb_b, oc) <= 0 { return 6 } 169 if t_fnv(fb_a, FB_BYTES) == t_fnv(fb_b, FB_BYTES) { return 6 } 170 171 // ---- KAT 7: proportion tables style-distinct ---- 172 a = 0 173 while a < NX_STYLE_COUNT { 174 var b2: nx_int = a + 1 175 while b2 < NX_STYLE_COUNT { 176 if nx_style_head_ratio_q10(a) == nx_style_head_ratio_q10(b2) { return 7 } 177 b2 = b2 + 1 178 } 179 a = a + 1 180 } 181 182 // ---- KAT 8: speed budget (Tier-0 quickstart, all 4 styles) ---- 183 let t0: nx_int = sys_now_ms() 184 var s8: nx_int = 0 185 while s8 < NX_STYLE_COUNT { 186 let rc: nx_int = nx_scene_quickstart(4242, 777, s8, "/tmp/nishi_scene_style.ppm" as *u8) 187 if rc != 0 { return 8 } 188 s8 = s8 + 1 189 } 190 let ms: nx_int = sys_now_ms() - t0 191 t_puts("SCENE-SPEED 4-style quickstart ms=" as *u8) 192 t_putn(ms) 193 t_puts(" budget_ms_per=" as *u8) 194 t_putn(NX_SCN_RENDER_BUDGET_MS) 195 t_puts("\n" as *u8) 196 if ms >= NX_SCN_RENDER_BUDGET_MS * NX_STYLE_COUNT { return 8 } 197 198 return 0 199}