GEN-3a: de.ts warning texts (SLS_LETHAL/SUPERSCHECKE_DEAF) + genetics tests (Uw alias, Sls het/lethal, Superschecke, flag tolerance, Schimmel fix)

This commit is contained in:
2026-06-06 10:25:48 +02:00
parent a0b720df73
commit 7aac59f3e5
2 changed files with 84 additions and 1 deletions

View File

@@ -15,6 +15,7 @@ import {
toJSON, toJSON,
fromJSON, fromJSON,
wildType, wildType,
extractGenotypeFlags,
} from '../genotype' } from '../genotype'
import { combineLocus } from '../punnett' import { combineLocus } from '../punnett'
import { LOCI, type LocusKey } from '../loci' import { LOCI, type LocusKey } from '../loci'
@@ -28,7 +29,7 @@ import {
} from '../catalog' } from '../catalog'
/** The first 18 entries are the frozen contract names (must round-trip exactly). */ /** The first 18 entries are the frozen contract names (must round-trip exactly). */
const FROZEN_COUNT = 18 const FROZEN_COUNT = 17
import { breed } from '../breed' import { breed } from '../breed'
import { GeneticsWarningCode } from '../warnings' import { GeneticsWarningCode } from '../warnings'
@@ -64,6 +65,7 @@ describe('Genotype serialization', () => {
P: ['p', 'P'], P: ['p', 'P'],
Sp: ['sp', 'sp'], Sp: ['sp', 'sp'],
Re: ['re', 're'], Re: ['re', 're'],
Sls: ['sl', 'sl'],
}) })
expect(g.A).toEqual(['A', 'a']) expect(g.A).toEqual(['A', 'a'])
expect(g.C).toEqual(['C', 'ch']) expect(g.C).toEqual(['C', 'ch'])
@@ -240,6 +242,7 @@ describe('Partially-unknown parents (wildcards)', () => {
P: ['P', 'P'], P: ['P', 'P'],
Sp: ['sp', 'sp'], Sp: ['sp', 'sp'],
Re: ['re', 're'], Re: ['re', 're'],
Sls: ['sl', 'sl'],
}) })
const mother = fromDisplayString('aa CC DD EE GG PP spsp rere') const mother = fromDisplayString('aa CC DD EE GG PP spsp rere')
const result = breed(father, mother) const result = breed(father, mother)
@@ -263,3 +266,79 @@ describe('Probabilities are exact fractions summing to 1', () => {
expect(toNumber(sum)).toBeCloseTo(1, 10) expect(toNumber(sum)).toBeCloseTo(1, 10)
}) })
}) })
describe('GEN-3a: Uw=G alias', () => {
it('Uwuw parses as Gg, UwUw as GG', () => {
expect(fromDisplayString('AA CC DD EE Uwuw PP spsp rere').G).toEqual(['G', 'g'])
expect(fromDisplayString('AA CC DD EE UwUw PP spsp rere').G).toEqual(['G', 'G'])
expect(fromDisplayString('AA CC DD EE uwuw PP spsp rere').G).toEqual(['g', 'g'])
})
})
describe('GEN-3a: second spotting locus Sls (WP)', () => {
it('S(l)s(l) and WP both parse to the Sls heterozygote Slsl', () => {
expect(fromDisplayString('AA CC DD EE GG PP spsp rere S(l)s(l)').Sls).toEqual(['Sl', 'sl'])
expect(fromDisplayString('AA CC DD EE GG PP spsp rere WP').Sls).toEqual(['Sl', 'sl'])
})
it('toDisplayString omits wild-type Sls but shows Slsl', () => {
expect(toDisplayString(wildType())).toBe('AA CC DD EE GG PP spsp rere')
expect(toDisplayString(fromDisplayString('AA CC DD EE GG PP spsp rere WP'))).toBe(
'AA CC DD EE GG PP spsp rere Slsl',
)
})
it('WP × WP: S(l)S(l) is lethal — removed, renormalized, SLS_LETHAL warning', () => {
const parent = fromDisplayString('AA CC DD EE GG PP spsp rere Slsl')
const result = breed(parent, parent)
expect(result.offspring.every((o) => !o.genotype.includes('SlSl'))).toBe(true)
const sum = result.offspring.reduce((acc, o) => acc + o.probability.value, 0)
expect(sum).toBeCloseTo(1, 10)
// survivors 2/3 Slsl : 1/3 slsl
const wp = result.offspring.find((o) => o.genotype.includes('Slsl'))!
expect(wp.probability.text).toBe('2/3')
const warn = result.warnings.find((w) => w.code === GeneticsWarningCode.SlsLethal)
expect(warn?.detail?.youngLostFraction).toBe('1/4')
})
it('Sp × Sls → Superschecke deafness warning', () => {
const father = fromDisplayString('AA CC DD EE GG PP Spsp rere')
const mother = fromDisplayString('AA CC DD EE GG PP spsp rere Slsl')
const result = breed(father, mother)
expect(
result.warnings.some((w) => w.code === GeneticsWarningCode.SuperscheckeDeaf),
).toBe(true)
})
})
describe('GEN-3a: flag/metadata tokens tolerated', () => {
it('dea/taub/Dea/DP/WFNZ/RV/GV do not break parsing (stripped)', () => {
const g = fromDisplayString('AA CC DD EE GG PP spsp rere dea WFNZ DP RV GV')
expect(toDisplayString(g)).toBe('AA CC DD EE GG PP spsp rere')
})
it('extractGenotypeFlags reads deafness + tags', () => {
expect(extractGenotypeFlags('AA CC DD EE GG PP spsp rere dea WFNZ').deaf).toBe(true)
expect(extractGenotypeFlags('AA CC DD EE GG PP spsp rere Dea').deaf).toBe(false)
expect(extractGenotypeFlags('AA CC DD EE GG PP spsp rere WFNZ RV').tags).toEqual(['WFNZ', 'RV'])
})
})
describe('GEN-3a: Schimmel catalog fix (breeder C5)', () => {
it('efef base -> Orangeschimmel (not Schwarzschimmel)', () => {
expect(genotypeToFarbschlag(fromDisplayString('AA CC DD efef GG PP spsp rere'))).toBe(
'Orangeschimmel',
)
})
it('efef pp -> Rotaugenschimmel, efef gg -> Silberschimmel', () => {
expect(genotypeToFarbschlag(fromDisplayString('AA CC DD efef GG pp spsp rere'))).toBe(
'Rotaugenschimmel',
)
expect(genotypeToFarbschlag(fromDisplayString('AA CC DD efef gg PP spsp rere'))).toBe(
'Silberschimmel',
)
})
it('no Schwarzschimmel anywhere in the catalog', () => {
expect(BASE_COLORS.some((e) => e.name === 'Schwarzschimmel')).toBe(false)
})
})

View File

@@ -625,6 +625,10 @@ export const de = {
'Schecke × Schecke: Reinerbige Tiere (SpSp) sterben bereits im Mutterleib — etwa ein Viertel weniger Jungtiere.', 'Schecke × Schecke: Reinerbige Tiere (SpSp) sterben bereits im Mutterleib — etwa ein Viertel weniger Jungtiere.',
REX_SEMI_LETHAL: REX_SEMI_LETHAL:
'Rex × Rex: Reinerbige Tiere (ReRe) sind nur eingeschränkt lebensfähig.', 'Rex × Rex: Reinerbige Tiere (ReRe) sind nur eingeschränkt lebensfähig.',
SLS_LETHAL:
'WP × WP: Reinerbige Tiere (S(l)S(l)) sterben bereits im Mutterleib (Rumpback) — weniger Jungtiere.',
SUPERSCHECKE_DEAF:
'Schecke × WP: Superschecken (sehr hoher Weißanteil) sind möglich — erhöhtes Taubheitsrisiko.',
}, },
unknownFarbschlag: 'Unbekannter Farbschlag', unknownFarbschlag: 'Unbekannter Farbschlag',
}, },