结构(旧 game/core+engine → 新 engine/ 域): - engine/kernel/ 时钟/随机/插件协议/fxqueue/timesense/format/urgency/guide/names(原 core) - engine/narrative/ legacy/报告/列传/谱系/年轴(原 core 叙事族) - engine/runtime/ World/creation/pcgen/ApiFacade/capabilities/pluginManager/boot/clocks + Systems/*(12 系统) - engine/sim/ Market(未来 WorldSim 同行) - 旧 game/core、engine/systems、engine/world.ts 等路径全部废弃(无 re-export 兼容层) 验证:35 套件/967 测试全绿(金钟罩三档零漂移=纯搬迁无行为变化) typecheck 0 error
337 lines
12 KiB
TypeScript
337 lines
12 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
MAJORS,
|
|
MAJOR_ORDER,
|
|
MAJOR_NAMES,
|
|
describeRealm,
|
|
realmTier,
|
|
compareRealm,
|
|
nextRealm,
|
|
maxRealmExp,
|
|
basePower,
|
|
breakthroughBaseChance,
|
|
realmDeathChance,
|
|
TECHNIQUE_GRADE_NAMES
|
|
} from '../src/renderer/game/data/realms'
|
|
import { ELEMENT_LIST, ROOT_GRADES, ROOT_GRADE_NAMES, describeRoots } from '../src/renderer/game/data/elements'
|
|
import { ITEMS, ARTIFACT_POWER } from '../src/renderer/game/data/items'
|
|
import { TECHNIQUES } from '../src/renderer/game/data/techniques'
|
|
import { BUILDINGS } from '../src/renderer/game/data/buildings'
|
|
import { MISSIONS, ENEMIES, missionById } from '../src/renderer/game/data/secrets'
|
|
import { NPCS, npcById } from '../src/renderer/game/data/npcs'
|
|
import { POSTS, POST_ORDER } from '../src/renderer/game/data/posts'
|
|
import { EVENTS } from '../src/renderer/game/data/events'
|
|
import { MAJOR_RATE, masteryRateOfMajor } from '../src/renderer/game/data/pacing'
|
|
import { SURNAME_POOL, MALE_GIVEN, FEMALE_GIVEN } from '../src/renderer/game/engine/kernel/names'
|
|
import { TRAITS } from '../src/renderer/game/data/traits'
|
|
|
|
describe('realms 境界表', () => {
|
|
it('每大境界结构完整(名称/层数/寿元/修为递增进)', () => {
|
|
for (const major of MAJOR_ORDER) {
|
|
const def = MAJORS[major]
|
|
expect(def.name).toBeTruthy()
|
|
expect(def.minorLayers).toBeGreaterThanOrEqual(0)
|
|
expect(def.lifespan).toBeGreaterThan(0)
|
|
expect(def.expBase).toBeGreaterThanOrEqual(0)
|
|
expect(def.expGrowth).toBeGreaterThanOrEqual(1)
|
|
}
|
|
expect(MAJOR_NAMES.mortal).toBe('凡人')
|
|
expect(MAJOR_NAMES.spirit).toBe('化神')
|
|
})
|
|
|
|
it('大境界寿元单调递增', () => {
|
|
for (let i = 1; i < MAJOR_ORDER.length; i++) {
|
|
expect(MAJORS[MAJOR_ORDER[i]].lifespan).toBeGreaterThan(MAJORS[MAJOR_ORDER[i - 1]].lifespan)
|
|
}
|
|
})
|
|
|
|
it('describeRealm 映射正确', () => {
|
|
expect(describeRealm({ major: 'mortal', minor: 0 })).toBe('凡人')
|
|
expect(describeRealm({ major: 'qi', minor: 0 })).toBe('炼气1层')
|
|
expect(describeRealm({ major: 'qi', minor: 8 })).toBe('炼气9层')
|
|
expect(describeRealm({ major: 'spirit', minor: 2 })).toBe('化神3层')
|
|
})
|
|
|
|
it('realmTier/compareRealm 正确排序', () => {
|
|
expect(realmTier({ major: 'mortal', minor: 0 })).toBe(0)
|
|
expect(realmTier({ major: 'qi', minor: 0 })).toBe(11)
|
|
expect(realmTier({ major: 'core', minor: 0 })).toBe(31)
|
|
expect(compareRealm({ major: 'qi', minor: 3 }, { major: 'foundation', minor: 0 })).toBeLessThan(0)
|
|
expect(compareRealm({ major: 'qi', minor: 9 }, { major: 'qi', minor: 5 })).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('nextRealm 链完整走完直至化神壁', () => {
|
|
let r = { major: 'mortal', minor: 0 } as { major: (typeof MAJOR_ORDER)[number]; minor: number }
|
|
r = nextRealm(r)!
|
|
expect(r).toEqual({ major: 'qi', minor: 0 })
|
|
r = nextRealm(r)!
|
|
expect(r).toEqual({ major: 'qi', minor: 1 })
|
|
r = { major: 'qi', minor: 8 }
|
|
r = nextRealm(r)!
|
|
expect(r).toEqual({ major: 'foundation', minor: 0 })
|
|
r = { major: 'foundation', minor: 2 }
|
|
r = nextRealm(r)!
|
|
expect(r).toEqual({ major: 'core', minor: 0 })
|
|
r = { major: 'spirit', minor: 2 }
|
|
expect(nextRealm(r)).toBeNull()
|
|
})
|
|
|
|
it('maxRealmExp 随层数严格增长', () => {
|
|
for (const major of ['qi', 'foundation', 'core', 'nascent'] as const) {
|
|
for (let i = 0; i < MAJORS[major].minorLayers - 1; i++) {
|
|
expect(maxRealmExp(major, i + 1)).toBeGreaterThan(maxRealmExp(major, i))
|
|
}
|
|
}
|
|
})
|
|
|
|
it('basePower 战力随境界单调递增', () => {
|
|
const seq: { major: (typeof MAJOR_ORDER)[number]; minor: number }[] = [
|
|
{ major: 'mortal', minor: 0 },
|
|
{ major: 'qi', minor: 0 },
|
|
{ major: 'qi', minor: 8 },
|
|
{ major: 'foundation', minor: 0 },
|
|
{ major: 'core', minor: 0 },
|
|
{ major: 'nascent', minor: 0 },
|
|
{ major: 'spirit', minor: 0 }
|
|
]
|
|
for (let i = 1; i < seq.length; i++) {
|
|
expect(basePower(seq[i])).toBeGreaterThan(basePower(seq[i - 1]))
|
|
}
|
|
})
|
|
|
|
it('突破基础概率与死亡风险在合法区间且递减/递增', () => {
|
|
const majors = ['mortal', 'qi', 'foundation', 'core', 'nascent', 'spirit']
|
|
for (const major of majors) {
|
|
const b = breakthroughBaseChance({ major: major as never, minor: 0 })
|
|
expect(b).toBeGreaterThan(0)
|
|
expect(b).toBeLessThanOrEqual(0.9)
|
|
}
|
|
for (let i = 1; i < majors.length; i++) {
|
|
expect(breakthroughBaseChance({ major: majors[i] as never, minor: 0 })).toBeLessThan(
|
|
breakthroughBaseChance({ major: majors[i - 1] as never, minor: 0 })
|
|
)
|
|
}
|
|
for (const major of majors) {
|
|
expect(realmDeathChance({ major: major as never, minor: 0 }, 5)).toBeGreaterThanOrEqual(0)
|
|
expect(realmDeathChance({ major: major as never, minor: 0 }, 5)).toBeLessThan(0.06)
|
|
}
|
|
})
|
|
|
|
it('品阶名数量一致(含凡阶哨兵)', () => {
|
|
expect(TECHNIQUE_GRADE_NAMES.length).toBe(6)
|
|
})
|
|
})
|
|
|
|
describe('elements 灵根', () => {
|
|
it('五行与品阶定义齐全', () => {
|
|
expect(ELEMENT_LIST.length).toBe(5)
|
|
for (const g of [0, 1, 2, 3, 4, 5]) {
|
|
expect(ROOT_GRADES[g]).toBeTruthy()
|
|
expect(ROOT_GRADES[g].drawWeight).toBeGreaterThan(0)
|
|
expect(ROOT_GRADES[g].expBonus).toBeGreaterThan(0)
|
|
}
|
|
expect(ROOT_GRADE_NAMES.length).toBe(6)
|
|
})
|
|
|
|
it('品阶修为加成单调', () => {
|
|
for (let g = 1; g <= 5; g++) {
|
|
expect(ROOT_GRADES[g].expBonus).toBeGreaterThan(ROOT_GRADES[g - 1].expBonus)
|
|
}
|
|
})
|
|
|
|
it('describeRoots 包含主属性与品阶', () => {
|
|
const label = describeRoots({ grade: 3, primary: '火', secondary: ['金', '水'] })
|
|
expect(label).toContain('火')
|
|
expect(label).toContain('真品灵根')
|
|
})
|
|
})
|
|
|
|
describe('items 物品表', () => {
|
|
it('物品 id 唯一且价格为正', () => {
|
|
const ids = new Set<string>()
|
|
for (const [id, item] of Object.entries(ITEMS)) {
|
|
expect(ids.has(id)).toBe(false)
|
|
ids.add(id)
|
|
expect(item.basePrice).toBeGreaterThan(0)
|
|
expect(item.name).toBeTruthy()
|
|
}
|
|
})
|
|
|
|
it('法宝战力表与法器条目一一对应', () => {
|
|
const artifacts = Object.entries(ITEMS).filter(([, v]) => v.kind === 'artifact')
|
|
for (const [id] of artifacts) {
|
|
expect(ARTIFACT_POWER[id]).toBeGreaterThan(0)
|
|
}
|
|
expect(Object.keys(ARTIFACT_POWER).length).toBe(artifacts.length)
|
|
})
|
|
})
|
|
|
|
describe('techniques 功法表', () => {
|
|
it('字段在法律区间且元素合法', () => {
|
|
for (const t of TECHNIQUES) {
|
|
expect(t.grade).toBeGreaterThanOrEqual(0)
|
|
expect(t.grade).toBeLessThanOrEqual(4)
|
|
expect(t.expBonus).toBeGreaterThan(0)
|
|
expect(t.powerBonus).toBeGreaterThan(0)
|
|
expect(ELEMENT_LIST).toContain(t.element)
|
|
expect(['剑修', '体修', '丹修', '阵修', '符修', '御灵']).toContain(t.path)
|
|
}
|
|
})
|
|
|
|
it('品阶越高综合面板越强(对比均值)', () => {
|
|
const avg = (g: number) => {
|
|
const pool = TECHNIQUES.filter((t) => t.grade === g)
|
|
const exp = pool.reduce((a, t) => a + t.expBonus, 0) / pool.length
|
|
const pow = pool.reduce((a, t) => a + t.powerBonus, 0) / pool.length
|
|
return exp + pow
|
|
}
|
|
for (let g = 2; g <= 4; g++) {
|
|
expect(avg(g)).toBeGreaterThan(avg(g - 1))
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('buildings 建筑表', () => {
|
|
it('所有建筑定义完整、升级成本递增', () => {
|
|
for (const [id, def] of Object.entries(BUILDINGS)) {
|
|
expect(def.maxLevel).toBeGreaterThan(0)
|
|
expect(def.name).toBeTruthy()
|
|
for (let l = 1; l < def.maxLevel; l++) {
|
|
const now = def.upgradeCost(l)
|
|
const next = def.upgradeCost(l + 1)
|
|
expect(next.stones).toBeGreaterThan(now.stones)
|
|
expect(next.lingkuang).toBeGreaterThan(now.lingkuang)
|
|
if (def.produceTable) {
|
|
const p0 = def.produceTable(l)
|
|
const p1 = def.produceTable(l + 1)
|
|
const key = Object.keys(p0)[0]
|
|
expect((p1[key] ?? 0)).toBeGreaterThanOrEqual((p0[key] ?? 0))
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
it('id 无重复', () => {
|
|
expect(new Set(Object.keys(BUILDINGS)).size).toBe(Object.keys(BUILDINGS).length)
|
|
})
|
|
})
|
|
|
|
describe('secrets 秘境', () => {
|
|
it('每个秘境阶段引用存在的敌人、奖励合法', () => {
|
|
const enemyIds = new Set(ENEMIES.map((e) => e.id))
|
|
for (const m of MISSIONS) {
|
|
expect(m.minMembers).toBeGreaterThan(0)
|
|
expect(m.minMembers).toBeLessThanOrEqual(m.maxMembers)
|
|
expect(m.risk).toBeGreaterThan(0)
|
|
expect(m.risk).toBeLessThanOrEqual(1)
|
|
expect(m.stages.length).toBeGreaterThan(0)
|
|
for (const st of m.stages) {
|
|
expect(['event', 'combat', 'resource', 'boss']).toContain(st.kind)
|
|
expect(st.months).toBeGreaterThan(0)
|
|
if (st.enemyId) expect(enemyIds.has(st.enemyId)).toBe(true)
|
|
if (st.loot) {
|
|
for (const r of Object.values(st.loot.resources)) {
|
|
expect(r[0]).toBeGreaterThanOrEqual(0)
|
|
expect(r[1]).toBeGreaterThanOrEqual(r[0])
|
|
}
|
|
}
|
|
}
|
|
for (const r of Object.values(m.completionLoot.resources)) {
|
|
expect(r[1]).toBeGreaterThan(r[0])
|
|
}
|
|
}
|
|
expect(missionById('m-anmoku').name).toBe('暗墨林')
|
|
expect(() => missionById('nope')).toThrow()
|
|
})
|
|
})
|
|
|
|
describe('npcs 势力表', () => {
|
|
it('四家齐全、growth 为正、leader 境界合法', () => {
|
|
expect(NPCS.length).toBe(4)
|
|
for (const n of NPCS) {
|
|
expect(n.powerGrowth[0]).toBeGreaterThan(0)
|
|
expect(n.powerGrowth[1]).toBeGreaterThanOrEqual(n.powerGrowth[0])
|
|
expect(MAJOR_ORDER).toContain(n.leaderRealm)
|
|
}
|
|
expect(npcById('n-nulei').name).toBe('怒雷祝氏')
|
|
expect(() => npcById('nope')).toThrow()
|
|
})
|
|
})
|
|
|
|
describe('posts 职事', () => {
|
|
it('职事完备且加成归一', () => {
|
|
expect(POST_ORDER.length).toBe(5)
|
|
expect(POSTS['head'].max).toBe(1)
|
|
for (const id of POST_ORDER) {
|
|
expect(POSTS[id].max).toBeGreaterThan(0)
|
|
expect(POSTS[id].effect.value).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('events 事件池', () => {
|
|
it('事件 id 唯一、结构合法、once 权重边界', () => {
|
|
const ids = new Set<string>()
|
|
for (const e of EVENTS) {
|
|
expect(ids.has(e.id)).toBe(false)
|
|
ids.add(e.id)
|
|
expect(e.weight).toBeGreaterThan(0)
|
|
expect(['daily', 'major', 'fate']).toContain(e.category)
|
|
expect(e.options.length).toBeGreaterThan(0)
|
|
expect(e.text.length).toBeGreaterThan(5)
|
|
for (const o of e.options) {
|
|
expect(o.label.length).toBeGreaterThan(0)
|
|
}
|
|
}
|
|
})
|
|
|
|
it('事件条件引用的建筑/资源存在', () => {
|
|
for (const e of EVENTS) {
|
|
const cond = e.cond
|
|
if (cond?.minBuilding) {
|
|
expect(BUILDINGS[cond.minBuilding.id]).toBeTruthy()
|
|
}
|
|
if (cond?.minResource) {
|
|
const id = cond.minResource.id
|
|
expect(id === 'stones' || ITEMS[id]).toBeTruthy()
|
|
}
|
|
if (cond?.relation) {
|
|
expect(NPCS.some((n) => n.id === cond.relation!.npcId)).toBe(true)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('pacing 节奏', () => {
|
|
it('全部大境界有修炼速率', () => {
|
|
for (const major of MAJOR_ORDER) {
|
|
const rate = masteryRateOfMajor(major)
|
|
expect(rate).toBeGreaterThan(0)
|
|
// 0.1.10 提速后 qi=2.2 属设计,仅验证仍低于高境界速率
|
|
}
|
|
// 高境界低于低境界
|
|
expect(MAJOR_RATE['spirit']).toBeLessThan(MAJOR_RATE['foundation'])
|
|
expect(MAJOR_RATE['nascent']).toBeLessThan(MAJOR_RATE['core'])
|
|
})
|
|
})
|
|
|
|
describe('names 人名池', () => {
|
|
it('姓氏/名池非空且无重复', () => {
|
|
expect(SURNAME_POOL.length).toBeGreaterThan(20)
|
|
expect(MALE_GIVEN.length).toBeGreaterThan(20)
|
|
expect(FEMALE_GIVEN.length).toBeGreaterThan(20)
|
|
expect(new Set(MALE_GIVEN).size).toBe(MALE_GIVEN.length)
|
|
expect(new Set(FEMALE_GIVEN).size).toBe(FEMALE_GIVEN.length)
|
|
})
|
|
})
|
|
|
|
describe('traits 秉性', () => {
|
|
it('全部特质定义完整、danger 在界', () => {
|
|
for (const t of Object.values(TRAITS)) {
|
|
expect(t.name).toBeTruthy()
|
|
expect(t.danger).toBeGreaterThanOrEqual(0)
|
|
expect(t.danger).toBeLessThanOrEqual(1)
|
|
}
|
|
})
|
|
})
|