10 · 技术架构与工程方案

10 · 技术架构与工程方案

目标:一套代码三端可用、低端手机流畅、模拟逻辑可独立测试、存档可靠、内容可增量生产。 状态:M1 已开工(2026-09-23)。技术栈已按本节方案复核落地,实际版本:Vite 8.3 / Vue 3.5 / TypeScript 6.0 / Pinia 4.0 / vue-i18n 11.4 / UnoCSS 66.10 / Dexie 4.4 / Vitest 5.0 / ESLint 10.11 / Playwright 1.63。


1. 技术栈(✅ 已按方案落地 M1)

层 选型(M1 已落地) 理由
构建 Vite 8 + TypeScript 6 团队已有 Vite+TS 经验(xiuxian_next);冷启动快、产物小
UI 框架 Vue 3 + Composition API + Pinia 三端响应式、表单面板多,Vue 模板对内容型 UI 效率高;AI 生成代码成熟
样式 UnoCSS(或 Tailwind)+ CSS Variables 设计令牌 原子化快速出稿;令牌承载绢本/灯下双主题
i18n vue-i18n 成熟、复数/日期格式化齐全
路由 Vue Router 面板场景多(人物/物品/卷宗),可分享/可返回
地图 自绘 Canvas 六边形层(非 DOM 格子、非重型地图库) 约 4900 格按视口裁剪;叠加 SVG 浮层做标记/浮卡
状态持久化 IndexedDB(Dexie 封装)+ 导出 JSON 容量大、无大小限制;支持手动导出"书档"
PWA vite-plugin-pwa + Service Worker(cache-first 静态资源,继承旧经验) 可装桌面、离线可玩
长列表 虚拟滚动(vue-virtual-scroller 等) 日志/名录/坊市,低配铁律
测试 Vitest(模拟层)+ Playwright(三端视口回归) 模拟规则必须有单测;Playwright 跑 375/1024/1280 三宽度
质量 ESLint + TypeScript strict + Prettier + simple-git-hooks
运行 Node 24(沿用 ../tools/node-v24.21.0-win-x64 绿色版)、npmmirror 免安装环境,沿用旧项目 .npmrc 经验

备选:React 生态。若更熟 React 可平替(Zustand/react-i18next),架构不变。已按 Vue 方案开工,之后不反复。

1.1 发布与客户端(2026-09-23 用户设想)

  • 试水发布:tabtab 创意工坊(或同类网页游戏平台)优先,验证玩法再决定投入;架构要求:
    • 平台差异全部收进 platform/publisher/ 适配层(登录/存档云同步/支付或广告/分享/排行榜/录屏),核心游戏零平台依赖,可一键构建"纯 PWA 版"与"tabtab 版";
    • 开工前先调研目标平台的接入 SDK、审核要求(版权/内容/版号相关)、包体与技术限制,调研结论补记本节;
  • PC 安装包(2026-09-23 用户定稿要做):试水验证玩法后,用 Tauri 2 打包 Windows(.msi/.exe)与 macOS(.dmg)桌面安装包;构建配置作为 src-tauri/ 独立目录,复用同一套前端产物;存档默认走应用数据目录(Tauri 下仍可用 IndexedDB,导出/导入 JSON 不变);
  • 移动端客户端:视试水数据再定,方案为 Capacitor(同一套代码包 iOS/安卓原生壳);
  • 不做小程序(2026-09-23 定稿)。

1.2 立绘/美术资产生成

  • 二期人物立绘走"特征数据 → 统一风格 AI 生图 → 人工抽检"管线(风格一致性靠固定提示词模板 + 参考图 + 命名约定),配置见 11;MVP 只用程序化字印/SVG。

2. 分层架构(最重要的工程决策)

┌──────────────────────────────────────────────┐
│ ui/        Vue:布局、面板、地图渲染、交互      │  可替换(将来小程序/Tauri 复用下层)
├──────────────────────────────────────────────┤
│ app/       用例编排:行动指令、快进、跨面板流程  │
├──────────────────────────────────────────────┤
│ game/      纯 TypeScript 模拟核心(零 DOM 依赖) │
│  ├─ world/     hex 生成、地形、灵脉、路水、秘境  │
│  ├─ time/      行动结算 + 惰性补跑(时辰/日/旬/年/旅途)│
│  ├─ society/   聚落、势力、民生数值、NPC、关系、组队│
│  ├─ cultivate/ 九境界小层、行为印记塑道、道心、寿元、功法│
│  ├─ craft/     丹/器/符/阵 配方与熟练度裁决       │
│  ├─ info/      知识过滤、人物认知卷、传闻、卷宗    │
│  ├─ news/      见闻卷:分卷入档、待传池、来源印记   │
│  ├─ events/    事件总线 + 事件抽样/事件链         │
│  ├─ combat/    实时演算引擎(可回放)、心魔 Recorder、妖兽│
│  ├─ life/      出身剧情、死亡痕迹、转世、小传、飞升│
│  ├─ truth/     假说库、碎片证据链、世界树元状态   │
│  └─ rng/       种子随机(mulberry32 等,可回放)  │
├──────────────────────────────────────────────┤
│ data/      内容即数据:出身剧情/事件库/丹方/器物/符箓/阵法/功法/对话/物品/假说碎片/地名库 │
├──────────────────────────────────────────────┤
│ platform/  storage(IndexedDB) / i18n / PWA / 性能档位 / 通知 / publisher(平台适配) │
└──────────────────────────────────────────────┘

