Files
ChronicleOfTheImmortalClan/tests/fingerprint.helper.ts
T
thzxx ff6df8054c refactor(0.1.14-P1): 内核归位——game/ 按引擎架构重排(零行为漂移)
结构(旧 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
2026-08-23 13:23:25 +08:00

64 lines
2.7 KiB
TypeScript

import { World } from '../src/renderer/game/engine/runtime/World'
import { GameState } from '../src/renderer/game/types/domain'
export function stateFingerprint(s: GameState): string {
const parts = [
s.year, s.month, s.seq, s.totalTicks,
s.family.stones,
s.family.reputation,
s.family.generation,
JSON.stringify(sortedInv(s.family.inventory)),
JSON.stringify(sortedBld(s.family.buildings)),
s.family.headId,
s.family.techniques.join(',')
]
const members = Object.values(s.members)
.map((c) => `${c.id}:${c.alive}:${c.realm.major}/${c.realm.minor}:${Math.floor(c.realmProgress * 10) / 10}:${Math.floor(c.health * 10) / 10}:${c.state}:${c.bornYear}:${c.techniqueRank ?? 0}:${Math.floor((c.techniqueProgress ?? 0) * 10) / 10}:${c.post ?? ''}:${c.aspiration ?? ''}:${c.spouseId ?? ''}:${c.children.join('-')}`)
.sort()
parts.push(members.join('|'))
parts.push(JSON.stringify(sortedNpc(s.npcFamilies)))
parts.push(s.missions.length + ':' + s.missions.map((m) => `${m.defId}:${m.stage}:${m.stageMonth}:${m.done}:${m.memberIds.join('+')}`).join(';'))
parts.push(s.chronicle.length + ':' + s.chronicle.slice(-5).map((e) => `${e.year}${e.month}${e.category}`).join(','))
parts.push(s.battles.length)
parts.push(Object.keys(s.completedEvents).length + ':' + s.completedEvents.slice(-3).join(','))
parts.push(JSON.stringify(sortedFlags(s.family.flag)))
parts.push(s.yearlyReports.length)
return hash32(parts.join('\u0001'))
}
function sortedInv(inv: Record<string, number>): Array<[string, number]> {
return Object.entries(inv).sort((a, b) => (a[0] < b[0] ? -1 : 1))
}
function sortedBld(b: Record<string, number>): Array<[string, number]> {
return Object.entries(b).sort((a, b) => (a[0] < b[0] ? -1 : 1))
}
function sortedFlags(f: Record<string, number | boolean | string>): Array<[string, string]> {
return Object.entries(f)
.map(([k, v]) => [k, String(v)] as [string, string])
.sort((a, b) => (a[0] < b[0] ? -1 : 1))
}
function sortedNpc(n: GameState['npcFamilies']): Array<[string, string]> {
return Object.entries(n)
.map(([k, v]) => [k, `${v.relation}:${v.power}:${v.allied}`] as [string, string])
.sort((a, b) => (a[0] < b[0] ? -1 : 1))
}
function hash32(str: string): string {
let h = 2166136261
for (let i = 0; i < str.length; i++) {
h ^= str.charCodeAt(i)
h = Math.imul(h, 16777619)
h = (h << 13) | (h >>> 19)
}
return (h >>> 0).toString(16).padStart(8, '0')
}
export function longRun(seed: string, months = 560): World {
const w = World.create({ seed, surname: '钟', familyName: '钟家', motto: 'm', difficulty: 'normal' })
for (let i = 0; i < months; i++) {
if (w.state.gameOver) break
w.advanceMonth()
}
return w
}