code wiki / _hdl_build / nx_game_breed.nx

nx_game_breed.nx source

↩ module page · 249 lines · 13231 B

1// nx_game_breed.nx -- the BREEDERS mechanic (ws=game-interact rung 3; operator-named first target; 2// the Palworld capture loop reshaped: weaken -> WIN -> DOMINATE -> the defeated one SUBMITS and joins 3// you -> BREED -> unique offspring). Composes the certified parts and adds ONLY the missing verbs: 4// nx_creature_battle = the fight (deterministic, typed, transcripted) 5// nx_game_companion = the ownership substrate (unique genes, investment, persistence) 6// nx_rpgstats = stat derivation (saturating, order-independent) 7// DESIGN LAWS HONORED: 8// - DOMINANCE IS EARNED, NOT ROLLED: the margin is computed from the battle's real outcome (winner's 9// remaining HP + level advantage). A scraped-by win does not dominate; a crushing one does. 10// - SUBMISSION IS THE LOSER'S CHOICE, DATA-DRIVEN: each species carries a submit_bar (permil) -- a 11// proud species must be beaten decisively. Rows are DATA; tuning never touches logic. 12// - THE ONE YOU DOMINATED IS THE ONE YOU OWN: capture copies the EXACT gene vector (species+4 IVs), 13// never re-rolls -- the endowment law from nx_game_companion carried through. 14// - INHERITANCE IS REAL: every offspring gene comes from a parent's same slot +/- a bounded mutation 15// (BRD_MUT), provable in-gate. No inheritance = no breeding game, just a slot machine. 16// - SCENE BEATS ARE DATA: the engine emits (tick,beat,who,with) rows -- WIN/DOMINATE/SUBMIT/MATE/ 17// BIRTH/DEFEAT. The presentation layer (VN/renderer) turns beats into scenes; adult content lives 18// in content packs, never in mechanics. (Beat ring idiom shared with nx_game_agent events; extract 19// to a common lib on the third consumer per migrate-on-touch.) 20// All integer, deterministic. LIB, no main. license_tier: ORIGINAL 21import "nx_syscalls.nx" 22import "nx_game_companion.nx" 23import "nx_creature_battle.nx" 24// WIRING, NOT NEW CODE (2026-07-31). gx_breed_modern and GX_REC are declared in nx_game_genetics.nx and 25// were unreachable from here. This file is the chokepoint for nx_game_breed_gate, nx_breeder_arena and 26// nx_breeder_arena_gate, so the one line closes all four -- migrate the CHOKEPOINT LIB, not the leaf. 27import "nx_game_genetics.nx" 28 29// ---- species battle+temperament rows: DATA, stride 8 ---- 30// [btype, hp_base, atk_base, def_base, spd_base, submit_bar_permil, pow_primary, pow_normal] 31const BRD_SP_STRIDE: i64 = 8 32func brd_species_default(tbl: *i64) -> i64 { 33 // 0 Sylph (Grass) gentle: submits at 350 34 tbl[0]=3; tbl[1]=34; tbl[2]=10; tbl[3]=8; tbl[4]=12; tbl[5]=350; tbl[6]=40; tbl[7]=30 35 // 1 Flamia (Fire) fierce: 450 36 tbl[8]=1; tbl[9]=30; tbl[10]=13; tbl[11]=7; tbl[12]=11; tbl[13]=450; tbl[14]=45; tbl[15]=30 37 // 2 Undine (Water) calm: 400 38 tbl[16]=2; tbl[17]=36; tbl[18]=9; tbl[19]=10; tbl[20]=9; tbl[21]=400; tbl[22]=40; tbl[23]=30 39 // 3 Voltra (Electric) proud: 500 40 tbl[24]=4; tbl[25]=28; tbl[26]=12; tbl[27]=7; tbl[28]=14; tbl[29]=500; tbl[30]=45; tbl[31]=25 41 // 4 Terra (Rock) stoic: 300 42 tbl[32]=5; tbl[33]=40; tbl[34]=11; tbl[35]=12; tbl[36]=6; tbl[37]=300; tbl[38]=40; tbl[39]=35 43 // 5 Luna (Normal) even: 400 44 tbl[40]=0; tbl[41]=32; tbl[42]=10; tbl[43]=9; tbl[44]=10; tbl[45]=400; tbl[46]=40; tbl[47]=40 45 return 6 46} 47func brd_sp_type(tbl: *i64, s: i64) -> i64 { return tbl[s*BRD_SP_STRIDE + 0] } 48func brd_sp_submitbar(tbl: *i64, s: i64) -> i64 { return tbl[s*BRD_SP_STRIDE + 5] } 49 50// ---- scene-beat ring: [0]=count, rows stride 4 = [tick, beat, actor_vec, partner_vec] ---- 51const BRD_BEAT_STRIDE: i64 = 4 52const BEAT_WIN: i64 = 1 53const BEAT_DEFEAT: i64 = 2 // the player lost (losable, by design) 54const BEAT_DOMINATE: i64 = 3 // margin cleared the bar 55const BEAT_SUBMIT: i64 = 4 // the beaten one yields and joins 56const BEAT_RESIST: i64 = 5 // won but NOT dominantly; she breaks away 57const BEAT_MATE: i64 = 6 58const BEAT_BIRTH: i64 = 7 59func brd_beats(bt: *i64) -> i64 { return bt[0] } 60func brd_beat_push(bt: *i64, cap: i64, tick: i64, kind: i64, a: i64, b: i64) -> i64 { 61 let n: i64 = bt[0] 62 if n >= cap { return 0-1 } 63 bt[1 + n*BRD_BEAT_STRIDE + 0] = tick 64 bt[1 + n*BRD_BEAT_STRIDE + 1] = kind 65 bt[1 + n*BRD_BEAT_STRIDE + 2] = a 66 bt[1 + n*BRD_BEAT_STRIDE + 3] = b 67 bt[0] = n + 1 68 return n 69} 70func brd_beat_kind(bt: *i64, i: i64) -> i64 { return bt[1 + i*BRD_BEAT_STRIDE + 1] } 71 72// ---- bridge: a roster companion -> a battle creature (i64[24], cb_ layout) ---- 73// stats derive from genes via nx_rpgstats: hp from IV0 (constitution role), atk IV1, def IV2, spd IV3. 74func brd_to_creature(ros: *i64, h: i64, tbl: *i64, c: *i64, name: *u8) -> i64 { 75 let sp: i64 = en_get(ros, h, C_SPEC) 76 let lvl: i64 = en_get(ros, h, C_LVL) 77 let r: i64 = sp * BRD_SP_STRIDE 78 let hp: i64 = rs_derived_hp(en_get(ros, h, C_IV0), lvl, tbl[r+1], 1, 2) 79 let atk: i64 = rs_sadd(rs_sadd(tbl[r+2], en_get(ros, h, C_IV1)), lvl) 80 let def: i64 = rs_sadd(rs_sadd(tbl[r+3], en_get(ros, h, C_IV2)), lvl) 81 let spd: i64 = rs_sadd(rs_sadd(tbl[r+4], en_get(ros, h, C_IV3)), lvl) 82 cb_set(c, tbl[r+0], lvl, hp, atk, def, spd, name) 83 cb_addmove(c, tbl[r+0], tbl[r+6], 90) 84 cb_addmove(c, 0, tbl[r+7], 100) 85 return 0 86} 87 88// ---- dominance margin from the battle's REAL outcome (never rolled) ---- 89// margin = winner remaining-HP permil + 50 per level of advantage (advantage clamped 0..5) 90func brd_margin(win_hp: i64, win_maxhp: i64, win_lvl: i64, lose_lvl: i64) -> i64 { 91 var hpp: i64 = 0 92 if win_maxhp > 0 { hpp = win_hp * 1000 / win_maxhp } 93 let adv: i64 = rs_clamp(win_lvl - lose_lvl, 0, 5) 94 return hpp + adv * 50 95} 96 97// ---- the duel: A (player's) vs B (the wild). Returns winner 0=A 1=B. 98// out[0]=margin out[1]=submitted(0/1) out[2]=winner_hp out[3]=turns(from transcript len proxy: 0) 99func brd_duel(rosA: *i64, hA: i64, rosB: *i64, hB: i64, tbl: *i64, seed: i64, 100 buf: *u8, bt: *i64, btcap: i64, tick: i64, out: *i64) -> i64 { 101 let cA: *i64 = sys_mmap(24*8) as *i64 102 let cB: *i64 = sys_mmap(24*8) as *i64 103 brd_to_creature(rosA, hA, tbl, cA, "Yours" as *u8) 104 brd_to_creature(rosB, hB, tbl, cB, "Wild" as *u8) 105 let win: i64 = cb_battle(cA, cB, seed, buf) 106 var wc: *i64 = cA 107 var lc: *i64 = cB 108 var lros: *i64 = rosB 109 var lh: i64 = hB 110 if win == 1 { wc = cB; lc = cA; lros = rosA; lh = hA } 111 let m: i64 = brd_margin(wc[3], wc[2], wc[1], lc[1]) 112 out[0] = m 113 out[2] = wc[3] 114 var submitted: i64 = 0 115 if win == 0 { 116 brd_beat_push(bt, btcap, tick, BEAT_WIN, comp_vec(rosA, hA), comp_vec(rosB, hB)) 117 let bar: i64 = brd_sp_submitbar(tbl, en_get(lros, lh, C_SPEC)) 118 if m >= bar { 119 brd_beat_push(bt, btcap, tick, BEAT_DOMINATE, comp_vec(rosA, hA), comp_vec(rosB, hB)) 120 brd_beat_push(bt, btcap, tick, BEAT_SUBMIT, comp_vec(rosB, hB), comp_vec(rosA, hA)) 121 submitted = 1 122 } 123 if m < bar { brd_beat_push(bt, btcap, tick, BEAT_RESIST, comp_vec(rosB, hB), comp_vec(rosA, hA)) } 124 } 125 if win == 1 { brd_beat_push(bt, btcap, tick, BEAT_DEFEAT, comp_vec(rosA, hA), comp_vec(rosB, hB)) } 126 out[1] = submitted 127 return win 128} 129 130// ---- capture: the submitted one JOINS -- exact genes, never re-rolled ---- 131// returns the new handle in dst (or -1 full). Removes her from the wild roster. 132func brd_capture(dst: *i64, src: *i64, h: i64) -> i64 { 133 let nh: i64 = en_spawn(dst) 134 if nh == EN_NULL { return 0-1 } 135 en_set(dst, nh, C_SPEC, en_get(src, h, C_SPEC)) 136 en_set(dst, nh, C_IV0, en_get(src, h, C_IV0)) 137 en_set(dst, nh, C_IV1, en_get(src, h, C_IV1)) 138 en_set(dst, nh, C_IV2, en_get(src, h, C_IV2)) 139 en_set(dst, nh, C_IV3, en_get(src, h, C_IV3)) 140 en_set(dst, nh, C_XP, en_get(src, h, C_XP)) 141 en_set(dst, nh, C_LVL, en_get(src, h, C_LVL)) 142 en_set(dst, nh, C_NICK, en_count(dst) - 1) 143 // her GENOME and her BOND state come with her -- capture transfers a creature, it does not mint a 144 // new one. (The taming path that captured her is what sets trust afterward; capture itself is honest.) 145 en_set(dst, nh, C_GENO, en_get(src, h, C_GENO)) 146 en_set(dst, nh, C_SEXG, en_get(src, h, C_SEXG)) 147 en_set(dst, nh, C_POLY, en_get(src, h, C_POLY)) 148 en_set(dst, nh, C_EPI, en_get(src, h, C_EPI)) 149 en_set(dst, nh, C_TRUST, en_get(src, h, C_TRUST)) 150 en_set(dst, nh, C_WILL, en_get(src, h, C_WILL)) 151 en_set(dst, nh, C_DISP, en_get(src, h, C_DISP)) 152 en_destroy(src, h) 153 return nh 154} 155 156// ---- breeding: two parents -> one offspring, REAL inheritance ---- 157const BRD_MUT: i64 = 2 // max mutation distance per gene (the provable bound) 158func brd_rng(s: *i64) -> i64 { 159 var x: i64 = s[0] 160 x = x ^ (x << 13); x = x ^ (x >> 7); x = x ^ (x << 17) 161 s[0] = x 162 if x < 0 { return 0 - x } 163 return x 164} 165// child gene: pick a parent's same-slot gene, mutate within +/-BRD_MUT, clamp to the IV range 166func brd_gene(ga: i64, gb: i64, s: *i64) -> i64 { 167 var g: i64 = ga 168 if (brd_rng(s) & 1) == 1 { g = gb } 169 let mu: i64 = (brd_rng(s) % (BRD_MUT*2 + 1)) - BRD_MUT 170 return rs_clamp(g + mu, 0, 31) 171} 172// species: same -> same; cross -> the DOMINANT parent's line carries (domA=1 means A dominated) 173func brd_cross_species(spA: i64, spB: i64, domA: i64) -> i64 { 174 if spA == spB { return spA } 175 if domA == 1 { return spA } 176 return spB 177} 178// breed A x B into the roster. Emits MATE + BIRTH beats. Returns the child's handle or -1. 179// ★TWO INHERITANCE LAYERS, both real biology (2026-07-27): 180// QUANTITATIVE (the IV stat vector) -- blended-with-mutation, the polygenic model: many small effects 181// summing to a continuous trait. This is why stats drift toward the parental mean. 182// MENDELIAN (the genome word) -- discrete alleles segregating per gx_breed_modern: independent 183// assortment, linkage+recombination on the HORN/TAIL pair, ZW sex determination, epigenetic marks 184// inherited-but-fading, rare permanent mutation. This is why VISIBLE traits jump in 3:1 ratios. 185// A real organism has both, so the game teaches both. info out: [0]=recombinations [1]=mutated 186// [2]=mutated_trait [3]=child sex. 187func brd_breed_info(ros: *i64, hA: i64, hB: i64, domA: i64, seed: i64, 188 bt: *i64, btcap: i64, tick: i64, info: *i64) -> i64 { 189 if en_valid(ros, hA) == 0 { return 0-1 } 190 if en_valid(ros, hB) == 0 { return 0-1 } 191 let s: *i64 = sys_mmap(16) as *i64 192 s[0] = seed * 2654435761 + comp_vec(ros, hA) * 40503 + comp_vec(ros, hB) * 69061 + 1 193 let ch: i64 = en_spawn(ros) 194 if ch == EN_NULL { return 0-1 } 195 brd_beat_push(bt, btcap, tick, BEAT_MATE, comp_vec(ros, hA), comp_vec(ros, hB)) 196 en_set(ros, ch, C_SPEC, brd_cross_species(en_get(ros, hA, C_SPEC), en_get(ros, hB, C_SPEC), domA)) 197 en_set(ros, ch, C_IV0, brd_gene(en_get(ros, hA, C_IV0), en_get(ros, hB, C_IV0), s)) 198 en_set(ros, ch, C_IV1, brd_gene(en_get(ros, hA, C_IV1), en_get(ros, hB, C_IV1), s)) 199 en_set(ros, ch, C_IV2, brd_gene(en_get(ros, hA, C_IV2), en_get(ros, hB, C_IV2), s)) 200 en_set(ros, ch, C_IV3, brd_gene(en_get(ros, hA, C_IV3), en_get(ros, hB, C_IV3), s)) 201 en_set(ros, ch, C_XP, 0) 202 en_set(ros, ch, C_LVL, 1) 203 en_set(ros, ch, C_NICK, en_count(ros) - 1) 204 // ---- the Mendelian half: pack both parents into gx records and run the modern cross ---- 205 let pA: *i64 = sys_mmap(GX_REC * 8) as *i64 206 let pB: *i64 = sys_mmap(GX_REC * 8) as *i64 207 let kid: *i64 = sys_mmap(GX_REC * 8) as *i64 208 pA[0] = en_get(ros, hA, C_GENO); pA[1] = en_get(ros, hA, C_SEXG) 209 pA[2] = en_get(ros, hA, C_POLY); pA[3] = en_get(ros, hA, C_EPI) 210 pB[0] = en_get(ros, hB, C_GENO); pB[1] = en_get(ros, hB, C_SEXG) 211 pB[2] = en_get(ros, hB, C_POLY); pB[3] = en_get(ros, hB, C_EPI) 212 gx_breed_modern(pA, pB, s, kid, info) 213 en_set(ros, ch, C_GENO, kid[0]) 214 en_set(ros, ch, C_SEXG, kid[1]) 215 en_set(ros, ch, C_POLY, kid[2]) 216 en_set(ros, ch, C_EPI, kid[3]) 217 // a child born to companions you already hold starts BONDED, not wild -- she was raised by you. 218 en_set(ros, ch, C_TRUST, 600) 219 en_set(ros, ch, C_WILL, 300) 220 en_set(ros, ch, C_DISP, 700) 221 brd_beat_push(bt, btcap, tick, BEAT_BIRTH, comp_vec(ros, ch), 0) 222 return ch 223} 224// thin wrapper: the original 8-arg signature every existing caller uses stays valid (add a verb, never 225// widen -- NishiLang has no call-arity check on old call sites that were compiled against the old shape). 226func brd_breed(ros: *i64, hA: i64, hB: i64, domA: i64, seed: i64, 227 bt: *i64, btcap: i64, tick: i64) -> i64 { 228 let info: *i64 = sys_mmap(8 * 8) as *i64 229 return brd_breed_info(ros, hA, hB, domA, seed, bt, btcap, tick, info) 230} 231 232// inheritance audit: 1 if EVERY gene of child is within BRD_MUT of the same slot of SOME parent 233func brd_inherit_ok(ros: *i64, ch: i64, hA: i64, hB: i64) -> i64 { 234 var k: i64 = 0 235 while k < 4 { 236 let comp: i64 = C_IV0 + k 237 let cg: i64 = en_get(ros, ch, comp) 238 var da: i64 = cg - en_get(ros, hA, comp) 239 if da < 0 { da = 0 - da } 240 var db: i64 = cg - en_get(ros, hB, comp) 241 if db < 0 { db = 0 - db } 242 var near: i64 = 0 243 if da <= BRD_MUT { near = 1 } 244 if db <= BRD_MUT { near = 1 } 245 if near == 0 { return 0 } 246 k = k + 1 247 } 248 return 1 249}