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(','), // 0.1.37 因果指纹:karma 值入指纹(因果调制影响灾年/NPC姿态/渡劫——必须锁定) `k:${Math.floor((s.family.karma ?? 0) * 10) / 10}` ] 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) const ws = s.worldSim if (ws) { const dyns = Object.entries(ws.npcDyn ?? {}) .map(([k, d]) => `${k}:${(d as { stance?: string; prosperity?: number }).stance ?? '-'}:${Math.floor((d as { prosperity?: number }).prosperity ?? 0) * 7}:${JSON.stringify((d as { relationsWithOthers?: Record }).relationsWithOthers ?? {})}`) .join(';') parts.push(`ws:${JSON.stringify(ws.marketPool ?? {})}:${JSON.stringify(ws.secretQi ?? {})}:${ws.tide ?? 0}:${ws.npcSuccessions ?? 0}:${(ws.newsFeed ?? []).length}:${ws.era ?? '-'}:${ws.eraStartYear ?? 0}:${ws.calamity ?? '-'}:${ws.calamityLeft ?? 0}:${dyns}`) } else { parts.push('ws:none') } parts.push(`pe:${s.pendingEvent ?? '-'}:${s.eventQueue ? s.eventQueue.length : 0}`) // P1-8 指纹补盲(0.1.28):世界轴线全量——sagaAnnals/distress/declineYears/worldGen 摘 req const wg = s.worldGen as { npcCount?: number; eras?: string; npcs?: unknown[] } | undefined parts.push(`wg:${wg?.npcCount ?? 0}:${wg?.eras ?? '-'}:${wg?.npcs?.length ?? 0}`) parts.push(`saga:${(ws?.sagaAnnals ?? []).length}:${((ws?.sagaAnnals ?? []) as { year: number }[]).slice(-1).map((a) => a.year).join(',')}`) parts.push(`distress:${ws?.distress?.id ?? '-'}`) const declSum = Object.values(ws?.npcDyn ?? {}) .map((d) => (d as { declineYears?: number })?.declineYears ?? 0) .reduce((a, b) => a + b, 0) parts.push(`decl:${declSum}`) return hash32(parts.join('\u0001')) } function sortedInv(inv: Record): Array<[string, number]> { return Object.entries(inv).sort((a, b) => (a[0] < b[0] ? -1 : 1)) } function sortedBld(b: Record): Array<[string, number]> { return Object.entries(b).sort((a, b) => (a[0] < b[0] ? -1 : 1)) } function sortedFlags(f: Record): 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 } /** 自动 resolve 长跑("现实"世界:每 tick 处理待决事件——与直调冻结世界对照) */ export function longRunResolved(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() let guard = 0 while (w.state.pendingEvent && guard < 4) { w.applyEventChoice(w.state.pendingEvent, 0) guard++ } } return w }