v0.1.9: 百年报告 & 发布之窗(报告/迁移/阵型/冬祷/哨兵)

- 百年族史报告:buildReport 纯函数(人口/声望/战力三线、死因榜、突破密度、
  大比战绩、代际长度带)+ 春秋原 SVG 折线视图 + 自动措辞结语
- 存档迁移管线 v2:migrate 链(v1→v2)+ 未来版本拒载(TOO_NEW)+ 导出信封
  带 appId/schemaVersion + 导入全链路迁移校验;saveState 自动写 CURRENT_SCHEMA
- 战斗阵型:锋阵(+10%攻)/雁阵(御5%+溃伤-15%)/蛇阵(溃伤-30%),三处出战接入
  (遣队/大比/劫掠)点将面板下拉 + 战报首行标阵
- 冬至岁祷:每年十一月三选(观星+3声望/祈福全族修为/问卜吉凶)
- 发布准备:scripts/verify-release.mjs 验收哨兵(npm run verify/verify:package)
  + README 全面重写(架构/守护/版本沿革);版本号三处归一
- 测试 861→881(报告四例/迁移五链/阵型六则含胜率统计/冬祷四则);
  金钟罩三指纹零漂移;typecheck/build 通过
This commit is contained in:
2026-08-23 10:11:22 +08:00
parent 3195f67455
commit f5a2c1b68a
18 changed files with 654 additions and 50 deletions
+47 -2
View File
@@ -143,6 +143,20 @@ export function dynamicEventFor(id: string, w?: World): EventDef | undefined {
]
}
}
if (id === 'ev-winterprayer') {
return {
id,
name: '冬至岁祷',
category: 'daily',
weight: 0,
text: '冬至夜长。家祠前灯影摇曳,族长问:今岁如何告辞?',
options: [
{ label: '登坛观星', hint: '声望+3', eff: { rep: 3 } },
{ label: '合族祈福', hint: '全族修为小精进', eff: { memberBy: { by: 'inspire', target: 'all', n: 2 } } },
{ label: '投签问卜', hint: '或有吉凶', eff: { flag: { prayerAsk: true } } }
]
}
}
if (id === 'ev-recruit') {
return {
id,
@@ -262,6 +276,17 @@ export function eventRoll(w: World): void {
fire(w, `ev-tournament-${tourneyNext}`)
return
}
// 冬至岁祷(每年十一月一掷),只在 season 系统开启时
if (s.month === 11 && w.sysEnabled('season')) {
const key = `prayerDone-${s.year}`
if (!s.family.flag[key]) {
s.family.flag[key] = true
if (w.rng.chance(0.6)) {
fire(w, 'ev-winterprayer')
return
}
}
}
// 四邻回声(偶数年一掷,不论成败封缄)
if (s.year % 2 === 0) {
const key = `echoDone-${s.year}`
@@ -304,7 +329,14 @@ export function fire(w: World, id: string): void {
w.pendingEvent(id)
}
export function applyEventChoice(w: World, eventId: string, optionIdx: number, squad?: string[]): void {
export function applyEventChoice(
w: World,
eventId: string,
optionIdx: number,
squad?: string[],
_extra?: unknown,
formation?: string
): void {
const s = w.state
const def = findEvent(eventId, w)
if (!def) {
@@ -313,6 +345,7 @@ export function applyEventChoice(w: World, eventId: string, optionIdx: number, s
}
const opt = def.options[optionIdx]
if (opt) {
if (formation) opt.eff.formation = formation
applyEffect(w, opt.eff, squad)
if (def.once && !s.completedEvents.includes(def.id)) s.completedEvents.push(def.id)
}
@@ -404,7 +437,7 @@ export function applyEffect(w: World, eff: EffectDef, squad?: string[]): void {
if (!fam.techniques.includes(eff.addTech)) fam.techniques.push(eff.addTech)
}
if (eff.tournament) {
runTournament(w, squad)
runTournament(w, squad, eff.formation as never)
}
if (eff.apprentice?.build) {
buildApprentice(w)
@@ -419,6 +452,18 @@ export function applyEffect(w: World, eff: EffectDef, squad?: string[]): void {
finalizeApprentice(w, stayId, 'stay')
delete fam.flag['apprenticeStay']
}
if (eff.flag?.['prayerAsk']) {
const blessing = w.rng.chance(0.6)
if (blessing) {
w.state.family.stones += 60
w.log('good', '问卜得吉:仓库中多出一笔异财。')
} else {
w.state.family.stones = Math.max(0, w.state.family.stones - 40)
w.state.family.reputation -= 1
w.log('bad', '问卜得凶:家宅小有晦气。')
}
delete w.state.family.flag['prayerAsk']
}
if (eff.trib) {
const c = w.state.members[eff.trib.memberId]
if (c?.alive) {