code wiki / _hdl_build / nx_input_abstract_part.nx
nx_input_abstract_part.nx source
↩ module page · 221 lines · 9483 B
1// nx_input_abstract_part.nx -- RENAMED FROM nx_input_abstract.nx 2026-07-31 (lib-reconcile).
2// PERMANENT FIX for a two-libraries-one-name collision. runtime/nx_input_abstract.nx is a DIFFERENT
3// program: the F1102 game-platform input CORE, whose API is ia_init(p) / ia_bind(p, code, act).
4// THIS file is the gamebench capability-43 composable PART, whose API is ia_init(s, nact) and
5// ia_bind(s, dev, act, raw) -- same names, INCOMPATIBLE arities. nx_cc binds an import to the
6// IMPORTER'S OWN DIRECTORY FIRST, so which API a caller got depended on WHICH DIRECTORY IT SAT IN,
7// and moving a file between directories silently swapped its input API. The rename removes the
8// ambiguity by construction: one name now denotes one program corpus-wide.
9//
10// WHY THIS PART EXISTS (measured, not guessed): nx_gamebench 2026-07-25 ranked platform reach and found
11// platform-desktop-native blocking 21 of 22 reference titles, vr-runtime a GAP for 4, and mobile only
12// PARTIAL -- while EVERY game in the ecosystem wired its own controls. One loop per platform is how a
13// codebase forks into four. This part is the single place a raw device code becomes a SEMANTIC ACTION,
14// so the game loop above it never learns which device the player used.
15//
16// THE CONTRACT (each clause is a gate tooth):
17// - DEVICE-INDEPENDENCE BY CONSTRUCTION: the same semantic action sequence delivered from keyboard,
18// gamepad, touch or a VR controller drives IDENTICAL downstream state. The game cannot tell them
19// apart because it never sees a device -- it sees actions. This is the flagship property.
20// - NO DEAD ACTION: an action reachable on one device but not another is a platform that cannot play.
21// ia_coverage MEASURES this per device; the gate requires 1000 permil on every bound device.
22// - UNAMBIGUOUS BINDINGS: binding a second action to a (device, raw-code) already in use is REFUSED
23// (IA_E_DUP) with state bit-identical. Ambiguous input is a bug you cannot debug later.
24// - AN UNBOUND CODE PRODUCES NOTHING: a stray device code can never fabricate an action.
25// - DETERMINISTIC REPLAY: the action trace replays to the same actions and the same checksum, and the
26// serializable span rides nx_gamesave like every other certified part (rpgstats T8 / entity T9).
27// Bindings are DATA, not code: a game declares its own action set and its own per-device codes, so a new
28// platform is a binding table, never a fork of the loop.
29// LIB, no main (ecosystem convention, cf. nx_swgpu.nx / nx_gamehud.nx). license_tier: ORIGINAL
30import "nx_syscalls.nx"
31
32// ---- devices (bit index). Deliberately the four the platform axes measure. ----
33const IA_D_KEY: i64 = 0
34const IA_D_PAD: i64 = 1
35const IA_D_TOUCH: i64 = 2
36const IA_D_VR: i64 = 3
37const IA_NDEV: i64 = 4
38
39// ---- semantic actions the HUD/menu layer consumes (nx_gamehud gh_menu_move / gh_menu_sel).
40// A game may declare up to IA_MAXACT actions; 0..4 are the universal navigation set.
41const IA_A_PREV: i64 = 0
42const IA_A_NEXT: i64 = 1
43const IA_A_CONFIRM: i64 = 2
44const IA_A_CANCEL: i64 = 3
45const IA_A_PAUSE: i64 = 4
46const IA_A_NONE: i64 = 0-1
47const IA_MAXACT: i64 = 12
48
49// ---- state layout (i64 words; ALL serializable words first, IA_NSV of them) ----
50const IA_F_NACT: i64 = 0 // declared action count
51const IA_F_NEV: i64 = 1 // events fed (lifetime)
52const IA_F_CK: i64 = 2 // order-sensitive rolling action checksum
53const IA_F_DEVM: i64 = 3 // bitmask of devices carrying at least one binding
54const IA_F_NTR: i64 = 4 // trace length
55const IA_O_BIND: i64 = 8 // binding table: IA_NDEV*IA_MAXACT raw codes -> 8 .. 8+48 = 56
56const IA_O_TRACE: i64 = 56 // recorded action trace -> 56 .. 56+64 = 120
57const IA_MAXTRACE: i64 = 64
58const IA_NSV: i64 = 120 // serializable span (IA_O_TRACE + IA_MAXTRACE)
59
60// ---- errors (negative; state is ALWAYS bit-identical on refusal) ----
61const IA_E_DEV: i64 = 0-1
62const IA_E_ACT: i64 = 0-2
63const IA_E_RAW: i64 = 0-3
64const IA_E_DUP: i64 = 0-4
65const IA_E_FULL: i64 = 0-5
66
67const IA_CK_SEED: i64 = 1469598103
68const IA_CK_PRIME: i64 = 16777619
69const IA_CK_MASK: i64 = 4611686018427387903 // 2^62-1: keep the checksum bounded and positive
70
71func ia_bit(i: i64) -> i64 { var v: i64=1; var k: i64=0; while k<i { v=v*2; k=k+1 } return v }
72func ia_hasbit(m: i64, i: i64) -> i64 { let b: i64=ia_bit(i); if (m/b)%2==1 { return 1 } return 0 }
73
74// clear all state and declare how many semantic actions this game uses
75func ia_init(s: *i64, nact: i64) -> i64 {
76 if nact < 1 { return IA_E_ACT }
77 if nact > IA_MAXACT { return IA_E_ACT }
78 var i: i64 = 0
79 while i < IA_NSV { s[i]=0; i=i+1 }
80 s[IA_F_NACT] = nact
81 s[IA_F_CK] = IA_CK_SEED
82 return 0
83}
84
85// raw code currently bound to (dev, act); 0 means unbound
86func ia_binding(s: *i64, dev: i64, act: i64) -> i64 {
87 return s[IA_O_BIND + dev*IA_MAXACT + act]
88}
89
90// Bind a device raw-code to a semantic action. REFUSES a code already bound to a DIFFERENT action on
91// that device (IA_E_DUP) -- ambiguous input can never enter the table. Re-binding the same pair is a
92// no-op success so a binding table is idempotent to apply twice (rule 10).
93func ia_bind(s: *i64, dev: i64, act: i64, raw: i64) -> i64 {
94 if dev < 0 { return IA_E_DEV }
95 if dev >= IA_NDEV { return IA_E_DEV }
96 if act < 0 { return IA_E_ACT }
97 if act >= s[IA_F_NACT] { return IA_E_ACT }
98 if raw == 0 { return IA_E_RAW }
99 var a: i64 = 0
100 while a < s[IA_F_NACT] {
101 if s[IA_O_BIND + dev*IA_MAXACT + a] == raw {
102 if a != act { return IA_E_DUP }
103 }
104 a = a + 1
105 }
106 s[IA_O_BIND + dev*IA_MAXACT + act] = raw
107 if ia_hasbit(s[IA_F_DEVM], dev) == 0 { s[IA_F_DEVM] = s[IA_F_DEVM] + ia_bit(dev) }
108 return 0
109}
110
111// Translate a raw device code into a semantic action WITHOUT touching state.
112// An unbound code yields IA_A_NONE -- a stray code can never fabricate an action.
113func ia_lookup(s: *i64, dev: i64, raw: i64) -> i64 {
114 if dev < 0 { return IA_A_NONE }
115 if dev >= IA_NDEV { return IA_A_NONE }
116 if raw == 0 { return IA_A_NONE }
117 var a: i64 = 0
118 while a < s[IA_F_NACT] {
119 if s[IA_O_BIND + dev*IA_MAXACT + a] == raw { return a }
120 a = a + 1
121 }
122 return IA_A_NONE
123}
124
125// order-sensitive fold: a swapped pair of actions changes the checksum
126func ia_mixck(ck: i64, v: i64) -> i64 {
127 var c: i64 = ck
128 c = (c + v + 1) % IA_CK_MASK
129 c = (c * IA_CK_PRIME) % IA_CK_MASK
130 return c
131}
132
133// Feed a raw device event. Returns the semantic action (or IA_A_NONE). ONLY the action is recorded --
134// the device is deliberately NOT part of the trace or the checksum, which is exactly why two devices
135// producing the same actions produce identical state.
136func ia_feed(s: *i64, dev: i64, raw: i64) -> i64 {
137 let act: i64 = ia_lookup(s, dev, raw)
138 if act == IA_A_NONE { return IA_A_NONE }
139 s[IA_F_NEV] = s[IA_F_NEV] + 1
140 s[IA_F_CK] = ia_mixck(s[IA_F_CK], act)
141 if s[IA_F_NTR] < IA_MAXTRACE {
142 s[IA_O_TRACE + s[IA_F_NTR]] = act
143 s[IA_F_NTR] = s[IA_F_NTR] + 1
144 }
145 return act
146}
147
148func ia_ck(s: *i64) -> i64 { return s[IA_F_CK] }
149func ia_nev(s: *i64) -> i64 { return s[IA_F_NEV] }
150func ia_trace_len(s: *i64) -> i64 { return s[IA_F_NTR] }
151func ia_trace_at(s: *i64, i: i64) -> i64 {
152 if i < 0 { return IA_A_NONE }
153 if i >= s[IA_F_NTR] { return IA_A_NONE }
154 return s[IA_O_TRACE + i]
155}
156
157// How much of the declared action set can this device actually reach, in permil.
158// Below 1000 means a player on that device cannot perform every action = that platform cannot play.
159func ia_coverage(s: *i64, dev: i64) -> i64 {
160 if dev < 0 { return 0 }
161 if dev >= IA_NDEV { return 0 }
162 let n: i64 = s[IA_F_NACT]
163 if n < 1 { return 0 }
164 var bound: i64 = 0
165 var a: i64 = 0
166 while a < n {
167 if s[IA_O_BIND + dev*IA_MAXACT + a] != 0 { bound = bound + 1 }
168 a = a + 1
169 }
170 return (bound*1000)/n
171}
172
173// device bitmask of every device that can reach this action
174func ia_reach(s: *i64, act: i64) -> i64 {
175 var m: i64 = 0
176 var d: i64 = 0
177 while d < IA_NDEV {
178 if s[IA_O_BIND + d*IA_MAXACT + act] != 0 { m = m + ia_bit(d) }
179 d = d + 1
180 }
181 return m
182}
183
184// Overall platform reach in permil: of every (bound device, declared action) pair, how many are bound.
185// This is the part's own honest self-score and the number the board's axis 43 is flipped against.
186func ia_reach_permil(s: *i64) -> i64 {
187 var got: i64 = 0
188 var tot: i64 = 0
189 var d: i64 = 0
190 while d < IA_NDEV {
191 if ia_hasbit(s[IA_F_DEVM], d) == 1 {
192 var a: i64 = 0
193 while a < s[IA_F_NACT] {
194 tot = tot + 1
195 if s[IA_O_BIND + d*IA_MAXACT + a] != 0 { got = got + 1 }
196 a = a + 1
197 }
198 }
199 d = d + 1
200 }
201 if tot < 1 { return 0 }
202 return (got*1000)/tot
203}
204
205// Replay a recorded trace into a fresh state: same actions, same checksum, no device involved.
206func ia_replay(src: *i64, dst: *i64) -> i64 {
207 let r: i64 = ia_init(dst, src[IA_F_NACT])
208 if r != 0 { return r }
209 var i: i64 = 0
210 while i < src[IA_F_NTR] {
211 let act: i64 = src[IA_O_TRACE + i]
212 dst[IA_F_NEV] = dst[IA_F_NEV] + 1
213 dst[IA_F_CK] = ia_mixck(dst[IA_F_CK], act)
214 if dst[IA_F_NTR] < IA_MAXTRACE {
215 dst[IA_O_TRACE + dst[IA_F_NTR]] = act
216 dst[IA_F_NTR] = dst[IA_F_NTR] + 1
217 }
218 i = i + 1
219 }
220 return 0
221}