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}