铁律(写入项目规则):

  1. game/ 与 data/ 不得 import 任何 DOM/API(document/window/indexedDB 全禁),输入事件、输出状态,天然可测;
  2. UI 不直接改数据,一切行动经 app/ 调模拟层命令(command 模式),事件回流到 store;
  3. 内容作者加事件只动 data/,不碰引擎。

3. 目录规划(开工后落地)

mofa-xiuxian/
├─ docs/ design/                 # 已存在
├─ public/                       # 图标/PWA 清单/字体子集
├─ src/
│  ├─ main.ts App.vue router.ts
│  ├─ ui/  (layout/phone|pad|pc, panels/, map/, log/, components/)
│  ├─ app/ (commands/, selectors/)
│  ├─ game/(见 §2,含 *.test.ts)
│  ├─ data/(events/conditions/items/names/copy/…)
│  └─ platform/(db.ts i18n/ pwa/ perf.ts)
├─ locales/ (zh-CN/*.json, en/*.json …)
├─ tools/ (seed-viewer 世界地图调试台、century-run 百年快进、balance 报表)
└─ vite.config.ts …

4. 世界生成与随机

  • seed:字符串/数字 → mulberry32;地形用 value-noise(自实现,零依赖);
  • 生成阶段:先抽本世真相(九假说库取一,写入 seed) → 地形地势(含边山)→ 水网 → 灵脉与灵眼 → 秘境与阵眼 → 聚落(间距约束继承旧设精神:村/镇/城/寨按人口四级、避边山与妖地、宗门据脉、势力染色)→ 道路津渡 → NPC 名册 → 按该真相铺设碎片与诱饵 → 初始事件前史(世界开局时已是"活过很多年"的状态:有旧坟、旧怨、旧县志);
  • 生成器版本号写入存档:生成算法改动后旧存档仍可用原版本算法(按版本保留生成器),不做"更新即废档"。

5. 存档设计(继承 seed 入存档铁律)

SaveBundle {
  version, createdAt, generatorVersion
  world: { seed, hexes(压缩/可重建), factions, settlements, people, rumorGraph, chronicle, calendar }
  lives: [LifeRecord...]          // 历世小传与痕迹索引(归档)
  current: { character, location, knowledge, logCursor, relationships, recorder … }
  settings: { language, perfTier, theme, a11y… }
}
  • 自动档:每日 tick 落一次 + 重大行动后落;写 IndexedDB 用临时库原子切换(防写一半刷新损坏);
  • seed 从存档读,绝不从环境重新随机(旧项目血泪教训);
  • 版本迁移:src/game/save/ 链式迁移(v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11 均已落地),迁移必须有测试;
  • 手动导出/导入整份 JSON(“书档”),可分享给他人/AI 排障;
  • 多世:世界档一份、当前世一份、历世归档(见 05 §9);

5.1 存档时机与载入(2026-09-24 实现说明,回答"什么时候存档")

当前(M4)存档已升到 SaveBundleV11(见 src/game/save/save-bundle.ts),落 IndexedDB autosave 记录。以下为 v3 起的字段基线(v4 加 news,v5 改见闻形态并加 pending(§5.5),v6 加 disk(§5.6),v7 加 disk.pills(§5.7),v8 加 disk.beasts 与 pets(§5.8),v9 加 statuses(§5.9),v10 加 skills(§5.10),v11 加 recorder/dao/steward/lastFlyShichen(§5.12)):

分组 字段
世界 world.seed、world.generatorVersion(hex 不入档,按 seed 重建)
人物 player(坐标)、cultivation(炼气层/功行)、inventory(物品 id→数)、cash(铜钱/文)、relations(NPC id→人情值)
时间 clock.totalShichen、society.simDay(民生已结算日)、society.states(各聚落人口/粮/安定)
流程 origin(出身篇进度,含 entry 出身分类)、exam(入门考核状态)、knowledge(迷雾)、settings.themeMode

触发时机:

时机 触发动作
任何改变状态的行动后 吐纳、闭关、采药、炼丹、破境、交谈、逐格/多格移动、切换纸色
出身篇结束 篇末点「上路」进入自由游戏时
入门考核通过 三题答罢立即落档
页面隐藏时 visibilitychange → hidden(切后台/锁屏)立即 saveNow()
页面卸出时 pagehide(Safari 回收、关页)立即落档

载入时机:应用挂载(onMounted → store.restore())时先读 localStorage 同步快照、再回退 IndexedDB autosave——无档则建新世界并立刻落一份;旧档走链式迁移到当前版本(老档的 M3 状态从空白起步、民生不追溯往日);刷新/重开页面后回到存档时的位置、时辰与剧情节点,载入后补跑一次民生 tick(同日则空转)。世界 hex 不进存档,只存 seed,载入后按相同生成器重建(逐格一致)。

5.2 iOS Safari 切后台重载(2026-09-24,当日加强)

  • 现象:Safari 切后台再回来页面整页重新加载。根因是 iOS 内存管理回收后台标签页,前端无法阻止系统回收;
  • 对策(已实现):
    1. 同步快照兜底:persist() 每次除 IndexedDB 外,还把 bundle 同步写入 localStorage(platform/db.ts writeSnapshot)——setItem 同步落盘,不依赖 iOS 可能来不及完成的 IndexedDB 异步事务;
    2. restore() 优先读同步快照,再回退 IndexedDB;v1/v2 裸档统一走链式迁移;
    3. 挂载时 navigator.storage.persist() 请求本站存储持久化,降低低存储时被系统清理的概率;
    4. 持续控制内存(Canvas 按需渲染、不缓存全图),降低被回收概率;
  • 不依赖 bfcache:quick tunnel + 无缓存头下 bfcache 不保证生效,存档还原是主路径。

5.3 弹窗结构:固定标题栏(2026-09-24 起通用模式)

所有面板/弹窗统一结构(《人物卷》《更多》首批落地,以后新弹窗照此):

sheet 容器(flex 列,max-height,overflow:hidden)
├─ .sheet-header(flex:none;标题 + 关闭按钮,始终可见)
└─ .sheet-body(overflow-y:auto;内容再长也只滚这里)

禁止把整个弹窗设为滚动容器(滚动后标题与关闭会消失)。

5.4 妖兽遭遇编排层(M4 第一段,2026-09-24)

  • 演算核心(game/combat/combat.ts 自动演算、beasts.ts、player-combat.ts)之外,新增遭遇编排层 game/combat/encounter.ts:纯 TS、无 DOM、随机源注入,把「对峙 → 迎战/脱身 → 终局 → 善后」编成不可变状态机(beginEncounter/pickStance/tryFlee/engage/encounterAftermath);store 只驱动状态与持久化,UI 只呈现。
  • store 侧遭遇态 {data, phase:'standoff'|'watching'|'aftermath', shown} 不入存档:遭遇未闭环不落档(与出身篇同模式),移动函数尾部触发遭遇即 return,时辰结算推迟到「处置收束」时一并落。
  • rng 定名:触发 encounter、演算 combat、脱身按第几次 flee1/flee2…——同种子同行动可回放(单测覆盖)。
  • e2e 口子:?forceEncounter=1 跳过概率判定强制遭遇(与 ?origin=、?devfree= 同为测试 seam,仅 URL 生效)。
  • 战报放映:UI 侧 setInterval 逐笔揭示(650ms),组件卸载必须清定时器;快进/只看结果直接推进揭示计数,演算数据一次算完、放映只影响呈现。

5.5 见闻入档 v5 与待传池(M4 第二段,2026-09-25)

  • 新增纯 TS 模块 src/game/news/news.ts:NewsEntry { at, tab, text, detail, source, from? }(at = 落笔时的总时辰数)、NewsTab = all|deeds|world|local|friends|factions、NewsSource = deed|talk|intel;常量 NEWS_MAX=200、PENDING_MAX=60、INTEL_COST=3、INTEL_RELEASE=3、TALK_RELEASE=1;
  • 存档 v4→v5:SavedNews 由 {shichen,text,tab} 改为 {at,text,detail,tab,source,from?},并新增待传池 pending(坊间风闻先入池不显示);迁移函数 v4_to_v5.ts 把旧 v4 见闻全部收进 pending(source='talk'、tab 非法→'local'、detail 取 text),显示卷 news: [](老档不凭空冒出见闻);迁移链 v1→v2→v3→v4→v5;
  • 生成三路:玩家自身行为落行迹卷(deed)、叙话放出 1 条(talk,from=NPC 姓名)、聚落打探花 3 文放 3 条(intel,from=牙人)。旧静态假见闻 news.items 已删除;
  • 时辰历:clock.ts 新增 Season 与 seasonOfJieqi(jieqiIndex)(立春(3)入春/立夏(9)入夏/立秋(15)入秋/立冬(21)入冬,冬至为岁首故冬季跨年首年尾),store 暴露 season、phaseOfDay,供 ClockSheet.vue 呈现;
  • 出生落点:nearestSettlement(world, from, kind) 按六边距离取最近同类聚落(同距取先生成者,同 seed 同果)+ spawnKindFor(entry)(庄户→村、镇上平民/富家→镇);OriginProgress.entry 入档,旧档缺 entry 兜底 'farmer'。

5.6 识海盘入档 v6(M4 第五段,2026-09-26)

  • 新增纯 TS 模块 src/game/shenshen/disk.ts:SENSE_ITEMS(器物→种类/占用寸)、senseCap(layer)(<7 无神识 0;7→12、8→17、9→22 寸)、usedSense/canEquip/equip/unequip/unequipAll、FORMATIONS=['road','mountain','fight','bare'] 与 applyFormation;DiskState = {tools, charms, formation, presets}——四套阵势各记各的常备,装/卸即同步进当前阵势(syncPreset),空身恒空、空身装物自动归行路;
  • 存档 v5→v6:新增 disk: DiskState 字段;迁移函数 v5_to_v6.ts 迁移即空盘起步(老档不凭空得物);迁移链 v1→…→v6;hydrate 前置 reviveDisk 防御性还原(非法 id/阵势名兜底,老档缺 disk 走空盘);
  • store:disk/diskSheetOpen 态 + senseCapNow/senseUsed/senseBorn 派生 + equipFromBag(校验神识/库存/超限,符箓按张、法器同件一件,不耗时辰)/unequipToBag/unequipSlotAll/setFormation;
  • UI:DiskSheet.vue(两栏 tab、占用条、逐件卸下、囊中可上盘、四阵势);入口 = 更多面板 + PC 左栏「识海盘」(layer≥7 解锁,未生则锁)+ 修行面板神识行 + 行囊法器/符箓分页「查看识海盘」;CultivationPanel 层名按三窍显示(阴窍/阳窍/中宫),封顶与破境守卫改 QI_LAYERS=9。

5.7 战丹入盘 v7(M4 第六段,2026-09-26)

  • disk.ts 增第三栏:DiskKind='tool'|'charm'|'pill',SENSE_ITEMS 添止血散/回灵丹(各 1 寸);DiskLoadout{+pills}、usedSense/canEquip/equippedCount/unequipAll/applyFormation/syncPreset 全部带战丹栏;新增 overLimit(盘上占神超上限)、equipForce(强装:只认神识生否与器物来路,不拦超限)、consumeFromDisk(战中耗用扣账并同步常备);
  • 战斗侧(combat.ts):CombatRules.assist?: CombatAssist(ward/bolt/heal/qi 四档介入,只作用于甲方)、新事件 ward/bolt/pill/qi、CombatResult.aid: AidUsed(战中用去计数,供战后还账);介入不额外消耗随机源——同种子同配置仍逐笔回放;AidPermit{charms,pills}(-1 为尽用,默认尽用)入 EncounterState.permit,setPermit 只在对峙期可改;
  • 折算(player-combat.ts):法器加成(锈铁剑攻 +3、木符牌御 +2)、超限即乱(overLimit 时身法 -3);combatAssistFor(disk, permit) 确定性分配——护身符先于攻符、伤药先于回灵;常量 WARD_CUT=0.6、BOLT_DMG=12、HEAL_AT=0.35/HEAL_PCT=0.3、QI_AT=0.25/QI_PCT=0.5;
  • 存档 v6→v7:disk.pills(当前盘与三套常备各补空栏),迁移函数 v6_to_v7.ts;reviveDisk 兜底带 pills;
  • store:equipFromBag(itemId, force?)(超限时走强装并提示)、unequipSlotAll 带战丹栏、senseOver 派生、setEncounterPermit,startEncounterFight 以 combatAssistFor(disk, permit) 入战、finishEncounter 按 result.aid 从盘上扣实;
  • UI:DiskSheet.vue 三栏(战丹)+ 满则「强装」、超限朱红提示;BagPanel.vue 战丹分栏与强装;CombatEncounter.vue 对峙「物用许可」两行勾选(不动/一份/两份/尽用)+ 盘上清单 + 超限警示,战报补 ward/bolt/pill/qi 四句与善后「这一战用去」行;i18n 补 shenshen.tabs.pill/force/forceNote/overNote、combat.standoff.permit.*、combat.event.{ward,bolt,boltSuffix,pill,qi}、combat.aftermath.aidUsed、bag.tabs.pill/pillsEmpty、两味战丹物名与注;
  • 数据:alchemy.json 增止血散(当归/骨碎补)、回灵丹(茶/石菖蒲)两方(炼丹列表自动出现,UI 零改动)。

5.8 灵宠入盘 v8(M4 第八段,2026-09-27)

  • 新模块 src/game/beast/pets.ts:Pet{id,kind,contract,loyalty,tier,feed,mood,daysStarving}、makePet/feedPet/canFight/tickPets/feedWarnings/roleBonus(契约忠诚档、1 肉 3 日粮、断供萎靡 3 日→饿馁、15/7/3 预警、品种职能加成);
  • cultivation.ts 筑基化:RealmId、FOUNDATION_STAGES=4、foundationPillForStage、liquefyChance/liquefy(胜算 0.35+存气/100×0.65−旧败×0.15,clamp 0.05~1;败则 −40 存气);senseCap(layer,realm) 筑基 27/32/37/42;
  • disk.ts 第四栏:DiskKind='tool'|'charm'|'pill'|'beast',DiskLoadout{+beasts},canEquipBeast/equipBeast/unequipBeast,阵势/预设同步;灵宠每件 3 寸、仅 canFight(content)可装;
  • encounter.ts 加善后去向 disposition:'loot'|'tame'——tame 时 loot 为空;取材五类全加兽肉(蛟 2、余 1);player-combat.ts 加筑基基线与灵宠 roleBonus;
  • 存档 v7→v8:各阵势补 beasts:[]、bundle 加 pets:[],迁移函数 v7_to_v8.ts(迁移测试 3 项,手搭 v7 bundle 直断言);hydrate 兜底 realm='qi'、daysStarving=0,petSeq 从 id 尾号恢复,reviveDisk 过滤已不存在的宠;
  • store:pets/petSheetOpen + 凝液/喂食/放归/装盘/立契善后全链路(tryLiquefy 费 12 时辰,正关丹与助凝丹皆服);跨日 tick 接 tickPets;
  • UI/i18n:新 PetSheet.vue、盘第四 tab、遭遇「取材/立契」善后、修炼面板凝液段与筑基层名;新建 pet.json 语言卷;
  • 数据:alchemy.json 增煮泉散/凝露丹/液化金液丹/三田归元丹/筑基大丹五方;gathering.ts 山地少见加灵髓;坊市 BASE 增兽肉。

5.9 战斗状态入档 v9(M4 第九段,2026-09-28)

  • 新增纯 TS 模块 src/game/combat/status.ts:StatusKind='poison'|'chill'|'burn'|'dread'(毒/寒滞/灼/煞压);STACK_CAP(毒 3/寒 2/灼 2/煞 2)、PERSIST_SHICHEN(毒 6/寒 4/灼 3/煞 5 时辰/层);战斗内效果 poisonTickDamage(2×层 / 每 20 息)、burnTickDamage(1×层 / 每 25 息,最低 1)、chillSpeedPenalty(2×层,出手间隔用有效身法、下限 2)、dreadPowerMult(0.85^层);toPersisted(until = now + 层数×每层时长)、decayPersisted(按当前时辰滤到期项)、revivePersisted(载入防御还原);
  • 妖兽状态技(beasts.ts):statusAttack{kind,chance}——蜈毒 0.55、蛟寒 0.40、魈煞 0.40、狐灼 0.45,赤鬃狼无;仅妖兽战路径生效(自设守卫 if(self.statusAttack)),同门切磋随机流零改动;
  • 战丹四味入方入战(combat.ts/player-combat.ts):CombatAssist 增 cleanse/blood/bane/turtle,新事件 venom/chill/scorch/dread/tick/cleanse/blood/bane/turtle;确定性介入序 heal→qi→cleanse→blood→bane→turtle;常量 BANE_IMMUNE_BREATHS=90、燃血战后养伤 +12 时辰/折功行 10、龟息 atRatio=0.15 以 fled 收束;
  • 存档 v8→v9:新增 statuses: PersistedStatus[](战后余恙,until 为到期总时辰点),迁移函数 v8_to_v9.ts(老档一律空数组);载入经 revivePersisted 防御还原 + decayPersisted 按当前时辰衰减;链 v1→…→v9;
  • 数据:alchemy.json 增四味战丹方(jieHanQuDuDan/ranXueDan/biShaDan/guiXiWan);LOOT_TABLE 追加三妖材(duNang/jiaoJiao/huWei)。

5.10 技艺熟练度入档 v10(M4 第十段,2026-09-28)

  • 新增纯 TS 模块 src/game/craft/proficiency.ts(docs/15 §3.2、docs/16 §1):SkillId='alchemy'|'forge'|'gather'(炼丹/炼器/采药)、SkillTier='raw'|'familiar'|'journeyman'|'master'|'transcend'(生/熟/匠/师/化);TIER_THRESHOLDS=[0,20,40,60,80]、TIER_MID(回落台阶=所在档中段 0/30/50/70/90);SkillEntry{id,value,floor,lastUsedAt};
  • 成长:GAIN_K=24,gainFor 成功 max(1, round(base·K/(K+value)))(越高越慢、边际递减、最低 1)、失败为成功值 1/3 上取整;回落:DECAY_SHICHEN=360(30 日 ×12)、每满一个周期 −1、最低至 floor、未满不动、不改写 lastUsedAt;raiseSkill 先衰减再涨、floor 抬到新值所在档中段、封顶 100、纯函数;reviveSkills 载入防御还原(非法 id 丢弃、值/台阶夹 0~100、lastUsedAt 取整 ≥0);
  • 额外产出:bonusChance(生/熟 0、匠 0.10、师 0.18、化 0.25)、rollBonus 无论档位都恰掷一次 rng 再比概率(保证可回放);临档提示 NEAR_MARGIN=2、isNearNext;
  • store(src/app/store.ts):skills ref + activeSkills computed(展示前按当前时辰回落,同 activeStatuses 模式);gatherHerbs/craftPotion/forgeTool 成功后涨熟练度(以行动前 totalShichen 为 now,base 采药 2/炼丹 3/炼器 3),额外掷点一律追加在既有 rng 抽取之后;匠档以上额外产出一份(判定用 tierBefore,skills.bonus.*);跨档落行迹(skills.rise.*);
  • 存档 v9→v10:新增 skills: SkillEntry[],迁移函数 v9_to_v10.ts(老档空起点);保存前先 decaySkills 衰减、载入经 reviveSkills 防御还原;链 v1→…→v10;
  • i18n:词条在 locales/zh-CN/cultivation.json 顶层 skills 对象,解析路径 cultivation.skills.*(命名空间按文件名挂载);含 skills.name.*/skills.tier.*/skills.flavor.*(每线 5 档近况 + near 临档句)/skills.rise.*/skills.bonus.*/skills.title。

5.11 画符与人对人交涉(M4 第十一段,2026-09-28)

  • 画符裁决:新模块 src/game/craft/talisman.ts(纯 TS、无 DOM、无随机,与 forge.ts/alchemy.ts 同构:canDraw/draw,只裁成与不成);符样 data/recipes/talisman.json(火鸦符 huoYaFu=fuZhi×2+zhuSha×1、金甲符 jinJiaFu=fuZhi×1+zhuSha×2);常量 TALISMAN_SHICHEN=2、TALISMAN_LAYER=9(炼气九层中宫开,筑基以上自然可用);
  • 新物料与坊市:fuZhi 符纸(base 4)、zhuSha 朱砂(base 6)入 society/market.ts 与 data/item-names.json;store 入口 drawTalisman(修行面板「画符」小节),成功涨画符熟练度第四线(SkillId 增 talisman,base 3,匠档以上手熟多出一张),额外掷点追加在既有 rng 抽取之后;
  • 人对人交涉状态机:新模块 src/game/combat/parley.ts(纯 TS、无 DOM、随机源注入):shouldMeetBandits(danger,rng)(BANDIT_BASE_CHANCE=0.08 + 凶煞/100×0.08,约 8%16%)、makeBandit(common|ruthless,BANDIT_RUTHLESS_CHANCE=0.3,斤两 0.701.05、买路价 2048、赎金 1240)、beginBandit/tryPersuade/tryIntimidate/tryBribe/tryFlee/fightBandit、banditAftermath(release|ransom|execute);常量 PARLEY_SHICHEN=1、PARLEY_YIELD_RATIO=0.25(人对人不致死,同切磋收手线,复用 resolveCombat 的 yieldRatio)、PERSUADE_CHANCE=0.15、INTIMIDATE_BASE=0.45(+小层×0.05,封顶 0.85,亡命徒减半)、BACKFIRE_CHANCE=0.2;出手/交手的战报与观战复用妖兽遭遇栈,三段式为对峙 → 观战放映 → 了结屏;
  • 接入点:store 在荒野行路终点、妖兽判定未过之后滚 bandit 遭遇(actionRng('bandit'));交涉/抽身费 1 时辰,败北遭劫 = 被劫 min(手头钱, 赎金) + 养伤 24 时辰(DEFEAT_RECOVER_SHICHEN)+ 折功行 20(DEFEAT_PROGRESS_LOSS,沿用妖兽败战口径);
  • 存档 v10 不变:画符/交涉无结构变更;遭遇态不入档(沿用妖兽遭遇铁律,未闭环刷新即回滚)。v1 留后(留后,非 bug):匪战的物用许可与打法基调、处决的道心/名声代价(v1 仅落行迹)、§1.1/§1.3 其余分支;
  • UI 与 i18n:修行面板「画符」小节、BanditPanel.vue(三段式 + 五钮 + 善后三选 + 遭劫屏),文案走 cultivation.talisman.*/bag.items.fuZhi/zhuSha/combat.bandit.*(i18n 由 ui-dev 同轮落地,见 08 §3.5.2/§3.10)。

5.12 心魔战·御器·执事入档 v11(M4 第十二段,2026-09-28)

  • 心魔战首版:新模块 src/game/combat/heart-demon.ts(纯 TS、无 DOM、纯函数、随机由调用方注入):RecorderProfile + emptyRecorder/reviveRecorder(防御还原)、recordFight/recordParley/recordSettle(纯计数、不耗随机源、不改入参)、favoriteStance/favorsHeal、buildQueries([release,execute,bribe,heal,fierce] 取有记录者最多 3 道,无记录退回出身题两道)、状态机 beginHeartDemon/answerQuery/engageHeartDemon(玩家快照镜像作心魔、favoriteStance 定基调、favorsHeal 则带一剂伤药、无收手线)、善后 heartDemonAftermath(HEART_DEMON_SHICHEN=2;胜明 +1;败浊 +2、存气 −20);
  • 触发接入:store tryLiquefy 首破成功入筑基前期(layer===1)时立起照见并提前返回(不落档,闭环于 finishHeartDemon);首破未闭环即刷新则整体回滚到凝液前;?forceDemon=1 供调试(仅炼气九层圆满可触发);
  • 战斗核心扩展:combat.ts Combatant 增 healKit?: number,pillCheck 补乙方规则——跌至 HEAL_KIT_AT_RATIO=0.25 即自服、回 HEAL_KIT_HEAL_PCT=0.25、记一笔 pill;healKit 为 0 或未设即不带药,不耗随机源、旧回归零改动;
  • 御器飞行:world/movement.ts 加 FLY_MAX_STEPS=2、FLY_SHICHEN_PER_STEP=1、FLY_COOLDOWN_SHICHEN=6、canFlyBySteps(steps,now,lastFlyShichen)、flyCooldownLeft;store flyUnlocked(筑基中期,层 ≥2)/canFlyNow/flyCooldownLeft/flyTo,travelPath 加 opts.fly(飞掠不触发遭遇与路途事件、不耗沿途干粮,行迹走 cultivation.news.fly/flyDetail,落地记 lastFlyShichen);
  • 外门执事:society/factions.ts 加 STEWARD_MIN_LAYER=3、STEWARD_STIPEND={cash:45,ration:3}、STEWARD_TASK={shichen:8,cash:25}、canAppointSteward(realm,layer,examPassed,steward);store steward/stewardReady/appointSteward/stewardTask(差遣费 8 时辰,可能带见闻);claimStipend 按 steward 发放加厚月例;
  • 存档 v10→v11:新增 recorder: RecorderProfile、dao: DaoState(明/识/浊)、steward: boolean、lastFlyShichen: number(-999 表从未飞过);迁移函数 v10_to_v11.ts(补初值),载入经 reviveRecorder 防御还原;链 v1→…→v11;
  • i18n:词条走 heartdemon.*(demonName/standoff.title/query./news.breakthrough·victory·defeat)、sect.steward.* 与 sect.news.appoint/stewardTask*、map.fly.*、cultivation.news.fly/flyDetail(词条由并行工程师同轮落地)。

6. 性能与低配策略

  • 预算目标:375px 宽、4 核安卓、2GB 内存:交互响应 <100ms,日常不低于 50fps(文字应用应远高于此);
  • 惰性模拟:时间按行动结算、无后台空转;屏外 hex 与远端聚落仅在补跑时按旬/年降频聚合;具名 NPC 仅在玩家"认知范围"内全量;
  • 战斗演算:实时 tick 固定步长(建议 100ms 逻辑帧)、纯数据运算极轻(无图形时只有数值/状态机),远期动画层用对象池;文字战报用队列节流输出,低端机不被打字机效果拖垮;
  • 日志/名录/坊市全部虚拟列表;历史日志分页存 IndexedDB;
  • 地图按视口裁剪 + 三档渲染(墨线 → 淡染 → 精细符号),低端档关雾动画;
  • 自动画质/性能档位检测沿用旧项目策略(首屏跑轻量基准,可手动覆盖);
  • 字体子集化(只含常用字 + 术语用字),首屏 JS gzip 目标 <300KB(不含字体)。

7. 三端适配实现

  • 布局:CSS clamp + 容器查询(@container)+ 断点三档(见 08 §2);
  • 触控:Pointer Events 统一鼠标/触摸(旧项目经验);全局 touch-action 按面板管理;
  • PWA standalone 下安全区适配;横屏手机独立小布局;
  • PC:键盘导航齐全、焦点环、Ctrl+K 命令面板;
  • Playwright 三视口快照测试:375×812 / 1024×768 / 1280×800。

8. 可观测与调试(提效关键,别省)

  • tools/seed-viewer:网页调试台,看任意 seed 的 hex/灵脉/势力图、民生数值、NPC 网络;
  • tools/century-run:CLI 百年快进,输出人口/粮价/势力平衡报表(断言与基线对比,见 06 §8);
  • 游戏内隐藏"开发者卷"(三连点版本号):跳时间、看真实数值(灵脉/control)、注入事件;
  • 结构化日志(dev only):事件流可过滤可回放;
  • 错误上报默认不外发(单机隐私),但提供"打包错误书档给开发者"按钮。

9. CI(最小化)

  • GitHub/Gitee 任选;PR 必跑:typecheck + lint + Vitest + Playwright 三视口冒烟;
  • 平衡回归:century-run 关键指标超阈值(人口归零、势力统一)即失败。

10. 已知坑与教训(持续追加)

本节从第一行代码开始累积。历史教训预记(继承自旧项目):

  • seed 不进存档必出事;
  • 含中文的 .ps1 必须 UTF-8 with BOM(PowerShell 5.1 按 GBK 解析);
  • Service Worker 缓存策略必须版本化,否则用户永远卡在旧版;
  • 触控监听绑 canvas 而 HUD 拦截会导致摇杆失效——输入监听挂 window + 区域过滤;
  • iOS Safari 切后台 rAF 暂停,回前台要清理按键/进行态;
  • 外部进程可能回滚文件,交付前读回验证关键改动。

M1 落地新增(2026-09-23):

  • TS 6 起 baseUrl 弃用:删 baseUrl,paths 的值必须写相对路径("./src/*"),相对 tsconfig 解析;
  • 媒体查询不提升特异性:@media 内 [data-part='dock']{display:none} 与基础规则 .bottom-nav{display:flex} 特异性相同,源码靠后者胜,PC 下底栏漏出;跨断点覆盖要主动提高特异性(.shell [data-part='dock']);
  • Playwright 浏览器国内下载设 PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright;
  • vite.config.ts 等 Node 配置文件需要 @types/node 并加入 tsconfig types;
  • Vite 8 的 Host 校验会拦 cloudflare 隧道:dev/preview 默认只放行本机 Host,quick tunnel 回源的 *.trycloudflare.com 会被挡(403/Blocked request);须在 server/preview 配 allowedHosts: ['.trycloudflare.com'],并设 host: true 供局域网访问;quick tunnel 已于 2026-09-28 弃用(本网络对 *.trycloudflare.com 随机连接重置),该配置暂留备用;
  • 外网访问(2026-09-28 起):改走 Cloudflare Pages 静态托管——应用 npm run build 后 npx wrangler pages deploy dist --project-name=mofa-xiuxian → https://mofa-xiuxian.pages.dev/;文档站 npm run docs:build(tools/build-docs.mjs 静态导出 docs/、design/、README/AGENTS)后 npx wrangler pages deploy dist-docs --project-name=mofa-xiuxian-docs → https://mofa-xiuxian-docs.pages.dev/;Pages 访问 .html 会 308 去后缀(正常);外网是构建快照,本地改动后需手动重部署。

M2 补丁新增(2026-09-24):

  • 多格移动:world/pathfinding.ts Dijkstra 按地形时辰代价寻最省时路径(水格/环边山不可经);点地图任意可见格即出路线预览(全程时辰/合几日/途经格数/沿途最大凶险),确认后时钟一次推进、沿途记 visited;
  • 底栏换位:底栏第 4 槽由「见闻」改为「闭关」(打坐半日推进时间),见闻入口移入《更多》面板(更多中见闻是唯一已启用项);底栏 active 态按 view/moreOpen 判定;
  • 弹窗标题固定:见 §5.3,标题/关闭不得随内容滚走;
  • iOS Safari 回收:见 §5.2,系统无法阻止,靠隐藏/卸出前 flush 存档 + 挂载还原。

M3 新增(2026-09-24):

  • 存档升 v3:修行/行囊/铜钱/人情/民生台账/考核入档;v2→v3 迁移见 v2_to_v3.ts(M3 状态从空白起步,民生不追溯老档往日);
  • 惰性民生 tick:society/world-sim.ts 按"已结算日 simDay → 今日"一次性补算(日食粮、秋收、安定浮动、人口缓变、逢集出传闻),结果只依赖 seed+聚落 id+日序,可回放;百年(1200 日)快进有单测断言不失控;
  • 行动随机注入:每次行动的随机源 = seed:动作名:行动时点 totalShichen,保证同档操作可回放,禁止在动作流程里用 Math.random/Date.now;
  • 名册角色错位坑:循环里 push 之后 roster.length 已增长,再与循环变量 i 相加会跳角色(村医变货郎);用 push 前缓存的 base 下标;
  • 坊市:society/market.ts 物价按 seed+日序确定(逢五逢十开集),只在镇上集市日开放;买卖改铜钱与物品栏,钱/货不足出世界内说法的行内提示,不弹系统框;
  • 组队七耗 v1:入门考核通过后两位同门随行(SECT_TEAMMATES),此后行路按 rationNeed(总时辰, 2) 预扣干粮,不足则行程不成立并提示份数;单人行路暂不扣粮(M2 行为不变)。

M3 交互补强(2026-09-24 第二批):

  • Playwright reuseExistingServer 坑:preview 端口上若有旧构建的 server,测试静默复用旧版——代码改了测试却跑旧产物(表现为新属性/新行为全部"失效");改完代码必须先重新 npm run build,或在跑测前确认 4173 无残留 server;
  • 离场动画拦截事件坑:v-if=false 后元素在 leave 动画的 200ms 内仍在 DOM 且默认继续接收事件,会短暂"吞"掉下一层界面的点击;统一给 .sheet-leave-active { pointer-events: none };
  • 动画中读坐标坑:跟手拖拽松手后的吸附动画期间(260ms),元素位置不断变化;紧接着读取位置会拿到中途值,测试/二次手势都要等动画落定;
  • 见闻拖拽(已弃用 2026-09-24 第四批:热区太小、手感差):手机端 .log-panel 收起态改为 fixed 浮底,位置完全由 top/bottom 决定,transition 同一属性即可动画;切到任何其他功能(setView/openMore)自动收回;现改为箭头按钮点击展开/缩回,见第四批;
  • 闭关页:SeclusionPanel 选定时辰(±与 3/6/12 快捷),预览预计功行;可选上限=shichenToFull(按平均增益估算),实际闭关中功行一满即止、按实际用时上账;
  • 采药引导:村内采药按钮文案改「村外才有灵草」,点击直接送进行路地图;只有非聚落的可进入地形才真正执行采药。

实现沿革简表(M3 第三批起;逐句结论如下,存量正文细节见右列 § 号,决策见 12 决策日志):

批次 / 段次 日期 一句话(关键口径 / 数字 / 存档版本) 详见
M3 第三批 2026-09-24 修复宗门整世缺席(随机碰山 → 确定性全图扫描选祖庭)、首发三出身齐、外门日常三差事与月例 12 §M3
M3 第四批 2026-09-24 出身改随机投生、导航重构(此处·修行·行路·行囊·更多)、区域行动分两类、当前格气泡、传闻 4→11 12 §M3
M3 第五批 2026-09-24 地图相机化(拖动/捏放/滚轮)、灵气五档外显 spiritBand、逐格耗时 6/10/12/15/24→2/3/4/5/8 时辰、远眺提示 03 §12
M3 第六批 2026-09-24 见闻改流式 grid 项 + body fixed 根治错位、结果弹窗、见闻入档 v4、特殊格标注、缩放下限 0.35 §5.1、§5.3、03 §12
M4 预备(含第一段) 2026-09-24 纯 TS 战斗核心(事件驱动时间轴/三基调/重手力竭、妖兽五类)+ 遭遇编排三段式 §5.4
M4 第二段 2026-09-25 见闻重构(行迹卷、待传池)、出生落点、灵气星点五档、时辰历、夜间可读性;存档 v5 §5.5
M4 第三段 2026-09-26 妖材掉落、4~6 层丹方拆回四张、地形私藏 35%、坊市添老酒/铁落;无存档变更(v5) 16 §3.2
M4 第四段 2026-09-26 切磋·同门试手(收手线 yieldRatio 二成五、赛后赠伤药/讨教/别过) 15 §1.5
M4 第五段 2026-09-26 炼气 7~9 层与识海盘首版(神识 12/17/22 寸、法器/符箓两栏、四阵势常备);存档 v6 §5.6
M4 第六段 2026-09-26 识海盘入战(战丹栏、物用许可、强装/超限即乱、法器加成);存档 v7 §5.7
M4 第七段 2026-09-27 8 层阳窍炼器(猎叉攻 +4/骨盾御 +3,各 5 寸,一件 8 时辰);无存档变更(v7) 16 §4
M4 第八段 2026-09-27 筑基四阶(凝液/正关丹/12 时辰、神识 27/32/37/42)+ 灵宠契约(盘第四栏 3 寸/件、三级预警);存档 v8 §5.8
M4 第九段 2026-09-28 战斗状态系统(毒/寒滞/灼/煞压四类层数、战后持久余恙)+ 战丹余四味入方入战 + 三妖材(毒囊/蛟角/狐尾);存档 v9 §5.9
M4 第十段 2026-09-28 技能熟练度五档(炼丹/炼器/采药三线、边际递减、回落有台阶、匠档以上手熟多出一份);存档 v10 §5.10
M4 第十一段 2026-09-28 中宫符箓工艺(画符:两符样、符纸朱砂、第四熟练度线)+ 人对人交涉(剪径匪:五选项、收手线 25%、善后三选);无存档变更(v10,遭遇态不入档) §5.11
M4 第十二段 2026-09-28 心魔战首版(Recorder 画像/照见问答/明浊善后)+ 筑基中期御器飞行 + 后期外门执事 + 明/识/浊三数;存档 v11 §5.12