Files
thzxx ea9f5c9c5d v0.1.17: 天下为市——世界真炉膛(市场实体化+NPC博弈+灾年持续)
【A 市场实体化(真库存循环)】
- 世界呼吸:池月供给 × 潮汐span - 常驻需求(worldSupplyRate 2% vs worldDemandRate 1.5%)
- NPC 上桌:def.sells/buys 逐月入池出池——池浅采不到→power-15%,池深抛货→市场暖意
- 玩家交易回写:buy 浅池价涨/sell 盈池价跌(tradeSettle 0.35 份额粒度);池深<35% 断供拒单(12% 是枯井)
- marketPrice 统一走 marketMultFor;本地 POOL_BASE/TECH 复刻清除(worldsim-data 单源)

【B 世界格局博弈】
- 战争伤骨:劫掠胜负回写 power(败-18%/胜+6%)+ dead 字段 raidCount/warCooldownYear 启用
- 社交回响:赠礼回礼(30% 灵石/30% 灵草)+ power 微涨;联姻/和约/结盟皆涨 power
- NPC 关系网:relationsWithOthers 填实(同风格亲30~55/异风格隙-35~15)+ 年首±5 漂移;世仇<-50 年首互攻(败方-18%)
- 灾年共苦:灾年 NPC 贸易 ×0.7;tide 对供给 ±0.3 弹性
- 灾年持续 6 月渐退;灾起/灾散快讯(kind 标记)

【UI】
- 天下面板:池深百分比(含断供阈值 tooltip)/本族天下排位/灾年余月
- 快讯响应化:行情见顶抛售5/见底吃进5/灾年救济(50灵石→关系8)
- MarketPanel 法帖价格改 techniquePrice()(硬编码清除)

【测试】994 全绿(38 套件,新增世界炉膛 8 例);金钟罩 0.1.17 基线固化
2026-08-23 14:24:23 +08:00

108 lines
4.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest'
import { World } from '../src/renderer/game/engine/runtime/World'
import { WorldSim } from '../src/renderer/game/engine/sim/WorldSim'
import { buyItem, sellItem, marketPrice, buyTechnique, techniquePrice } from '../src/renderer/game/engine/sim/Market'
import { resolveRaid } from '../src/renderer/game/engine/runtime/Systems/combat'
import { POOL_BASE } from '../src/renderer/game/engine/sim/worldsim-data'
function worldWithSim(seed: string): World {
const w = World.create({ seed, surname: '万', familyName: '万家', motto: 'm', difficulty: 'normal' })
w.advanceMonth()
w.state.totalTicks = 60
return w
}
describe('0.1.17 世界真炉膛', () => {
it('买卖回写池:买浅卖盈,断供拒单', () => {
const w = worldWithSim('w1')
const sim = new WorldSim(w)
const p0 = sim.marketMultFor('lingcao')
expect(buyItem(w, 'lingcao', 5)).toBe(true)
const p1 = sim.marketMultFor('lingcao')
expect(p1).toBeLessThan(p0) // 买浅 → 价涨
expect(sellItem(w, 'lingcao', 5)).toBe(true)
const p2 = sim.marketMultFor('lingcao')
expect(p2).toBeGreaterThan(p1) // 卖盈 → 价跌回来
// 断供:池压平后拒单
const ws = w.state.worldSim as { marketPool: Record<string, number> }
ws.marketPool['lingcao'] = POOL_BASE.lingcao * 0.3
const before = w.state.family.inventory['lingcao'] ?? 0
expect(buyItem(w, 'lingcao', 1)).toBe(false)
expect(w.state.family.inventory['lingcao'] ?? 0).toBe(before)
})
it('市场呼吸有界:长跑 600 月池不枯竭亦不冲顶', () => {
const w = World.create({ seed: 'w2', surname: '安', familyName: '安家', motto: 'm', difficulty: 'normal' })
for (let i = 0; i < 600; i++) w.advanceMonth()
const ws = w.state.worldSim as { marketPool: Record<string, number> }
for (const id of Object.keys(POOL_BASE)) {
expect(ws.marketPool[id]).toBeGreaterThan(POOL_BASE[id] * 0.1)
expect(ws.marketPool[id]).toBeLessThanOrEqual(POOL_BASE[id] * 2.5)
}
})
it('NPC 上桌:长期活跃于池(需料者逢底而购)', () => {
const w = World.create({ seed: 'w3', surname: '风', familyName: '风家', motto: 'm', difficulty: 'normal' })
for (let i = 0; i < 240; i++) w.advanceMonth()
const ws = w.state.worldSim as { marketPool: Record<string, number> }
const beast = ws.marketPool['beastcore']
expect(beast).toBeGreaterThan(POOL_BASE.beastcore * 0.12)
expect(beast).toBeLessThan(POOL_BASE.beastcore * 2.4)
})
it('劫掠胜利:NPC 伤筋动骨(power-18%、raidCount/warCooldownYear 写值)', () => {
const w = worldWithSim('w4')
const npcId = Object.keys(w.state.npcFamilies)[0]!
const npc = w.state.npcFamilies[npcId]
npc.power = 200
const team = [w.state.members['x1']!, w.state.members['x2']!, w.state.members['x3']!, w.state.members['x4']!]
for (const c of team) {
c.realm = { major: 'spirit', minor: 0 }
c.health = 100
}
const res = resolveRaid(w, npcId, team)
expect(res.win).toBe(true)
expect(npc.power).toBeLessThan(200)
expect(npc.raidCount).toBeGreaterThan(0)
expect(npc.warCooldownYear).toBe(w.state.year)
})
it('灾年持续 6 月并散去(B12', () => {
const w = worldWithSim('w5')
w.state.month = 1
void w
const ws = w.state.worldSim as { calamity?: string; calamityLeft?: number }
// 强制触发:首月+高概率 seed 抽——健壮断言:任何时刻 calamityLeft ∈ [0,6]
for (let i = 0; i < 24; i++) {
w.advanceMonth()
const left = ws.calamityLeft ?? 0
expect(left).toBeLessThanOrEqual(6)
if (ws.calamity) expect(left).toBeGreaterThan(0)
}
})
it('NPC 关系网填充:同风格亲近、异风格有隙,且年首漂移', () => {
const w = worldWithSim('w6')
const dyn = (w.state.worldSim as { npcDyn: Record<string, { relationsWithOthers: Record<string, number> }> }).npcDyn
const xuan = dyn['n-xuanying']?.relationsWithOthers ?? {}
expect(Object.keys(xuan).length).toBeGreaterThanOrEqual(3)
const after = (w.state.worldSim as { npcDyn: Record<string, { relationsWithOthers: Record<string, number> }> }).npcDyn
// 长跑后关系网仍存在(不丢)
expect(Object.keys(after['n-xuanying']?.relationsWithOthers ?? {}).length).toBeGreaterThanOrEqual(3)
})
it('功法价格与 techniquePrice 一致(面板引用安全)', () => {
const w = worldWithSim('w7')
const t = w.state.family.techniques[0]
if (!t) return
expect(techniquePrice(t)).toBeGreaterThan(0)
expect(buyTechnique(w, t, techniquePrice(t))).toBe(false) // 已录
})
it('无 sim 世界市场价正常(兜底不崩)', () => {
const w = World.create({ seed: 'w8', surname: '石', familyName: '石家', motto: 'm', difficulty: 'normal' })
w.state.worldSim = undefined as never
expect(marketPrice(w, 'lingcao')).toBeGreaterThan(0)
})
})