code wiki / _hdl_build / nx_game_genetics.nx
nx_game_genetics.nx source
↩ module page · 479 lines · 21009 B
1// nx_game_genetics.nx -- REAL MENDELIAN GENETICS as the breeding core (ws=game-interact rung 3b;
2// operator: "use Mendelian genetics so we can really reuse systems, fun not overwhelming... educational
3// while entertaining, teach real world science").
4//
5// THE SCIENCE (this is textbook genetics, implemented honestly -- not a "genetics-flavoured" rng):
6// * DIPLOID genome: every trait carries TWO alleles, one from each parent.
7// * MENDEL'S 1st LAW (SEGREGATION): a parent passes ONE randomly-chosen allele of its pair (a gamete).
8// * MENDEL'S 2nd LAW (INDEPENDENT ASSORTMENT): each trait segregates independently of the others.
9// * The famous emergent ratios are therefore NOT hardcoded -- they FALL OUT: monohybrid Aa x Aa gives
10// genotypes 1:2:1 and phenotypes 3:1; a dihybrid cross gives 9:3:3:1; a test cross gives 1:1.
11// The gate MEASURES them. That is the difference between teaching science and cosplaying it.
12// * Three real inheritance MODES, each the textbook exemplar:
13// SIMPLE dominant/recessive (pea plants; horned/hornless here)
14// INCOMPLETE blending heterozygote (snapdragon flower colour: red x white -> PINK, and F2 is
15// 1:2:1 PHENOTYPES -- the fact that distinguishes it from simple dominance)
16// CODOM multiple alleles + codominance (human ABO blood groups: A and B both expressed -> AB)
17// * CARRIERS are real and visible-to-the-game: a heterozygote showing the dominant phenotype still
18// carries the recessive allele -- the whole reason selective breeding is interesting.
19//
20// FUN, NOT OVERWHELMING (the design constraint): FIVE visible traits, each with an obvious readable
21// phenotype. The player never sees a genotype string unless they ask; the Punnett square (gx_punnett)
22// is the ONE teaching widget -- 4 cells, the real thing, computed not illustrated.
23//
24// Genome packs into ONE i64: trait t occupies 4 bits (two 2-bit alleles) => 5 traits = 20 bits, and it
25// rides the existing companion component word, so nothing else in the parts library changes shape.
26// All integer, deterministic. LIB, no main. license_tier: ORIGINAL
27import "nx_syscalls.nx"
28
29// ---- traits (DATA-indexed; adding a trait = adding a row, never editing logic) ----
30const GX_NTRAIT: i64 = 5
31const GX_HORN: i64 = 0 // SIMPLE H dominant (horned) / h recessive (hornless)
32const GX_WING: i64 = 1 // SIMPLE W dominant (wingless) / w recessive (winged -- rare, prized)
33const GX_COAT: i64 = 2 // INCOMPLETE crimson / rose (blend) / white
34const GX_ELEM: i64 = 3 // CODOM ABO-style: A, B codominant, O recessive
35const GX_TAIL: i64 = 4 // SIMPLE T dominant (long) / t recessive (short)
36
37const GX_SIMPLE: i64 = 0
38const GX_INCOMPLETE: i64 = 1
39const GX_CODOM: i64 = 2
40
41func gx_mode(t: i64) -> i64 {
42 if t == GX_COAT { return GX_INCOMPLETE }
43 if t == GX_ELEM { return GX_CODOM }
44 return GX_SIMPLE
45}
46
47// ---- genome word access: trait t -> bits [t*4 .. t*4+3], allele0 low 2 bits, allele1 next 2 ----
48func gx_a0(g: i64, t: i64) -> i64 { return (g >> (t*4)) & 3 }
49func gx_a1(g: i64, t: i64) -> i64 { return (g >> (t*4 + 2)) & 3 }
50func gx_set(g: i64, t: i64, a: i64, b: i64) -> i64 {
51 let mask: i64 = 15 << (t*4)
52 var v: i64 = g & (0 - 1 - mask)
53 v = v | ((a & 3) << (t*4))
54 v = v | ((b & 3) << (t*4 + 2))
55 return v
56}
57// homozygous / heterozygous -- the vocabulary the game teaches by using it
58func gx_homozygous(g: i64, t: i64) -> i64 { if gx_a0(g, t) == gx_a1(g, t) { return 1 } return 0 }
59func gx_heterozygous(g: i64, t: i64) -> i64 { if gx_a0(g, t) != gx_a1(g, t) { return 1 } return 0 }
60
61// ---- PHENOTYPE: what you actually SEE. The mode decides how the pair is expressed. ----
62// SIMPLE: allele 1 = dominant. phenotype 1 if either allele is 1, else 0.
63// INCOMPLETE: 0/0 -> 0 (white), 0/1 or 1/0 -> 1 (rose blend), 1/1 -> 2 (crimson).
64// CODOM: alleles 0=O 1=A 2=B. phenotypes 0=O 1=A 2=B 3=AB.
65func gx_phenotype(g: i64, t: i64) -> i64 {
66 let a: i64 = gx_a0(g, t)
67 let b: i64 = gx_a1(g, t)
68 let m: i64 = gx_mode(t)
69 if m == GX_SIMPLE {
70 if a == 1 { return 1 }
71 if b == 1 { return 1 }
72 return 0
73 }
74 if m == GX_INCOMPLETE {
75 return a + b
76 }
77 // CODOM (ABO)
78 var hasA: i64 = 0
79 var hasB: i64 = 0
80 if a == 1 { hasA = 1 }
81 if b == 1 { hasA = 1 }
82 if a == 2 { hasB = 1 }
83 if b == 2 { hasB = 1 }
84 if hasA == 1 { if hasB == 1 { return 3 } }
85 if hasA == 1 { return 1 }
86 if hasB == 1 { return 2 }
87 return 0
88}
89
90// a CARRIER shows the dominant phenotype but hides a recessive allele (SIMPLE traits only)
91func gx_carrier(g: i64, t: i64) -> i64 {
92 if gx_mode(t) != GX_SIMPLE { return 0 }
93 if gx_phenotype(g, t) != 1 { return 0 }
94 return gx_heterozygous(g, t)
95}
96
97// ---- names (the teaching surface; the UI reads these, never raw numbers) ----
98func gx_trait_name(t: i64) -> *u8 {
99 if t == GX_HORN { return "Horns" as *u8 }
100 if t == GX_WING { return "Wings" as *u8 }
101 if t == GX_COAT { return "Coat" as *u8 }
102 if t == GX_ELEM { return "Affinity" as *u8 }
103 return "Tail"
104}
105func gx_mode_name(t: i64) -> *u8 {
106 let m: i64 = gx_mode(t)
107 if m == GX_INCOMPLETE { return "incomplete dominance" as *u8 }
108 if m == GX_CODOM { return "codominant, multiple alleles" as *u8 }
109 return "simple dominance"
110}
111func gx_pheno_name(t: i64, ph: i64) -> *u8 {
112 if t == GX_HORN { if ph == 1 { return "horned" as *u8 } return "hornless" }
113 if t == GX_WING { if ph == 1 { return "wingless" as *u8 } return "WINGED" }
114 if t == GX_COAT {
115 if ph == 2 { return "crimson" as *u8 }
116 if ph == 1 { return "rose" as *u8 }
117 return "white"
118 }
119 if t == GX_ELEM {
120 if ph == 3 { return "dual-affinity (AB)" as *u8 }
121 if ph == 2 { return "storm (B)" as *u8 }
122 if ph == 1 { return "ember (A)" as *u8 }
123 return "null (O)"
124 }
125 if ph == 1 { return "long tail" as *u8 }
126 return "short tail"
127}
128// allele letter for a genotype readout, e.g. "Hh" -- real notation, the way a textbook writes it
129func gx_allele_char(t: i64, a: i64) -> *u8 {
130 if t == GX_ELEM {
131 if a == 1 { return "A" as *u8 }
132 if a == 2 { return "B" as *u8 }
133 return "O"
134 }
135 if t == GX_COAT {
136 if a == 1 { return "R" as *u8 }
137 return "W"
138 }
139 if t == GX_HORN { if a == 1 { return "H" as *u8 } return "h" }
140 if t == GX_WING { if a == 1 { return "W" as *u8 } return "w" }
141 if a == 1 { return "T" as *u8 }
142 return "t"
143}
144
145// ---- rng (caller-owned stream => deterministic, replayable) ----
146func gx_rng(s: *i64) -> i64 {
147 var x: i64 = s[0]
148 x = x ^ (x << 13); x = x ^ (x >> 7); x = x ^ (x << 17)
149 s[0] = x
150 if x < 0 { return 0 - x }
151 return x
152}
153
154// ---- MENDEL'S 1st LAW: a gamete carries ONE allele of the pair, chosen 50/50 ----
155func gx_gamete(g: i64, t: i64, s: *i64) -> i64 {
156 if (gx_rng(s) & 1) == 1 { return gx_a1(g, t) }
157 return gx_a0(g, t)
158}
159
160// ---- MENDEL'S 2nd LAW: every trait assorts independently -> the child genome ----
161func gx_cross(gA: i64, gB: i64, s: *i64) -> i64 {
162 var child: i64 = 0
163 var t: i64 = 0
164 while t < GX_NTRAIT {
165 let fromA: i64 = gx_gamete(gA, t, s)
166 let fromB: i64 = gx_gamete(gB, t, s)
167 child = gx_set(child, t, fromA, fromB)
168 t = t + 1
169 }
170 return child
171}
172
173// ---- THE PUNNETT SQUARE: the teaching widget, computed not drawn from a lookup ----
174// Fills out[0..3] with the four possible child genotype pairs (packed a*4+b, in the classic
175// row-major order A0xB0, A0xB1, A1xB0, A1xB1) and out[4..7] with each cell's PHENOTYPE.
176// Returns the number of DISTINCT phenotypes among the four cells (1..4) -- what the player learns.
177func gx_punnett(gA: i64, gB: i64, t: i64, out: *i64) -> i64 {
178 let a0: i64 = gx_a0(gA, t)
179 let a1: i64 = gx_a1(gA, t)
180 let b0: i64 = gx_a0(gB, t)
181 let b1: i64 = gx_a1(gB, t)
182 out[0] = a0*4 + b0
183 out[1] = a0*4 + b1
184 out[2] = a1*4 + b0
185 out[3] = a1*4 + b1
186 var i: i64 = 0
187 while i < 4 {
188 let pa: i64 = out[i] / 4
189 let pb: i64 = out[i] % 4
190 out[4 + i] = gx_phenotype(gx_set(0, t, pa, pb), t)
191 i = i + 1
192 }
193 var distinct: i64 = 0
194 var j: i64 = 0
195 while j < 4 {
196 var seen: i64 = 0
197 var k: i64 = 0
198 while k < j { if out[4 + k] == out[4 + j] { seen = 1 } k = k + 1 }
199 if seen == 0 { distinct = distinct + 1 }
200 j = j + 1
201 }
202 return distinct
203}
204// probability (in permil) that this pairing yields phenotype `ph` for trait t -- the Punnett square
205// read as odds, which is exactly how a breeder plans a pairing.
206func gx_odds_permil(gA: i64, gB: i64, t: i64, ph: i64) -> i64 {
207 let sq: *i64 = sys_mmap(16 * 8) as *i64
208 gx_punnett(gA, gB, t, sq)
209 var hits: i64 = 0
210 var i: i64 = 0
211 while i < 4 { if sq[4 + i] == ph { hits = hits + 1 } i = i + 1 }
212 return hits * 250
213}
214
215// ---- founder genomes: a starting population with real allele diversity (so breeding has somewhere
216// to go). Homozygous-dominant founders would make the recessive traits unreachable = a dead game.
217func gx_founder(seed: i64) -> i64 {
218 let s: *i64 = sys_mmap(16) as *i64
219 s[0] = seed * 2654435761 + 12345
220 var g: i64 = 0
221 var t: i64 = 0
222 while t < GX_NTRAIT {
223 var hi: i64 = 2
224 if t == GX_ELEM { hi = 3 }
225 let a: i64 = gx_rng(s) % hi
226 let b: i64 = gx_rng(s) % hi
227 g = gx_set(g, t, a, b)
228 t = t + 1
229 }
230 return g
231}
232
233// a compact "how rare is this creature" score in permil: recessive/rare phenotypes are worth more.
234// Winged (recessive), crimson (homozygous), and AB (needs both alleles) are the prizes -- so the
235// player learns WHY they are rare by having to breed for them.
236func gx_rarity_permil(g: i64) -> i64 {
237 var r: i64 = 0
238 if gx_phenotype(g, GX_WING) == 0 { r = r + 400 } // winged: needs ww
239 if gx_phenotype(g, GX_COAT) == 2 { r = r + 250 } // crimson: needs RR
240 if gx_phenotype(g, GX_ELEM) == 3 { r = r + 250 } // AB: needs A and B
241 if gx_phenotype(g, GX_HORN) == 0 { r = r + 50 }
242 if gx_phenotype(g, GX_TAIL) == 0 { r = r + 50 }
243 return r
244}
245
246// ===================================================================================================
247// MODERN GENETICS LAYER -- the phenomena that BREAK naive Mendelism, which is exactly why they teach
248// it. Each one is a real, named mechanism with the textbook behaviour, and each has a gate tooth that
249// MEASURES the deviation rather than asserting it. (operator: "modern with epigenetics and all the
250// other things so its fun but fundamentals are definitely learned")
251// ===================================================================================================
252
253// ---- 1. SEX DETERMINATION + SEX-LINKED INHERITANCE (the ZW system: birds/reptiles/butterflies) ----
254// We use ZW (female ZW, male ZZ) rather than XY on purpose: in ZW it is the MOTHER who determines
255// offspring sex, which corrects the common misconception that it is always the father.
256// The sex-linked trait sits on the Z: a male (ZZ) has two copies, a female (ZW) has ONE -- so a
257// female shows a recessive Z allele with no second copy to mask it (HEMIZYGOUS). That asymmetry is
258// the classic sex-linkage signature (colour-blindness in humans, barring in chickens).
259const GX_SEX_F: i64 = 0
260const GX_SEX_M: i64 = 1
261// sex + the Z-linked allele(s) pack into their own word: bit0 = sex, bits1-2 = Z allele A,
262// bits3-4 = Z allele B (males only; females carry W = no allele)
263func gx_sex(sg: i64) -> i64 { return sg & 1 }
264func gx_z0(sg: i64) -> i64 { return (sg >> 1) & 3 }
265func gx_z1(sg: i64) -> i64 { return (sg >> 3) & 3 }
266func gx_sexgene(sex: i64, z0: i64, z1: i64) -> i64 { return (sex & 1) | ((z0 & 3) << 1) | ((z1 & 3) << 3) }
267// the Z-linked trait: 1 = dominant (plain), 0 = recessive (LUMINOUS -- the prize)
268func gx_z_phenotype(sg: i64) -> i64 {
269 if gx_sex(sg) == GX_SEX_M {
270 if gx_z0(sg) == 1 { return 1 }
271 if gx_z1(sg) == 1 { return 1 }
272 return 0
273 }
274 // female: hemizygous, the single Z allele is expressed with nothing to mask it
275 if gx_z0(sg) == 1 { return 1 }
276 return 0
277}
278// a MALE heterozygote (Z^1 Z^0) is a carrier; a female CANNOT be a carrier -- she just shows it.
279func gx_z_carrier(sg: i64) -> i64 {
280 if gx_sex(sg) != GX_SEX_M { return 0 }
281 if gx_z0(sg) == gx_z1(sg) { return 0 }
282 return 1
283}
284// ZW cross: mother (ZW) gives Z or W; father (ZZ) always gives a Z. W from mother => daughter.
285func gx_cross_sex(sgMother: i64, sgFather: i64, s: *i64) -> i64 {
286 let fromFather: i64 = gx_z0(sgFather)
287 var ff: i64 = fromFather
288 if (gx_rng(s) & 1) == 1 { ff = gx_z1(sgFather) }
289 if (gx_rng(s) & 1) == 1 {
290 // mother passed her Z -> son (ZZ), one Z from each parent
291 return gx_sexgene(GX_SEX_M, gx_z0(sgMother), ff)
292 }
293 // mother passed W -> daughter (ZW), her single Z comes from her FATHER
294 return gx_sexgene(GX_SEX_F, ff, 0)
295}
296
297// ---- 2. GENETIC LINKAGE + RECOMBINATION (Morgan/Sturtevant: genes on the same chromosome) ----
298// Mendel's 2nd law assumes independent assortment -- true only for genes on DIFFERENT chromosomes.
299// Linked genes travel together UNLESS crossing-over separates them. Recombination frequency is the
300// map distance (1% recombination = 1 centimorgan) -- how the first gene maps were ever drawn.
301// HORN and TAIL are linked here at 12 cM: they co-inherit 88% of the time.
302const GX_LINK_A: i64 = 0 // = GX_HORN
303const GX_LINK_B: i64 = 4 // = GX_TAIL
304const GX_LINK_CM: i64 = 120 // recombination frequency in per-mille (120 = 12 cM)
305// a linked gamete: pick a haplotype (which parental chromosome), then allow crossing-over
306func gx_gamete_linked(g: i64, s: *i64, out: *i64) -> i64 {
307 var side: i64 = 0
308 if (gx_rng(s) & 1) == 1 { side = 1 }
309 var aA: i64 = gx_a0(g, GX_LINK_A)
310 var aB: i64 = gx_a0(g, GX_LINK_B)
311 if side == 1 { aA = gx_a1(g, GX_LINK_A); aB = gx_a1(g, GX_LINK_B) }
312 var recombined: i64 = 0
313 if (gx_rng(s) % 1000) < GX_LINK_CM {
314 // crossing-over: the B locus comes from the OTHER chromosome
315 recombined = 1
316 if side == 1 { aB = gx_a0(g, GX_LINK_B) }
317 if side == 0 { aB = gx_a1(g, GX_LINK_B) }
318 }
319 out[0] = aA
320 out[1] = aB
321 return recombined
322}
323// full cross honouring linkage for the linked pair and independent assortment for everything else
324func gx_cross_linked(gA: i64, gB: i64, s: *i64, info: *i64) -> i64 {
325 let ga: *i64 = sys_mmap(8 * 8) as *i64
326 let gb: *i64 = sys_mmap(8 * 8) as *i64
327 let rA: i64 = gx_gamete_linked(gA, s, ga)
328 let rB: i64 = gx_gamete_linked(gB, s, gb)
329 info[0] = rA + rB // how many recombinant gametes this birth used
330 var child: i64 = 0
331 child = gx_set(child, GX_LINK_A, ga[0], gb[0])
332 child = gx_set(child, GX_LINK_B, ga[1], gb[1])
333 var t: i64 = 0
334 while t < GX_NTRAIT {
335 var linked: i64 = 0
336 if t == GX_LINK_A { linked = 1 }
337 if t == GX_LINK_B { linked = 1 }
338 if linked == 0 { child = gx_set(child, t, gx_gamete(gA, t, s), gx_gamete(gB, t, s)) }
339 t = t + 1
340 }
341 return child
342}
343
344// ---- 3. POLYGENIC TRAIT (quantitative genetics: many genes -> a continuous bell-curve trait) ----
345// Size is not one gene. Four additive loci (0/1 each, both alleles counted) give a dose 0..8, which
346// is the discrete version of the normal distribution real quantitative traits follow. This is why
347// height is not "tall vs short" -- and why selective breeding SHIFTS A MEAN rather than flipping a switch.
348const GX_POLY_N: i64 = 4
349func gx_poly_dose(pg: i64) -> i64 {
350 var d: i64 = 0
351 var i: i64 = 0
352 while i < GX_POLY_N * 2 { d = d + ((pg >> i) & 1); i = i + 1 }
353 return d
354}
355func gx_poly_gamete(pg: i64, s: *i64) -> i64 {
356 var out: i64 = 0
357 var i: i64 = 0
358 while i < GX_POLY_N {
359 var a: i64 = (pg >> (i*2)) & 1
360 if (gx_rng(s) & 1) == 1 { a = (pg >> (i*2 + 1)) & 1 }
361 out = out | (a << i)
362 i = i + 1
363 }
364 return out
365}
366func gx_poly_cross(pgA: i64, pgB: i64, s: *i64) -> i64 {
367 let ha: i64 = gx_poly_gamete(pgA, s)
368 let hb: i64 = gx_poly_gamete(pgB, s)
369 var child: i64 = 0
370 var i: i64 = 0
371 while i < GX_POLY_N {
372 child = child | (((ha >> i) & 1) << (i*2))
373 child = child | (((hb >> i) & 1) << (i*2 + 1))
374 i = i + 1
375 }
376 return child
377}
378func gx_size_name(dose: i64) -> *u8 {
379 if dose <= 1 { return "tiny" as *u8 }
380 if dose <= 3 { return "small" as *u8 }
381 if dose <= 5 { return "average" as *u8 }
382 if dose <= 7 { return "large" as *u8 }
383 return "colossal"
384}
385
386// ---- 4. EPIGENETICS (the modern layer: heritable expression change WITHOUT DNA sequence change) ----
387// Real mechanisms, honestly modelled:
388// * METHYLATION SILENCES a gene: the allele is still there and still passed on, but not expressed.
389// (Agouti mice: identical DNA, diet-driven methylation, visibly different coats.)
390// * The mark is acquired from the ENVIRONMENT the parent lived in -- not from its genes.
391// * It is INHERITED but REVERSIBLE and it FADES across generations (transgenerational epigenetic
392// inheritance decays; this is the key difference from a mutation, which is permanent).
393// * THE FUNDAMENTAL THE PLAYER MUST LEARN: a silenced dominant allele still SEGREGATES normally.
394// Breed from a silenced parent and the allele reappears in grandchildren -- genotype is untouched.
395// Epigenome word: bits 0-4 = methylation mark per trait, bits 8-12 = mark "strength" (fades).
396const GX_EPI_FADE: i64 = 1
397func gx_epi_marked(eg: i64, t: i64) -> i64 { return (eg >> t) & 1 }
398func gx_epi_strength(eg: i64, t: i64) -> i64 { return (eg >> (8 + t*3)) & 7 }
399func gx_epi_set(eg: i64, t: i64, strength: i64) -> i64 {
400 var v: i64 = eg
401 if strength > 0 { v = v | (1 << t) }
402 if strength <= 0 { v = v & (0 - 1 - (1 << t)) }
403 let mask: i64 = 7 << (8 + t*3)
404 v = v & (0 - 1 - mask)
405 var st: i64 = strength
406 if st > 7 { st = 7 }
407 if st < 0 { st = 0 }
408 v = v | (st << (8 + t*3))
409 return v
410}
411// environment imprints a mark (e.g. a lush vs harsh den) -- acquired, not inherited from genes
412func gx_epi_imprint(eg: i64, t: i64, env_intensity: i64) -> i64 {
413 return gx_epi_set(eg, t, env_intensity)
414}
415// EXPRESSED phenotype = genotype phenotype, unless silenced -> the recessive/base phenotype shows
416func gx_expressed(g: i64, eg: i64, t: i64) -> i64 {
417 if gx_epi_marked(eg, t) == 0 { return gx_phenotype(g, t) }
418 if gx_mode(t) == GX_SIMPLE { return 0 } // dominant allele silenced -> recessive look
419 if gx_mode(t) == GX_INCOMPLETE {
420 let ph: i64 = gx_phenotype(g, t)
421 if ph > 0 { return ph - 1 } // one step toward white
422 return 0
423 }
424 return 0 // codominant silenced -> null (O)
425}
426// inheritance of the marks: passed on but WEAKER each generation, and gone at zero (reversibility)
427func gx_epi_inherit(egA: i64, egB: i64) -> i64 {
428 var child: i64 = 0
429 var t: i64 = 0
430 while t < GX_NTRAIT {
431 var st: i64 = gx_epi_strength(egA, t)
432 let sb: i64 = gx_epi_strength(egB, t)
433 if sb > st { st = sb }
434 st = st - GX_EPI_FADE
435 if st < 0 { st = 0 }
436 child = gx_epi_set(child, t, st)
437 t = t + 1
438 }
439 return child
440}
441
442// ---- 5. MUTATION (rare, permanent, the ultimate source of all the variation above) ----
443// Distinguished from an epigenetic mark BY CONSTRUCTION: this edits the genome word itself, so it
444// does not fade and it is passed on forever. Rate is per-birth in per-mille.
445const GX_MUT_PERMIL: i64 = 8
446func gx_mutate(g: i64, s: *i64, out: *i64) -> i64 {
447 out[0] = 0
448 if (gx_rng(s) % 1000) >= GX_MUT_PERMIL { return g }
449 let t: i64 = gx_rng(s) % GX_NTRAIT
450 var hi: i64 = 2
451 if t == GX_ELEM { hi = 3 }
452 let newa: i64 = gx_rng(s) % hi
453 out[0] = 1
454 out[1] = t
455 if (gx_rng(s) & 1) == 1 { return gx_set(g, t, gx_a0(g, t), newa) }
456 return gx_set(g, t, newa, gx_a1(g, t))
457}
458
459// ---- THE FULL MODERN CROSS: one call, every mechanism, deterministic. ----
460// Creature record (caller-held, stride 4): [genome, sexgene, polygene, epigenome]
461// info out: [0]=recombinations [1]=mutated(0/1) [2]=mutated_trait [3]=child sex
462const GX_REC: i64 = 4
463func gx_breed_modern(pA: *i64, pB: *i64, s: *i64, child: *i64, info: *i64) -> i64 {
464 // mother = whichever parent is female; ZW means SHE decides the child's sex
465 var mother: *i64 = pA
466 var father: *i64 = pB
467 if gx_sex(pA[1]) == GX_SEX_M { mother = pB; father = pA }
468 let mut_out: *i64 = sys_mmap(8 * 8) as *i64
469 var g: i64 = gx_cross_linked(pA[0], pB[0], s, info)
470 g = gx_mutate(g, s, mut_out)
471 child[0] = g
472 child[1] = gx_cross_sex(mother[1], father[1], s)
473 child[2] = gx_poly_cross(pA[2], pB[2], s)
474 child[3] = gx_epi_inherit(pA[3], pB[3])
475 info[1] = mut_out[0]
476 info[2] = mut_out[1]
477 info[3] = gx_sex(child[1])
478 return 0
479}