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}