v0.1.19: 八方风雨 + 孤立系统归并(双主线,1002 全绿)
【主线一:世界自演进深化《八方风雨》】 - NPC 战略姿态 stance:守成/扩张/隐忍/结盟四态,年首评估(era>景气>power>relation) 全行为调制:raid 概率×2/×0.4/×0、互攻阈值 -40/-70/不攻、贸量 ×1.3/×0.7、 赠礼收益 ally×1.3、结盟门槛 ally 放宽至30——NPC 从统计物理到行为战略 - 超卖潮(谷贱伤农):池>1.8×base 世界产能回调×0.7 + 年度播报(防玩家玩崩市场) - 盟约连坐:结盟令世仇(rel<-50)对玩家-20;盟者世仇 raid 概率×2 - 盟友求援:互攻失利盟友告急(distress 旗标)——WorldPanel 响应(出资200/见死不救) - 干预天下:调停(50灵石令世仇-60→-25、声望+2)/ 资助盟友;恩怨网情报视图(各家关系展开+调停钮) 【主线二:孤立系统归并引擎】 - P0 兜底修复:empty() 收敛 makeWorldSimState 单源 + normalize 补 tideTicks/calamityLeft/ era/eraStartYear/prosperity/distress/overfluxYear(旧档 NaN 级联防死) - FxGate 从 engine/kernel/fxqueue.ts 并入 ui/fx.ts(kernel 清纯引擎;deltas 顶层导出, 3 处引用 10 行改动);engine 侧零 UI 依赖再下一城 - 音效语义补充:onFx→blade/pulse/spark→sWar/sGong/sBell;onLog kind 兜底保留(不静音) - store.advance drift/season 段左移 fx.ts(fxSeason 复活接管 season 检测) - 死码清除:timesense.tideOf/tintLabel、tideDir 全链(类型/初始/写入/domain) - domain.ts worldSim 局部类型补全 0.1.18/0.1.19 全部新字段(断言类型安全) 【测试】1002 全绿(41 套件):stance-world 7 例(兜底/姿态/raid/超卖/连坐/调停/求援)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/** WorldSim —— 世界自进化引擎(game/engine/sim/WorldSim.ts) */
|
||||
import { World } from '../runtime/World'
|
||||
import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, CALAMITY_EFFECT, CALAMITY_FAMILY, CalamityName, POOL_BASE } from './worldsim-data'
|
||||
import { WorldSimState, NpcDynamics, WORLDSIM, ERA_CONF, ERA_DURA, makeWorldSimState, NpcStance, CALAMITY_EFFECT, CALAMITY_FAMILY, CalamityName, POOL_BASE } from './worldsim-data'
|
||||
import { pack } from '../../data/registry'
|
||||
import { ITEMS } from '../../data/items'
|
||||
import { npcById } from '../../data/npcs'
|
||||
@@ -196,6 +196,8 @@ function initSim(w: World): WorldSimState {
|
||||
for (const id of Object.keys(w.state.npcFamilies)) {
|
||||
s.npcDyn[id] = {
|
||||
prosperity: 50,
|
||||
stance: 'guardian',
|
||||
stanceSinceYear: 1,
|
||||
leaderName: '新任宗主',
|
||||
leaderRealmIdx: MAJOR_ORDER.indexOf(npcById(id).leaderRealm),
|
||||
leaderAge: 40 + w.rng.int(0, 29),
|
||||
@@ -209,18 +211,7 @@ function initSim(w: World): WorldSimState {
|
||||
}
|
||||
|
||||
function empty(): WorldSimState {
|
||||
return {
|
||||
marketPool: { lingcao: 600, lingkuang: 300, beastcore: 80, 'pill-qiyuan': 90, 'pill-ningyuan': 40 },
|
||||
npcDyn: {},
|
||||
secretQi: {},
|
||||
tide: 0.5,
|
||||
tideDir: 1,
|
||||
tideTicks: 0,
|
||||
calamityYear: -99,
|
||||
newsFeed: [],
|
||||
lastNewsMonth: -99,
|
||||
npcSuccessions: 0
|
||||
}
|
||||
return { ...makeWorldSimState() }
|
||||
}
|
||||
|
||||
/** A2+A3:世界常驻供给与需求(潮汐乘化)——池有了呼吸 */
|
||||
@@ -231,14 +222,25 @@ function worldBreath(w: World, s: WorldSimState): void {
|
||||
const supplySpan = Math.max(0.75, Math.min(1.35, span))
|
||||
const eraSupply = era.supplyMult
|
||||
const eraDemand = era.demandMult
|
||||
let overflow = false
|
||||
for (const id of MARKET_IDS) {
|
||||
const base = poolBase(id)
|
||||
const cur = s.marketPool[id] ?? base
|
||||
const supply = base * WORLDSIM.worldSupplyRate * supplySpan * eraSupply
|
||||
// 超卖潮:池过剩 > 1.8×base 时世界产能自发回调(谷贱伤农)
|
||||
const saturated = cur > base * 1.8
|
||||
if (saturated) overflow = true
|
||||
const supply = base * WORLDSIM.worldSupplyRate * supplySpan * eraSupply * (saturated ? 0.7 : 1)
|
||||
const demand = base * WORLDSIM.worldDemandRate * eraDemand
|
||||
s.marketPool[id] = clamp(cur + supply - demand, base * 0.12, base * WORLDSIM.tradeCeilPct)
|
||||
}
|
||||
void w
|
||||
// 超卖播报(年一次)——玩家能看到"别把市场玩崩"
|
||||
if (overflow && s.overfluxYear !== w.state.year) {
|
||||
s.overfluxYear = w.state.year
|
||||
s.newsFeed.push({
|
||||
year: w.state.year, month: w.state.month, src: '坊市', text: '谷贱伤农——市面货丰价滞,灵田多有弃耕,天下产能暂歇。', kind: 'quote'
|
||||
})
|
||||
if (w.state.totalTicks > 12) w.log('bad', '【坊市】谷贱伤农,世界产能回调。')
|
||||
}
|
||||
}
|
||||
|
||||
/** A4:NPC 按 def.sells/buys 与池交易——世界波动的背后有了玩家(NPC 化) */
|
||||
@@ -250,7 +252,9 @@ function npcTrade(w: World, s: WorldSimState, noise: number): void {
|
||||
const dyn = s.npcDyn[id] ?? initDynFor(id)
|
||||
s.npcDyn[id] = dyn
|
||||
const isCalamity = !!s.calamity
|
||||
const rate = WORLDSIM.npcTradeRate * (isCalamity ? 0.7 : 1)
|
||||
const stance2 = (dyn.stance ?? 'guardian') as string
|
||||
const tradeMult = stance2 === 'expand' ? 1.3 : stance2 === 'endure' ? 0.7 : 1
|
||||
const rate = WORLDSIM.npcTradeRate * (isCalamity ? 0.7 : 1) * tradeMult
|
||||
// 景气驱动(1-2):入不敷出则衰、仓廪常足则旺
|
||||
let prosperity = dyn.prosperity ?? 50
|
||||
// 卖:NPC 抛货 → 池增;有货自给,景气微涨
|
||||
@@ -336,6 +340,16 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
const lifespan = MAJORS[curRealm].lifespan
|
||||
if (w.state.month === 1) {
|
||||
dyn.leaderAge++
|
||||
// 姿态评估(era>景气>power>relation)
|
||||
if (w.state.month === 1 && (w.state.year - (dyn.stanceSinceYear ?? 1)) >= 8) {
|
||||
const newStance = assessStance(w, id, dyn)
|
||||
if (newStance !== dyn.stance) {
|
||||
dyn.stance = newStance
|
||||
dyn.stanceSinceYear = w.state.year
|
||||
pushNews(w, s, [id])
|
||||
w.log('info', `【天下】${def.name} 态度一变——${STANCE_NAME[newStance]}。`)
|
||||
}
|
||||
}
|
||||
// 关系漂移:年首各 ±5(邻近的讲合、世仇的愈深)
|
||||
for (const oid of ids) {
|
||||
if (oid === id) continue
|
||||
@@ -363,7 +377,10 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
const foe = w.state.npcFamilies[oid]
|
||||
if (!foe) continue
|
||||
const rel = dyn.relationsWithOthers[oid] ?? 0
|
||||
if (rel < -50 && w.rng.chance(WORLDSIM.npcEventChance)) {
|
||||
const foeStance = dyn.stance ?? 'guardian'
|
||||
const hateBar = foeStance === 'expand' ? -40 : foeStance === 'endure' ? -70 : -50
|
||||
if (foeStance === 'ally') void hateBar
|
||||
if (rel < hateBar && foeStance !== 'ally' && w.rng.chance(WORLDSIM.npcEventChance)) {
|
||||
// 1-3 蝴蝶效应:互攻按实力加权(强者越可能胜,弱者一败再败)
|
||||
const wSum = npc.power + foe.power
|
||||
const winner = w.rng.chance(wSum > 0 ? npc.power / wSum : 0.5) ? npc : foe
|
||||
@@ -384,6 +401,15 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
text: `${winner.name} 与 ${loser.name} 起衅——痛挫其锋,势力大动。`
|
||||
})
|
||||
w.log('info', `【天下】${winner.name} 击破 ${loser.name},气象一新。`)
|
||||
// 盟友求援:下盟友处告急,请其同盟(玩家)出手
|
||||
if (loser.allied) {
|
||||
s.distress = { id: loser.id, year, month: 1 }
|
||||
s.newsFeed.push({
|
||||
year, month: 1, src: loser.name,
|
||||
text: `${loser.name} 遭${winner.name}重创,遣使来吾族求援:请解囊相助。`, kind: 'npc'
|
||||
})
|
||||
w.log('bad', `【天下】盟邦 ${loser.name} 遇袭告急!`)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -391,10 +417,35 @@ function evolveNpc(w: World, s: WorldSimState, noise: number): void {
|
||||
}
|
||||
}
|
||||
|
||||
const STANCE_NAME: Record<NpcStance, string> = { guardian: '守成', expand: '扩张', endure: '隐忍', ally: '结盟' }
|
||||
|
||||
/** 姿态评估:乱世逼扩张、低谷存隐忍、盛世好结盟、元气足则守成 */
|
||||
function assessStance(w: World, id: string, dyn: NpcDynamics): NpcStance {
|
||||
const npc = w.state.npcFamilies[id]
|
||||
const era = ((w.state.worldSim as WorldSimState)?.era ?? 'pingshi') as string
|
||||
const prosperity = dyn.prosperity ?? 50
|
||||
const power = npc?.power ?? 100
|
||||
const relation = npc?.relation ?? 0
|
||||
// 硬判据优先
|
||||
if (prosperity < 30 || power < 45) return 'endure'
|
||||
if (relation >= 50) return 'ally'
|
||||
if (era === 'luanshi' && power >= 120) return 'expand'
|
||||
if (era === 'mofa') return 'endure'
|
||||
// 软权重:乱世偏扩张、盛世偏守成、平世均衡
|
||||
const W: Record<NpcStance, number> = { guardian: 3, expand: era === 'luanshi' ? 6 : 2, endure: era === 'mofa' ? 5 : 2, ally: era === 'shengshi' || era === 'pingshi' ? 4 : 1 }
|
||||
const pick = (Object.keys(W) as NpcStance[]).sort((a, b) => W[b] - W[a])
|
||||
// 用幂等 rng 抽取(先取最高权)+ 30% 软翻转
|
||||
const top = pick[0]!
|
||||
if (w.rng.chance(0.7)) return top
|
||||
return pick[1]!
|
||||
}
|
||||
|
||||
function initDynFor(id: string): NpcDynamics {
|
||||
const def = npcById(id)
|
||||
return {
|
||||
prosperity: 50,
|
||||
stance: 'guardian',
|
||||
stanceSinceYear: 1,
|
||||
leaderName: `${def.name.replace('氏', '')}氏宗主`,
|
||||
leaderRealmIdx: MAJOR_ORDER.indexOf(def.leaderRealm),
|
||||
leaderAge: 45,
|
||||
|
||||
Reference in New Issue
Block a user