feat(shanhai): M5 代码灌装(占位资产)——5图/敌32/Boss10/sim参数化/meta解锁

- balance.js: MAPS 5图(裂谷原+不周山/昆仑墟/归墟/钟山占位)+ENEMIES 32+BOSSES 10+D2-D5解锁占位+各图波表占位
- sim.js: createSim mapId 参数化(按图取数,map1零变化硬证三局逐字节一致)+_debugFill限定敌混
- meta.js: unlockedMaps解锁持久化(存档data内,schemaVersion=1向后兼容)+胜局解锁下一图
- meta-tables.js: dex_full阈值gte:51->DEX_TOTAL动态(修既有硬编码双写bug)
- game-logic.js: 出击页地图行驱动MAPS+isMapUnlocked接meta
- 占位数值/sprite标待美术待平衡;map2-5占位pacing待平衡;选图入局接线待后续
- 回归全绿:真实13/13+meta11/11+build-tables9/9+meta-tables6/6;ui-trigger2 test4 pre-existing渲染层失败非M5引入
- M5完整验收需美术资产(图2-5/29兽sprite/BGM6)+真平衡扫描
This commit is contained in:
lili 2026-07-25 04:07:08 -07:00
parent 18ba0bd60f
commit 6ca44e3b2c
7 changed files with 419 additions and 30 deletions

View File

@ -174,3 +174,17 @@ R1 八字段不是并列的,存在依赖树,三条共前置基线各自被
逐层排除过程:round-7 修 juice 顿帧 bug 后九婴正常刷新但贴脸集火 rate=0(九婴不近身);扩 forensics 暴露 boss 坐标后 round-8 调九婴数值(hp7400→3600 等)群体仿真(sim-scan)显示多 build 能 win,但 CDP 真机 seed42 lose(出生 burst 不可持续);round-9 改 driver 抱稳安全距(删自保退远反射、tight-hold、冲撞切向闪避,走位机械目标全达成)仍 lose,坐实瓶颈在武器集火不到远处 Boss。sim-scan 群体"多 build win"是无满怪潮/不同度量的结果,被 CDP 真机满怪潮证伪。
九婴数值调整保留(balance.js 九婴 hp3600/dps30/冲撞四件削弱/狂化削平,狰王 hp2000;已调到普通人可用区间,群体层面有效)。**⑧win 胜路径记为已知限制,交 W-AXIS/游戏设计判断**:终 Boss 战是否给武器 Boss 优先级(动 sim.js/auto-targeting 机制)或降 1200s 后怪潮密度(动数值),二者均影响玩法平衡且与⑦c2 收敛反向拉扯,属游戏设计决策,不在采证范畴。driver(win-jiuying.mjs,坐标驱动贴脸集火)与 round-7~9 全套诊断证据留存山海 evidence/,游戏设计裁定后修复续跑可推进 win。
### 6.8 2026-07-25 背景线全收口 + ⑧win 胜路径达成(推翻 §6.7 已知限制)
背景拼接问题经五步彻底解决并定稿入库:① 纯代码缓解(视差+羽化)→ ② 加有界竞技场(ARENA 1560×2960,玩家 clamp + 镜头 clamp + drawPlayer 解耦)+ 接 AI 草图整幅背景图(创始人审"还行")→ ③ 微调贴边/裁切(clamp 边距提到岩壁内沿)+ 自然暗角 + 降颗粒 → ④ 治本去除 jpg 烤黑边(裁黑带 + bicubic 回填,四边岩纹收口)→ ⑤ 加边界后重跑⑧。背景定稿为 1560×2960 矿彩国风裂谷原整图、四边岩纹/暗崖收口、无拼接无黑框,右崖本征暗阴影保留作暗底夜巡风格。commit 链:3e4f6fa3(juice 修复+forensics)→ 6059f837(九婴数值+⑧已知限制初记)→ a4dc91c4(加边界+接图+微调)→ f6f817da(去黑边)→ 18ba0bd6(⑧win 达成+driver 适配)。
**⑧win 胜路径达成,推翻 §6.7「已知限制交游戏设计」**。round-14 加边界后 bounded-cruise 竖直椭圆巡航摊薄怪潮 + 满级 build(level 52)贴脸集火,九婴进入武器 AoE 有效射程(sustained rate 95-210 hp/s,非无界时的 5-8),硬证「巡山礼成」结算 + 九婴伏诛 + bossKills=2 + 满血 win。无界 + 低 build 时武器索敌确受限,有界 + 满级 build 贴脸则可达。⑧胜/负/重玩/进度四路径齐全,⑧可由 partial 升 verified(待创始人最终验玩)。普通玩家手玩贴脸集火的难度归⑦c2/可玩性评估,不阻断⑧胜路径成立。注意:降九婴 hp 使⑦c2 反向(max 漂到 xuanming@100% FAIL),⑦收敛与⑧win 的平衡待调参协调。
至此山海行纪金标阶段二①②③④⑧基本就绪(④⑧待创始人眼验/验玩),剩⑤BGM 双 missing、⑥M5 内容(地图2-5/敌32/D2-D5)、⑦c2 收敛三项需开发,加创始人验玩签认。
### 6.9 2026-07-25 M5 代码灌装完成(占位资产,三检查点)
M5 代码灌装经三检查点完成,全回归绿:检查点1 balance 数据层(MAPS 5图=裂谷原+不周山/昆仑墟/归墟/钟山占位、ENEMIES 3→32、BOSSES 2→10、D2-D5 解锁占位、各图波表占位)→ 检查点2 sim.js createSim mapId 参数化(按图取 waves/enemyMix/bossIds/arena,map1 零变化硬证:MAPS[0].waves===WAVES 同一对象 + 默认/mapId:'map1'/mapId:0 三局 1500 帧逐字节一致 + 非法 mapId 兜底)+ _debugFill 注敌限定当前图敌混 + game-logic 出击页地图行驱动 MAPS → 检查点3 meta.js unlockedMaps 解锁持久化(存档 data 内,save-progress 仅包壳不含 data 字段故可安全加字段,schemaVersion 保持 1 向后兼容,旧档 normalize 默认 ['map1'])+ 胜局顺序解锁下一图 + meta-tables.js dex_full 阈值 gte:51→DEX_TOTAL 动态(修既有硬编码双写 bug)+ game-logic isMapUnlocked 接 meta。
回归:真实行为 13/13 + meta.test 11/11 + build-tables 9/9 + meta-tables 6/6 全过;map1 pacing 11.21h 不被破坏。ui-trigger2 test4「空槽虚位」为 pre-existing 渲染层失败(基线即存在,与 M5 无关,渲染线另修)。占位数值/占位 sprite 均标待美术/待平衡;map2-5 占位 pacing(headless 快仿真 lose@12-41s)标待平衡,终 Boss atS=100000 占位不可达;选图入局接线(startRun 传 mapId)待后续检查点。M5 完整验收仍需美术资产(图2-5 tileset/29兽 sprite/BGM6 首)+ 真平衡扫描,代码灌装本身已闭环。⑥维度:代码灌装完成,占位资产待美术,仍 partial。

View File

