code wiki / (root) / nx_wasm_breeders.nx

nx_wasm_breeders.nx source

↩ module page · 783 lines · 36197 B

1// nx_wasm_breeders.nx -- NISHI BREEDERS, SOVEREIGN. The whole game -- world, characters, animation, 2// genetics, combat -- is NishiLang emitting pixels into a packed-RGB framebuffer. Nothing outside this 3// file draws anything. The browser's only job is to copy the framebuffer to a canvas and hand back key 4// codes; that shim is the single interop boundary and it holds no game logic. 5// 6// BASE-RELATIVE by construction: every address is `base + offset`, so the identical code runs natively 7// (base = mmap) and in wasm (base = 0). The native run renders the same frames to PNG, which is how the 8// wasm build is verified without a browser -- the established "verify wasm by native PNG" loop. 9// 10// Genetics are the real thing, ported from nx_game_genetics.nx: diploid alleles, Mendel's segregation 11// and independent assortment, incomplete dominance on coat, codominant ABO affinity, recessive wings. 12// license_tier: ORIGINAL 13import "nx_syscalls.nx" 14import "nx_input_abstract.nx" 15const O_MAGIC_374761393: i64 = 374761393 16const O_MAGIC_668265263: i64 = 668265263 17const O_MAGIC_1442695041: i64 = 1442695041 18const O_MAGIC_4611686018427387903: i64 = 4611686018427387903 19const O_MAGIC_1274126177: i64 = 1274126177 20const O_MAGIC_1024: i64 = 1024 21const O_MAGIC_3072: i64 = 3072 22const O_MAGIC_1048576: i64 = 1048576 23const O_MAGIC_88172645463325252: i64 = 88172645463325252 24const O_MAGIC_99999: i64 = 99999 25const O_MAGIC_2654435761: i64 = 2654435761 26const O_MAGIC_1013904223: i64 = 1013904223 27const O_MAGIC_12345: i64 = 12345 28const O_MAGIC_7919: i64 = 7919 29const O_MAGIC_30000: i64 = 30000 30const O_MAGIC_1600: i64 = 1600 31const O_MAGIC_999999999: i64 = 999999999 32const O_MAGIC_3000: i64 = 3000 33const O_MAGIC_1400: i64 = 1400 34const O_MAGIC_9973: i64 = 9973 35const O_MAGIC_20260727: i64 = 20260727 36 37const W: i64 = 480 38const H: i64 = 300 39const O_FB: i64 = 0 // framebuffer W*H i64 packed R|G<<8|B<<16 40const O_ST: i64 = W*H*8 // state block 41const O_WLD: i64 = O_ST + 64*8 // tile map MW*MH i64 42const MW: i64 = 40 43const MH: i64 = 26 44const TS: i64 = 32 45const O_WILD: i64 = O_WLD + MW*MH*8 // wilds: 6 rows x 12 i64 46const NWILD: i64 = 6 47const WSTR: i64 = 12 48const O_PARTY: i64 = O_WILD + NWILD*WSTR*8 // party: 8 rows x 6 i64 49const PSTR: i64 = 6 50const O_INPUT: i64 = O_PARTY + 8*PSTR*8 // nx_input_abstract arena (64 words == IA_WORDS, gate-checked) 51const WORDS: i64 = O_INPUT/8 + 64 + 64 52 53// state slots 54const S_PX:i64=0; const S_PY:i64=1; const S_T:i64=2; const S_HP:i64=3; const S_NP:i64=4 55const S_SEED:i64=5; const S_OVER:i64=6; const S_FACE:i64=7; const S_WALK:i64=8 56const S_MSG:i64=9; const S_MSGT:i64=10; const S_BORN:i64=11; const S_TGT:i64=12 57const S_SHAKE:i64=13; const S_FLASH:i64=14; const S_CHARM:i64=15 58// wild slots 59const V_X:i64=0; const V_Y:i64=1; const V_G:i64=2; const V_POLY:i64=3; const V_HP:i64=4 60const V_ALIVE:i64=5; const V_HX:i64=6; const V_HY:i64=7; const V_LVL:i64=8; const V_FACE:i64=9 61const V_WALK:i64=10; const V_PH:i64=11 62// tiles 63const T_GRASS:i64=0; const T_TREE:i64=1; const T_WATER:i64=2; const T_ROCK:i64=3; const T_DEN:i64=4 64 65func st(base: i64) -> *i64 { return (base + O_ST) as *i64 } 66func fb(base: i64) -> *i64 { return (base + O_FB) as *i64 } 67func wld(base: i64) -> *i64 { return (base + O_WLD) as *i64 } 68func wildp(base: i64, i: i64) -> *i64 { return (base + O_WILD + i*WSTR*8) as *i64 } 69func party(base: i64, i: i64) -> *i64 { return (base + O_PARTY + i*PSTR*8) as *i64 } 70func inp(base: i64) -> *i64 { return (base + O_INPUT) as *i64 } 71 72// ---------- integer helpers (no float anywhere: identical result on x86 and wasm) ---------- 73func iabs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 74func clamp(v: i64, lo: i64, hi: i64) -> i64 { if v<lo {return lo} if v>hi {return hi} return v } 75func imin(a: i64, b: i64) -> i64 { if a<b {return a} return b } 76func isqrt(n: i64) -> i64 { 77 if n <= 0 { return 0 } 78 var x: i64 = n 79 var y: i64 = (x+1)/2 80 var g: i64 = 0 81 while g < 32 { if y < x { x = y; y = (x + n/x)/2 } g = g + 1 } 82 return x 83} 84// hash -> 0..1023 85func hsh(x: i64, y: i64, s: i64) -> i64 { 86 var n: i64 = x*O_MAGIC_374761393 + y*O_MAGIC_668265263 + s*O_MAGIC_1442695041 87 n = n & O_MAGIC_4611686018427387903 88 n = (n ^ (n >> 13)) * O_MAGIC_1274126177 89 n = n & O_MAGIC_4611686018427387903 90 return (n ^ (n >> 16)) % O_MAGIC_1024 91} 92// smooth value noise, fixed point 0..1023 93func vnz(x: i64, y: i64, s: i64, sc: i64) -> i64 { 94 let ix: i64 = x/sc 95 let iy: i64 = y/sc 96 let tx: i64 = ((x % sc)+sc)%sc * O_MAGIC_1024 / sc 97 let ty: i64 = ((y % sc)+sc)%sc * O_MAGIC_1024 / sc 98 let sx: i64 = tx*tx*(O_MAGIC_3072-2*tx)/O_MAGIC_1048576/O_MAGIC_1024*O_MAGIC_1024/O_MAGIC_1024 99 let n00: i64 = hsh(ix,iy,s) 100 let n10: i64 = hsh(ix+1,iy,s) 101 let n01: i64 = hsh(ix,iy+1,s) 102 let n11: i64 = hsh(ix+1,iy+1,s) 103 let a: i64 = n00 + (n10-n00)*tx/O_MAGIC_1024 104 let b: i64 = n01 + (n11-n01)*tx/O_MAGIC_1024 105 return a + (b-a)*ty/O_MAGIC_1024 106} 107 108// ---------- genetics (Mendel, ported exactly) ---------- 109const G_HORN:i64=0; const G_WING:i64=1; const G_COAT:i64=2; const G_ELEM:i64=3; const G_TAIL:i64=4 110func al0(g: i64, t: i64) -> i64 { return (g >> (t*4)) & 3 } 111func al1(g: i64, t: i64) -> i64 { return (g >> (t*4+2)) & 3 } 112func gput(g: i64, t: i64, a: i64, b: i64) -> i64 { 113 var v: i64 = g 114 var m: i64 = 15 << (t*4) 115 v = v - (v & m) 116 v = v | ((a & 3) << (t*4)) 117 v = v | ((b & 3) << (t*4+2)) 118 return v 119} 120func phen(g: i64, t: i64) -> i64 { 121 let a: i64 = al0(g,t) 122 let b: i64 = al1(g,t) 123 if t == G_COAT { return a + b } // incomplete dominance: white/rose/crimson 124 if t == G_ELEM { // codominant ABO 125 var hA: i64 = 0 126 var hB: i64 = 0 127 if a==1 { hA=1 } if b==1 { hA=1 } 128 if a==2 { hB=1 } if b==2 { hB=1 } 129 if hA==1 { if hB==1 { return 3 } return 1 } 130 if hB==1 { return 2 } 131 return 0 132 } 133 if a==1 { return 1 } if b==1 { return 1 } 134 return 0 135} 136func rng(base: i64) -> i64 { // xorshift on the state seed 137 let s: *i64 = st(base) 138 var x: i64 = s[S_SEED] 139 x = x ^ (x << 13); x = x ^ (x >> 7); x = x ^ (x << 17) 140 x = x & O_MAGIC_4611686018427387903 141 if x == 0 { x = O_MAGIC_88172645463325252 } 142 s[S_SEED] = x 143 return x 144} 145func gamete(base: i64, g: i64, t: i64) -> i64 { // Mendel's 1st law 146 if (rng(base) & 1) == 1 { return al1(g,t) } 147 return al0(g,t) 148} 149func gcross(base: i64, ga: i64, gb: i64) -> i64 { // Mendel's 2nd law 150 var c: i64 = 0 151 var t: i64 = 0 152 while t < 5 { c = gput(c, t, gamete(base,ga,t), gamete(base,gb,t)); t = t + 1 } 153 return c 154} 155func founder_g(seed: i64) -> i64 { 156 var g: i64 = 0 157 var t: i64 = 0 158 while t < 5 { 159 var hi: i64 = 2 160 if t == G_ELEM { hi = 3 } 161 g = gput(g, t, hsh(seed,t,7)%hi, hsh(seed,t,11)%hi) 162 t = t + 1 163 } 164 g = gput(g, G_COAT, 0, 1) // founders heterozygous at the prize loci so 165 g = gput(g, G_WING, 1, 0) // 1:2:1 keeps segregating and ww stays reachable 166 return g 167} 168// palette from genome 169func hair_r(g: i64) -> i64 { let c: i64=phen(g,G_COAT); if c==2 {return 196} if c==1 {return 236} return 240 } 170func hair_g(g: i64) -> i64 { let c: i64=phen(g,G_COAT); if c==2 {return 44} if c==1 {return 152} return 236 } 171func hair_b(g: i64) -> i64 { let c: i64=phen(g,G_COAT); if c==2 {return 62} if c==1 {return 176} return 244 } 172func eye_r(g: i64) -> i64 { let e: i64=phen(g,G_ELEM); if e==1 {return 255} if e==2 {return 92} if e==3 {return 214} return 130 } 173func eye_g(g: i64) -> i64 { let e: i64=phen(g,G_ELEM); if e==1 {return 152} if e==2 {return 172} if e==3 {return 128} return 196 } 174func eye_b(g: i64) -> i64 { let e: i64=phen(g,G_ELEM); if e==1 {return 64} if e==2 {return 255} if e==3 {return 255} return 180 } 175 176// ---------- raster primitives (every pixel this game draws goes through these) ---------- 177func pack(r: i64, g: i64, b: i64) -> i64 { 178 return (clamp(r,0,255)) | (clamp(g,0,255) << 8) | (clamp(b,0,255) << 16) 179} 180func px(base: i64, x: i64, y: i64, c: i64) -> i64 { 181 if x<0 {return 0} if y<0 {return 0} if x>=W {return 0} if y>=H {return 0} 182 let f: *i64 = fb(base); f[y*W+x] = c; return 0 183} 184func rect(base: i64, x0: i64, y0: i64, x1: i64, y1: i64, c: i64) -> i64 { 185 var y: i64 = clamp(y0,0,H-1) 186 let ye: i64 = clamp(y1,0,H-1) 187 let xs: i64 = clamp(x0,0,W-1) 188 let xe: i64 = clamp(x1,0,W-1) 189 let f: *i64 = fb(base) 190 while y <= ye { var x: i64 = xs; while x <= xe { f[y*W+x]=c; x=x+1 } y=y+1 } 191 return 0 192} 193// shaded ellipse: light from upper-left, so bodies read as volumes not flat cut-outs 194func ell(base: i64, cx: i64, cy: i64, rx: i64, ry: i64, r: i64, g: i64, b: i64) -> i64 { 195 if rx<=0 {return 0} if ry<=0 {return 0} 196 var y: i64 = 0-ry 197 while y <= ry { 198 var x: i64 = 0-rx 199 while x <= rx { 200 if x*x*1000/(rx*rx) + y*y*1000/(ry*ry) <= 1000 { 201 var lit: i64 = 100 + (0-x)*34/rx + (0-y)*34/ry 202 lit = clamp(lit,62,150) 203 px(base, cx+x, cy+y, pack(r*lit/100, g*lit/100, b*lit/100)) 204 } 205 x = x + 1 206 } 207 y = y + 1 208 } 209 return 0 210} 211// sweep a tapered limb: overlapping discs from (x0,y0) to (x1,y1), radius r0 -> r1. One continuous 212// mass, so a bent knee reads as a bend instead of a seam between two shapes. 213func limb(base: i64, x0: i64, y0: i64, x1: i64, y1: i64, r0: i64, r1: i64, r: i64, g: i64, b: i64) -> i64 { 214 var i: i64 = 0 215 let n: i64 = 14 216 while i <= n { 217 let px2: i64 = x0 + (x1-x0)*i/n 218 let py2: i64 = y0 + (y1-y0)*i/n 219 let rr: i64 = r0 + (r1-r0)*i/n 220 ell(base, px2, py2, rr, rr, r, g, b) 221 i = i + 1 222 } 223 return 0 224} 225func tri(base: i64, x0:i64,y0:i64,x1:i64,y1:i64,x2:i64,y2:i64, c: i64) -> i64 { 226 var ylo: i64 = y0; if y1<ylo {ylo=y1} if y2<ylo {ylo=y2} 227 var yhi: i64 = y0; if y1>yhi {yhi=y1} if y2>yhi {yhi=y2} 228 var y: i64 = clamp(ylo,0,H-1) 229 let ye: i64 = clamp(yhi,0,H-1) 230 while y <= ye { 231 var xlo: i64 = O_MAGIC_99999 232 var xhi: i64 = 0-O_MAGIC_99999 233 // edge scan 234 if (y0<=y) != (y1<=y) { let xa: i64 = x0 + (x1-x0)*(y-y0)/(y1-y0+1); if xa<xlo {xlo=xa} if xa>xhi {xhi=xa} } 235 if (y1<=y) != (y2<=y) { let xb: i64 = x1 + (x2-x1)*(y-y1)/(y2-y1+1); if xb<xlo {xlo=xb} if xb>xhi {xhi=xb} } 236 if (y2<=y) != (y0<=y) { let xc: i64 = x2 + (x0-x2)*(y-y2)/(y0-y2+1); if xc<xlo {xlo=xc} if xc>xhi {xhi=xc} } 237 if xhi >= xlo { var x: i64 = clamp(xlo,0,W-1); let xe: i64 = clamp(xhi,0,W-1) 238 while x <= xe { px(base,x,y,c); x=x+1 } } 239 y = y + 1 240 } 241 return 0 242} 243 244// ---------- world ---------- 245func tile(base: i64, tx: i64, ty: i64) -> i64 { 246 if tx<0 {return T_ROCK} if ty<0 {return T_ROCK} 247 if tx>=MW {return T_ROCK} if ty>=MH {return T_ROCK} 248 let m: *i64 = wld(base); return m[ty*MW+tx] 249} 250func solidt(t: i64) -> i64 { if t==T_TREE {return 1} if t==T_WATER {return 1} if t==T_ROCK {return 1} return 0 } 251func blocked(base: i64, wx: i64, wy: i64) -> i64 { 252 let r: i64 = 8 253 if solidt(tile(base,(wx-r)/TS,(wy-r)/TS))==1 {return 1} 254 if solidt(tile(base,(wx+r)/TS,(wy-r)/TS))==1 {return 1} 255 if solidt(tile(base,(wx-r)/TS,(wy+r)/TS))==1 {return 1} 256 if solidt(tile(base,(wx+r)/TS,(wy+r)/TS))==1 {return 1} 257 return 0 258} 259func genworld(base: i64, seed: i64) -> i64 { 260 let m: *i64 = wld(base) 261 var y: i64 = 0 262 while y < MH { 263 var x: i64 = 0 264 while x < MW { 265 let e: i64 = vnz(x*16,y*16,seed,144)*55/100 + vnz(x*16,y*16,seed+31,72)*30/100 + vnz(x*16,y*16,seed+61,32)*15/100 266 // ★forest density sampled on an ANISOTROPIC lattice with a second decorrelating octave. 267 // Sampling x and y with the SAME stride/scale made the lattice align and the woodland came 268 // out in vertical COLUMNS -- visible in the render, invisible to the tile-mix gate (the 269 // ratios were correct, the ARRANGEMENT was not). A mix can be right and still look wrong. 270 let f: i64 = vnz(x*16,y*23,seed+97,52)*60/100 + vnz(x*29,y*11,seed+131,31)*40/100 271 var t: i64 = T_GRASS 272 if e < 340 { t = T_WATER } 273 if e >= 340 { if e > 800 { t = T_ROCK } } 274 if t == T_GRASS { if f > 600 { t = T_TREE } } 275 if x<1 { t=T_ROCK } if y<1 { t=T_ROCK } 276 if x>=MW-1 { t=T_ROCK } if y>=MH-1 { t=T_ROCK } 277 m[y*MW+x] = t 278 x = x + 1 279 } 280 y = y + 1 281 } 282 var cy: i64 = 2 283 while cy < 8 { var cx: i64 = 2; while cx < 9 { m[cy*MW+cx]=T_GRASS; cx=cx+1 } cy=cy+1 } 284 m[4*MW+5] = T_DEN 285 return 0 286} 287 288// ---------- CHARACTER: anime proportions, drawn entirely in NishiLang ---------- 289// head = 1/5 of stature (the anime canon in nx_scene_contract); widths are the measured fractions 290// biacromial .23 / waist .15 / hip .19 / thigh .10 of stature. 291func drawgirl(base: i64, cx: i64, cy: i64, h: i64, g: i64, t: i64, face: i64, walk: i64) -> i64 { 292 let HD: i64 = h*205/1000 293 let sr: i64 = 232 - (g%3)*8 294 let sg2: i64 = 196 - (g%5)*6 295 let sb: i64 = 178 - (g%7)*4 296 let hr: i64 = hair_r(g) 297 let hg: i64 = hair_g(g) 298 let hb: i64 = hair_b(g) 299 let er: i64 = eye_r(g) 300 let eg: i64 = eye_g(g) 301 let eb: i64 = eye_b(g) 302 let shW: i64 = h*115/1000 303 let wsW: i64 = h*75/1000 304 let hpW: i64 = h*105/1000 305 let shY: i64 = cy - h*700/1000 306 let wsY: i64 = cy - h*500/1000 307 let hpY: i64 = cy - h*420/1000 308 let hdY: i64 = cy - h*855/1000 309 // walk cycle: legs swing, body bobs -- integer sine via a small table 310 var sw: i64 = 0 311 if walk == 1 { sw = ((t*11) % 62) - 31 } // triangle wave, cheap and deterministic 312 let bob: i64 = 0 313 // shadow 314 ell(base, cx, cy, h*115/1000, h*28/1000, 12, 12, 20) 315 // wings (recessive) 316 if phen(g,G_WING) == 0 { 317 ell(base, cx - h*90/1000, cy - h*620/1000, h*48/1000, h*90/1000, 206, 214, 250) 318 ell(base, cx + h*90/1000, cy - h*620/1000, h*48/1000, h*90/1000, 206, 214, 250) 319 } 320 // ★WALK CYCLE: thigh + calf with a knee, striding out of phase. A single straight ellipse per leg 321 // could not read as walking however fast it slid; the knee is what makes a stride legible. 322 let legTop: i64 = cy - h*400/1000 323 let kneeY: i64 = cy - h*200/1000 324 var s1: i64 = sw*h/700 325 var s2: i64 = 0 - s1 326 // ★ONE CONTINUOUS TAPERED LIMB PER LEG. Drawing thigh and calf as two ellipses with different 327 // shading left a visible SEAM, and at 92px the far leg + near leg + skirt hem + feet stacked into 328 // horizontal BANDS -- the figure stopped reading as a body. A limb is swept as overlapping discs 329 // from hip to ankle, radius tapering, so the knee is a BEND and never a joint line. 330 // ⚠STANCE: hpW/4 apart with r=h*30/1000 put the two limbs 2.4px apart with 2.8px radii at 92px -- 331 // they overlapped into ONE COLUMN. Legs need a visible gap: widen the stance, slim the limb. 332 let stance: i64 = hpW*48/100 333 limb(base, cx - stance, legTop, cx - stance + s1, cy - h*14/1000, h*24/1000, h*13/1000, 334 sr*84/100, sg2*84/100, sb*84/100) 335 limb(base, cx + stance, legTop, cx + stance + s2, cy - h*14/1000, h*25/1000, h*14/1000, sr, sg2, sb) 336 ell(base, cx - stance + s1, cy - h*10/1000, h*17/1000, h*10/1000, 58, 44, 62) 337 ell(base, cx + stance + s2, cy - h*10/1000, h*18/1000, h*10/1000, 62, 48, 68) 338 // torso: shoulders -> waist -> hips 339 ell(base, cx, shY + h*60/1000, shW, h*90/1000, sr, sg2, sb) 340 ell(base, cx, wsY, wsW, h*70/1000, sr, sg2, sb) 341 ell(base, cx, hpY, hpW, h*72/1000, sr, sg2, sb) 342 // outfit: bodice + skirt, tinted off the genome so each girl is dressed and distinct 343 let oc_r: i64 = 60 + (g*13)%90 344 let oc_g: i64 = 52 + (g*29)%80 345 let oc_b: i64 = 110 + (g*47)%80 346 ell(base, cx, shY + h*80/1000, shW*94/100, h*80/1000, oc_r, oc_g, oc_b) 347 ell(base, cx, wsY + h*10/1000, wsW*104/100, h*54/1000, oc_r, oc_g, oc_b) 348 ell(base, cx, hpY + h*36/1000, hpW*122/100, h*54/1000, oc_r*8/10, oc_g*8/10, oc_b*8/10) 349 // arms 350 ell(base, cx - shW - h*8/1000 + s2/2, shY + h*100/1000, h*16/1000, h*88/1000, sr*9/10, sg2*9/10, sb*9/10) 351 ell(base, cx + shW + h*8/1000 + s1/2, shY + h*100/1000, h*16/1000, h*88/1000, sr, sg2, sb) 352 // neck 353 ell(base, cx, hdY + HD*52/100, h*17/1000, h*26/1000, sr*88/100, sg2*88/100, sb*88/100) 354 // ★hair BACK MASS, silhouette varies by hairstyle allele so companions are distinguishable at a 355 // glance rather than being one shape in different colours. 356 let hs: i64 = g % 4 357 if hs == 0 { // long fall 358 ell(base, cx, hdY + HD*30/100, HD*66/100, HD*96/100, hr*70/100, hg*70/100, hb*70/100) 359 } 360 if hs == 1 { // twin tails 361 ell(base, cx, hdY + HD*6/100, HD*58/100, HD*70/100, hr*70/100, hg*70/100, hb*70/100) 362 ell(base, cx - HD*72/100, hdY + HD*46/100, HD*22/100, HD*54/100, hr*76/100, hg*76/100, hb*76/100) 363 ell(base, cx + HD*72/100, hdY + HD*46/100, HD*22/100, HD*54/100, hr*76/100, hg*76/100, hb*76/100) 364 } 365 if hs == 2 { // bob 366 ell(base, cx, hdY + HD*12/100, HD*62/100, HD*62/100, hr*70/100, hg*70/100, hb*70/100) 367 } 368 if hs == 3 { // voluminous 369 ell(base, cx, hdY + HD*8/100, HD*74/100, HD*76/100, hr*68/100, hg*68/100, hb*68/100) 370 ell(base, cx - HD*54/100, hdY + HD*30/100, HD*26/100, HD*36/100, hr*74/100, hg*74/100, hb*74/100) 371 ell(base, cx + HD*54/100, hdY + HD*30/100, HD*26/100, HD*36/100, hr*74/100, hg*74/100, hb*74/100) 372 } 373 // head: cranium + a narrower jaw below it, so the face tapers to a chin instead of being a ball 374 ell(base, cx, hdY - HD*6/100, HD*48/100, HD*46/100, sr, sg2, sb) 375 ell(base, cx, hdY + HD*26/100, HD*34/100, HD*30/100, sr, sg2, sb) 376 // horns (simple dominance) 377 if phen(g,G_HORN) == 1 { 378 tri(base, cx-HD*30/100, hdY-HD*30/100, cx-HD*14/100, hdY-HD*95/100, cx-HD*2/100, hdY-HD*30/100, pack(238,230,206)) 379 tri(base, cx+HD*30/100, hdY-HD*30/100, cx+HD*14/100, hdY-HD*95/100, cx+HD*2/100, hdY-HD*30/100, pack(238,230,206)) 380 } 381 // eyes: sclera, iris, pupil, highlight -- large, anime 382 let ew: i64 = HD*19/100 383 let eh: i64 = HD*23/100 384 ell(base, cx-HD*24/100, hdY+HD*8/100, ew, eh, 253, 251, 255) 385 ell(base, cx+HD*24/100, hdY+HD*8/100, ew, eh, 253, 251, 255) 386 ell(base, cx-HD*24/100, hdY+HD*10/100, ew*76/100, eh*78/100, er, eg, eb) 387 ell(base, cx+HD*24/100, hdY+HD*10/100, ew*76/100, eh*78/100, er, eg, eb) 388 ell(base, cx-HD*24/100, hdY+HD*12/100, ew*34/100, eh*42/100, 24, 16, 30) 389 ell(base, cx+HD*24/100, hdY+HD*12/100, ew*34/100, eh*42/100, 24, 16, 30) 390 px(base, cx-HD*30/100, hdY+HD*2/100, pack(255,255,255)) 391 px(base, cx+HD*18/100, hdY+HD*2/100, pack(255,255,255)) 392 ell(base, cx-HD*30/100, hdY+HD*1/100, ew*24/100, eh*22/100, 255,255,255) 393 ell(base, cx+HD*18/100, hdY+HD*1/100, ew*24/100, eh*22/100, 255,255,255) 394 // upper lash line: the single strongest cue that reads a face at small scale 395 rect(base, cx-HD*24/100-ew, hdY+HD*8/100-eh, cx-HD*24/100+ew, hdY+HD*8/100-eh+1, pack(42,24,44)) 396 rect(base, cx+HD*24/100-ew, hdY+HD*8/100-eh, cx+HD*24/100+ew, hdY+HD*8/100-eh+1, pack(42,24,44)) 397 // hair front fringe 398 ell(base, cx, hdY - HD*36/100, HD*54/100, HD*26/100, hr, hg, hb) 399 ell(base, cx - HD*46/100, hdY - HD*4/100, HD*16/100, HD*40/100, hr, hg, hb) 400 ell(base, cx + HD*46/100, hdY - HD*4/100, HD*16/100, HD*40/100, hr, hg, hb) 401 // affinity marks (codominant: AB shows BOTH) 402 let e2: i64 = phen(g,G_ELEM) 403 if e2 == 1 { ell(base, cx-shW*55/100, shY+h*20/1000, h*10/1000, h*10/1000, 255,150,60) } 404 if e2 == 2 { ell(base, cx+shW*55/100, shY+h*20/1000, h*10/1000, h*10/1000, 90,180,255) } 405 if e2 == 3 { ell(base, cx-shW*55/100, shY+h*20/1000, h*10/1000, h*10/1000, 255,150,60) 406 ell(base, cx+shW*55/100, shY+h*20/1000, h*10/1000, h*10/1000, 90,180,255) } 407 return 0 408} 409 410// ---------- init / tick / render : the exported game ---------- 411func init_impl(base: i64, seed: i64) -> i64 { 412 let s: *i64 = st(base) 413 var k: i64 = 0 414 while k < 64 { s[k] = 0; k = k + 1 } 415 s[S_SEED] = seed*O_MAGIC_2654435761 + O_MAGIC_1013904223 416 if s[S_SEED] == 0 { s[S_SEED] = O_MAGIC_12345 } 417 ia_init(inp(base)) 418 s[S_PX] = 5*TS + TS/2 419 s[S_PY] = 6*TS + TS/2 420 s[S_HP] = 100 421 s[S_FACE] = 1 422 s[S_CHARM] = 3 423 genworld(base, seed) 424 var i: i64 = 0 425 while i < NWILD { 426 let v: *i64 = wildp(base, i) 427 // place on open ground, away from the den 428 var tries: i64 = 0 429 var wx: i64 = 0 430 var wy: i64 = 0 431 var ok: i64 = 0 432 while tries < 200 { 433 if ok == 0 { 434 wx = (2 + hsh(i,tries,seed)%(MW-5))*TS + TS/2 435 wy = (2 + hsh(tries,i,seed+5)%(MH-5))*TS + TS/2 436 if blocked(base,wx,wy) == 0 { if iabs(wx-s[S_PX])+iabs(wy-s[S_PY]) > 5*TS { ok = 1 } } 437 } 438 tries = tries + 1 439 } 440 v[V_X]=wx; v[V_Y]=wy; v[V_HX]=wx; v[V_HY]=wy 441 v[V_G]=founder_g(seed+i*O_MAGIC_7919) 442 v[V_POLY]=hsh(i,i,seed)%256 443 v[V_HP]=100; v[V_ALIVE]=1; v[V_FACE]=1; v[V_WALK]=0 444 v[V_LVL]=2 445 if i > 0 { v[V_LVL] = 2 + hsh(i,3,seed)%4 } 446 v[V_PH] = hsh(i,7,seed)%628 447 i = i + 1 448 } 449 return 0 450} 451// dir: 0 up 1 down 2 left 3 right 4 interact 5 breed 452func tick_impl(base: i64, dir: i64) -> i64 { 453 let s: *i64 = st(base) 454 s[S_T] = s[S_T] + 1 455 if s[S_SHAKE] > 0 { s[S_SHAKE] = s[S_SHAKE] - 1 } 456 if s[S_FLASH] > 0 { s[S_FLASH] = s[S_FLASH] - 1 } 457 if s[S_MSGT] > 0 { s[S_MSGT] = s[S_MSGT] - 1 } 458 if s[S_OVER] != 0 { return s[S_OVER] } 459 // player move 460 var dx: i64 = 0 461 var dy: i64 = 0 462 if dir == 0 { dy = 0-1 } 463 if dir == 1 { dy = 1 } 464 if dir == 2 { dx = 0-1; s[S_FACE] = 0-1 } 465 if dir == 3 { dx = 1; s[S_FACE] = 1 } 466 s[S_WALK] = 0 467 if dx != 0 { if blocked(base, s[S_PX]+dx*6, s[S_PY]) == 0 { s[S_PX]=s[S_PX]+dx*6; s[S_WALK]=1 } } 468 if dy != 0 { if blocked(base, s[S_PX], s[S_PY]+dy*6) == 0 { s[S_PY]=s[S_PY]+dy*6; s[S_WALK]=1 } } 469 // interact 470 if dir == 4 { do_interact(base) } 471 if dir == 5 { do_breed(base) } 472 // wilds roam + notice 473 var i: i64 = 0 474 while i < NWILD { 475 let v: *i64 = wildp(base,i) 476 if v[V_ALIVE] == 1 { 477 let ddx: i64 = s[S_PX]-v[V_X] 478 let ddy: i64 = s[S_PY]-v[V_Y] 479 let d2: i64 = ddx*ddx + ddy*ddy 480 v[V_WALK] = 0 481 if d2 < O_MAGIC_30000 { if d2 > O_MAGIC_1600 { // notices you, closes in 482 var mx: i64 = 0 483 var my: i64 = 0 484 if ddx > 2 { mx = 3 } if ddx < 0-2 { mx = 0-3 } 485 if ddy > 2 { my = 3 } if ddy < 0-2 { my = 0-3 } 486 if blocked(base, v[V_X]+mx, v[V_Y]) == 0 { v[V_X]=v[V_X]+mx; v[V_WALK]=1 } 487 if blocked(base, v[V_X], v[V_Y]+my) == 0 { v[V_Y]=v[V_Y]+my; v[V_WALK]=1 } 488 if mx > 0 { v[V_FACE]=1 } if mx < 0 { v[V_FACE]=0-1 } 489 } } 490 if d2 >= O_MAGIC_30000 { // wanders near home 491 v[V_PH] = (v[V_PH] + 7) % 628 492 var wx2: i64 = v[V_HX] + ((v[V_PH]%157)-78)/2 493 var wy2: i64 = v[V_HY] + ((v[V_PH]%97)-48)/2 494 var mx2: i64 = 0 495 var my2: i64 = 0 496 if wx2 > v[V_X]+2 { mx2 = 2 } if wx2 < v[V_X]-2 { mx2 = 0-2 } 497 if wy2 > v[V_Y]+2 { my2 = 2 } if wy2 < v[V_Y]-2 { my2 = 0-2 } 498 if blocked(base, v[V_X]+mx2, v[V_Y]) == 0 { v[V_X]=v[V_X]+mx2; if mx2!=0 {v[V_WALK]=1} } 499 if blocked(base, v[V_X], v[V_Y]+my2) == 0 { v[V_Y]=v[V_Y]+my2; if my2!=0 {v[V_WALK]=1} } 500 } 501 } 502 i = i + 1 503 } 504 return s[S_OVER] 505} 506func nearest_wild(base: i64) -> i64 { 507 let s: *i64 = st(base) 508 var best: i64 = 0-1 509 var bd: i64 = O_MAGIC_999999999 510 var i: i64 = 0 511 while i < NWILD { 512 let v: *i64 = wildp(base,i) 513 if v[V_ALIVE] == 1 { 514 let dx: i64 = v[V_X]-s[S_PX] 515 let dy: i64 = v[V_Y]-s[S_PY] 516 let d: i64 = dx*dx+dy*dy 517 if d < bd { bd = d; best = i } 518 } 519 i = i + 1 520 } 521 if bd > O_MAGIC_3000 { return 0-1 } 522 return best 523} 524func do_interact(base: i64) -> i64 { 525 let s: *i64 = st(base) 526 let idx: i64 = nearest_wild(base) 527 if idx < 0 { s[S_MSG]=1; s[S_MSGT]=90; return 0 } // nothing here 528 let v: *i64 = wildp(base,idx) 529 // duel: her level bites, your party helps. Same shape as nx_breeder_arena. 530 var php: i64 = 62 + s[S_NP]*14 531 let pmax: i64 = php 532 var whp: i64 = v[V_LVL]*17 + v[V_HP]*45/100 533 var guard: i64 = 0 534 while guard < 60 { 535 if php > 0 { if whp > 0 { 536 whp = whp - (6 + (rng(base)%9)) 537 if whp > 0 { php = php - (3 + v[V_LVL]*2 + (rng(base)%4)) } 538 } } 539 guard = guard + 1 540 } 541 s[S_SHAKE] = 6 542 if php <= 0 { 543 s[S_HP] = s[S_HP] - 22 544 s[S_FLASH] = 8 545 v[V_HP] = 100 546 s[S_MSG]=2; s[S_MSGT]=110 547 if s[S_HP] <= 0 { s[S_OVER] = 0-1 } 548 return 0 549 } 550 let margin: i64 = php*1000/pmax 551 var bar: i64 = 350 552 let bsel: i64 = v[V_G] % 6 553 if bsel==1 { bar=450 } if bsel==2 { bar=400 } if bsel==3 { bar=500 } 554 if bsel==4 { bar=300 } if bsel==5 { bar=400 } 555 v[V_HP] = clamp(v[V_HP]*margin/O_MAGIC_1400, 6, 100) 556 if margin >= bar { 557 v[V_ALIVE] = 0 558 if s[S_NP] < 8 { 559 let p: *i64 = party(base, s[S_NP]) 560 p[0]=v[V_G]; p[1]=v[V_POLY]; p[2]=v[V_LVL]; p[3]=1 561 s[S_NP] = s[S_NP] + 1 562 } 563 s[S_MSG]=3; s[S_MSGT]=140 564 } 565 if margin < bar { s[S_MSG]=4; s[S_MSGT]=110 } 566 return 0 567} 568func do_breed(base: i64) -> i64 { 569 let s: *i64 = st(base) 570 if s[S_NP] < 2 { s[S_MSG]=5; s[S_MSGT]=100; return 0 } 571 // must stand at the den 572 let dx: i64 = iabs(s[S_PX] - (5*TS+TS/2)) 573 let dy: i64 = iabs(s[S_PY] - (4*TS+TS/2)) 574 if dx+dy > TS { s[S_MSG]=6; s[S_MSGT]=100; return 0 } 575 let a: *i64 = party(base,0) 576 let b: *i64 = party(base,1) 577 let kid: i64 = gcross(base, a[0], b[0]) 578 if s[S_NP] < 8 { 579 let p: *i64 = party(base, s[S_NP]) 580 p[0]=kid; p[1]=(a[1]+b[1])/2; p[2]=1; p[3]=1 581 s[S_NP] = s[S_NP] + 1 582 } 583 s[S_BORN] = s[S_BORN] + 1 584 s[S_MSG]=7; s[S_MSGT]=170 585 if phen(kid,G_COAT)==2 { s[S_OVER]=1 } 586 if phen(kid,G_WING)==0 { s[S_OVER]=1 } 587 return 0 588} 589 590// ---------- render: the entire frame, drawn in NishiLang ---------- 591func render_impl(base: i64) -> i64 { 592 let s: *i64 = st(base) 593 // camera follows the player, clamped to the world 594 var camx: i64 = s[S_PX] - W/2 595 var camy: i64 = s[S_PY] - H/2 596 camx = clamp(camx, 0, MW*TS - W) 597 camy = clamp(camy, 0, MH*TS - H) 598 if s[S_SHAKE] > 0 { camx = camx + (s[S_SHAKE]%3) - 1; camy = camy + (s[S_SHAKE]%2) } 599 // terrain 600 var ty: i64 = camy/TS 601 while ty <= (camy+H)/TS { 602 var tx: i64 = camx/TS 603 while tx <= (camx+W)/TS { 604 let t: i64 = tile(base,tx,ty) 605 let sx: i64 = tx*TS - camx 606 let sy: i64 = ty*TS - camy 607 let n: i64 = vnz(tx*16,ty*16,s[S_SEED]%O_MAGIC_9973+29,96) 608 // ★per-pixel ground from continuous world-space noise. Flat per-tile fills made the map 609 // read as a checkerboard -- the tile grid was the first thing the eye caught. 610 if t != T_DEN { 611 var qy: i64 = 0 612 while qy < TS { 613 var qx: i64 = 0 614 while qx < TS { 615 let wx2: i64 = (tx*TS+qx) 616 let wy2: i64 = (ty*TS+qy) 617 let g2: i64 = vnz(wx2*4, wy2*4, s[S_SEED]%O_MAGIC_9973+29, 220) 618 var cc: i64 = 0 619 if t == T_WATER { 620 let wv: i64 = vnz(wx2*4+s[S_T]*3, wy2*4, 71, 90) 621 cc = pack(18+wv/40, 52+wv/22, 96+wv/16) 622 } 623 if t == T_ROCK { cc = pack(54+g2/26, 50+g2/28, 66+g2/26) } 624 if t == T_GRASS { cc = pack(26+g2/44, 50+g2/22, 32+g2/44) } 625 if t == T_TREE { cc = pack(22+g2/50, 44+g2/26, 28+g2/50) } 626 px(base, sx+qx, sy+qy, cc) 627 qx = qx + 1 628 } 629 qy = qy + 1 630 } 631 } 632 if t == T_DEN { 633 rect(base,sx,sy,sx+TS-1,sy+TS-1, pack(44,92,62)) 634 rect(base,sx+3,sy+3,sx+TS-4,sy+4, pack(150,240,190)) 635 rect(base,sx+3,sy+TS-5,sx+TS-4,sy+TS-4, pack(150,240,190)) 636 } 637 tx = tx + 1 638 } 639 ty = ty + 1 640 } 641 // ★DEPTH PASS 1: trees whose base sits ABOVE the player draw BEHIND her. 642 // (Pass 2 after the actors draws the nearer trees in FRONT, so you walk into the treeline 643 // instead of sliding over it. One flat pass made every character float on top of the world.) 644 var ty2: i64 = camy/TS 645 while ty2 <= (camy+H)/TS { 646 var tx2: i64 = camx/TS 647 while tx2 <= (camx+W)/TS { 648 var draw_now: i64 = 0 649 if tile(base,tx2,ty2) == T_TREE { if ty2*TS + TS <= s[S_PY] { draw_now = 1 } } 650 if draw_now == 1 { 651 let jx: i64 = (hsh(tx2,ty2,83)%TS) - TS/2 652 let jy: i64 = (hsh(tx2,ty2,89)%(TS/2)) - TS/4 653 let sc: i64 = 70 + hsh(tx2,ty2,101)%50 654 let cx: i64 = tx2*TS - camx + TS/2 + jx/2 655 let cy: i64 = ty2*TS - camy + TS - 4 + jy/2 656 ell(base, cx, cy, TS*30/100*sc/100, TS*9/100*sc/100, 10,10,16) 657 rect(base, cx-2, cy-TS*34/100*sc/100, cx+2, cy, pack(72,50,32)) 658 ell(base, cx, cy-TS*58/100*sc/100, TS*40/100*sc/100, TS*36/100*sc/100, 74, 140, 72) 659 } 660 tx2 = tx2 + 1 661 } 662 ty2 = ty2 + 1 663 } 664 // wilds 665 var i: i64 = 0 666 while i < NWILD { 667 let v: *i64 = wildp(base,i) 668 if v[V_ALIVE] == 1 { 669 let sx: i64 = v[V_X]-camx 670 let sy: i64 = v[V_Y]-camy 671 if sx > 0-60 { if sx < W+60 { if sy > 0-90 { if sy < H+90 { 672 drawgirl(base, sx, sy, 88, v[V_G], s[S_T], v[V_FACE], v[V_WALK]) 673 // health pip when hurt 674 if v[V_HP] < 100 { 675 rect(base, sx-14, sy+4, sx+14, sy+6, pack(30,14,20)) 676 rect(base, sx-14, sy+4, sx-14+28*v[V_HP]/100, sy+6, pack(230,110,130)) 677 } 678 } } } } 679 } 680 i = i + 1 681 } 682 // party trailing behind you 683 var p: i64 = 0 684 while p < s[S_NP] { 685 if p < 3 { 686 let pp: *i64 = party(base,p) 687 let ox: i64 = s[S_PX]-camx - (p+1)*16*s[S_FACE] 688 drawgirl(base, ox, s[S_PY]-camy+4, 74, pp[0], s[S_T]+p*9, s[S_FACE], s[S_WALK]) 689 } 690 p = p + 1 691 } 692 // player 693 drawgirl(base, s[S_PX]-camx, s[S_PY]-camy, 92, 0, s[S_T], s[S_FACE], s[S_WALK]) 694 // ★DEPTH PASS 2: trees whose base is BELOW the player redraw OVER her -- she walks into the treeline 695 var fy3: i64 = camy/TS 696 while fy3 <= (camy+H)/TS { 697 var fx3: i64 = camx/TS 698 while fx3 <= (camx+W)/TS { 699 var front: i64 = 0 700 if tile(base,fx3,fy3) == T_TREE { if fy3*TS + TS > s[S_PY] { front = 1 } } 701 if front == 1 { 702 let jx3: i64 = (hsh(fx3,fy3,83)%TS) - TS/2 703 let jy3: i64 = (hsh(fx3,fy3,89)%(TS/2)) - TS/4 704 let sc3: i64 = 70 + hsh(fx3,fy3,101)%50 705 let cx3: i64 = fx3*TS - camx + TS/2 + jx3/2 706 let cy3: i64 = fy3*TS - camy + TS - 4 + jy3/2 707 ell(base, cx3, cy3, TS*30/100*sc3/100, TS*9/100*sc3/100, 10,10,16) 708 rect(base, cx3-2, cy3-TS*34/100*sc3/100, cx3+2, cy3, pack(72,50,32)) 709 ell(base, cx3, cy3-TS*58/100*sc3/100, TS*40/100*sc3/100, TS*36/100*sc3/100, 74, 140, 72) 710 } 711 fx3 = fx3 + 1 712 } 713 fy3 = fy3 + 1 714 } 715 // hurt flash 716 if s[S_FLASH] > 0 { 717 var fy: i64 = 0 718 while fy < H { var fx2: i64 = 0 719 while fx2 < W { 720 let f2: *i64 = fb(base) 721 let c: i64 = f2[fy*W+fx2] 722 f2[fy*W+fx2] = pack((c&255)*7/10+70, ((c>>8)&255)*6/10, ((c>>16)&255)*6/10) 723 fx2 = fx2 + 3 } 724 fy = fy + 1 } 725 } 726 // HUD: vitality bar, party count, births 727 rect(base, 6, 6, 6+104, 6+10, pack(18,14,30)) 728 rect(base, 7, 7, 7+clamp(s[S_HP],0,100), 6+9, pack(224,86,110)) 729 var q: i64 = 0 730 while q < s[S_NP] { // one pip per bonded companion 731 rect(base, 122+q*9, 7, 122+q*9+6, 13, pack(255,200,110)) 732 q = q + 1 733 } 734 var bq: i64 = 0 735 while bq < s[S_BORN] { 736 rect(base, 122+bq*9, 16, 122+bq*9+6, 20, pack(160,240,190)) 737 bq = bq + 1 738 } 739 // banner on win/lose 740 if s[S_OVER] == 1 { rect(base, W/2-96, H/2-14, W/2+96, H/2+14, pack(24,70,44)) 741 rect(base, W/2-92, H/2-10, W/2+92, H/2-8, pack(150,240,190)) } 742 if s[S_OVER] == 0-1 { rect(base, W/2-96, H/2-14, W/2+96, H/2+14, pack(70,20,30)) 743 rect(base, W/2-92, H/2-10, W/2+92, H/2-8, pack(255,140,150)) } 744 return 0 745} 746 747// input-driven tick: ONE dir derived from the device-agnostic action state. One-shot actions come 748// from the PRESSED edge (interact cannot repeat-fire while held); movement comes from HELD, so the 749// old per-frame tick semantics are preserved exactly -- tick_impl itself is untouched and the gate's 750// golden checksums stay valid. 751func step_impl(base: i64) -> i64 { 752 let q: *i64 = inp(base) 753 let held: i64 = ia_held(q) 754 let pressed: i64 = ia_frame(q) 755 var dir: i64 = 9 756 if (held & IA_UP) != 0 { dir = 0 } 757 if (held & IA_DOWN) != 0 { dir = 1 } 758 if (held & IA_LEFT) != 0 { dir = 2 } 759 if (held & IA_RIGHT) != 0 { dir = 3 } 760 if (pressed & IA_A) != 0 { dir = 4 } 761 if (pressed & IA_B) != 0 { dir = 5 } 762 return tick_impl(base, dir) 763} 764 765// ---------- the exported wasm surface (base = 0 in linear memory) ---------- 766func init() -> i64 { return init_impl(0, O_MAGIC_20260727) } 767func init_seed(sd: i64) -> i64 { return init_impl(0, sd) } 768func tick(dir: i64) -> i64 { return tick_impl(0, dir) } 769func render() -> i64 { return render_impl(0) } 770func fb_off() -> i64 { return O_FB } 771func ww() -> i64 { return W } 772func hh() -> i64 { return H } 773func hp() -> i64 { let s: *i64 = st(0); return s[S_HP] } 774func party_n() -> i64 { let s: *i64 = st(0); return s[S_NP] } 775func born() -> i64 { let s: *i64 = st(0); return s[S_BORN] } 776func over() -> i64 { let s: *i64 = st(0); return s[S_OVER] } 777func msg() -> i64 { let s: *i64 = st(0); if s[S_MSGT] > 0 { return s[S_MSG] } return 0 } 778// device doors (nx_input_abstract): the shim forwards RAW events; meaning lives sovereign-side. 779func key(code: i64, down: i64) -> i64 { return ia_key(inp(0), code, down) } 780func padb(btn: i64, down: i64) -> i64 { return ia_pad(inp(0), btn, down) } 781func paxis(idx: i64, milli: i64) -> i64 { return ia_axis(inp(0), idx, milli) } 782func touch(tx: i64, ty: i64, down: i64) -> i64 { return ia_touch(inp(0), tx, ty, W, H, down) } 783func step() -> i64 { return step_impl(0) }