import { describe, expect, it } from 'vitest' import { Rng, seedToRng } from '../src/renderer/game/engine/kernel/rng' import { perAttemptChance, resolveBreakthrough } from '../src/renderer/game/engine/runtime/Systems/cultivation' import { World } from '../src/renderer/game/engine/runtime/World' import { MAJORS } from '../src/renderer/game/data/realms' describe('矩阵:随机序列与概率域', () => { it.each(Array.from({ length: 10 }, (_, i) => [`r-${i}`, i * 7 + 3]))('seed %s 序列前 12 步可重放', (seed, salt) => { const a = new Rng(seedToRng(`${seed}-${salt}`)) const b = new Rng(seedToRng(`${seed}-${salt}`)) const seqA = Array.from({ length: 12 }, () => a.next()) const seqB = Array.from({ length: 12 }, () => b.next()) expect(seqA).toEqual(seqB) }) it.each([ ['分布-1', 'd1'], ['分布-2', 'd2'], ['分布-3', 'd3'], ['分布-4', 'd4'], ['分布-5', 'd5'] ])('%s:1000 次 rng.next 在 [0,1) 且均值粗测 0.3~0.7', (_t, seed) => { const r = new Rng(seedToRng(seed)) let sum = 0 for (let i = 0; i < 1000; i++) { const v = r.next() expect(v).toBeGreaterThanOrEqual(0) expect(v).toBeLessThan(1) sum += v } const avg = sum / 1000 expect(avg).toBeGreaterThan(0.3) expect(avg).toBeLessThan(0.7) }) it.each([ ['int-下界', 'i1', 1, 9], ['int-上界', 'i2', 1, 9], ['int-单值', 'i3', 5, 5], ['int-负界', 'i4', -5, 5] ])('%s:rng.int 范围恒等', (_t, seed, lo, hi) => { const r = new Rng(seedToRng(seed)) for (let i = 0; i < 60; i++) { const v = r.int(lo, hi) expect(v).toBeGreaterThanOrEqual(lo) expect(v).toBeLessThanOrEqual(hi) } }) it.each([ ['chance-0', 'c0', 0], ['chance-1', 'c1', 1], ['chance-05', 'c2', 0.5], ['chance-031', 'c3', 0.31], ['chance-099', 'c4', 0.99] ])('%s:chance 确定性(同 seed 同结果序列)', (_t, seed, p) => { const a = new Rng(seedToRng(seed)) const b = new Rng(seedToRng(seed)) const sa = Array.from({ length: 40 }, () => a.chance(p)) const sb = Array.from({ length: 40 }, () => b.chance(p)) expect(sa).toEqual(sb) }) it.each([ ['pick-池1', 'p1', ['a', 'b', 'c']], ['pick-池2', 'p2', ['x']], ['pick-池3', 'p3', ['-1', '0', '1', '2', '3', '9']] ])('%s:pick 元素恒在池内', (_t, seed, pool) => { const r = new Rng(seedToRng(seed)) for (let i = 0; i < 40; i++) { expect(pool).toContain(r.pick(pool)) } }) it.each([ ['qi', 'qi', 0], ['foundation', 'foundation', 1], ['core', 'core', 2], ['nascent', 'nascent', 3], ['spirit', 'spirit', 4] ])('perAttemptChance %s 在 [0.02,0.95] 且随心智单调不降', (label, major, _i) => { const w = World.create({ seed: `pc-${label}`, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' }) const c = Object.values(w.state.members)[0]! c.realm = { major: major as never, minor: 0 } c.mind = 3 const low = perAttemptChance(w, c) c.mind = 8 const high = perAttemptChance(w, c) expect(low).toBeGreaterThanOrEqual(0.02) expect(high).toBeLessThanOrEqual(0.95) expect(high).toBeGreaterThanOrEqual(low) }) it.each([ ['测1', 'bt-1', 'qi', 99], ['测2', 'bt-2', 'foundation', 99], ['测3', 'bt-3', 'core', 99], ['测4', 'bt-4', 'nascent', 99] ])('resolveBreakthrough %s:boost 99 必成(上限安全)', (_t, seed, major, boost) => { const w = World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' }) const c = Object.values(w.state.members)[0]! c.realm = { major: major as never, minor: 0 } c.realmProgress = 100 c.health = 100 const prev = c.realm resolveBreakthrough(w, c, boost) expect(c.realm).not.toEqual(prev) // 必晋升 expect(c.realmProgress).toBe(0) }) it.each([ ['弱智', 'rp-1', 1], ['平庸', 'rp-2', 5], ['聪颖', 'rp-3', 9], ['天人', 'rp-4', 12] ])('突破概率 %s:心智档位覆盖率非空且封顶', (label, seed, mind) => { const w = World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' }) const c = Object.values(w.state.members)[0]! c.realm = { major: 'foundation', minor: MAJORS.foundation.minorLayers - 1 } c.mind = mind const p = perAttemptChance(w, c) expect(p).toBeGreaterThan(0) expect(p).toBeLessThanOrEqual(0.95) void label }) })