@ -135,3 +135,7 @@ sot-impact: none本档是金标 SoT 的消费记录视图,不替代 docs/ar
round-14 加边界 + bounded-cruise 竖直椭圆巡航(摊薄怪潮)+ 满级 build(level 52)贴脸集火,⑧win 胜路径**达成**。硬证:07-win-jiuying.png「巡山礼成」结算(九婴伏诛、存活 20:17/20:00、击杀 5922、Lv52、Boss2)+ reachedWin.reached=win + bossKills=2(九婴+狰王)+ 九婴 hp 末段 3453→1921→0(rate 95→210 hp/s,贴脸 dist 145-243 进满级 AoE 甜区)+ latch.after=win pass=true。
上轮「⑧win 已知限制(武器索敌 nearestViaGrid 打怪不打 Boss)」结论**修正**:加边界摊薄怪潮 + 满级 build 贴脸时九婴进入武器 AoE 有效射程,win 可达(无界 + 低 build 时确受限,有界 + 满级 build 贴脸则可达)。注:00-win-result.json 的 strategyNote 自述「仍受武器索敌限制 sustained 5-8dps」系巡航段(九婴远处只吃溅射)观察与模板残留,与 KILL 段贴脸硬证(rate 95-210 + win 结算)矛盾,以硬证为准。win 依赖加边界 + 满级 build 贴脸(driver 自动化真玩);普通玩家手玩贴脸集火的难度归⑦c2/可玩性评估,不阻断⑧胜路径成立。⑧负(lose latch)/重玩(restart→play)/进度(推进 1200 九婴刷新)同轮重采有效。至此⑧胜/负/重玩/进度四路径齐全,⑧维度可由 partial 升 verified(待创始人最终验玩)。
## ⑥M5 代码灌装完成(2026-07-25,占位资产待美术)
M5 代码灌装三检查点完成:MAPS 5图 + ENEMIES 32 + BOSSES 10 + D2-D5 解锁 + sim mapId 参数化(map1 零变化硬证)+ meta 解锁持久化(存档向后兼容)+ 修 dex_full/_debugFill 扩表副作用。回归全绿(真实13/13+meta11/11+build-tables9/9+meta-tables6/6),map1 pacing 11.21h 不破。占位数值/占位 sprite 标待美术/待平衡,map2-5 占位 pacing 待平衡,选图入局接线待后续。⑥维度代码灌装完成,占位资产待美术 + 真平衡扫描,仍 partial。M5 完整验收需美术资产(图2-5/29兽sprite/BGM6)。ui-trigger2 test4 pre-existing 渲染层失败非 M5 引入。

View File

