nx_phys3d_toi_gate.nx source
↩ module page · 134 lines · 7586 B
1// nx_phys3d_toi_gate.nx -- PG26: CAN A FAST MOVER EVER END A TICK INSIDE A WALL? (the 2D sweep tooth lifted to 3D)
2//
3// SUBJECT: nx_phys3d.ph_toi through ph_step (in-process). The rung's done-rule in its own words: a 400-per-tick
4// mover stops AT a 4-unit wall, lifted to 3D, and a fast mover can never end a tick inside. Every tooth reads the
5// engine's own positions; the overlap oracle is ph_max_overlap. The neg-control switches CCD OFF and must TUNNEL --
6// it proves the fixture reaches the condition and that the advancement, not the wall, is doing the stopping.
7import "nx_syscalls.nx"
8import "nx_gate_verdict.nx"
9import "nx_phys3d.nx"
10const TG_WALL_X: i64 = 20000 // fx256 (78 m)
11const TG_WALL_HX: i64 = 512 // 4-unit wall: half-thickness 2 units
12const TG_WALL_HY: i64 = 4096
13const TG_WALL_HZ: i64 = 4096
14const TG_FLOOR_Y: i64 = 0 - 512 // top face at -256, so a unit sphere at y=0 rests on it
15const TG_FLOOR_HX: i64 = 60000
16const TG_FLOOR_HY: i64 = 256
17const TG_FLOOR_HZ: i64 = 8192
18const TG_R: i64 = 256 // the mover: a unit sphere
19const TG_SPEED: i64 = 400 // pos units per tick (400 > r: a FAST mover)
20const TG_START_GAP: i64 = 200 // it starts 200 units short of the wall face: one discrete step tunnels
21const TG_TICKS: i64 = 12
22const TG_SPH_X: i64 = 30000
23const TG_SPH_R: i64 = 512
24const TG_OBL_VX: i64 = 300
25const TG_OBL_VZ: i64 = 200
26const TG_INVM: i64 = 256
27const TG_REST: i64 = 0
28const TG_FRIC: i64 = 64
29
30func tg_world(add_sphere: i64) -> i64 {
31 let base: i64 = sys_mmap(ph_bytes()) as i64
32 ph_init(base)
33 ph_add(base, 1, TG_WALL_X, 0, 0, TG_WALL_HX, TG_WALL_HY, TG_WALL_HZ, 0, 128, TG_FRIC)
34 ph_add(base, 1, 0, TG_FLOOR_Y, 0, TG_FLOOR_HX, TG_FLOOR_HY, TG_FLOOR_HZ, 0, 128, TG_FRIC)
35 if add_sphere == 1 { ph_add(base, 0, TG_SPH_X, 0, 0, TG_SPH_R, TG_SPH_R, TG_SPH_R, 0, 128, TG_FRIC) }
36 return base
37}
38func tg_face() -> i64 { return TG_WALL_X - TG_WALL_HX }
39
40func main() -> i64 {
41 let ctr: *i64 = gv_ctr()
42 gv_head("=== NX-PHYS3D-TOI gate (PG26): a fast mover advances to its time of impact and never ends a tick inside ===" as *u8)
43 // 1. the 2D tooth in 3D: 400 per tick at a 4-unit wall, starting 200 short of the face
44 let base: i64 = tg_world(0)
45 let x0: i64 = tg_face() - TG_R - TG_START_GAP
46 let m: i64 = ph_add(base, 0, x0, 0, 0, TG_R, TG_R, TG_R, TG_INVM, TG_REST, TG_FRIC)
47 ph_set_vel(base, m, TG_SPEED * 256, 0, 0)
48 gv_check("fixture-reaches-the-condition: displacement per tick exceeds the radius AND the gap (400 > 256, 400 > 200)" as *u8, ((TG_SPEED > TG_R) as i64) * ((TG_SPEED > TG_START_GAP) as i64), ctr)
49 let toi: i64 = ph_toi(base, m, TG_SPEED, 0, 0)
50 gv_check("toi-is-a-fraction-of-the-tick (0 < toi < 256) when the wall is closer than one step" as *u8, ((toi > 0) as i64) * ((toi < PH_TOI_Q) as i64), ctr)
51 ph_step(base)
52 let b: *i64 = ph_body(base, m)
53 let x1: i64 = b[1]
54 let ov1: i64 = ph_max_overlap(base, m)
55 gv_check("stops-AT-the-wall: after one tick the sphere touches the face (overlap within slop, not beyond it)" as *u8, ((ov1 <= PH_SLOP + PH_TOI_EPS) as i64) * ((x1 + TG_R >= tg_face() - PH_SLOP - PH_TOI_EPS * 2) as i64), ctr)
56 gv_check("not-beyond-the-wall: the centre stays on the near side of the face" as *u8, (x1 + TG_R <= tg_face() + PH_SLOP) as i64, ctr)
57 // 2. never inside over TG_TICKS ticks (gravity, floor, the contact solver all in play)
58 var worst: i64 = 0 - 1000000
59 var beyond: i64 = 0
60 var k: i64 = 0
61 while k < TG_TICKS {
62 ph_step(base)
63 let ov: i64 = ph_max_overlap(base, m)
64 if ov > worst { worst = ov }
65 if b[1] > TG_WALL_X { beyond = 1 }
66 k = k + 1
67 }
68 gv_check("never-inside-over-the-run: worst overlap across every tick stays within slop" as *u8, (worst <= PH_SLOP + PH_TOI_EPS) as i64, ctr)
69 gv_check("never-tunnels: the centre never crosses the wall's centre plane" as *u8, (beyond == 0) as i64, ctr)
70 // 3. neg-control: CCD off, the same fixture TUNNELS (the discrete step lands inside or beyond)
71 let nb: i64 = tg_world(0)
72 let nm: i64 = ph_add(nb, 0, x0, 0, 0, TG_R, TG_R, TG_R, TG_INVM, TG_REST, TG_FRIC)
73 ph_set_vel(nb, nm, TG_SPEED * 256, 0, 0)
74 ph_ccd_off(1)
75 ph_step(nb)
76 ph_ccd_off(0)
77 let nbody: *i64 = ph_body(nb, nm)
78 let ovn: i64 = ph_max_overlap(nb, nm)
79 gv_check("neg-control-discrete-step-EMBEDS: with CCD off the same tick ends deep inside the wall (overlap far beyond slop)" as *u8, (ovn > PH_SLOP * 8) as i64, ctr)
80 // 4. a slow mover is untouched by the sweep: its position is the plain discrete integrate
81 let sb: i64 = tg_world(0)
82 let sm: i64 = ph_add(sb, 0, 0, 0, 0, TG_R, TG_R, TG_R, TG_INVM, TG_REST, TG_FRIC)
83 let slow: i64 = TG_R / 2
84 ph_set_vel(sb, sm, slow * 256, 0, 0)
85 ph_step(sb)
86 let sbody: *i64 = ph_body(sb, sm)
87 gv_check_eq("slow-mover-takes-the-discrete-path (x advances by exactly v/256)" as *u8, sbody[1], slow, ctr)
88 // 5. sphere target: the mover stops at a static sphere's surface
89 let cb: i64 = tg_world(1)
90 let cx0: i64 = TG_SPH_X - TG_SPH_R - TG_R - TG_START_GAP
91 let cm: i64 = ph_add(cb, 0, cx0, 0, 0, TG_R, TG_R, TG_R, TG_INVM, TG_REST, TG_FRIC)
92 ph_set_vel(cb, cm, TG_SPEED * 256, 0, 0)
93 ph_step(cb)
94 let ovc: i64 = ph_max_overlap(cb, cm)
95 let cbody: *i64 = ph_body(cb, cm)
96 gv_check("sphere-target: stops at the static sphere's surface (touching, not inside)" as *u8, ((ovc <= PH_SLOP + PH_TOI_EPS) as i64) * ((cbody[1] + TG_R >= TG_SPH_X - TG_SPH_R - PH_SLOP - PH_TOI_EPS * 2) as i64), ctr)
97 // 6. oblique 3D approach: velocity with x and z components, several ticks, never inside
98 let ob: i64 = tg_world(0)
99 let om: i64 = ph_add(ob, 0, tg_face() - TG_R - TG_OBL_VX - 100, 0, 0, TG_R, TG_R, TG_R, TG_INVM, TG_REST, TG_FRIC)
100 ph_set_vel(ob, om, TG_OBL_VX * 256, 0, TG_OBL_VZ * 256)
101 var oworst: i64 = 0 - 1000000
102 k = 0
103 while k < 4 {
104 ph_step(ob)
105 let ov2: i64 = ph_max_overlap(ob, om)
106 if ov2 > oworst { oworst = ov2 }
107 k = k + 1
108 }
109 gv_check("oblique-approach-never-inside (x and z velocity, four ticks)" as *u8, (oworst <= PH_SLOP + PH_TOI_EPS) as i64, ctr)
110 // 7. determinism: the whole run twice, byte-equal position
111 let d1: i64 = tg_world(0)
112 let dm1: i64 = ph_add(d1, 0, x0, 0, 0, TG_R, TG_R, TG_R, TG_INVM, TG_REST, TG_FRIC)
113 ph_set_vel(d1, dm1, TG_SPEED * 256, 0, 0)
114 let d2: i64 = tg_world(0)
115 let dm2: i64 = ph_add(d2, 0, x0, 0, 0, TG_R, TG_R, TG_R, TG_INVM, TG_REST, TG_FRIC)
116 ph_set_vel(d2, dm2, TG_SPEED * 256, 0, 0)
117 k = 0
118 while k < TG_TICKS { ph_step(d1); ph_step(d2); k = k + 1 }
119 let e1: *i64 = ph_body(d1, dm1)
120 let e2: *i64 = ph_body(d2, dm2)
121 gv_check("determinism: two identical runs end byte-equal (x, y, z, vx)" as *u8, ((e1[1] == e2[1]) as i64) * ((e1[2] == e2[2]) as i64) * ((e1[3] == e2[3]) as i64) * ((e1[4] == e2[4]) as i64), ctr)
122 gv_values_head()
123 gv_kv("toi_q8_first_tick" as *u8, toi)
124 gv_kv("x_after_first_tick" as *u8, x1)
125 gv_kv("wall_face_x" as *u8, tg_face())
126 gv_kv("overlap_after_first_tick" as *u8, ov1)
127 gv_kv("worst_overlap_over_run" as *u8, worst)
128 gv_kv("negcontrol_overlap_ccd_off" as *u8, ovn)
129 gv_kv("negcontrol_x_ccd_off" as *u8, nbody[1])
130 gv_kv("sphere_target_overlap" as *u8, ovc)
131 gv_kv("oblique_worst_overlap" as *u8, oworst)
132 gv_kv("slow_mover_x" as *u8, sbody[1])
133 return gv_verdict("NX-PHYS3D-TOI" as *u8, ctr, "a fast mover is advanced to its time of impact; the tick can end touching, never inside" as *u8)
134}