From 3e4ed4d88226796726640a38f7ae1901065b908c Mon Sep 17 00:00:00 2001 From: thzxx <1440196015@qq.com> Date: Sun, 23 Aug 2026 21:34:29 +0800 Subject: [PATCH] =?UTF-8?q?v0.1.33b:=20=E5=BC=95=E6=93=8E=E6=AD=A2?= =?UTF-8?q?=E8=A1=80=E2=80=94=E2=80=94=E7=99=BD=E5=B1=8F/=E7=BB=8F?= =?UTF-8?q?=E6=B5=8E/=E6=B2=BB=E7=90=86=2015=20=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【白屏】equip() 校验物品存在;MemberModal 空守卫(B1 属 UI 同阶段) 【经济】tribBoost 渡劫成/败/陨落三分支尾部清零(+12% 破境丹不再永久白送); assistAlly 先校验 NPC 再扣款;giftNpc/makePeace 加 npc 守卫 【治理】about 版本透传(删硬编码 0.1.29);engineFromSnapshot 尊重 noAutoCreate(不再重放创世); 回声事件 npc 空池守卫;squad 陈旧 id 过滤;mission 日志悬空 join 修复; flag 修剪扩 legacyDone/tournamentYear + tauntCD 随家族消亡删;purgeNpc 删无效 auction 键; chronicle/battle splice 去重;lifecycle 注释 240→200 一致;worldsim-data 俄语注释中文化; maxRealmIdx 占位 0 且 normalize 重算(防虚报);TECH_GRADE_BASE 复用删除(实为局部 const 未动) 【测试】gameengine about 断言修正(0.1.32 透传) 5030 全绿 --- src/renderer/game/engine/GameEngine.ts | 24 ++++++++++++------- .../game/engine/runtime/Systems/diplomacy.ts | 6 ++--- .../game/engine/runtime/Systems/events.ts | 9 ++++--- .../game/engine/runtime/Systems/lifecycle.ts | 2 +- .../game/engine/runtime/Systems/missions.ts | 2 +- .../engine/runtime/Systems/tribulation.ts | 3 +++ src/renderer/game/engine/runtime/World.ts | 13 +++++----- src/renderer/game/engine/runtime/creation.ts | 2 +- src/renderer/game/engine/sim/WorldSim.ts | 1 - src/renderer/game/engine/sim/worldsim-data.ts | 2 +- tests/gameengine.test.ts | 4 ++-- 11 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/renderer/game/engine/GameEngine.ts b/src/renderer/game/engine/GameEngine.ts index e5cd183..e678482 100644 --- a/src/renderer/game/engine/GameEngine.ts +++ b/src/renderer/game/engine/GameEngine.ts @@ -43,14 +43,20 @@ export class GameEngine { this.datapack = PACK if (opts.datapack) this.datapack.override(opts.datapack) // 组装World(内核注入→时钟/随机/总线单源) - const state = opts.seed ? createWorldState({ - seed: this._seed, - surname: opts.surname?.trim() || '林', - familyName: opts.familyName?.trim() || `${opts.surname?.trim() || '林'}氏`, - motto: opts.motto?.trim() || '耕读传家,术法继世', - difficulty: opts.difficulty ?? 'normal' - }) : (globalThis as never as { __state?: GameState }).__state - this.world = new World(state!, [], this.kernel) + if (opts.noAutoCreate) { + this.world = new World(createWorldState({ + seed: this._seed, surname: '占位', familyName: '占位', motto: '', difficulty: 'normal' + }), [], this.kernel) + } else { + const state = opts.seed ? createWorldState({ + seed: this._seed, + surname: opts.surname?.trim() || '林', + familyName: opts.familyName?.trim() || `${opts.surname?.trim() || '林'}氏`, + motto: opts.motto?.trim() || '耕读传家,术法继世', + difficulty: opts.difficulty ?? 'normal' + }) : (globalThis as never as { __state?: GameState }).__state + this.world = new World(state!, [], this.kernel) + } this.facade = new ApiFacade(this.world, 1) // Kernel 总线 → World.out 桥接(attached 后同步 feed) this.world.out = this.world.out.concat([]) @@ -92,7 +98,7 @@ export class GameEngine { about(): { title: string; version: string; modules: number; systems: number; plugins: number; packFingerprint: string } { const a = this.facade.about() - return { ...a, version: '0.1.29' } + return a } installPlugin(p: CotycPlugin): { ok: boolean; reason?: string } { diff --git a/src/renderer/game/engine/runtime/Systems/diplomacy.ts b/src/renderer/game/engine/runtime/Systems/diplomacy.ts index 446c3b4..c83bc4a 100644 --- a/src/renderer/game/engine/runtime/Systems/diplomacy.ts +++ b/src/renderer/game/engine/runtime/Systems/diplomacy.ts @@ -70,9 +70,9 @@ export const GIFT_TIERS = [40, 120, 300] as const export function giftNpc(w: World, npcId: string, stones: number): boolean { const fam = w.state.family - if (stones <= 0 || fam.stones < stones) return false - fam.stones -= stones const npc = w.state.npcFamilies[npcId] + if (!npc || stones <= 0 || fam.stones < stones) return false + fam.stones -= stones let gain = calcGiftGain(stones) // 结盟亲善势:报之以加倍情面 const stance = (w.state.worldSim?.npcDyn?.[npcId] as { stance?: string } | undefined)?.stance @@ -96,7 +96,7 @@ export function giftNpc(w: World, npcId: string, stones: number): boolean { export function makePeace(w: World, npcId: string): boolean { const fam = w.state.family const npc = w.state.npcFamilies[npcId] - if (fam.stones < 200) return false + if (!npc || fam.stones < 200) return false fam.stones -= 200 npc.relation = Math.max(npc.relation + 35, 30) npc.power = Math.min(900, npc.power + 5) diff --git a/src/renderer/game/engine/runtime/Systems/events.ts b/src/renderer/game/engine/runtime/Systems/events.ts index 2f94c9e..b7c59c0 100644 --- a/src/renderer/game/engine/runtime/Systems/events.ts +++ b/src/renderer/game/engine/runtime/Systems/events.ts @@ -377,8 +377,9 @@ export function eventRoll(w: World): void { const key = `echoDone-${s.year}` if (!s.family.flag[key]) { s.family.flag[key] = true - if (w.rng.chance(0.7)) { - const npc = w.rng.pick(Object.values(s.npcFamilies)) + const npcs = Object.values(s.npcFamilies) + if (npcs.length > 0 && w.rng.chance(0.7)) { + const npc = w.rng.pick(npcs) fire(w, `ev-echo-${npc.id}-${s.year}`) return } @@ -716,7 +717,9 @@ export function applyEffect(w: World, eff: EffectDef, squad?: string[], _extra?: .sort((a, b) => rankPower(w, b) - rankPower(w, a)) .slice(0, 4) if (squad && squad.length > 0) { - team = squad.map((id) => w.memberById(id)).filter((c) => c.alive && c.state !== 'expedition' && w.ageOf(c) >= 16) + team = squad + .map((id) => w.state.members[id] ? w.memberById(id) : null) + .filter((c): c is Character => !!c && c.alive && c.state !== 'expedition' && w.ageOf(c) >= 16) } if (team.length > 0) { resolveRaid(w, eff.raid.npcId, team) diff --git a/src/renderer/game/engine/runtime/Systems/lifecycle.ts b/src/renderer/game/engine/runtime/Systems/lifecycle.ts index 4fb6dbd..5c8c3bd 100644 --- a/src/renderer/game/engine/runtime/Systems/lifecycle.ts +++ b/src/renderer/game/engine/runtime/Systems/lifecycle.ts @@ -11,7 +11,7 @@ export function deathTick(w: World): void { if (!c.alive) continue const age = w.ageOf(c) const span = lifespanOf(w, c) - // P0-A 天年:仙家 240 年为限(span 供凡人/低境界;高境界同样 240 内退场——世界人口会换血) + // P0-A 天年:仙家 200 年为限(span 供凡人/低境界;高境界同样 240 内退场——世界人口会换血) const cap = Math.min(span, 200) const softCap = cap * 0.85 let p = 0 diff --git a/src/renderer/game/engine/runtime/Systems/missions.ts b/src/renderer/game/engine/runtime/Systems/missions.ts index 8f191b5..546f005 100644 --- a/src/renderer/game/engine/runtime/Systems/missions.ts +++ b/src/renderer/game/engine/runtime/Systems/missions.ts @@ -57,7 +57,7 @@ export function missionTick(w: World): void { if (stage.kind === 'boss') { m.done = true m.result = res.draw ? 'stalemate' : 'retreat' - m.log.join(' ') + m.log = [m.log.join(' ')] w.chronicle('exploration', `${def.name}探路不遂,${resultText(m)}。`, undefined, false) } } diff --git a/src/renderer/game/engine/runtime/Systems/tribulation.ts b/src/renderer/game/engine/runtime/Systems/tribulation.ts index 20e5ff3..e0c6a53 100644 --- a/src/renderer/game/engine/runtime/Systems/tribulation.ts +++ b/src/renderer/game/engine/runtime/Systems/tribulation.ts @@ -60,6 +60,7 @@ export function resolveTribulation(w: World, c: Character, mode: 'rash' | 'guard if (next.major === 'spirit' && !s.flags['firstSpirit']) { s.flags['firstSpirit'] = s.year } + c.tribBoost = 0 return 'success' } @@ -76,6 +77,7 @@ export function resolveTribulation(w: World, c: Character, mode: 'rash' | 'guard c.deathCause = '渡劫陨落' w.chronicle('death', `${c.name} 天劫加身,灵石俱焚而陨。`, c.id, true) w.log('bad', `☠ ${c.name} 渡劫陨落。`) + c.tribBoost = 0 return 'dead' } if (guardian) { @@ -87,6 +89,7 @@ export function resolveTribulation(w: World, c: Character, mode: 'rash' | 'guard } } w.log('bad', text) + c.tribBoost = 0 return 'fail' } diff --git a/src/renderer/game/engine/runtime/World.ts b/src/renderer/game/engine/runtime/World.ts index 1ad3eb3..08e8b26 100644 --- a/src/renderer/game/engine/runtime/World.ts +++ b/src/renderer/game/engine/runtime/World.ts @@ -10,7 +10,7 @@ import { } from '../../types/domain' import { BUILDINGS, bonusOf, buildingById } from '../../data/buildings' -import { itemById, pillRecipeByOutput, forgeRecipeByOutput } from '../../data/items' +import { itemById as defById, pillRecipeByOutput, forgeRecipeByOutput } from '../../data/items' import { POSTS } from '../../data/posts' import { aspirationById as aspirationOf } from '../../data/aspirations' import { computeLegacy, resolveLegacy, peakRealmIndex, grandTechniqueCount, LegacyArch } from '../narrative/legacy' @@ -405,14 +405,12 @@ export class World { } this.state.chronicle.push(entry) if (this.state.chronicle.length > 520) this.state.chronicle.splice(0, this.state.chronicle.length - 520) - if (this.state.chronicle.length > 520) this.state.chronicle.splice(0, this.state.chronicle.length - 520) this.out.forEach((o) => o.onChronicle(entry, important)) } battle(log: BattleLog): void { this.state.battles.push(log) if (this.state.battles.length > 220) this.state.battles.splice(0, this.state.battles.length - 220) - if (this.state.battles.length > 220) this.state.battles.splice(0, this.state.battles.length - 220) this.out.forEach((o) => o.onBattle(log)) } @@ -480,8 +478,10 @@ export class World { const fam = this.state.family const cutoff = this.state.year - 3 for (const key of Object.keys(fam.flag)) { - const m = /^(auction-|prayerDone-|echoDone-|recruitDone-)(\d+)$/.exec(key) + const m = /^(auction-|prayerDone-|echoDone-|recruitDone-|legacyDone-|tournamentYear-)(\d+)$/.exec(key) if (m && Number(m[2]) < cutoff) delete fam.flag[key] + // tauntCD- 非年份键:随目标家族消亡删除(防长线膨胀) + if (key.startsWith('tauntCD-') && !this.state.npcFamilies[key.slice('tauntCD-'.length)]) delete fam.flag[key] } } @@ -616,10 +616,10 @@ export class World { const fam = this.state.family const ws = this.state.worldSim as { distress?: { id: string } } | undefined if (!ws?.distress || ws.distress.id !== npcId) return false - if (fam.stones < cost) return false - fam.stones -= cost const npc = this.state.npcFamilies[npcId] if (!npc) return false + if (fam.stones < cost) return false + fam.stones -= cost npc.power = Math.min(900, npc.power + 15) npc.relation = Math.min(100, npc.relation + 8) fam.reputation += 3 @@ -728,6 +728,7 @@ export class World { } equip(memberId: Id, artifact: string): void { + if (!defById(artifact)) return const c = this.memberById(memberId) if (!c.alive) return c.equipment = artifact diff --git a/src/renderer/game/engine/runtime/creation.ts b/src/renderer/game/engine/runtime/creation.ts index 64b894b..aa58646 100644 --- a/src/renderer/game/engine/runtime/creation.ts +++ b/src/renderer/game/engine/runtime/creation.ts @@ -97,7 +97,7 @@ export function createWorldState(opts: NewGameOptions): GameState { stats: { repPeak: 5, popPeak: 5, - maxRealmIdx: 25, + maxRealmIdx: 0, // normalize(World) 时按开局成员重算真实峰值,此处仅为占位 techniqueGrand: 0, tourneyHistory: [], feishengCount: 0 diff --git a/src/renderer/game/engine/sim/WorldSim.ts b/src/renderer/game/engine/sim/WorldSim.ts index d881a46..b80c4e5 100644 --- a/src/renderer/game/engine/sim/WorldSim.ts +++ b/src/renderer/game/engine/sim/WorldSim.ts @@ -598,7 +598,6 @@ function purgeNpc(w: World, s: WorldSimState, id: string): void { if (dyn?.relationsWithOthers && id in dyn.relationsWithOthers) delete dyn.relationsWithOthers[id] } delete w.state.family.flag[`raidCD-${id}`] - delete w.state.family.flag[`auction-${id}`] delete w.state.family.flag[`tauntCD-${id}`] w.state.eventQueue = (w.state.eventQueue ?? []).filter((e) => !e.startsWith('ev-raid-') || !e.endsWith(id)) if (s.distress?.id === id) s.distress = undefined diff --git a/src/renderer/game/engine/sim/worldsim-data.ts b/src/renderer/game/engine/sim/worldsim-data.ts index 5c70f51..b1431fc 100644 --- a/src/renderer/game/engine/sim/worldsim-data.ts +++ b/src/renderer/game/engine/sim/worldsim-data.ts @@ -200,7 +200,7 @@ export const WORLDSIM = { // —— 新贵补位 —— greatNewbornChance: 0.06, // 年首新贵出现率(乱世 ×2;上限 4 家) // —— 盟约连坐(加性) —— - allianceGriefRaid: 0.012, // кажд 世仇盟连加性 raid 概率 + allianceGriefRaid: 0.012, // 每 世仇盟连加性 raid 概率 // —— 天下事件 —— calamityChance: 0.18, calamities: ['旱灾', '涝灾', '蝗灾', '疫病', '兽潮', '寒潮'] as const, diff --git a/tests/gameengine.test.ts b/tests/gameengine.test.ts index 3b5ddb3..d51759d 100644 --- a/tests/gameengine.test.ts +++ b/tests/gameengine.test.ts @@ -47,11 +47,11 @@ describe('GameEngine 引擎门面', () => { expect(e.status()).toBe('running') }) - it('about 元数据含 plugins(≥3)与版本 0.1.20', () => { + it('about 元数据含 plugins(≥3)与版本透传', () => { const e = new GameEngine({ seed: 'engine-5' }) const a = e.about() expect(a.plugins).toBeGreaterThanOrEqual(3) - expect(a.version).toContain('0.1.29') + expect(a.version).toContain('0.1.32') }) })