@ -14,6 +14,13 @@
* M3 增量20 分钟正式波表(WAVES 重排, 18:00 放量收割期)/ 潮汐脉冲(TIDE)/
* 精英词缀(ELITE+AFFIXES 6)/ 1 Boss(BOSSES,战技组入表)/ 局内事件 4 (EVENTS)/
* XP 经济二次曲线(单局升级 2228 次锚)/ 难度表增词缀密度轴(eliteMul/affixMax)
* M5 增量·检查点1=数据层灌表(占位)MAPS 5 (裂谷原/不周山/昆仑墟/归墟/钟山,各带波表引用/
* 敌混/Boss 引用/解锁条件)/ 敌表扩至 32 (ENEMIES,山海经兽谱五图分布)/ Boss 扩至 10(5 ×2,
* BOSSES)/ 难度 D2D5 解锁条件占位 + 波表压缩轴占位(waveCompress)/ map25 占位波表(WAVES_MAP2..5)
* 本检查点只灌数据层:sim.js 仍硬编码图 1(读全局 WAVES + BOSSES 表按 atS 出场),不读 MAPS/
* 新图波表;新增兽/新图 Boss 均不进图 1 行为路径( Boss atS 占位超图 1 局时长 ROUND_S=1200新兽
* 不进图 1 波表 mix) 游戏行为零变化,回归安全占位数值/占位 sprite 均注释标待平衡/待美术,
* sim mapId 参数化 + meta 解锁接线 + 真平衡扫描留待后续检查点
*/
'use strict';
@ -265,17 +272,30 @@ export const PASSIVES = [
];
/**
* 难度表 D0D5(§5.2数值曲线抬升+词缀密度+波表压缩;本期启用 D0/D1,
* D2D5 为草案骨架波表压缩轴随 M5 落地,locked 表示出击页暂锁)
* 难度表 D0D5(§5.2数值曲线抬升+词缀密度+波表压缩)
* M3 增词缀密度轴:eliteMul = 精英出现概率乘区;affixMax = 单个精英词缀叠加上限(§5.2词缀可叠加)
* M5 检查点1 增量(占位):
* waveCompress 波表压缩轴·占位待平衡§5.2波表压缩第三轴的表化高难度把波表时间轴压缩
* (同量怪在更短局内时长涌出,节奏更紧)1.0 = 不压缩(基准 20 分钟局);越小节奏越压
* d0/d1 1.0(本期启用档不压);d2d5 占位递减值,真值待 M5 平衡线扫描sim 当前不读本轴
* (波表时间轴接线留待 sim mapId 参数化同期),故加此字段零行为变化
* unlock 解锁条件·占位待 meta 解锁逻辑d2d5 meta 解锁条件声明,结构对齐 ROLES.unlock
* ({stat,gte}),供后续 meta 解锁接线复用 isUnlocked 模式;note 标明占位语义locked 本检查点
* 保持 true 不动出击页可选难度门(game-logic.js:119/134/337/467 消费 locked)行为零变化,
* meta 解锁逻辑接线后按 unlock 条件动态置 locked=false pacing §5.3:D2D3 = 第10h 攻坚,
* D4/D5 = 超承诺长尾
*/
export const DIFFICULTY = {
d0: { name: '平潮', hpMul: 1.0, dpsMul: 1.0, rateMul: 1.0, speedMul: 1.0, eliteMul: 1.0, affixMax: 1, locked: false },
d1: { name: '涌潮', hpMul: 1.15, dpsMul: 1.2, rateMul: 1.45, speedMul: 1.08, eliteMul: 1.3, affixMax: 2, locked: false },
d2: { name: '怒潮', hpMul: 2.0, dpsMul: 1.9, rateMul: 1.7, speedMul: 1.18, eliteMul: 1.6, affixMax: 2, locked: true },
d3: { name: '狂潮', hpMul: 2.8, dpsMul: 2.1, rateMul: 2.0, speedMul: 1.22, eliteMul: 2.0, affixMax: 3, locked: true },
d4: { name: '噬潮', hpMul: 3.8, dpsMul: 2.4, rateMul: 2.5, speedMul: 1.26, eliteMul: 2.5, affixMax: 4, locked: true },
d5: { name: '灭潮', hpMul: 5.0, dpsMul: 2.7, rateMul: 3.1, speedMul: 1.3, eliteMul: 3.0, affixMax: 5, locked: true },
d0: { name: '平潮', hpMul: 1.0, dpsMul: 1.0, rateMul: 1.0, speedMul: 1.0, eliteMul: 1.0, affixMax: 1, waveCompress: 1.0, locked: false },
d1: { name: '涌潮', hpMul: 1.15, dpsMul: 1.2, rateMul: 1.45, speedMul: 1.08, eliteMul: 1.3, affixMax: 2, waveCompress: 1.0, locked: false },
d2: { name: '怒潮', hpMul: 2.0, dpsMul: 1.9, rateMul: 1.7, speedMul: 1.18, eliteMul: 1.6, affixMax: 2, waveCompress: 0.9, locked: true,
unlock: { stat: 'mapWin', gte: 2, note: '占位待 meta 解锁逻辑(通关 2 图解怒潮)' } },
d3: { name: '狂潮', hpMul: 2.8, dpsMul: 2.1, rateMul: 2.0, speedMul: 1.22, eliteMul: 2.0, affixMax: 3, waveCompress: 0.82, locked: true,
unlock: { stat: 'mapWin', gte: 4, note: '占位待 meta 解锁逻辑(通关 4 图解狂潮)' } },
d4: { name: '噬潮', hpMul: 3.8, dpsMul: 2.4, rateMul: 2.5, speedMul: 1.26, eliteMul: 2.5, affixMax: 4, waveCompress: 0.74, locked: true,
unlock: { stat: 'diffClear', gte: 2, note: '占位待 meta 解锁逻辑(D2/D3 各通关若干解噬潮,长尾)' } },
d5: { name: '灭潮', hpMul: 5.0, dpsMul: 2.7, rateMul: 3.1, speedMul: 1.3, eliteMul: 3.0, affixMax: 5, waveCompress: 0.66, locked: true,
unlock: { stat: 'diffClear', gte: 4, note: '占位待 meta 解锁逻辑(D2D4 深通解灭潮,超承诺长尾)' } },
};
/**
@ -293,13 +313,63 @@ export const XP = {
};
/**
* 敌表(1 基础三兽;词缀精英/ Boss 已随 M3 落地=AFFIXES/BOSSES, 32 种扩表归 M5)
* 敌表(M5 扩至 32 = 1 基础三兽 + 29 兽占位;词缀精英/ Boss AFFIXES/BOSSES)
* dps = 接触时对玩家的每秒伤害;gem = 掉落灵晶价值(×波表 gemMul 经济乘区)
* 五图分布(§5.2 各图专属敌表;map 字段 = 该兽主属图, MAPS.enemyMix 与后续 sim 参数化引用):
* map1 裂谷原:/梼杌/穷奇( 3 ,行为锁定不变);map2 不周山 6;map3 昆仑墟 6;
* map4 归墟 6;map5 钟山 11每个新兽都被某图 MAPS.enemyMix 引用,无孤儿条目
*
* 占位纪律(M5 检查点1)
* 数值占位·待平衡新兽 hp/speed/dps/r/gem 均按图序基础抬升 + 定位(快脆/中坚/坦克)铺的
* 草案锚,非真平衡值;真值待 M5 平衡线按 §8.1 单位 HP(w)=H0×g^w 曲线逐图扫描收敛 1 三兽
* 数值保持 M3 原值不动(已随金标收敛,改动会破坏 sim 硬编码图 1 行为与既有测试)
* sprite 占位·待美术现有 render 层按 type 查内置帧表,查不到自动 fallback BEAST.zheng
* (render.js:554)/程序色块 C.qiongqi(render.js:558),故新兽暂无专属帧也不崩渲染即当前
* sprite 占位策略 = 复用 beasts 图集帧循环(zheng )+ 程序色兜底各兽专属 6 (§9 异兽 32×6)
* 待美术管线批产后补进 render BEAST 帧表;balance 层不内联 sprite 字段(渲染映射归 render 单点)
*/
export const ENEMIES = {
zheng: { name: '狰', hp: 22, speed: 88, r: 10, dps: 9, gem: 1 }, // 快而脆:压走位
taowu: { name: '梼杌', hp: 50, speed: 60, r: 14, dps: 13, gem: 2 }, // 中坚
qiongqi: { name: '穷奇', hp: 140, speed: 40, r: 20, dps: 18, gem: 4 }, // 慢坦克:压火力
// ── map1 裂谷原(基础三兽,M3 原值锁定;sim 图 1 波表 mix 实际敌混,改动破坏现行为)──
zheng: { name: '狰', hp: 22, speed: 88, r: 10, dps: 9, gem: 1, map: 'map1' }, // 快而脆:压走位
taowu: { name: '梼杌', hp: 50, speed: 60, r: 14, dps: 13, gem: 2, map: 'map1' }, // 中坚
qiongqi: { name: '穷奇', hp: 140, speed: 40, r: 20, dps: 18, gem: 4, map: 'map1' }, // 慢坦克:压火力
// ── map2 不周山(天柱折·焦土火山;基础血量 ≈图1×1.3。数值占位待平衡)──
bifang: { name: '毕方', hp: 30, speed: 92, r: 10, dps: 11, gem: 1, map: 'map2' }, // 火鸟·快脆(占位)
gudiao: { name: '蛊雕', hp: 65, speed: 58, r: 15, dps: 15, gem: 2, map: 'map2' }, // 雕身·中坚(占位)
zhuyan: { name: '朱厌', hp: 45, speed: 80, r: 12, dps: 13, gem: 2, map: 'map2' }, // 兆兵·快中(占位)
dangkang: { name: '当康', hp: 26, speed: 95, r: 9, dps: 8, gem: 1, map: 'map2' }, // 兆丰·脆(占位)
mingshe: { name: '鸣蛇', hp: 55, speed: 66, r: 13, dps: 14, gem: 2, map: 'map2' }, // 蛇·中(占位)
taotie: { name: '饕餮', hp: 180, speed: 38, r: 21, dps: 20, gem: 4, map: 'map2' }, // 贪食·坦克(占位)
// ── map3 昆仑墟(昆仑守卫;基础血量 ≈图1×1.7。数值占位待平衡)──
yingzhao: { name: '英招', hp: 40, speed: 90, r: 11, dps: 14, gem: 2, map: 'map3' }, // 昆仑卫·快(占位)
liwu: { name: '陆吾', hp: 85, speed: 55, r: 16, dps: 18, gem: 3, map: 'map3' }, // 昆仑神·中坚(占位)
zhujian: { name: '诸犍', hp: 36, speed: 96, r: 10, dps: 13, gem: 2, map: 'map3' }, // 快(占位)
zhuai: { name: '诸怀', hp: 70, speed: 62, r: 15, dps: 16, gem: 3, map: 'map3' }, // 中(占位)
yayu: { name: '猰貐', hp: 240, speed: 36, r: 22, dps: 23, gem: 5, map: 'map3' }, // 食人·坦克(占位)
xiezhi: { name: '獬豸', hp: 90, speed: 58, r: 16, dps: 17, gem: 3, map: 'map3' }, // 中坚(占位)
// ── map4 归墟(深渊之水;基础血量 ≈图1×2.2。数值占位待平衡)──
huashe: { name: '化蛇', hp: 50, speed: 94, r: 11, dps: 16, gem: 2, map: 'map4' }, // 水蛇·快(占位)
feiyi: { name: '肥遗', hp: 95, speed: 60, r: 16, dps: 20, gem: 3, map: 'map4' }, // 双头蛇·中(占位)
luoyu: { name: '蠃鱼', hp: 40, speed: 88, r: 10, dps: 14, gem: 2, map: 'map4' }, // 鱼·脆(占位)
fuzhu: { name: '夫诸', hp: 55, speed: 90, r: 12, dps: 17, gem: 2, map: 'map4' }, // 兆水·快(占位)
changyou: { name: '长右', hp: 110, speed: 56, r: 17, dps: 21, gem: 3, map: 'map4' }, // 中(占位)
xiangliu: { name: '相柳', hp: 320, speed: 34, r: 24, dps: 26, gem: 6, map: 'map4' }, // 九首·坦克(占位)
// ── map5 钟山(终图·混沌;基础血量 ≈图1×3。数值占位待平衡)──
hundun: { name: '混沌', hp: 420, speed: 32, r: 26, dps: 30, gem: 7, map: 'map5' }, // 巨坦(占位)
jiuwei: { name: '九尾', hp: 60, speed: 98, r: 11, dps: 18, gem: 2, map: 'map5' }, // 九尾狐·快脆(占位)
tiangou: { name: '天狗', hp: 70, speed: 95, r: 12, dps: 19, gem: 3, map: 'map5' }, // 快(占位)
bashe: { name: '巴蛇', hp: 500, speed: 30, r: 28, dps: 32, gem: 8, map: 'map5' }, // 吞象·巨坦(占位)
fei: { name: '蜚', hp: 120, speed: 64, r: 17, dps: 23, gem: 3, map: 'map5' }, // 兆疫·中(占位)
qitu: { name: '鵸鵌', hp: 55, speed: 92, r: 11, dps: 17, gem: 2, map: 'map5' }, // 鸟·快(占位)
shusi: { name: '数斯', hp: 45, speed: 86, r: 10, dps: 15, gem: 2, map: 'map5' }, // 脆(占位)
yong: { name: '颙', hp: 130, speed: 58, r: 18, dps: 24, gem: 4, map: 'map5' }, // 中(占位)
lilu: { name: '狸力', hp: 65, speed: 90, r: 12, dps: 18, gem: 2, map: 'map5' }, // 快(占位)
huahuai: { name: '猾褢', hp: 140, speed: 60, r: 18, dps: 25, gem: 4, map: 'map5' }, // 中(占位)
baize: { name: '白泽', hp: 160, speed: 56, r: 19, dps: 26, gem: 5, map: 'map5' }, // 瑞兽·中坚(占位)
};
/**
@ -384,7 +454,7 @@ export const BOSSES = [
{
id: 'zhengwang', name: '狰王·赤鬣', atS: 600,
hp: 2000, speed: 55, r: 30, dps: 38, gem: 60, gemPer: 4, // hp 3000→2000:⑧win 让普通人在 6001200 窗斩杀,免与九婴分火力
slowResist: 0.8, dropHarvest: true, final: false,
slowResist: 0.8, dropHarvest: true, final: false, map: 'map1',
skills: {
charge: { cooldown: 9, telegraphS: 0.9, speedMul: 3.8, durS: 0.7 }, // speedMul 5.2→3.8:冲撞减速,普通人可侧移躲
summon: { cooldown: 14, n: 6, type: 'zheng' },
@ -393,13 +463,211 @@ export const BOSSES = [
{
id: 'jiuying', name: '九婴', atS: 1200,
hp: 3600, speed: 48, r: 38, dps: 30, gem: 100, gemPer: 4, // hp 7400→3600(降击杀时间,107dps≈34s)/ dps 60→30(抬存活窗,贴身毙命 1.67s→3.3s)
slowResist: 0.8, dropHarvest: false, final: true,
slowResist: 0.8, dropHarvest: false, final: true, map: 'map1',
skills: {
charge: { cooldown: 12, telegraphS: 1.3, speedMul: 3.6, durS: 0.6 }, // 冲撞四件:cd 8→12(更疏)/预警 0.8→1.3(可读+侧闪)/速 5.5→3.6(264→173px/s 可躲)/程 0.75→0.6(横扫更窄)
summon: { cooldown: 18, n: 5, type: 'taowu' },
enrage: { hpPct: 0.3, speedMul: 1.2, dpsMul: 1.25 }, // 狂化削平:speedMul 1.35→1.2 / dpsMul 1.4→1.25
},
},
/*
* M5 检查点1:map25 各双 Boss 占位( 8,5 ×2 = 10 含图1 Boss)
* atS 占位超界·保回归 Boss atS 全设 100000( 图1 局时长 ROUND_S=1200),sim 硬编码图1
* 按全 BOSSES atS 出场(sim.js:637),故新 Boss 在图1 局永不 spawn 行为零变化测试不破
* map 字段标归属图; atS( Boss600 / Boss1200) sim mapId 参数化后按图配
* final 语义每图终 Boss 击杀即该图胜利(对齐图1 jiuying final:true):map2 天柱 / map3 陆吾 /
* map4 共工 / map5 烛龙 为各图终 Boss(final:true); Boss final:false Boss spawn 图1,
* final 标记不触发图1 判胜 安全
* 数值/战技占位待平衡hp/dps/charge/summon/enrage 均按图序抬升铺草案锚,非真值;真值待 M5
* 平衡线参照图1 九婴dps/存活窗不等式(本表顶部 round-8 调参注)逐图校准
* sprite 占位待美术render bossDef.id BOSS_SPRITE,查不到 fallback 'luwu'(render.js:535),
* 故新 Boss 暂无专属帧也不崩渲染; Boss 专属 10 (§9 Boss 10×10)待美术批产补 render BOSS_SPRITE
* 命名id 前缀 boss_ 与敌表 key 区分(避免与敌表 xiangliu/hundun 等同名兽混淆);名取山海经公版
* 大兽原创演绎,代理定名,最终命名待文案
*/
// ── map2 不周山双 Boss(占位)──
{
id: 'boss_yanling', name: '炎灵·毕方', atS: 100000, map: 'map2',
hp: 2600, speed: 58, r: 30, dps: 42, gem: 60, gemPer: 4,
slowResist: 0.8, dropHarvest: true, final: false,
skills: {
charge: { cooldown: 9, telegraphS: 0.9, speedMul: 4.0, durS: 0.7 },
summon: { cooldown: 14, n: 6, type: 'bifang' },
},
},
{
id: 'boss_tianzhu', name: '天柱·不周守', atS: 100000, map: 'map2',
hp: 4200, speed: 50, r: 38, dps: 34, gem: 100, gemPer: 4,
slowResist: 0.8, dropHarvest: false, final: true,
skills: {
charge: { cooldown: 12, telegraphS: 1.2, speedMul: 3.6, durS: 0.6 },
summon: { cooldown: 18, n: 5, type: 'gudiao' },
enrage: { hpPct: 0.3, speedMul: 1.2, dpsMul: 1.25 },
},
},
// ── map3 昆仑墟双 Boss(占位)──
{
id: 'boss_yingzhao', name: '英招·昆仑卫', atS: 100000, map: 'map3',
hp: 3200, speed: 60, r: 31, dps: 46, gem: 60, gemPer: 4,
slowResist: 0.8, dropHarvest: true, final: false,
skills: {
charge: { cooldown: 9, telegraphS: 0.9, speedMul: 4.2, durS: 0.7 },
summon: { cooldown: 14, n: 6, type: 'yingzhao' },
},
},
{
id: 'boss_luwu', name: '陆吾·昆仑君', atS: 100000, map: 'map3',
hp: 5000, speed: 52, r: 39, dps: 38, gem: 100, gemPer: 4,
slowResist: 0.8, dropHarvest: false, final: true,
skills: {
charge: { cooldown: 12, telegraphS: 1.2, speedMul: 3.6, durS: 0.6 },
summon: { cooldown: 18, n: 5, type: 'liwu' },
enrage: { hpPct: 0.3, speedMul: 1.22, dpsMul: 1.28 },
},
},
// ── map4 归墟双 Boss(占位)──
{
id: 'boss_xiangliu', name: '相柳·九首蛇', atS: 100000, map: 'map4',
hp: 4000, speed: 56, r: 33, dps: 50, gem: 60, gemPer: 4,
slowResist: 0.8, dropHarvest: true, final: false,
skills: {
charge: { cooldown: 9, telegraphS: 0.85, speedMul: 4.2, durS: 0.7 },
summon: { cooldown: 14, n: 6, type: 'huashe' },
},
},
{
id: 'boss_gonggong', name: '共工·怒触山', atS: 100000, map: 'map4',
hp: 6000, speed: 48, r: 42, dps: 42, gem: 100, gemPer: 4,
slowResist: 0.8, dropHarvest: false, final: true,
skills: {
charge: { cooldown: 12, telegraphS: 1.2, speedMul: 3.8, durS: 0.6 },
summon: { cooldown: 18, n: 5, type: 'xiangliu' },
enrage: { hpPct: 0.3, speedMul: 1.24, dpsMul: 1.3 },
},
},
// ── map5 钟山双 Boss(占位;终图终 Boss = 烛龙)──
{
id: 'boss_hundun', name: '浑沌·无面', atS: 100000, map: 'map5',
hp: 5200, speed: 54, r: 36, dps: 54, gem: 60, gemPer: 4,
slowResist: 0.8, dropHarvest: true, final: false,
skills: {
charge: { cooldown: 9, telegraphS: 0.85, speedMul: 4.4, durS: 0.7 },
summon: { cooldown: 14, n: 6, type: 'hundun' },
},
},
{
id: 'boss_zhulong', name: '烛龙·钟山神', atS: 100000, map: 'map5',
hp: 7500, speed: 46, r: 44, dps: 45, gem: 120, gemPer: 4,
slowResist: 0.8, dropHarvest: false, final: true, // 终图终 Boss:击杀即全篇胜利(占位,数值待平衡)
skills: {
charge: { cooldown: 12, telegraphS: 1.2, speedMul: 3.8, durS: 0.6 },
summon: { cooldown: 18, n: 5, type: 'jiuwei' },
enrage: { hpPct: 0.3, speedMul: 1.26, dpsMul: 1.32 },
},
},
];
/**
* 地图表(5 ;M5 检查点1 数据层接入点声明)
* §5.2 地图 5,各带 1 个空间机制 / 专属敌表 / Boss / 专属 BGM / 专属背景本表是后续 sim mapId
* 参数化的**单一接入点**:每图声明 id/name/主题/战场(arena)/波表(waves)/敌混(enemyMix)/
* Boss(bossIds)/背景(bg)/解锁条件(unlock)
*
* 当前不接线(保回归)
* sim.js 本检查点仍硬编码图 1:波次读**全局 WAVES**(=MAPS[0].waves 同一对象)Boss 按全 BOSSES
* atS 出场敌按图 1 波表 mix 均不经由本表 MAPS 是纯新增声明数据,sim/meta 都不读,
* 游戏行为零变化sim mapId 参数化后改读 MAPS[mapId].{waves,arena,bossIds,}(留待后续检查点)
*
* 占位纪律
* map1 裂谷原:映射现状(现主题/现波表/现敌混/现双 Boss/现背景),行为锁定不变
* map25:主题为代理定名(待美术/文案);波表 = 图1 波表缩放变体(wavesVariant,占位待平衡);
* 敌混/Boss 引用本图 ENEMIES/BOSSES 条目;arena 暂复用图1 尺寸(各图专属战场待 M5 );
* bg 占位待美术(各图专属背景图集);unlock meta 解锁条件占位(结构对齐 ROLES.unlock,待接线)
*/
/**
* map25 占位波表变体工厂(占位待平衡):以图 1 波表 WAVES 为基线,按图序做到达率(rate×rateScale)/
* 个体血量(hpMul×hpScale)缩放 + 敌混替换为该图敌种;时刻点(t)与经济/速度乘区沿用图1(占位)
* 纯占位真波表待 M5 平衡线逐图扫描收敛;此处只为让 MAPS 各图有可读波表引用sim 不读本表
*/
const wavesVariant = (mix, rateScale, hpScale) => WAVES.map((row) => ({
...row,
rate: Math.round(row.rate * rateScale * 100) / 100,
hpMul: Math.round(row.hpMul * hpScale * 1000) / 1000,
mix,
}));
/** map2 不周山波表(占位:图1 节奏 ×1.1 到达 / ×1.15 血量;敌混=不周山六兽)。 */
export const WAVES_MAP2 = wavesVariant(
{ bifang: 0.45, gudiao: 0.22, zhuyan: 0.13, dangkang: 0.08, mingshe: 0.08, taotie: 0.04 }, 1.1, 1.15);
/** map3 昆仑墟波表(占位:×1.2 到达 / ×1.35 血量;敌混=昆仑墟六兽)。 */
export const WAVES_MAP3 = wavesVariant(
{ yingzhao: 0.4, liwu: 0.22, zhujian: 0.13, zhuai: 0.12, xiezhi: 0.08, yayu: 0.05 }, 1.2, 1.35);
/** map4 归墟波表(占位:×1.3 到达 / ×1.6 血量;敌混=归墟六兽)。 */
export const WAVES_MAP4 = wavesVariant(
{ huashe: 0.38, fuzhu: 0.2, feiyi: 0.17, luoyu: 0.1, changyou: 0.1, xiangliu: 0.05 }, 1.3, 1.6);
/** map5 钟山波表(占位:×1.45 到达 / ×2.0 血量;敌混=钟山十一兽,终图最高压)。 */
export const WAVES_MAP5 = wavesVariant(
{ jiuwei: 0.2, tiangou: 0.15, qitu: 0.12, lilu: 0.12, shusi: 0.1, fei: 0.1, yong: 0.07,
huahuai: 0.06, baize: 0.04, hundun: 0.02, bashe: 0.02 }, 1.45, 2.0);
/** 5 图表(序 = 解锁序;map1 首图恒解锁,map25 依 meta 进度顺序解锁〔占位待 meta 接线〕)。 */
export const MAPS = [
{
id: 'map1', name: '裂谷原',
theme: '山海裂隙,百兽潮涌出处;赭石裂谷、岩画图腾(现图1,主题/行为锁定不变)',
arena: ARENA, // 现图1 战场 1560×2960
waves: WAVES, // 现图1 波表(sim 当前读全局 WAVES,与此同一对象)
enemyMix: ['zheng', 'taowu', 'qiongqi'], // sim 图1 波表实际敌混(保持现行为;M5 扩混占位待平衡)
bossIds: ['zhengwang', 'jiuying'], // 图1 双 Boss(中=狰王 / 终=九婴)
bg: 'bg-map1-arena-01', // 现图1 背景(资产一期已入库)
bgm: '占位待美术(map1 专属 BGM,M5 BGM6 之一)',
unlock: { always: true }, // 首图恒解锁
},
{
id: 'map2', name: '不周山',
theme: '天柱折、地维绝;焦土火山,毕方衔火而行(占位主题,代理定名·待美术/文案)',
arena: ARENA, // 占位:暂复用图1 战场尺寸(各图专属 arena 待 M5 定)
waves: WAVES_MAP2, // 占位波表(图1 缩放变体,待平衡)
enemyMix: ['bifang', 'gudiao', 'zhuyan', 'dangkang', 'mingshe', 'taotie'],
bossIds: ['boss_yanling', 'boss_tianzhu'], // 中=炎灵·毕方 / 终=天柱·不周守
bg: '占位待美术(map2 专属背景图集)',
bgm: '占位待美术(map2 专属 BGM)',
unlock: { stat: 'mapWin', gte: 1, note: '占位待 meta 解锁逻辑(通关图1解锁不周山)' },
},
{
id: 'map3', name: '昆仑墟',
theme: '昆仑之墟,神明所守;英招陆吾,巡卫天柱(占位主题,代理定名·待美术/文案)',
arena: ARENA,
waves: WAVES_MAP3,
enemyMix: ['yingzhao', 'liwu', 'zhujian', 'zhuai', 'yayu', 'xiezhi'],
bossIds: ['boss_yingzhao', 'boss_luwu'], // 中=英招·昆仑卫 / 终=陆吾·昆仑君
bg: '占位待美术(map3 专属背景图集)',
bgm: '占位待美术(map3 专属 BGM)',
unlock: { stat: 'mapWin', gte: 2, note: '占位待 meta 解锁逻辑(通关图2解锁昆仑墟)' },
},
{
id: 'map4', name: '归墟',
theme: '归墟无底,众水所归;深渊化蛇,相柳盘波(占位主题,代理定名·待美术/文案)',
arena: ARENA,
waves: WAVES_MAP4,
enemyMix: ['huashe', 'feiyi', 'luoyu', 'fuzhu', 'changyou', 'xiangliu'],
bossIds: ['boss_xiangliu', 'boss_gonggong'], // 中=相柳·九首蛇 / 终=共工·怒触山
bg: '占位待美术(map4 专属背景图集)',
bgm: '占位待美术(map4 专属 BGM)',
unlock: { stat: 'mapWin', gte: 3, note: '占位待 meta 解锁逻辑(通关图3解锁归墟)' },
},
{
id: 'map5', name: '钟山',
theme: '钟山之巅,烛龙司昼夜;混沌未凿,终局之所(占位主题,代理定名·待美术/文案;终图)',
arena: ARENA,
waves: WAVES_MAP5,
enemyMix: ['hundun', 'jiuwei', 'tiangou', 'bashe', 'fei', 'qitu', 'shusi', 'yong', 'lilu', 'huahuai', 'baize'],
bossIds: ['boss_hundun', 'boss_zhulong'], // 中=浑沌·无面 / 终=烛龙·钟山神(全篇终 Boss)
bg: '占位待美术(map5 专属背景图集)',
bgm: '占位待美术(map5 专属 BGM)',
unlock: { stat: 'mapWin', gte: 4, note: '占位待 meta 解锁逻辑(通关图4解锁钟山·终图)' },
},
];
/**

View File

@ -25,7 +25,7 @@
'use strict';
import { createSim } from './sim.js';
import { JOYSTICK, ROUND_S, FEEDBACK, DIFFICULTY } from './balance.js';
import { JOYSTICK, ROUND_S, FEEDBACK, DIFFICULTY, MAPS } from './balance.js';
import { WEAPON_BY_ID } from './build-model.js';
import { createMeta } from './meta.js';
import { drawGame, layoutButton, layoutPauseButton, menuLayout } from './render.js';
@ -63,6 +63,7 @@ export function createGame(opts) {
let runRoleId = 'chixun'; // 本局出战角色(meta 增益来源)
let runIsDaily = false; // 本局是否每日挑战
let runDailyDate = ''; // 每日挑战日期串(dailyDone 去重键)
let runMapId = 'map1'; // 本局巡域(检查点3:出击页尚不能选图入局,startRun 恒图1;选图接线后传真图 → 解锁 mapN+1)
let recorded = false; // 本局是否已回写 meta(终态只回写一次)
/** 摇杆核(手 13;注入态与插件共核=driver 可驱真游戏,未注入直建同实现)。 */
@ -128,8 +129,14 @@ export function createGame(opts) {
rows.push({ type: 'char', roleId: r.id, name: r.unlocked ? `${r.name} · ${w ? w.name : ''}` : r.name, locked: !r.unlocked, selected: r.selected });
}
rows.push({ type: 'header', label: '巡域' });
rows.push({ type: 'map', name: '图一 · 裂谷原', locked: false, selected: true });
for (let i = 0; i < 4; i++) rows.push({ type: 'map', name: '未探明', locked: true });
// M5 检查点2/3:地图行遍历 balance.MAPS 生成(名取真图名、解锁态由 isMapUnlocked 出;render 按 locked 绘 🔒)。
// map1 首图恒解锁选中;map25 解锁态由 meta.isMapUnlocked 持久化驱动(检查点3 接线:通关 mapN 解锁 mapN+1;
// 当前仅图1 可入局,故实际解锁 progression = 图1 胜 → 图2 解锁,选图入局接线后自动支持图N→图N+1)。
// 行数恒 5(MAPS.length)= 出击页行几何不变(巡域 10..14 / 潮汛 15 / d0 16 / d1 17)。
for (const m of MAPS) {
const unlocked = isMapUnlocked(m);
rows.push({ type: 'map', mapId: m.id, name: m.name, locked: !unlocked, selected: m.id === 'map1' });
}
rows.push({ type: 'header', label: '潮汛' });
for (const key of Object.keys(DIFFICULTY)) {
rows.push({ type: 'diff', key, name: `${key.toUpperCase()} · ${DIFFICULTY[key].name}`, locked: DIFFICULTY[key].locked });
@ -138,6 +145,15 @@ export function createGame(opts) {
}
/** 重建出击页行(角色解锁/选中变化后刷新;行数恒 = roster 8 + 结构固定,menuList.count 不变)。 */
function refreshMenuRows() { menuRows = buildMenuRows(); }
/**
* 地图解锁判定(M5 检查点3 接线):首图 map1(unlock.always)恒解锁;map25 meta 解锁持久化
* 驱动meta.isMapUnlocked unlockedMaps 顺序态(通关 mapN 解锁 mapN+1)本函数仅翻转各行
* locked 布尔,行数恒 = MAPS.length(5),出击页行几何不变(巡域 10..14 / 潮汛 15 / d0 16 / d1 17)
*/
function isMapUnlocked(m) {
if (m.unlock && m.unlock.always) return true; // 首图恒解锁(兜底)
return meta.isMapUnlocked(m.id); // 余图读 meta 解锁持久化态
}
/** 滚动列表(canvas-ui-kit v1.1;拖=滚 / 点=选)。 */
const menuList = (plugins.uiKit && plugins.uiKit.makeScrollList ? plugins.uiKit.makeScrollList.bind(plugins.uiKit) : createScrollList)({
rect: menuLayout(viewport).listRect,
@ -160,7 +176,7 @@ export function createGame(opts) {
function startRun() {
const metaBonus = meta.deriveMetaBonus(); // 选中角色 trait + 已习得天赋 → sim 增益包
runRoleId = metaBonus.roleId; runIsDaily = false; runDailyDate = ''; recorded = false;
runRoleId = metaBonus.roleId; runIsDaily = false; runDailyDate = ''; runMapId = 'map1'; recorded = false;
sim = createSim({ seed: runSeed, difficulty: selDifficulty, meta: metaBonus });
scene = 'run';
paused = false;
@ -173,7 +189,7 @@ export function createGame(opts) {
function startDaily() {
const cfg = meta.getDailyConfig();
const metaBonus = meta.deriveMetaBonus(cfg.roleId); // 每日锁定角色(不吃出击页选择)
runRoleId = cfg.roleId; runIsDaily = true; runDailyDate = cfg.date; recorded = false;
runRoleId = cfg.roleId; runIsDaily = true; runDailyDate = cfg.date; runMapId = cfg.mapId || 'map1'; recorded = false;
sim = createSim({ seed: cfg.seed, difficulty: cfg.difficulty, meta: metaBonus });
scene = 'run';
paused = false;
@ -197,7 +213,7 @@ export function createGame(opts) {
gems: s.stats.gems, durationS: s.timeS,
weaponsSeen, passivesSeen, evolutionsSeen,
beastsSeen: s.seenBeasts, bossesSeen: s.seenBosses,
roleId: runRoleId, isDaily: runIsDaily, dailyDate: runDailyDate,
roleId: runRoleId, isDaily: runIsDaily, dailyDate: runDailyDate, mapId: runMapId,
});
} catch (_) { /* meta 回写降级:内存态仍在,不连坐渲染 */ }
refreshMenuRows(); // 角色可能新解锁 → 刷新出击页

View File

@ -157,6 +157,13 @@ export const TALENTS = [
{ id: 'zhong8', name: '还魂', desc: '符阵还魂,阵亡续命 +1 次', cost: 500, ring: 4, requires: ['zhong5', 'zhong6', 'zhong7'], effect: { axis: 'revive', value: 1 } },
];
/**
* 图鉴总条目数(收集投影上限;成就 dex_full 阈值与此同源,防双写漂移)
* buildDexCatalog 定义于下方图鉴函数声明整体提升,此处前向引用安全;
* 提前落 const 是为让成就表(紧随其后)能在字面量内直接引用 DEX_TOTAL(const 有暂时性死区,须先用后声明)
*/
export const DEX_TOTAL = buildDexCatalog().length;
/**
* 成就(60;§5.2 收集长尾)
* 每成就 = id / name / desc / cond{stat,gte}(累计战绩阈值, 即解锁)
@ -221,7 +228,7 @@ export const ACHIEVEMENTS = [
{ id: 'dex_10', name: '识微', desc: '图鉴收录 10', cond: { stat: 'dexSeen', gte: 10 } },
{ id: 'dex_25', name: '博览', desc: '图鉴收录 25', cond: { stat: 'dexSeen', gte: 25 } },
{ id: 'dex_40', name: '通志', desc: '图鉴收录 40', cond: { stat: 'dexSeen', gte: 40 } },
{ id: 'dex_full', name: '山海全志', desc: '图鉴全数收录', cond: { stat: 'dexSeen', gte: 51 } },
{ id: 'dex_full', name: '山海全志', desc: '图鉴全数收录', cond: { stat: 'dexSeen', gte: DEX_TOTAL } },
// 难度(4)
{ id: 'diff_d0', name: '平潮渡', desc: '通关平潮(D0)', cond: { stat: 'maxDiffBeatIdx', gte: 0 } },
{ id: 'diff_d1', name: '涌潮渡', desc: '通关涌潮(D1)', cond: { stat: 'maxDiffBeatIdx', gte: 1 } },
@ -260,8 +267,7 @@ export function buildDexCatalog() {
return cat;
}
/** 图鉴总条目数(收集投影上限;成就 dex_full 阈值与此同源,防双写)。 */
export const DEX_TOTAL = buildDexCatalog().length;
// 图鉴总条目数 DEX_TOTAL 已前移至成就表之前(const 暂时性死区:成就 dex_full 须在字面量内引用它,先用后声明)。
/**
* 每日挑战(§5.1 每日挑战;固定种子天下同题)

View File

@ -27,6 +27,9 @@ import {
ROLES, TALENTS, ACHIEVEMENTS, SAVE_SCHEMA_VERSION,
buildDexCatalog, DEX_TOTAL, dailyConfig, DIFF_ORDER,
} from './meta-tables.js';
// 巡域解锁序唯一来源:MAPS 表序 = 解锁序(map1 首图恒解锁,map25 顺序解锁;见 balance.js MAPS 注释)。
// meta 只读其 id 序推「通关 mapN 解锁 mapN+1」,不改 balance 一字。
import { MAPS } from './balance.js';
/** 存档业务键(save-progress 自动拼命名空间前缀 shanhai-xingji:;M5 切正式 save: 槽时对齐)。 */
export const SAVE_KEY = 'meta';
@ -38,6 +41,8 @@ const TALENT_BY_ID = {};
for (const t of TALENTS) TALENT_BY_ID[t.id] = t;
const DEX_CATALOG = buildDexCatalog();
const DEX_IDS = new Set(DEX_CATALOG.map((d) => d.id));
/** 巡域解锁序(MAPS 序 = 解锁序;「通关 mapN 解锁 mapN+1」的下一图据此推)。 */
const MAP_ORDER = MAPS.map((m) => m.id);
/** 生涯统计默认(全 0;成就 cond.stat 的取值面)。 */
function defaultStats() {
@ -48,7 +53,7 @@ function defaultStats() {
};
}
/** 全新存档默认态(schemaVersion=1 的 data 正文)。chixun 恒解锁。 */
/** 全新存档默认态(schemaVersion=1 的 data 正文)。chixun 恒解锁;巡域 map1 恒解锁。 */
function defaultState() {
return {
gold: 0,
@ -56,6 +61,7 @@ function defaultState() {
selectedDifficulty: 'd0',
talents: {},
roster: { chixun: true },
unlockedMaps: ['map1'], // 已解锁巡域(map1 首图恒解锁;胜局通关 mapN 顺序解锁 mapN+1)
dex: {},
achievements: {},
daily: { lastDate: '', dailyDone: 0 },
@ -133,6 +139,15 @@ export function createMeta(opts) {
if (data.roster && typeof data.roster === 'object') {
for (const id of Object.keys(data.roster)) if (ROLE_BY_ID[id] && data.roster[id]) s.roster[id] = true;
}
// 已解锁巡域:map1 恒在(兜底,即便坏档缺键);仅收合法图 id、去重、保序。
// 旧 v1 档无此字段(加字段向后兼容,无需 schema 版本 +1)→ 默认 ['map1']。
s.unlockedMaps = ['map1'];
if (Array.isArray(data.unlockedMaps)) {
const seen = new Set(['map1']);
for (const id of data.unlockedMaps) {
if (MAP_ORDER.includes(id) && !seen.has(id)) { seen.add(id); s.unlockedMaps.push(id); }
}
}
if (data.dex && typeof data.dex === 'object') {
for (const id of Object.keys(data.dex)) if (DEX_IDS.has(id) && data.dex[id]) s.dex[id] = true;
}
@ -210,6 +225,31 @@ export function createMeta(opts) {
}
function selectDifficulty(key) { state.selectedDifficulty = key; persist(); }
/* ── 巡域(地图)解锁:unlockedMaps 顺序态持久化 + 通关推进 + 查询 ── */
/**
* 某图是否已解锁(map1 首图恒解锁兜底;余图按 unlockedMaps 顺序态)
* 出击页地图行 locked 态的唯一判据(game-logic.isMapUnlocked 经此驱动)
*/
function isMapUnlocked(mapId) {
if (mapId === 'map1') return true; // 首图恒解锁(即便存档异常亦保底)
return state.unlockedMaps.includes(mapId);
}
/** 已解锁巡域 id 只读快照(出击页/诊断/门读)。 */
function getUnlockedMaps() { return state.unlockedMaps.slice(); }
/**
* 通关 mapId 顺序解锁下一图(mapN+1)幂等:下一图已解锁/已是终图/未知图 null
* @returns {string|null} 本次新解锁图 id(无则 null)
*/
function unlockNextMap(mapId) {
const idx = MAP_ORDER.indexOf(mapId);
if (idx < 0 || idx + 1 >= MAP_ORDER.length) return null; // 未知图或已是终图(map5)
const nextId = MAP_ORDER[idx + 1];
if (state.unlockedMaps.includes(nextId)) return null; // 已解锁(幂等,不重复)
state.unlockedMaps.push(nextId);
return nextId;
}
/* ── 图鉴收集投影 ── */
/** 标记一批图鉴条目为已见(id 须在目录内;返回新增数)。 */
@ -278,8 +318,8 @@ export function createMeta(opts) {
* @param {object} summary 局末摘要:
* { win, difficulty, kills, level, evolutions, harvests, eliteKills, bossKills, gems, durationS,
* weaponsSeen[], passivesSeen[], evolutionsSeen[], beastsSeen[], bossesSeen[], roleId,
* isDaily?, dailyDate? }
* @returns {{ goldGained, newRoles[], newAchievements[], dexAdded }}
* isDaily?, dailyDate?, mapId? } // mapId 缺省 'map1'(当前恒图1;选图入局接线后传真图)
* @returns {{ goldGained, newRoles[], newAchievements[], dexAdded, newMaps[] }} // newMaps: 本局新解锁巡域
*/
function recordRun(summary) {
const s = summary || {};
@ -319,10 +359,15 @@ export function createMeta(opts) {
state.daily.lastDate = s.dailyDate;
state.daily.dailyDone += 1;
}
// 巡域解锁:胜局通关当前图(mapId 缺省 map1)→ 顺序解锁下一图(mapN→mapN+1)。
// 当前出击页尚不能选图入局(startRun 恒图1),故实际只会由图1 胜局解锁图2;
// 逻辑按 MAP_ORDER 通用,选图入局接线后自动支持图N→图N+1(占位先行,前向兼容)。
let newMap = null;
if (s.win) newMap = unlockNextMap(s.mapId || 'map1');
// 角色解锁 + 成就评估(顺序:先解锁角色再评成就,rosterUnlocked 才最新)
const unlocks = evaluateUnlocks();
persist();
return { goldGained, newRoles: unlocks.roles, newAchievements: unlocks.achievements, dexAdded };
return { goldGained, newRoles: unlocks.roles, newAchievements: unlocks.achievements, dexAdded, newMaps: newMap ? [newMap] : [] };
}
/* ── deriveMetaBonus:局外增益 → sim 注入包 ── */
@ -421,6 +466,8 @@ export function createMeta(opts) {
talentStatus, canBuyTalent, buyTalent, getTalentProjection,
// 角色
isRoleUnlocked, selectRole, selectDifficulty, getRosterProjection,
// 巡域(地图解锁持久化)
isMapUnlocked, getUnlockedMaps,
// 图鉴
markDex, getDexProjection,
// 成就

View File

@ -33,13 +33,20 @@ import {
PLAYER, WEAPONS, PASSIVES, XP, ENEMIES, WAVES, ARENA,
MAX_ALIVE, MAX_ZONES, SPAWN_R, GRID_CELL, SEPARATION,
CRIT, SLOTS, OFFER, EVO_PASSIVE_MIN, CHAIN_R, HARVEST, DIFFICULTY,
TIDE, ELITE, AFFIXES, BOSSES, EVENTS,
TIDE, ELITE, AFFIXES, BOSSES, EVENTS, MAPS,
} from './balance.js';
/** 词缀索引(id → 定义;spawn 热路径免重查)。 */
const AFFIX_BY_ID = {};
for (const a of AFFIXES) AFFIX_BY_ID[a.id] = a;
/** 地图索引(id → 地图定义;createSim mapId 参数化按 id 查图。M5 检查点2)。 */
const MAP_BY_ID = {};
for (const m of MAPS) MAP_BY_ID[m.id] = m;
/** Boss 索引(id → Boss 定义;按图 bossIds 从全表组装本局出场序列时查表。M5 检查点2)。 */
const BOSS_BY_ID = {};
for (const b of BOSSES) BOSS_BY_ID[b.id] = b;
/** ease-in(二次):磁吸飞行「先慢后快」曲线(手 8)。 */
function easeInQuad(p) { return p * p; }
@ -57,10 +64,13 @@ const ARENA_WALL_INSET = 84;
/**
* 创建一局纯逻辑模拟
* @param {{seed?: number, fireEnabled?: boolean, difficulty?: string, meta?: object}} [opts]
* @param {{seed?: number, fireEnabled?: boolean, difficulty?: string, mapId?: (string|number), meta?: object}} [opts]
* - seed:受控随机种子(缺省固定值,保证不传也确定);
* - fireEnabled:自动开火开关(缺省 true;headless关火纯走位存活 60s对照局用 false);
* - difficulty:难度键(d0..d5,缺省 d0;表驱动乘区,判据①②③④跑 d1);
* - mapId:M5 检查点2地图 id('map1'..'map5';亦受数组下标 0..4;缺省/非法 = 'map1')创建期定图,
* balance.MAPS 取该图 waves/arena/enemyMix/bossIds;map1 取到的就是原硬编码全局表同一组对象,
* 故缺省局行为逐字节不变只在选择时刻读表一次,不进每帧状态(确定性铁律)
* - meta:M4局外 meta 增益包(deriveMetaBonus ;缺省 = meta,逐值同 M3):
* startWeaponId(角色初始符箓)/ startPassives(角色 trait 起手被动级, plv 算持有)/
* hpMul·hpAdd(最大生命)/ reviveCount(阵亡续命次数)/ bonus{...}(纯数值折叠, build-model)
@ -76,6 +86,26 @@ export function createSim(opts) {
/* ── M4 meta 增益(缺省 no-op;无 meta 时后续 derive/HP/续命全部退化为 M3 原路径)── */
const meta = (opts && opts.meta) || null;
/* M5 2:mapId (; map1 = ,)
mapId 只在创建时读表一次,下面产出的 WAVES/ARENA/enemyMix/BOSSES 是本局常量,被各嵌套函数闭包消费;
不进每帧状态不参与每帧分支 确定性铁律不破( mapId+seed+difficulty+meta 同局) */
let mapId = 'map1';
if (opts && opts.mapId != null) {
if (MAP_BY_ID[opts.mapId]) mapId = opts.mapId; // 字符串 id('map1'…)
else if (Number.isInteger(opts.mapId) && MAPS[opts.mapId]) mapId = MAPS[opts.mapId].id; // 数组下标(0…)
}
const mapDef = MAP_BY_ID[mapId];
/* //(,):map1 balance WAVES/ARENA
(MAPS[0].waves===WAVESMAPS[0].arena===ARENAenemyMix=['zheng','taowu','qiongqi']), map1 局逐字节
等于原硬编码图1;currentWave/spawnEnemy/竞技场 clamp 全部经由这些局部常量,自然按图取数 */
const WAVES = mapDef.waves;
const ARENA = mapDef.arena;
const enemyMix = mapDef.enemyMix;
/* Boss : bossIds BOSSES ( = bossIds )map1 bossIds=[zhengwang,jiuying]
与原全局 BOSSES 前两项同一组定义同一 atS(600/1200),出场与判胜行为不变;新图 Boss atS 占位 100000
( ROUND_S=1200)即便误入某图序列也永不 spawnbossSpawned 与本序列同长,update 出场循环只遍历本图 Boss */
const BOSSES = mapDef.bossIds.map((id) => BOSS_BY_ID[id]);
/* ── 局面状态 ── */
let t = 0; // 局内时间(秒)
let phase = 'play'; // play | levelup | win | lose(levelup=全局冻结,手 9)
@ -1160,7 +1190,11 @@ export function createSim(opts) {
const ng = (counts && counts.gems) || 0;
if (counts && counts.boss) bossBar = { ...counts.boss };
if (counts && counts.drop) drops.push({ x: player.x + counts.drop.dx, y: player.y + counts.drop.dy, id: ++eidSeq, ttl: HARVEST.ttlS });
const types = Object.keys(ENEMIES);
// M5 检查点2:注敌限定本图敌混(map1 = 基线三兽 zheng/taowu/qiongqi),不再按 Object.keys(ENEMIES) 全表轮转。
// 修 build-tables「五族武器全部真出伤」:ENEMIES 扩至 32 兽后全表轮转破了该测试写作时的基线三兽脆皮
// 轮转确定性(seed 506 下 orbit 族 shanzi 出伤条件不再成立);回到本图敌混即恢复基线。测试辅助路径,
// 不影响真实波表(正式出怪走 spawnEnemy → currentWave().mix)。
const types = enemyMix;
for (let i = 0; i < ne; i++) {
const def = ENEMIES[types[i % types.length]];
const ang = rng() * Math.PI * 2;