OCC 战斗与关卡 · 开发实施方案 v1
后续内容方案(要做什么) 与 开发规格(字段与协议) 的执行版: 任务拆到"一个人能领走、有依赖、有验收、有人天"的粒度。
用法:拿到任务号(如 T1.3)→ 去规格查字段 → 写代码 → 跑该任务对应的 e2e → 才算完。 估量单位:人天(一个熟悉这个代码库的人)。总账 ≈ 64 人天 + 20% buffer ≈ 77 人天。
0. 交付边界
0.1 本期做
| 交付物 | 归属 |
|---|---|
| 任务节点系统(7 种规则)+ 撤离结算 | M1 |
| 图纸掉落 → 基地制造 → 装配 | M1 + M2 |
| 模块系统(4 槽 + 超载 + 容量 + 套装 + 22 个模块) | M2 |
| 6 种发射物(散花 / 回旋 / 集束 / 链弧 / 穿刺 / 哨戒) | M2 |
| 地貌管线 + 6 种新地貌 + 23 套主题 | M3a / M5 |
| 威胁等级 T1~T10 + 深潜 | M4 |
| 敌人 6 种 archetype + Boss 三阶段 | M0 / M3b |
| 环境危害 5 种(低温 / 打滑 / 雷击 / 熔岩 / 尘暴) | M4 |
0.2 本期不做(写下来防止被顺手加)
随机词条(已否决)、地形改造、地貌随事件变化、模块升级强化等级、随机生成关卡、 护送 NPC / 潜行、元素伤害表、PvP 排位、跑酷墙跳、独立段位。
0.3 一条硬边界
🔴 不动 plains 的地形公式、不动现有 battle / home 的世界参数、不动 profile.upgrades(冻结字段)。 这三样任何一处变了,老玩家的存档就会错位。本期所有改动只增不改。
1. 文件总表(一眼看清工作量)
1.1 新增(7 个源文件 + 11 个 e2e)
| 文件 | 行数估 | 职责 | 任务 |
|---|---|---|---|
server/missions.js | ~420 | 节点定义 + 纯函数(start / onKill / tick / settle / loadout) | T1.1 |
server/enemies.js | ~200 | archetype 表 + 配置集 + 威胁缩放 | T0.2 |
server/modules.js | ~260 | 22 个模块定义 + 套装判定 + 容量计算 | T2.1 |
public/src/missions.js | ~340 | HUD 目标条 / 撤离 / 结算 / 危害读数 | T1.6 |
public/src/modules.js | ~380 | 装配面板(5 格 + 容量 + 套装) | T2.3 |
public/src/terrain-style.js | ~300 | 6 种地貌的 elevation + 配套参数 | T3a.1 |
tools/e2e/*.cjs | 11 个 × ~90 | 见规格 §13 断言矩阵 | 各期 |
1.2 修改(16 个)
| 文件 | 改什么 | 风险 | 任务 |
|---|---|---|---|
server/server.js | 5 条新消息 + 下包 3 键 + bldRecipes 带 profile | 中 | T1.3 |
server/store.js | 9 个方法 + missions 自愈 | 中 | T1.2 |
server/npc.js | 按 archetype 生成;新增不动实体 | 🔴 高 | T0.2 |
server/world-boss.js | 三阶段 + 弱点 + 落点预警 | 🔴 高 | T3b.3 |
server/sim.js | 模块配方 + recipeIdsOf(type, profile) | 低 | T2.1 |
server/items.js | shard / circuit_worn / 新弹药 | 低 | T2.1 |
server/tech.js | 5 个模块解锁节点 + 2 个容量节点 | 低 | T2.1 |
server/world-config.js | 6 个字段 + 新星域 | 低 | T3b.1 |
server/world.js | applyWorldToConfig 传 terrainStyle | 🔴 高 | T3a.1 |
public/src/terrain-field.js | style 分派 + spawnPrefer | 🔴 高 | T3a.1 |
public/src/resource-field.js | oreWeights | 中 | T3a.4 |
public/src/theme.js | 23 套 theme(只加数据) | 低 | T3a.3 起 |
public/src/starmap.js | 节点层 UI | 低 | T1.4 |
public/src/paper-planes.js | 6 种 kind | 🔴 高 | T2.4 |
public/src/bag.js | equip.modules 槽位 | 中 | T2.2 |
public/src/main.js | 组装 + 消息路由 | 中 | T1.6 |
2. WBS
M0 · 最小爽点(4 天)
目的:今天上线就能感觉到不一样,顺手清掉挂最久的那个 bug。
| 任务 | 内容 | 文件 | 依赖 | 人天 | 验收 |
|---|---|---|---|---|---|
T0.1 | 特种弹药真消耗:客户端攻击不再本地扣,服务端在 fire 分支扣背包;ammoPref 为空时用标准弹(不扣) | server.js fire / bag.js | — | 0.5 | ammo_consume.cjs:爆破弹打 3 次背包 −3,且产生 AoE;标准弹不扣 |
T0.2 | enemies.js + npc.js 接 archetype(先只接 car / tank,保证零行为变化) | enemies.js npc.js | T0.1 | 1 | 焦土刷怪数量 / 位置 / 血量与改动前完全一致(回归对照) |
T0.3 | 两种新敌人:turret(固定,无移动)+ drone(飞行,自爆) | npc.js enemies.js | T0.2 | 1 | 铁锈带测试 world 上炮台不移动、无人机能撞到玩家并炸 |
T0.4 | 坚守节点最小闭环:信标实体 + hold 计时 + 三波敌人 + 失败判定 | missions.js(先只放 hold) server.js | T0.3 | 1 | 站 180s 通;信标被打爆 → missionEnd{reason:'death'} |
T0.5 | 散花发射物(emit:3 + 扇形) | paper-planes.js | T0.1 | 0.5 | 一次点击出 3 条尾迹,各自能命中不同目标 |
M0 完成定义:进焦土能接到"坚守"这一种节点,用散花清掉一波无人机,爆破弹真的炸且真扣弹药。
M1 · 节点图 + 图纸(8 天)
目的:把"关卡"这个概念立起来,并让首通变成唯一的变强来源。
| 任务 | 内容 | 文件 | 依赖 | 人天 | 验收 |
|---|---|---|---|---|---|
T1.1 | missions.js 完整:7 种 kind + 定义表 + 8 个纯函数 + 单测(纯函数可以 node 直跑) | missions.js | T0.4 | 1.5 | 单测覆盖 start/onKill/tick/settle 的分支;协议在此冻结 |
T1.2 | store.js 9 个方法 + missions 字段自愈 + active 落盘(立刻写一次 + 10s 节流) | store.js | T1.1 | 1 | 老存档读进来 missions 自动补默认;接单后强杀进程重连,active 还在 |
T1.3 | server.js 5 条消息 + npcTimer 挂 missions.tick(包 safe())+ 登录下包 3 键 | server.js | T1.2 | 1 | missionList 返回 5 个节点且带锁定原因;missionGoal 击杀后 200ms 内到达 |
T1.4 | 星图节点层:星域详情下半部列节点(图标 / 名称 / 通关态 / 威胁选择 / 出击按钮) | starmap.js | T1.3 | 1.5 | 5 个节点可点、锁定节点灰掉并给出原因、切威胁 1~3 可点 |
T1.5 | 撤离圈:服务端 canExtract(球面点积,R 用世界半径)+ 20s 站桩 + 失败分支(死亡 / 中止 / 超时) | missions.js server.js | T1.3 | 1.5 | extract_circle.cjs 三种拒绝 + 一种成功;死亡后背包掉 50% |
T1.6 | public/src/missions.js:HUD 目标条(4/12)、撤离倒计时、结算弹窗、断线恢复提示 | missions.js(新) main.js hud.js | T1.3 | 1 | 目标条每变一次就跟;撤离时全屏倒计时;结算弹窗列出拿到的东西 |
T1.7 | 图纸掉落与登记:lootDrop 里塞图纸 → lootPick 时 blueprintGrant → 推 blueprintGot | missions.js store.js server.js | T1.5 | 0.5 | mission.cjs:首通拿到 w_spread,重复不给图纸只给结晶 ×2 |
T1.8 | e2e 四个(mission / abort / death / resume)+ 修漏 | tools/e2e/ | T1.4~T1.7 | 1 | 四个全绿,且用独立 OCC_DATA(不撞共享档) |
M1 完成定义:玩家在星图上看到 5 个节点,打完"清扫"拿到图纸,回指挥中心造出散花,装上能打。 中途撤退不给首通。
M2 · 模块系统(9 天)
| 任务 | 内容 | 文件 | 依赖 | 人天 | 验收 |
|---|---|---|---|---|---|
T2.1 | modules.js 22 条定义 + 6 条配方 + recipeIdsOf(type, profile) 按图纸过滤 + tech.js 加 7 个节点 | modules.js sim.js items.js tech.js server.js | T1.7 | 1.5 | blueprint_recipe.cjs:没图纸时配方列表里没有它,捡到就有 |
T2.2 | 容量 / 套装计算 + equip.modules 槽位校验 + store 三个方法 | modules.js bag.js store.js | T2.1 | 1 | modules.cjs:容量 12 装不下 5+5+5;超载槽 cost ×2;防御套装 +2 容量 / −5% 速度 |
T2.3 | 装配面板(5 格 + 容量条 + 模块库 + 套装提示) | modules.js(新) bag.js | T2.2 | 2 | 拖进去立刻回包刷新;装不下时按钮禁用并说明"还差 3 容量" |
T2.4a | 三种发射物:spread / boomerang / cluster | paper-planes.js | T0.5 | 1.5 | 回旋能对同一目标判定 2 次;集束落地有预警圈 |
T2.4b | 三种发射物:chain / lance / sentry | paper-planes.js | T2.4a | 1.5 | 链弧跳 2 个目标;穿刺贯穿但不出地图;哨戒塔能自动索敌 |
T2.5 | 模块效果在机甲基数上生效(CD / 速度 / 摩擦 / 护盾 / 回复) | mecha.js player.js | T2.4b | 1 | 装冲刺后 Shift 有效且是 5s CD;冰面上刹车距离明显变长 |
T2.6 | e2e 两个 + 首次平衡实测(同节点打 5 组 build 记录时间) | tools/e2e/ | T2.3 T2.5 | 1 | modules.cjs 绿;Build 耗时表落在 _logs/ 里 |
M2 完成定义:玩家能装 3 个模块,明显感觉到"我的打法和别人不一样";容量挡住无限堆叠。
M3a · 地貌管线(5 天)
🔴 这是"新地貌也要做"的前提,必须先做,且做之前先停服。
| 任务 | 内容 | 文件 | 依赖 | 人天 | 验收 |
|---|---|---|---|---|---|
T3a.1 | 抽 terrain-style.js,plains 逐字复制现公式;terrain-field.js 改分派;world.js 传参;写 terrain_equiv.cjs | terrain-style.js(新) terrain-field.js world.js | — | 2 | 🔴 200 采样点误差 === 0;焦土/苗圃实拍与改动前逐像素对比无差异 |
T3a.2 | wasteland 地貌(低起伏 + 风蚀小脊 + 台地) | terrain-style.js | T3a.1 | 1.5 | 卫星图能看出"荒原"而不是"另一片草原";出生点不落水 |
T3a.3 | rust 主题七层(色带 / 植被 / 天空 / 雾 / 光 / 大气 / 雷达) | theme.js | T3a.2 | 1 | 七层都接(判据:任何一处颜色和焦土相同都值得怀疑) |
T3a.4 | spawnPrefer / oreWeights / 尘暴危害接入 | terrain-style.js resource-field.js world.js | T3a.2 | 0.5 | terrain_new.cjs 绿:两端出生点一致、锡矿仍可采 |
M3a 完成定义:一颗"荒原"星球能进去、能出生、能采矿、能建基地,且焦土一点没变。
M3b · 星域铺开 + Boss(9 天)
| 任务 | 内容 | 文件 | 依赖 | 人天 | 验收 |
|---|---|---|---|---|---|
T3b.1 | 铁锈带 world 配置(独立 seedOffset + 4 节点 + enemySet) | world-config.js missions.js | T3a.4 | 1.5 | 星图上铁锈带从"锁定"变"可跃迁" |
T3b.2 | 赤铁荒原(同 style 换 theme) | theme.js world-config.js | T3b.1 | 1 | 两张荒原图一眼看出是两颗星球 |
T3b.3 | Boss 三阶段 + 弱点 + 落点预警圈 | world-boss.js boss-fx.js | T3b.1 | 2.5 | 血量 70% / 30% 切阶段;榴弹落点有圈;弱点被科技 t3_bossintel 显示 |
T3b.4 | 星门等级门槛 + 解锁链(节点 → Boss → 下一星域) | tech.js missions.js starmap.js | T3b.3 | 1 | 没打 Boss 时下一星域锁定原因写"需击败铁锈带首领" |
T3b.5 | 节点 UI 支持多星域(现在只有 5 个节点的排版假设) | starmap.js | T3b.4 | 0.5 | 30 节点也不溢出、不重叠 |
T3b.6 | 性能实测(新地貌下 draw call / 三角形 / 帧率) | — | T3b.2 | 1 | 不超基线:draw call ~51、三角形 ~50.8 万(见性能方案) |
T3b.7 | 威胁曲线实测校准(T1~T7 各跑 3 次) | _logs/ | T3b.3 | 1.5 | 满科技合理 Build 卡在 T7~T8;T5 打得过、T10 打不过 |
M3b 完成定义:两颗新星球可玩,Boss 有机制不是血包,威胁等级能明显改变体感。
M4 · 终局(7 天)
| 任务 | 内容 | 依赖 | 人天 | 验收 |
|---|---|---|---|---|
T4.1 | 威胁等级全局生效(血量 / 伤害 / 数量 / 掉落按 §4.3 曲线) | T3b.7 | 1 | 同节点 T1 vs T7 的敌人血量差 2.4 倍、种类也不同 |
T4.2 | 深潜:波次 + 递增 + 撤离结算 | T4.1 | 1.5 | 打到第 8 波会被拦;撤离按波次给料 |
T4.3 | 5 种环境危害(低温 / 打滑 / 雷击 / 熔岩 / 尘暴)+ 全部可读 | T4.1 | 2 | hazard_readable.cjs:落雷前 2s 一定收到 hazard |
T4.4 | 每日合同(可选,先做接口后做 UI) | T4.2 | 1.5 | 每天 3 个随机节点 + 掉落加成,刷新不可重复领 |
T4.5 | e2e + 经济实测(产出 vs 消耗速率) | — | 1 | 每小时结晶产出与科技需求表对得上 |
M5 · 地貌扩充(22 天,分批)
每批 = 1 种地貌(4 天)+ 3 套 theme(4.5 天)+ 1 个星域节点包(1.5 天), 上线即开放 4 颗星域。顺序:ash → ice → volcanic → rift → ruin。
| 任务 | 内容 | 人天 |
|---|---|---|
T5.x.1 | 该地貌 elevation + 出生偏好 + 矿权重 + 危害 | 2 |
T5.x.2 | 该地貌的"玩法特征"落地(打滑 / 熔岩伤害 / 高低差) | 2 |
T5.x.3 | 3 套 theme | 4.5 |
T5.x.4 | 3 个星域的节点包 + 解锁链接入 | 1.5 |
T5.x.5 | 回归(地形等价 / 性能 / 平衡) | 1 |
3. 关键路径与并行
T0.1 → T0.2 → T0.3 → T0.4 ─┐
├→ T1.1(🔴 协议冻结)┬→ T1.2 → T1.3 ┬→ T1.4 ┐
T0.5 ──────────────────────┘ │ ├→ T1.5 ┤
│ └→ T1.6 ┴→ T1.7 → T2.1
↓
T3a.1 → T3a.2 → T3a.3 → T3a.4 ─────→ T3b.x ←────────── T2.2
三条可并行的支线(前提是 T1.1 已冻结协议):
| 支线 | 任务 | 可并行的人 |
|---|---|---|
| A 服务端 | T1.2 / T1.3 / T1.5 / T1.7 | 后端 |
| B 客户端 UI | T1.4 / T1.6 / T2.3 | 前端 |
| C 地貌与美术 | T3a.1 ~ T3a.4 / T3b.2 / 所有 theme | 另一人(技术美术) |
⇒ 两人并行时,64 人天可压到约 40 天(服务端 + 客户端);三人(加技术美术)可压到约 30 天。
4. 兼容性与回滚
| 项 | 做法 |
|---|---|
| 老存档 | 所有新字段读取时补默认(沿用 ensureWorlds 的自愈模式),不写迁移脚本 |
| 老客户端 | 新消息是 if 链末端新增,老客户端不会发也不会收 → 无影响;bag 包新增 3 键会被忽略 |
| 老玩法 | plains 地形 / battle 参数 / profile.upgrades 一字不动;tank 接 archetype 时先做"零行为变化"回归 |
| 开关 | 服务端加 OCC_MISSIONS=0,关闭任务系统(节点入口隐藏、active 忽略)—— 出问题不用回滚代码 |
| 地貌回滚 | 新星域失败只需从 worlds 里摘掉,plains 两球不受影响 |
| 数据污染 | 所有新 e2e 一律用 OCC_DATA 独立存档,不碰 _data/state.json |
5. 风险表
| # | 风险 | 影响 | 对策 |
|---|---|---|---|
| 1 | 🔴 plains 公式不等价 | 全存档坐标 / 建筑 / 障碍物 id 错位,不可逆 | T3a.1 先写等价断言再动代码;改前停服 + 备份 _data/ |
| 2 | 🔴 弹药扣费从客户端挪到服务端 | 出现"卡弹"(本地打了、服务端没扣/没发) | 客户端只做表现;服务端回包为准;ammo_consume.cjs 带"连打 20 次"压测 |
| 3 | 🔴 任务进度丢 | 玩家打完 12 只怪重连回 4/12 | active 立刻写一次 + 10s 节流;mission_resume.cjs |
| 4 | 新地貌性能超基线 | 帧率掉、移动端不可用 | T3b.6 实测;超了就砍 floraRate,不砍地形精度 |
| 5 | Boss 阶段化改坏现有天罗 | 老玩法回归、多人同时打出现判定错乱 | 阶段机只读服务端血量;保留旧分支开关一天 |
| 6 | 容量系统与 profile.upgrades 叠加算错 | 老玩家属性翻倍 / 归零 | wallet.derived 是两源叠加,只读不改;容量是独立字段 |
| 7 | 节点 UI 在 30 星域下溢出 | 星图变难用 | T3b.5 用 30 个假节点先做压力排版 |
6. 上线顺序
| 批次 | 内容 | 灰度判据 |
|---|---|---|
| 1 | M0 | 只有弹药 + 敌人 + 坚守节点,不加任何新 UI 入口(老玩家无感,只感觉"怪变多了") |
| 2 | M1 | 星图多一层节点;观察"接单率 / 通关率 / 撤离率"三个数 |
| 3 | M2 | 开模块系统;观察"平均装配数"(低于 2 说明容量太紧或获取太难) |
| 4 | M3a + M3b | 开铁锈带;观察"新星域首日到达率" |
| 5 | M4 / M5 | 逐批开 |
每一批上线前必过三项:
- 该批的 e2e 全绿(含
terrain_equiv) - 本机
scripts/svc.py status正常、_data已备份 - 发布走
OCC_SSH_TARGET=menpai-aliyun bash deploy/prod/push.sh,新增素材目录要手动加--exclude