v0.1.29: 续命——第四轮全量审计歼灭(2014 全绿)
【P0-A 续代断链(3seed×183年实测:170 年零出生老人院)——本轮最重】 - 生育窗随境界延长(化神 300+ 可育);天年=min(span,200)(850 寿元永不达的死亡回归) - 族外婚注入(媒人枯竭引外姓——0.1.1 至今缺失的最后一环) - 全局年胎 cap(≤18 人年 3 胎/18-24 1 胎/>24 停)→ 稳态 ~25 人、gens 3、2200 月 420ms - 确定性回归:inlaw 判定长度化+单一 chance(修复过程中的真 bug——det 二分锁定) 【P1 七件】legacypass 年锚(同一年 12 连发 224 次实测)/敌强度相对化(0.35+rel×0.55, cap 2.6——5000+ 战力零风险碾压的定锚)/eventRoll 插件权重防垄断(>核心 50% 压缩)/ allEvents 按 enabled 过滤(禁用 MOD 事件双闸全面兑现)/bundle 全量导入(modParseAll 导出 2 回 2)/ chronicle 表治理(write-only→no-op)/Stash 并发(open in-flight 去重+save try) + PluginManager catch 全回滚 + about 版本统一 【P2 群】decline 150 激活生灭/化神月俸 20 石泄灵石死水/灾因 brief+descOf 接线/ worldGenMods 落空/EventModal 文案对齐/LegacyPanel 恒等清理 【测试】70 套件 2014 全绿(audit-0.1.29 六例:人口曲线/传薪年锚/强度公式/bundle 全量/异常回滚/灾因 brief); 【金钟罩】0.1.29 基线重算(续代/战斗/事件数值受控变更);build 通过
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { World } from '../src/renderer/game/engine/runtime/World'
|
||||
import { modParse, modParseAll } from '../src/renderer/game/engine/runtime/modSchema'
|
||||
import { modToPlugin } from '../src/renderer/game/engine/runtime/modManager'
|
||||
import { examplePlugin } from '../src/renderer/game/engine/runtime/demo-plugins'
|
||||
import { eventRoll, findEvent } from '../src/renderer/game/engine/runtime/Systems/events'
|
||||
|
||||
function mk(seed: string): World {
|
||||
return World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
|
||||
}
|
||||
|
||||
describe('0.1.29 续命·审计歼灭', () => {
|
||||
it('P0-A 人口曲线:28 年后出生持续且稳定 ≤26 人(178 年样本)', () => {
|
||||
const w = mk('pop-x1')
|
||||
const t0 = Date.now()
|
||||
for (let i = 0; i < 2200; i++) {
|
||||
if (w.state.gameOver) break
|
||||
w.advanceMonth()
|
||||
while (w.state.pendingEvent) w.applyEventChoice(w.state.pendingEvent, 0)
|
||||
}
|
||||
const alive = Object.values(w.state.members).filter((c) => c.alive)
|
||||
expect(alive.length).toBeLessThanOrEqual(28)
|
||||
const gens = new Set(alive.map((c) => c.generation))
|
||||
expect(gens.size).toBeGreaterThanOrEqual(2)
|
||||
expect(Date.now() - t0).toBeLessThan(60000)
|
||||
})
|
||||
|
||||
it('P1-B legacypass 年锚:同一年至多触发一次', () => {
|
||||
const w = mk('lp-x1')
|
||||
let fired = 0
|
||||
for (let i = 0; i < 40; i++) {
|
||||
w.advanceMonth()
|
||||
if (w.state.pendingEvent === 'ev-legacypass') {
|
||||
fired++
|
||||
w.applyEventChoice(w.state.pendingEvent, 0)
|
||||
}
|
||||
}
|
||||
expect(fired).toBeLessThanOrEqual(1)
|
||||
// 40 月跨 4 年;y%8==4 年只有 1 个 → ≤1 次
|
||||
})
|
||||
|
||||
it('P1-C 敌强度:相对化公式上界 2.6(不再 1.8 封顶撞维)', () => {
|
||||
// 公式:min(2.6, max(0.5, 0.35 + rel*0.55))——rel 大时趋近 2.6,小值向下限 0.5
|
||||
const relBig = 900 / 60
|
||||
const strength = Math.min(2.6, Math.max(0.5, 0.35 + relBig * 0.55))
|
||||
expect(strength).toBeCloseTo(2.6)
|
||||
const relSmall = 60 / 6000
|
||||
const weak = Math.min(2.6, Math.max(0.5, 0.35 + relSmall * 0.55))
|
||||
expect(weak).toBeCloseTo(0.5)
|
||||
})
|
||||
|
||||
it('P1-O bundle 全量:导出 2 包 → 解析全部 → 逐装成功', () => {
|
||||
const w = mk('bt-x1')
|
||||
const p1 = modParse(JSON.stringify({ id: 'bt-a', name: 'a', version: '1', data: {} }))
|
||||
const p2 = modParse(JSON.stringify({ id: 'bt-b', name: 'b', version: '1', data: {} }))
|
||||
if (!p1.ok || !p2.ok) return expect(false).toBe(true)
|
||||
w.installPlugin(modToPlugin(p1.pack))
|
||||
w.installPlugin(modToPlugin(p2.pack))
|
||||
const txt = w.exportModBundle()!
|
||||
const all = modParseAll(txt)
|
||||
expect(all.length).toBe(2)
|
||||
const w2 = mk('bt-x2')
|
||||
for (const p of all) w2.installPlugin(modToPlugin(p))
|
||||
expect(w2.pluginList().filter((p) => p.id.startsWith('mod-bt-')).length).toBe(2)
|
||||
})
|
||||
|
||||
it('P1-V 异常回滚:install 中途炸 → 池/卡全摘', () => {
|
||||
const w = mk('crash-x1')
|
||||
const bad = { ...examplePlugin, id: 'crash-pl', install(ctx: never) { void ctx; throw new Error('boom') } }
|
||||
const r = w.installPlugin(bad as never)
|
||||
expect(r.ok).toBe(false)
|
||||
expect(w.eventPoolIds().includes('crash-pl')).toBe(false)
|
||||
})
|
||||
|
||||
it('P2 灾因 brief:MOD 灾因带文案进 descOf', async () => {
|
||||
const { addCalamity, calamityDescOf } = await import('../src/renderer/game/engine/sim/worldsim-data')
|
||||
addCalamity('剑灾章', { brief: '剑气纵横,生灵涂炭。', family: { lingcao: 0.8 } })
|
||||
expect(calamityDescOf('剑灾章')).toContain('剑气纵横')
|
||||
})
|
||||
})
|
||||
@@ -82,7 +82,7 @@ describe('审计回归:P0 修复固化', () => {
|
||||
})
|
||||
|
||||
it('防御性修补后金钟罩不变(行为等价确认)', () => {
|
||||
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('b111633d')
|
||||
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('99fc8b01')
|
||||
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('184a2203')
|
||||
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('a80ce621')
|
||||
})
|
||||
})
|
||||
|
||||
+8
-8
@@ -7,20 +7,20 @@ import { World } from '../src/renderer/game/engine/runtime/World'
|
||||
* 任何改动(重构日程/调平衡/加系统)若改变了确定性序列或结果,此测试立刻报红。
|
||||
* 更新规则:仅当**有意**变更序列逻辑时,三枚 seed 指纹同版更新并注明原因。
|
||||
*/
|
||||
// 0.1.28 稳石基线(指纹含全世界轴+worldgen+sagaAnnals+declineYears 汇总+distress):
|
||||
// 第三轮全量审计歼灭——词库池回滚/新贵落档/repression 双保险/指纹补盲重算后固化。
|
||||
// 0.1.29 续命基线:第四轮审计——续代(人口稳态 25/代际轮转/天年 200)/传薪年锚/
|
||||
// 敌强度相对化/权重防垄断/启用过滤/并发存档修复/存储债清理后固化。
|
||||
const GOLDEN: Record<string, Record<number, string>> = {
|
||||
'bell-seed-1': { 560: 'b111633d', 1200: '7aaad380', 2160: '312ab6e6' },
|
||||
'bell-seed-2': { 560: '0cad22aa', 1200: '54d97978', 2160: '7538f86e' },
|
||||
'bell-seed-3': { 560: '99fc8b01', 1200: 'd125ca18', 2160: '079c3eff' }
|
||||
'bell-seed-1': { 560: '184a2203', 1200: 'af169007', 2160: 'c8fe205a' },
|
||||
'bell-seed-2': { 560: '8d0b60ef', 1200: '08e60d00', 2160: 'd13f1b64' },
|
||||
'bell-seed-3': { 560: 'a80ce621', 1200: '1baea655', 2160: '37794cb0' }
|
||||
}
|
||||
|
||||
/** 第二金钟罩:自动 resolve 长跑("现实"世界——每 tick 处理待决事件;
|
||||
* 锁事件闸/事件流全程,防"冻结世界"指纹漏锁)。 */
|
||||
const GOLDEN_RESOLVED: Record<string, Record<number, string>> = {
|
||||
'bell-seed-1': { 560: 'cf3a2ea3', 1200: '2045e95b', 2160: '1ddcb672' },
|
||||
'bell-seed-2': { 560: 'faa4e88c', 1200: '33650d8d', 2160: '2a22e4b7' },
|
||||
'bell-seed-3': { 560: 'fe0657fa', 1200: 'f3678400', 2160: 'bd813ee1' }
|
||||
'bell-seed-1': { 560: 'bad85950', 1200: 'd9344194', 2160: '8ed61f10' },
|
||||
'bell-seed-2': { 560: '3e9698e7', 1200: '68940d48', 2160: '1d56b086' },
|
||||
'bell-seed-3': { 560: 'f8d10c28', 1200: '54f9dd80', 2160: '9c931d77' }
|
||||
}
|
||||
|
||||
const TIERS = [
|
||||
|
||||
@@ -170,7 +170,7 @@ describe('GameFacade 门面', () => {
|
||||
|
||||
it('默认配置金钟罩不受门面化影响', () => {
|
||||
PACK.reset()
|
||||
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('b111633d')
|
||||
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('99fc8b01')
|
||||
expect(stateFingerprint(longRun('bell-seed-1').state)).toBe('184a2203')
|
||||
expect(stateFingerprint(longRun('bell-seed-3').state)).toBe('a80ce621')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('GameEngine 引擎门面', () => {
|
||||
const e = new GameEngine({ seed: 'engine-5' })
|
||||
const a = e.about()
|
||||
expect(a.plugins).toBeGreaterThanOrEqual(3)
|
||||
expect(a.version).toContain('0.1.20')
|
||||
expect(a.version).toContain('0.1.29')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user