code wiki / _hdl_build / nx_breeder_arena.nx
nx_breeder_arena.nx source
↩ module page · 273 lines · 11105 B
1// nx_breeder_arena.nx -- THE COMPOSED PLAYABLE LOOP (ws=game-interact rung 4): Palworld's shape from
2// our certified parts, nothing else. One tick of the world is:
3// player moves (nx_game_actor: collision + bump) -> each wild THINKS (nx_game_agent: patrol/LOS/
4// A*-chase/attack/flee) -> contact resolves as a DUEL (nx_game_breed -> nx_creature_battle) ->
5// a crushing win DOMINATES -> she SUBMITS -> brd_capture (exact genes, nx_game_companion roster) ->
6// two companions at the DEN -> brd_breed -> BIRTH (provable inheritance) = the WIN.
7// Lose a duel and the player BLEEDS (BA_LOSS_HP); at zero the run is LOST -- genre-honest, losable.
8// The engine is the sovereign truth the JS last-mile must byte-match (frontier/drift doctrine).
9// All tuning numbers are named DATA consts here, never inline. LIB, no main. license_tier: ORIGINAL
10import "nx_syscalls.nx"
11import "nx_game_agent.nx"
12import "nx_game_breed.nx"
13
14// ---- arena tuning (DATA; the "why this number" ledger) ----
15const BA_W: i64 = 24
16const BA_H: i64 = 16
17const BA_MAXW: i64 = 6 // wild population cap
18const BA_TICKS: i64 = 400 // run length cap
19const BA_PLAYER_HP: i64 = 30 // three bad duels end the run (12+12+12 > 30)
20const BA_LOSS_HP: i64 = 12 // cost of losing a duel
21const BA_DUEL_CD: i64 = 6 // ticks before the same wild duels again
22const BA_DEN_X: i64 = 3
23const BA_DEN_Y: i64 = 3
24const BA_NEED_CAPTURES: i64 = 2
25
26// ---- game state arena (i64 words), all offsets named ----
27// [0]=tick [1]=outcome(0 running 1 WIN -1 LOSE 2 timeout) [2]=captures [3]=duels [4]=births
28// [5]=chain ck [6]=player actor hnd [7]=starter roster hnd [8]=child vec (on win)
29// wilds: 6 rows stride 4 at 16: [actor_hnd, wildros_hnd, cooldown_until, alive]
30const BA_O_WILD: i64 = 16
31const BA_WSTRIDE: i64 = 4
32func ba_words() -> i64 { return BA_O_WILD + BA_MAXW*BA_WSTRIDE }
33
34func ba_world_build(wd: *i64) -> i64 {
35 wd_init(wd, BA_W, BA_H)
36 var i: i64 = 0
37 while i < BA_W { wd_set_wall(wd, i, 0, 1); wd_set_wall(wd, i, BA_H - 1, 1); i = i + 1 }
38 var j: i64 = 0
39 while j < BA_H { wd_set_wall(wd, 0, j, 1); wd_set_wall(wd, BA_W - 1, j, 1); j = j + 1 }
40 // cover: two wall clusters that force real routing (and give LOS shadows to hide in)
41 var c: i64 = 8
42 while c <= 11 { wd_set_wall(wd, c, 6, 1); wd_set_wall(wd, 16, c - 3, 1); c = c + 1 }
43 wd_set_wall(wd, 9, 10, 1); wd_set_wall(wd, 10, 10, 1)
44 return 0
45}
46
47// deterministic wild population from a seed: species/level/genes all derived
48func ba_seed_wilds(wd: *i64, ar: *i64, rosW: *i64, G: *i64, seed: i64) -> i64 {
49 var n: i64 = 0
50 var s: i64 = seed
51 let px: *i64 = sys_mmap(8 * 8) as *i64
52 px[0]=18; px[1]=5; px[2]=20; px[3]=12; px[4]=6; px[5]=12; px[6]=13; px[7]=3
53 var w: i64 = 0
54 while w < 4 {
55 s = s * 2654435761 + 40503
56 var r: i64 = s
57 if r < 0 { r = 0 - r }
58 let sp: i64 = r % 6
59 let lvl: i64 = 2 + (r / 7) % 4
60 let rh: i64 = en_spawn(rosW)
61 en_set(rosW, rh, C_SPEC, sp)
62 en_set(rosW, rh, C_IV0, (r/11) % 32); en_set(rosW, rh, C_IV1, (r/353) % 32)
63 en_set(rosW, rh, C_IV2, (r/11317) % 32); en_set(rosW, rh, C_IV3, (r/362249) % 32)
64 en_set(rosW, rh, C_XP, 0); en_set(rosW, rh, C_LVL, lvl)
65 en_set(rosW, rh, C_NICK, w)
66 let ah: i64 = ac_spawn(wd, ar, px[w*2], px[w*2+1], 1, 20, sp % 3, lvl, s)
67 G[BA_O_WILD + w*BA_WSTRIDE + 0] = ah
68 G[BA_O_WILD + w*BA_WSTRIDE + 1] = rh
69 G[BA_O_WILD + w*BA_WSTRIDE + 2] = 0
70 G[BA_O_WILD + w*BA_WSTRIDE + 3] = 1
71 n = n + 1
72 w = w + 1
73 }
74 return n
75}
76
77// nearest living wild to the player (deterministic tie-break: lowest index). -1 if none.
78func ba_nearest_wild(ar: *i64, G: *i64) -> i64 {
79 let hp2: i64 = G[6]
80 var best: i64 = 0-1
81 var bestd: i64 = 999999
82 var w: i64 = 0
83 while w < BA_MAXW {
84 if G[BA_O_WILD + w*BA_WSTRIDE + 3] == 1 {
85 let ah: i64 = G[BA_O_WILD + w*BA_WSTRIDE + 0]
86 if en_valid(ar, ah) == 1 {
87 let d: i64 = ac_dist2(ar, hp2, ah)
88 if d < bestd { bestd = d; best = w }
89 }
90 }
91 w = w + 1
92 }
93 return best
94}
95func ba_wild_by_actor(G: *i64, ah: i64) -> i64 {
96 var w: i64 = 0
97 while w < BA_MAXW {
98 if G[BA_O_WILD + w*BA_WSTRIDE + 3] == 1 {
99 if G[BA_O_WILD + w*BA_WSTRIDE + 0] == ah { return w }
100 }
101 w = w + 1
102 }
103 return 0-1
104}
105
106// resolve one duel between the player's STARTER and wild w. Returns 1 if the wild submitted+was captured.
107func ba_duel_wild(wd: *i64, ar: *i64, rosP: *i64, rosW: *i64, G: *i64, w: i64, tbl: *i64,
108 buf: *u8, bt: *i64, btcap: i64, out: *i64) -> i64 {
109 let tick: i64 = G[0]
110 let ah: i64 = G[BA_O_WILD + w*BA_WSTRIDE + 0]
111 let rh: i64 = G[BA_O_WILD + w*BA_WSTRIDE + 1]
112 G[3] = G[3] + 1
113 let seed: i64 = 90001 + tick * 131 + w * 17
114 let win: i64 = brd_duel(rosP, G[7], rosW, rh, tbl, seed, buf, bt, btcap, tick, out)
115 G[BA_O_WILD + w*BA_WSTRIDE + 2] = tick + BA_DUEL_CD
116 if win == 0 {
117 if out[1] == 1 {
118 // SUBMITTED: she leaves the field and joins the roster, genes intact
119 brd_capture(rosP, rosW, rh)
120 ac_remove(wd, ar, ah)
121 G[BA_O_WILD + w*BA_WSTRIDE + 3] = 0
122 G[2] = G[2] + 1
123 return 1
124 }
125 // RESISTED: she breaks away hurt -- the brain's own flee threshold takes over
126 let mhp: i64 = en_get(ar, ah, A_MAXHP)
127 var newhp: i64 = mhp * out[2] / 1000
128 if newhp < 1 { newhp = 1 }
129 en_set(ar, ah, A_HP, newhp)
130 return 0
131 }
132 // the player LOST: bleed; the wild recovers her nerve at full field HP
133 en_set(ar, G[6], A_HP, en_get(ar, G[6], A_HP) - BA_LOSS_HP)
134 en_set(ar, ah, A_HP, en_get(ar, ah, A_MAXHP))
135 return 0
136}
137
138// fold the world+rosters into the run chain (bit-exact replay proof)
139func ba_fold(G: *i64, wd: *i64, ar: *i64, rosP: *i64) -> i64 {
140 var ck: i64 = G[5]
141 ck = ((ck ^ ac_ck(wd, ar)) * 1099511) & 4611686018427387903
142 ck = ((ck ^ (en_count(rosP) * 977 + G[2] * 31 + G[3] * 7)) * 1099511) & 4611686018427387903
143 G[5] = ck
144 return ck
145}
146
147// ---- ONE WORLD TICK. policy: 0 = HUNT-AND-BREED (the winnable line), 1 = RECKLESS (duel on cd, no retreat) ----
148func ba_tick(wd: *i64, ar: *i64, rosP: *i64, rosW: *i64, G: *i64, tbl: *i64, scr: *i64,
149 ev: *i64, evcap: i64, bt: *i64, btcap: i64, buf: *u8, policy: i64, out: *i64) -> i64 {
150 if G[1] != 0 { return G[1] }
151 let tick: i64 = G[0]
152 let hp2: i64 = G[6]
153
154 // -- player move: toward the den when ready to breed, else toward the nearest wild
155 var tx: i64 = BA_DEN_X
156 var ty: i64 = BA_DEN_Y
157 var hunting: i64 = 1
158 if G[2] >= BA_NEED_CAPTURES { hunting = 0 }
159 if hunting == 1 {
160 let nw: i64 = ba_nearest_wild(ar, G)
161 if nw >= 0 {
162 let nah: i64 = G[BA_O_WILD + nw*BA_WSTRIDE + 0]
163 tx = en_get(ar, nah, A_X)
164 ty = en_get(ar, nah, A_Y)
165 }
166 }
167 let prc: i64 = ag_step_astar(wd, ar, hp2, tx, ty, scr, out)
168 if prc == AC_BUMP {
169 let bw: i64 = ba_wild_by_actor(G, out[0])
170 if bw >= 0 {
171 if tick >= G[BA_O_WILD + bw*BA_WSTRIDE + 2] {
172 ba_duel_wild(wd, ar, rosP, rosW, G, bw, tbl, buf, bt, btcap, out)
173 }
174 }
175 }
176
177 // -- wild brains
178 var w: i64 = 0
179 while w < BA_MAXW {
180 if G[BA_O_WILD + w*BA_WSTRIDE + 3] == 1 {
181 let ah: i64 = G[BA_O_WILD + w*BA_WSTRIDE + 0]
182 if en_valid(ar, ah) == 1 {
183 let st: i64 = ag_tick(wd, ar, ah, hp2, tbl, scr, ev, evcap, tick, out)
184 if st == AG_ATTACK {
185 if tick >= G[BA_O_WILD + w*BA_WSTRIDE + 2] {
186 var engage: i64 = 1
187 if policy == 0 { if en_get(ar, hp2, A_HP) <= BA_LOSS_HP { engage = 0 } }
188 if engage == 1 { ba_duel_wild(wd, ar, rosP, rosW, G, w, tbl, buf, bt, btcap, out) }
189 }
190 }
191 }
192 }
193 w = w + 1
194 }
195
196 // -- den: breed the two newest captures (starter is parent A: your bond, her line)
197 if G[1] == 0 { if G[2] >= BA_NEED_CAPTURES {
198 let px2: i64 = en_get(ar, hp2, A_X)
199 let py2: i64 = en_get(ar, hp2, A_Y)
200 var atden: i64 = 0
201 var ddx: i64 = px2 - BA_DEN_X
202 if ddx < 0 { ddx = 0 - ddx }
203 var ddy: i64 = py2 - BA_DEN_Y
204 if ddy < 0 { ddy = 0 - ddy }
205 if ddx + ddy <= 1 { atden = 1 }
206 if atden == 1 {
207 let n: i64 = en_count(rosP)
208 let pa: i64 = en_nth(rosP, n - 2)
209 let pb: i64 = en_nth(rosP, n - 1)
210 let ch: i64 = brd_breed(rosP, pa, pb, 1, 60601 + G[0], bt, btcap, G[0])
211 if ch > 0 {
212 G[4] = G[4] + 1
213 G[8] = comp_vec(rosP, ch)
214 G[1] = 1
215 }
216 }
217 } }
218
219 // -- defeat / timeout
220 if en_get(ar, hp2, A_HP) <= 0 { G[1] = 0-1 }
221 G[0] = tick + 1
222 if G[0] >= BA_TICKS { if G[1] == 0 { G[1] = 2 } }
223 ba_fold(G, wd, ar, rosP)
224 return G[1]
225}
226
227// ---- run a whole game. Returns outcome; G retains the full story (captures/duels/chain/child). ----
228func ba_run(seed: i64, policy: i64, G: *i64, bt: *i64, btcap: i64, ev: *i64, evcap: i64) -> i64 {
229 let wd: *i64 = sys_mmap(wd_bytes(BA_W, BA_H)) as *i64
230 ba_world_build(wd)
231 let ar: *i64 = sys_mmap(en_bytes(32, AC_NC)) as *i64
232 en_init(ar, 32, AC_NC)
233 let rosP: *i64 = sys_mmap(en_bytes(64, COMP_NC)) as *i64
234 en_init(rosP, 64, COMP_NC)
235 let rosW: *i64 = sys_mmap(en_bytes(64, COMP_NC)) as *i64
236 en_init(rosW, 64, COMP_NC)
237 let tbl: *i64 = sys_mmap(64 * 8) as *i64
238 ag_species_default(tbl)
239 let btbl: *i64 = sys_mmap(64 * 8) as *i64
240 brd_species_default(btbl)
241 let scr: *i64 = sys_mmap(ag_scratch_words(BA_W, BA_H) * 8) as *i64
242 let buf: *u8 = sys_mmap(65536) as *u8
243 let out: *i64 = sys_mmap(64) as *i64
244 var gi: i64 = 0
245 while gi < ba_words() { G[gi] = 0; gi = gi + 1 }
246 G[5] = 1469598103
247 // the player + a decent (not champion) starter: capturable wilds must still take 2-3 tries
248 G[6] = ac_spawn(wd, ar, 12, 13, 0, BA_PLAYER_HP, 0, 5, seed)
249 let sh: i64 = en_spawn(rosP)
250 en_set(rosP, sh, C_SPEC, 1)
251 en_set(rosP, sh, C_IV0, 22); en_set(rosP, sh, C_IV1, 24)
252 en_set(rosP, sh, C_IV2, 20); en_set(rosP, sh, C_IV3, 23)
253 en_set(rosP, sh, C_XP, 0); en_set(rosP, sh, C_LVL, 6)
254 en_set(rosP, sh, C_NICK, 0)
255 G[7] = sh
256 ba_seed_wilds(wd, ar, rosW, G, seed)
257 if policy == 1 {
258 // reckless: the runt fights the field -- losable by construction
259 en_set(rosP, sh, C_IV0, 2); en_set(rosP, sh, C_IV1, 2)
260 en_set(rosP, sh, C_IV2, 2); en_set(rosP, sh, C_IV3, 2)
261 en_set(rosP, sh, C_LVL, 1)
262 }
263 var t: i64 = 0
264 var done: i64 = 0
265 while t < BA_TICKS {
266 if done == 0 {
267 let rc: i64 = ba_tick(wd, ar, rosP, rosW, G, tbl, scr, ev, evcap, bt, btcap, buf, policy, out)
268 if rc != 0 { done = 1 }
269 }
270 t = t + 1
271 }
272 return G[1]
273}