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(平台适配) │
└──────────────────────────────────────────────┘
铁律(写入项目规则):
game/与data/不得 import 任何 DOM/API(document/window/indexedDB 全禁),输入事件、输出状态,天然可测;- UI 不直接改数据,一切行动经
app/调模拟层命令(command 模式),事件回流到 store; - 内容作者加事件只动
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 内存管理回收后台标签页,前端无法阻止系统回收;
- 对策(已实现):
- 同步快照兜底:
persist()每次除 IndexedDB 外,还把 bundle 同步写入localStorage(platform/db.ts writeSnapshot)——setItem同步落盘,不依赖 iOS 可能来不及完成的 IndexedDB 异步事务; restore()优先读同步快照,再回退 IndexedDB;v1/v2 裸档统一走链式迁移;- 挂载时
navigator.storage.persist()请求本站存储持久化,降低低存储时被系统清理的概率; - 持续控制内存(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):skillsref +activeSkillscomputed(展示前按当前时辰回落,同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%)、1.05、买路价 20makeBandit(common|ruthless,BANDIT_RUTHLESS_CHANCE=0.3,斤两 0.7048、赎金 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.tsCombatant增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;storeflyUnlocked(筑基中期,层 ≥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);storesteward/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并加入 tsconfigtypes; - 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.tsDijkstra 按地形时辰代价寻最省时路径(水格/环边山不可经);点地图任意可见格即出路线预览(全程时辰/合几日/途经格数/沿途最大凶险),确认后时钟一次推进、沿途记 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 |