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 基线固化
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user