feat(content): GP+商店房实施批落地——九型格面+随机掉落+Boss前商店房+统一加算区(台账 116)
This commit is contained in:
1 parent
ba6b5e6046
commit
bb34335a1e
40 files changed
+1251
-126
No files matched your search
@@ -25,12 +25,14 @@
|
||||
|
||||
```
|
||||
开始冒险(一条命)
|
||||
→ 关卡:节点链逐节点推进(战斗 / 奖励 / 恢复 → 关底 Boss)
|
||||
→ 关卡:节点链逐节点推进(战斗 / 奖励 / 恢复 / 商店 → 关底 Boss)
|
||||
→ Boss 倒下 → 关卡大奖励(血回满、上限提升)→ 下一关
|
||||
→ 打穿全部关卡 → 冒险完成
|
||||
→ 任意一场战斗倒下 → 冒险结束
|
||||
```
|
||||
|
||||
路上的商店房是关底 Boss 前一位的补给点:战斗掉落的金币(GP)在这花掉,换攻击、生命上限或护甲的提升——打完仗钱到账,进店花掉,带着变强去见关底 Boss。
|
||||
|
||||
一条血走完整场冒险:战后剩多少血就带多少进下一场,恢复点回一部分,通关一关血回满还加上限。所以越往后,你越在意"这局已经走到第几层"。
|
||||
|
||||
## 死了之后:为什么想再开一局
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 内容规模
|
||||
|
||||
一句话定位:**三关、按关配置的棋盘、一套格面资源池、一套状态表**——刚好撑起一次完整的"扫雷决策 → 战斗兑现"循环,除此之外一律不做。
|
||||
一句话定位:**三关、按关配置的棋盘、一套格面资源池、一套状态表、一套商店配置**——刚好撑起一次完整的"扫雷决策 → 战斗兑现"循环,除此之外一律不做。
|
||||
|
||||
> **体例说明(2026-09-17 数值纪律收口批)**:本文件只写内容规模的结构框架——几个维度、各分几档、扩展走什么流程。具体种数、行数与幅度数值一律不在此抄录,事实源为准:数据表读 `prototypes/dungeon-sweeper-vertical-slice/data/tables/*.tres`,实体登记读 `design/registry/entities.yaml`;现行行数与面值以表和 registry 为准。
|
||||
|
||||
@@ -30,7 +30,7 @@ MVP 不加新敌种,也不做同深度多敌种混搭;敌种扩容走数据
|
||||
|
||||
## 战斗道具(格面资源)
|
||||
|
||||
**格面资源池**(`data/tables/reward.tres`,现行行数读表为准):结构为格面八型 × 正负两向(十六格——2026-09-20 八型批后现行),加清图奖励槽一类。全部按交付方式当场兑付、不进背包——入背包的"结算窗口选用"类道具(小刀 / 精工短刀 / 治疗包)已随格面资源批整体删除,现行池内无可选类道具、无轮末结算窗口。
|
||||
**格面资源池**(`data/tables/reward.tres`,现行行数读表为准):结构为格面九型 × 正负两向(十八格——2026-09-20 GP+商店房批九型后现行),加清图奖励槽一类。全部按交付方式当场兑付、不进背包——入背包的"结算窗口选用"类道具(小刀 / 精工短刀 / 治疗包)已随格面资源批整体删除,现行池内无可选类道具、无轮末结算窗口。
|
||||
|
||||
行级语义见[地图战斗资源](systems/战斗循环/地图战斗资源.md)。MVP 不做道具之间的组合协同;扩池走 reward.tres 加行 + registry 登记。
|
||||
|
||||
@@ -46,6 +46,12 @@ registry 登记 **一套封闭清单**(数据表未建,属待落地内容)
|
||||
|
||||
MVP 不加新状态;扩容走 status_effects.tres 加行 + registry 登记。
|
||||
|
||||
## 商店
|
||||
|
||||
**一套商店配置**(`data/tables/shop.tres`,现行行数读表为准):商品三行(攻击 / 生命上限 / 护甲等级的 flat 档)、GP 掉落参数三行(普通 / 精英预留行 / Boss,均值与方差区间)与价格参数(关级掉落加值、关级价格加成、商品步长)。
|
||||
|
||||
数值全部 provisional(骨架先行、数值后置)——商店房节点的机制归[关卡节点地图](systems/冒险循环/关卡节点地图.md),GP 钱包账本归[冒险结构](systems/冒险循环/冒险结构.md),调参归[难度曲线与Boss](systems/冒险循环/难度曲线与Boss.md)曲线调参批。扩商品或改价格结构走 shop.tres 加行 + registry 登记。
|
||||
|
||||
## 暂不做 / 后续再说
|
||||
|
||||
垂直切片阶段明确砍掉或推迟的内容,写在这里防止回潮:
|
||||
@@ -71,3 +77,4 @@ MVP 不加新状态;扩容走 status_effects.tres 加行 + registry 登记。
|
||||
| v0.4 | 2026-09-20 | 层名改名批:第一层「苔藓回廊」→「裂颚洞窟」(用户拍板——苔藓意象与地精巢穴不契合;新名致敬 DND 5e《凡戴尔的失落矿坑》地精部族「裂颚」Cragmaw,洞窟纵深连通幽暗地域)。关卡表 display_name / registry / 00-游戏概念 / 切片首页按钮 / README 随批同步 |
|
||||
| v0.5 | 2026-09-20 | 层名二改批:「裂颚洞窟」→「地精洞窟」(用户拍板——不绑定 DND 专名部族、规避 IP 指涉,直取地精巢穴意象;v0.4 出典随之作废仅留档)。全仓 8 处消费面随批同步 |
|
||||
| v0.6 | 2026-09-20 | **第三关内容批(灰矮人矿城——与切片代码 / registry / 难度曲线与Boss / 棋盘生成 / 地形变异 同批)**:层名表加第三层(测试直入口已落地、正式序列推进待收尾——正式三关序列推进仍登记 Alpha 验收条件);棋盘尺寸条款由两档改按关配置(第三关 13×13 / Boss 14×14 随批铺行);棋盘节地形变异句随批补第三关一对(落石/矿尘);暂不做「更多关卡」行更新为已按扩展批流程落地。行值读 `data/tables/level.tres` / `board_gen.tres` / `variation.tres`(registry 为准) |
|
||||
| v0.7 | 2026-09-20 | **GP+商店房批(与切片代码 / registry / 关卡节点地图 / 冒险结构 / 战斗结算 / 地图战斗资源 / 难度曲线与Boss 同批)**:格面资源池"八型 × 正负两向(十六格)"→"九型 × 正负两向(十八格)"(+gp 金币);新增「商店」小节一行——shop.tres 读表纪律(商品三行 / 掉落参数 / 价格参数,全部 provisional 归曲线调参批);一句话定位句补"一套商店配置" |
|
||||
@@ -9,7 +9,7 @@
|
||||
- **重叙事**:不写前人日志、剧情演出这类长篇文本。世界观只做碎片点缀,卖点在玩法不在文本。
|
||||
- **即时操作玩法**:所有决策都在回合内慢慢想,不考反应、不考手速。任何"限时点击""走位闪避"都免谈。
|
||||
- **chord(经典扫雷的双击快开)**:本作每一格的兑现都要花行动点,chord 一键开一片会让行动点预算失去意义。
|
||||
- **抽象货币 / 中间资源折算**:扫雷收益全部直接兑成战斗效果,中间隔一层货币会稀释"排雷的因 = 战斗的果"这条因果可见的底线。
|
||||
- **抽象货币 / 中间资源折算**:扫雷收益全部直接兑成战斗效果,中间隔一层货币会稀释"排雷的因 = 战斗的果"这条因果可见的底线。**显式例外:格面金币(GP,显示名「金币」)**——金币有定名、明码定价与余额常驻呈现,因果链 = 排雷 → 战斗 → 金币 → 属性提升,每一段玩家都看得见,可见性底线不破;金币之外不再引入任何"扫雷收益折算成货币再兑换效果"的夹层。
|
||||
- **旗做成背包道具或限量**:旗只是棋盘上的标记,想用多少用多少;它的稀缺性由行动点价目承载,不做第二重限制。
|
||||
- **未开格显示奖励身份**:推雷是本作的本征玩法,预读会拆掉推理。允许"恢复推理原料"类道具(显影/预读窄域),不允许替代推理。
|
||||
- **第二套随机惩罚**:所有风险都从雷和未知格派生,不再引入平行的随机惩罚源——惩罚多了,玩家就没法归因了。
|
||||
|
||||
+5
-11
@@ -163,7 +163,7 @@
|
||||
|
||||
#### 雷格与安全格的资源分布差异化(Alpha 批)
|
||||
|
||||
**现状**:现行雷安同池——安全格与雷格从同一类型池、同一权重分布摇定类型身份(结构冻结项,地图战斗资源 v0.4 七项之一)。用户 2026-09-17 拍板要做这件差异化的事,定位 **Alpha 版本**上线;垂直切片期先「把游戏做完整」不动结构(读数基线刚被护甲换装+技能池批与数值压缩批归零,验收重测与真人试玩未跑)。调研底座已交付:[proposal-reward-cell-differentiation-2026-09-17.md](../research/proposal-reward-cell-differentiation-2026-09-17.md)——三条候选路径(双池切分〔旧提案 mine_cell 有成案可翻〕/ 单池双列 weight_mine / 修饰位摇后过滤);差异化必破「雷安同池」冻结项、走 GDD 修订流程(用户已明示冻结约束执行侧不约束 owner,允许下破冻结即合法修订);纯权重调整零破冻结但有 9 处连带同步位(含 2 处 v0.7 漏网过期注释实证);外部无「同池不同权」中间态先例。关联条目:见本节「冒险前资源配比预设(构筑提案)」(2026-09-18 登记的玩家侧分布控制权提案——同为类型分布语义的结构修订,立项时评估合并);另见[关卡节点地图](systems/冒险循环/关卡节点地图.md)「冒险循环内成长提案(临时资源 GP + 商店房节点)」(2026-09-18 登记——GP 格面枚举同为奖励身份词汇的结构修订,立项评估时一并入列)
|
||||
**现状**:现行雷安同池——安全格与雷格从同一类型池、同一权重分布摇定类型身份(结构冻结项,地图战斗资源 v0.4 七项之一)。用户 2026-09-17 拍板要做这件差异化的事,定位 **Alpha 版本**上线;垂直切片期先「把游戏做完整」不动结构(读数基线刚被护甲换装+技能池批与数值压缩批归零,验收重测与真人试玩未跑)。调研底座已交付:[proposal-reward-cell-differentiation-2026-09-17.md](../research/proposal-reward-cell-differentiation-2026-09-17.md)——三条候选路径(双池切分〔旧提案 mine_cell 有成案可翻〕/ 单池双列 weight_mine / 修饰位摇后过滤);差异化必破「雷安同池」冻结项、走 GDD 修订流程(用户已明示冻结约束执行侧不约束 owner,允许下破冻结即合法修订);纯权重调整零破冻结但有 9 处连带同步位(含 2 处 v0.7 漏网过期注释实证);外部无「同池不同权」中间态先例。关联条目:见本节「冒险前资源配比预设(构筑提案)」(2026-09-18 登记的玩家侧分布控制权提案——同为类型分布语义的结构修订,立项时评估合并);原「冒险循环内成长提案」条目的 GP 格面枚举合并评估面已于 2026-09-20 GP+商店房批关闭(gp 型入池,格面九型现行),差异化批的权重再平衡按九型池执行
|
||||
|
||||
**前置**:① 验收批读数重测与真人试玩先跑——差异化核心未知数(玩家是否利用类型分布先验绕雷/追格)只有真人数据能回答;② 拍掉调研报告第 8 章八个待决项(差异化区分的轴 / 雷·安当量分化后的行动经济条款③⑤与恒等式对账窗口 / heal 权重整数档位 / tier_min_buff 口径 / 同步流程口径扩充等)后出定稿提案
|
||||
|
||||
@@ -232,19 +232,13 @@
|
||||
|
||||
**状态**:待办
|
||||
|
||||
#### 冒险循环内成长提案(临时资源 GP + 商店房节点)
|
||||
#### 关卡内结构随机生成(哈迪斯 / 哥布林弹珠式布局变化)
|
||||
|
||||
**现状**:2026-09-18 用户提案,登记在案、未评估未拍板。动机 = 冒险循环内的中途成长缺口:关卡与关卡之间、战斗与战斗之间,怪物数值随进程抬升(节点编号即强度台阶),主角数值也应有对应提升——现行冒险内主角属性开局定死,奖励房只发功能道具、恢复房只回血,属性轴零中途成长。提案引入一类**冒险类临时资源**(暂拟名 **GP**,DND 金币意象):与钥石(跨局持久、换永久强化)相对,GP 局内有效、只提供**一次性的冒险类属性提升**(本冒险内生效)。两个核心问题,用户各给初步方案:
|
||||
- **资源怎么拿(两途径)**:① 打败怪物后的掉落——战斗胜利奖励临时资源 GP;② 地图战斗资源新增一种格面枚举——某些安全格或雷格的奖励身份 = 金币 GP。
|
||||
- **资源怎么转化**:关卡节点图在奖励房、恢复房之外新增**商店房**节点,店内用 GP 购买属性提升;商品**随机三选一** + **刷新机会**(次数与费用未提)保持随机性——拿到金币不必然买到当下想要的属性。
|
||||
**现状**:用户 2026-09-20 方向性提及(哈迪斯 / 哥布林弹珠式的关卡内布局随机变化)。现行切片关卡为固定流程——节点图行集读关卡表,图版结构不随局随机;本事项未立项、未设计。
|
||||
|
||||
**数值语言注记(2026-09-18)**:全项目数值语言已拍板小整数加法核(见[01-核心循环](01-核心循环.md) 数值语言条款)——GP 商店属性提升档位锁定 **flat 整数**(与永久强化三属性同族),开放问题④的档位词汇评估时免议;提案本身维持未评估不设计。
|
||||
**归属**:[关卡节点地图](systems/冒险循环/关卡节点地图.md)
|
||||
|
||||
**开放问题(评估前须过)**:① GP 格面与八型池的结构关系——进不进「雷安同池」同一摇号池、权重占比几何;雷格侧 GP 的极性语义(负向 = 扣 GP / 并入挖雷惩罚 / GP 面中性无极性);② 战斗掉落的量级与记账——按敌种/节点编号定值还是掷定,入账归冒险结构资源账本还是战斗结算兑付;③ 商店房进图版结构——关卡节点地图 v1.0 节点封闭枚举 +1 的修订面:编排规则(出现频率/必现位/深度约束)、图合法性校验连带、确认选路交互、兑付移交语义;④ 商店内容与定价——属性提升的档位词汇(与永久强化三属性同族还是冒险内独立档)、价格曲线、三选一候选池、刷新次数/费用;⑤ 双币分工与呈现——GP 与钥石记账分离(局内清零不入存档)、GP 余额与商店界面的 HUD 归属;⑥ 成长与验收纪律——商店属性提升是通关率带的新玩家侧杠杆(换算率表强条目纪律同类),验收读数须按商店存在与否分层,GP 供给量级与难度曲线加难杠杆族的关系。
|
||||
|
||||
**归属**:[关卡节点地图](systems/冒险循环/关卡节点地图.md)修订批(商店房节点与图版结构,主责);GP 格面枚举归[地图战斗资源](systems/战斗循环/地图战斗资源.md);战斗掉落兑付归[战斗结算](systems/战斗循环/战斗结算.md)与[冒险结构](systems/冒险循环/冒险结构.md)共裁;成长节奏与验收带联动归[难度曲线与Boss](systems/冒险循环/难度曲线与Boss.md);GP 与钥石分工知会[元进度](systems/冒险循环/元进度.md)。
|
||||
|
||||
**状态**:待办(2026-09-20 用户拍板:**立项提前**——原 Alpha 排期作废,与第三关内容批捆绑推进,作为 L3 基线红档的成长侧前置;定稿提案已交付 [proposal-gp-shop-room-2026-09-20.md](../research/proposal-gp-shop-room-2026-09-20.md)(六开放问题逐条推荐 + 数值锚 + 实施拆批),六问推荐待用户逐条拍板后进实施批)
|
||||
**状态**:方向观察待立项(2026-09-20 登记防散佚;立项前不展开设计)
|
||||
|
||||
### 难度曲线与Boss
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
|
||||
钥石是玩家在局内夺得、在局外消费的永久资源,2026-09-14 拍板定名。渠道有二(2026-09-17 拍板统一):关卡大奖励入账(每通关一关兑付,行值读冒险表 keystone_per_level)、关底 Boss 掉落。钥石存进存档、永不丢失,就算之后倒下也不收回。
|
||||
|
||||
**与 GP 的分工(2026-09-20 GP+商店房批注记,知会性质)**:钥石 = 跨局持久的解锁资源(入存档永不丢失);GP(显示名「金币」)= 冒险内一次性的属性提升货币——局内清零、不入存档,钱包账本归[冒险结构](冒险结构.md)。两币记账与生命周期完全分离,永不互兑。
|
||||
|
||||
**重复获取规则(2026-09-18 用户拍板)**:同一 Boss **首次击杀给大额**,之后的重复击杀只给**固定小额**——不设递减曲线,存档只记首杀标记位。设计意图:深度关的产出优势是长线驱动(配合定价几何递增,见下),重复刷老关仍有小额意义但不构成无上限的产出泵。两档数值与两渠道量级配比归元进度设计批配读数投值,行值读表。
|
||||
|
||||
### 营地三柜台:永久强化的内容承载
|
||||
@@ -154,3 +156,4 @@ MVP 不要求本系统的闭环([不做清单](../../05-不做清单.md)整体
|
||||
| v0.3 | 2026-09-17 | 数值纪律收口批:正文 knob 现行值改指路存根(事实源 data/tables/*.tres 与 registry),3 处失同步修正(钥石入账 +10/关 三处改「每关固定值,行值读冒险表 keystone_per_level」) |
|
||||
| v1.0 | 2026-09-18 | **元进度定稿批(用户拍板六件收口)**:定名「永久强化」(系统族名元进度保留,「天赋树」退役、「营地」为界面壳);营地三柜台(属性锻造三档含护甲等级 / 道具工坊六件首版通关次数渠道 / 奖励格图鉴结构位);Boss 重复获取 = 首杀全量 + 重复杀固定小额;定价几何递增原则;区间平移换算率启用(换算率表首版三行、护甲行强条目、数值读 data/tables);注入 = 冒险开始参数面 + 覆盖值通道(override 集合操作:追加 / 替换);解锁界面双入口框架。本文自范围草案升定稿 |
|
||||
| v1.1 | 2026-09-18 | **数值语言拍板批(与 01-核心循环 数值语言条款 / 难度曲线与Boss / registry / 06-待办清单 / 切片代码同批)**:营地三柜台节新增投值语言锁定条款——属性投值语言锁定 flat 整数加减,数值投值仍归设计批 |
|
||||
| v1.2 | 2026-09-20 | GP+商店房批注记(知会性质——与冒险结构 v0.5 同批):钥石节新增与 GP 的分工一行——钥石 = 跨局持久解锁、GP(「金币」)= 冒险内一次性属性提升、局内清零不入存档,双币生命周期切分成文、永不互兑。机制零改动 |
|
||||
@@ -20,7 +20,7 @@
|
||||
|---|---|
|
||||
| 节点图结构规则 | 什么算一张合法的关卡节点图(唯一编号、唯一入口、唯一终点、不可回头)——结构校验的规则面 |
|
||||
| 选路与行进 | 玩家亲手选路前进;兑付后确认、零自动推进(交互形态定稿) |
|
||||
| 节点兑付触发 | 行进到各类型节点时触发对应兑付:战斗移交、奖励入库、恢复请求 |
|
||||
| 节点兑付触发 | 行进到各类型节点时触发对应兑付:战斗移交、奖励入库、恢复请求、商店覆盖层开闭 |
|
||||
| 战斗发起组装 | 组装定位信息(关卡序号 + 节点编号),移交战斗结算开战 |
|
||||
| 关卡收束点 | Boss 战胜利或英雄死亡时定局:先调战斗结算的关卡收束清空库存,再向冒险结构宣布关卡结束 |
|
||||
|
||||
@@ -39,18 +39,18 @@
|
||||
## 在核心循环中的位置
|
||||
|
||||
```
|
||||
冒险结构 ──开始关卡──▶ 关卡节点地图:节点图选路推进(战斗 / 奖励 / 恢复 → 关底 Boss)──关卡结束──▶ 冒险结构
|
||||
冒险结构 ──开始关卡──▶ 关卡节点地图:节点图选路推进(战斗 / 奖励 / 恢复 / 商店 → 关底 Boss)──关卡结束──▶ 冒险结构
|
||||
```
|
||||
|
||||
[核心循环](../../01-核心循环.md)里"关卡:节点链逐节点推进"这一段就是本系统在跑:它从冒险结构接过一关,把玩家沿图送到关底 Boss,再把结局交回去。图上每场战斗都由本系统发起移交;战斗内部的事不归它管。
|
||||
|
||||
## 核心规则
|
||||
|
||||
### 关卡的形状:分支节点图与四类节点
|
||||
### 关卡的形状:分支节点图与五类节点
|
||||
|
||||
一关内部是一张**有方向的节点图**:节点之间用连线(本文含义:节点之间允许前进的通道)连接,玩家沿连线从一个节点走到下一个节点。图有三条结构铁律:连线只朝前进方向(没有回头路,整张图无环);图有一个唯一的入口节点;**关底 Boss 恒为全图唯一的终点**——所有路线最终都汇到它,打赢它这一关就通了。违反任何一条的行集是配置违约,加载期拦截。
|
||||
|
||||
图上只有四种节点,封闭枚举,扩类一律走文档修订:
|
||||
图上只有五种节点,封闭枚举,扩类一律走文档修订:
|
||||
|
||||
| 节点类型 | 机制承诺 |
|
||||
|---|---|
|
||||
@@ -58,6 +58,9 @@
|
||||
| Boss | 恒为图终点;行进至此开 Boss 战;胜利即关卡收束点 |
|
||||
| 奖励 | 行进至此触发节点奖励(兑付见下节);反馈呈现后回到选路 |
|
||||
| 恢复 | 行进至此向冒险结构发治疗封顶回报;反馈呈现后回到选路 |
|
||||
| 商店 | 行进至此打开商店覆盖层,用 GP(显示名「金币」,战斗掉落的冒险内货币)购买属性提升;离开后兑付确认回选路;不携带战斗行 |
|
||||
|
||||
商店房的编排规则(2026-09-20 用户拍板):每关**恰 1 个**商店房,位置 = **关底 Boss 前一位**——哈迪斯式的"决战前最后一个补给点":买到的属性还有整段战斗可消费,错过的成长也没有第二家店可补。Boss 前一位同时是**必经位**:图拓扑收束使所有入口→Boss 的路径都穿过它,成长保底成为图结构事实而非玩家自觉。这条位置约束不由加载校验承载——图合法性六检零新增,商店房的"每关恰 1 + 必经"由冒烟断言把关。
|
||||
|
||||
每个节点在关卡内领一个**节点编号**(本文含义:关卡内不重复的序号,用来当战斗的定位地址;数据键沿用切片字段 `node_depth`)。Boss 的编号恒为全图最大——"最深处的房间是关底"这条直感由此保留。单链特例:一张每步只有一条连线的图就是一条链,此时节点编号等于它前面走过的战斗场数。第一关行集就是单链特例,切片代码零改动运行;分支图行集随第二关内容批落地(见 P1)。
|
||||
|
||||
@@ -79,7 +82,7 @@
|
||||
|
||||
| 纪律 | 内容 |
|
||||
|---|---|
|
||||
| 兑付后确认 | 到达节点立即兑付,不设"进入前确认"步;兑付反馈呈现完,玩家确认才继续。战斗节点的确认挂在战斗结算画面上,奖励/恢复节点挂在兑付反馈覆盖层上——复用既有呈现位,零新增界面 |
|
||||
| 兑付后确认 | 到达节点立即兑付,不设"进入前确认"步;兑付反馈呈现完,玩家确认才继续。战斗节点的确认挂在战斗结算画面上,奖励/恢复节点挂在兑付反馈覆盖层上,商店节点挂在商店覆盖层的离开动作上——复用既有呈现位,零新增交互范式 |
|
||||
| 零自动推进 | 确认之后是选路口:岔路口玩家选一条连线,单链路口确认"继续"。选了才走;不选就停在原地 |
|
||||
|
||||
开始关卡后的第一次行进确认就是"出发"。Boss 兑付(胜利或英雄死亡)触发关卡收束点,此后不再有选路——关卡已定局,后续流程归冒险结构。
|
||||
@@ -93,6 +96,7 @@
|
||||
| 战斗 / Boss | 组装定位信息(关卡序号 + 节点编号),移交[战斗结算](../战斗循环/战斗结算.md)开战;战斗结果(胜负与战后生命)经战斗结果上报回流冒险结构(契约语义归[冒险结构](冒险结构.md),本文不复述) |
|
||||
| 恢复 | 向冒险结构发治疗封顶回报(载荷 = 该关恢复比例,读本关配置行);按比例回血并封顶的执行归冒险结构 |
|
||||
| 奖励 | 触发节点奖励兑付:获得一件功能道具并入持有域(见下) |
|
||||
| 商店 | 打开商店覆盖层(商品陈列与定价读商店表);购买即时生效、写统一冒险加算区(与关卡大奖励同一本账——账本语义归[冒险结构](冒险结构.md),本文不复述);离开覆盖层即兑付确认。商店房不携带战斗行:零敌人、零棋盘、零战斗初始化 |
|
||||
|
||||
**遭遇构成的编排面**——本文对"放什么敌人"的全部职责只有三条:
|
||||
|
||||
@@ -126,6 +130,7 @@
|
||||
| 上游 | [魔法道具](../排雷循环/魔法道具.md) | 节点奖励的道具内容、持有域与使用窗口语义(内容重定批交付;切片期直通空态) |
|
||||
| 上游 | [敌人系统](../战斗循环/敌人系统.md) | 定位信息的消费方:本文递出关卡序号 + 节点编号,按编号生成单位归它 |
|
||||
| 上游 | [难度曲线与Boss](难度曲线与Boss.md) | 敌我数值曲线与遭遇投放节奏的调参所有权;本文零数值 |
|
||||
| 上游 | [数据配置](../表现与支撑/数据配置.md) | 商店表(商品行 / GP 掉落参数 / 价格参数)的承载与统一读取;商店覆盖层的商品与定价数据源 |
|
||||
| 下游 | [战斗结算](../战斗循环/战斗结算.md) | 两个调用入口的发起方:战斗发起组装(定位信息)、关卡收束(清库) |
|
||||
| 下游 | [HUD 框架](../表现与支撑/HUD框架.md) | 节点图呈现(方块地牢观感)、当前位置与选路口、兑付反馈覆盖层的数据源 |
|
||||
|
||||
@@ -134,7 +139,7 @@
|
||||
1. 从冒险结构接收开始关卡(关卡序号、当前生命、生效上限),加载本关节点图,玩家确认出发,行进自入口节点开始。
|
||||
2. 每节点兑付完成后玩家选路前进;岔路选一条、单链点继续;零自动推进。
|
||||
3. 战斗 / Boss 节点:组装关卡序号 + 节点编号,移交战斗结算开战;战斗结束向冒险结构上报战斗结果。
|
||||
4. 奖励节点:触发节点奖励兑付(切片期 = 直通合法空态);恢复节点:向冒险结构发治疗封顶回报。
|
||||
4. 奖励节点:触发节点奖励兑付(切片期 = 直通合法空态);恢复节点:向冒险结构发治疗封顶回报;商店节点:打开商店覆盖层,购买即时生效,离开后兑付确认。
|
||||
5. Boss 战胜利或任意战斗倒下 = 关卡收束点:先调战斗结算的关卡收束清库,再宣布关卡结束。
|
||||
6. 第一关行集 = 单链特例(图的最简形态),切片按单链运行零改动;分支图行集与图版实现随第二关内容批(P1)。
|
||||
|
||||
@@ -146,7 +151,7 @@
|
||||
|---|---|---|
|
||||
| P1 | 分支节点图内容与实现批 | 图结构行集(节点+连线)、切片行进代码的图版实现、跨文档"节点深度/节点链"措辞对齐——随第二关内容批同批落;落地前单链特例运行(合法态) |
|
||||
| P2 | 精英节点与精英遭遇 | 不设编码位——精英以内容方式承载(某编号节点配强敌行集);与难度曲线、战斗侧批联动设计 |
|
||||
| P2 | 事件 / 商人类节点候选 | 无事件内容系统承载,商人另撞"抽象货币不做"红线;扩类一律走文档修订 |
|
||||
| P2 | 事件类节点候选 | 商店房已随 2026-09-20 GP+商店房批落地(v1.1),"商人类撞抽象货币红线"的旧注记由[不做清单](../../05-不做清单.md)的现行条款替代;事件类节点仍无内容系统承载;扩类一律走文档修订 |
|
||||
|
||||
## 调参表
|
||||
|
||||
@@ -160,6 +165,7 @@
|
||||
- 兑付后确认:战斗节点的继续确认挂战斗结算画面,奖励/恢复挂兑付反馈覆盖层;到达与兑付之间无确认步。
|
||||
- 恢复兑付:治疗封顶回报载荷 = 该关恢复比例(读关卡配置行);满血回复 0 是合法状态(执行语义归冒险结构)。
|
||||
- 奖励兑付:奖励节点兑付恰触发一次;切片期直通空态合法,内容批后 = 功能道具入库。
|
||||
- 商店房编排:每关恰 1 个商店房、Boss 前一位必经位(所有入口→Boss 路径都穿过它)——由冒烟断言把关,图合法性校验零新增检查;商店房不携带战斗行;行进至此打开商店覆盖层、离开后兑付确认回选路。
|
||||
- 收束有序且唯一:关卡清库发生在关卡结束宣布之前;每关恰宣布一次关卡结束;Boss 胜 = 通关、倒下 = 失败。
|
||||
- 四件接口全款:与冒险结构登记的开始关卡、战斗结果上报、治疗封顶回报、关卡结束逐件对上,不另起平行通道。
|
||||
- 单链特例合法:单链行集(每步恰一条连线)是合法图形态;第一关按单链运行,切片零改动。
|
||||
@@ -177,3 +183,4 @@
|
||||
| v0.2 | 2026-09-15 | 待拍板"多敌数量的消费面"条目同步教学位掷骰口径(随[难度曲线与Boss](难度曲线与Boss.md)同批修订):删除"教学位节点固定不掷" |
|
||||
| v1.0 | 2026-09-17 | **定稿**(逐问题拍板,全用户裁决):① 形态重开——用户拍板重开[05-不做清单](../../05-不做清单.md)「路径分支地图后置」裁定,定稿为**分支节点图**(分层节点图,杀戮尖塔 / 哥布林弹珠同构;方块地牢美术观感、逻辑为节点图),P0 图定稿 + 单链特例先行;② 节点类型定稿四类封闭枚举不扩类;③ 行进交互定稿全节点确认 + 兑付后确认、零自动推进;④ 遭遇构成收窄为编排面三条(战斗内部初始化全归战斗小循环),寻址定稿节点编号升级(`node_depth` 语义升级为关卡内唯一节点编号,表键与字段名零改动,单链零漂移);⑤ 节点奖励拍板为功能道具的正式渠道(内容归魔法道具内容重定批,切片期直通为合法空态);⑥ 「精英与 Boss 编码位」(同层多遭遇由节点编号消解,不设编码位)与「多敌数量的消费面」(关卡层零消费)两悬案收口,同场多敌种面拆归战斗侧;⑦ 切片行进代码对照的接口漂移清单登记 06 |
|
||||
| v1.0.1 | 2026-09-17 | **P1 分支节点图内容与实现批落地**(用户拍板任务批——切片代码 / 难度曲线与Boss v0.7 / registry / 06 待办同批):首张分支图 = 切片关卡 2「第二层 · 幽暗地域」行集落地(9 节点网状图:入口 0 → 中间三层 2/3/2 → Boss 8,真实岔路 4 处,奖励点 + 恢复点各 1,战斗节点 6 + Boss);行进器图版推进(`choices_presented` 选路态 + `pick_next_node`——零自动推进,战斗胜利不再自动推进、`continue_after_node` 泛化为兑付确认,06 接口漂移清单 ④⑤⑧ 清偿);图合法性校验落地 `LevelTable.validate`(单链与图同一套算法,L1 零迁移过校验,违规加载致命);节点图选路 UI(选路覆盖层整图自绘——房间方块 + 通道连线 + 当前/候选高亮,占位图形质量;顶栏图形态渲染行进序列 + 候选);首页"关卡 2 试玩"直入口(测试模式,≠ 两关序列生效)。本版本号只记实现批状态,图规则口径零改动 |
|
||||
| v1.1 | 2026-09-20 | **商店房扩类(GP+商店房批,用户拍板——与冒险结构 / 战斗结算 / 地图战斗资源 / 难度曲线与Boss / registry / 切片同批)**:① 节点封闭枚举四类→五类(+商店房);② 编排规则定稿——每关恰 1、关底 Boss 前一位必经位(哈迪斯式"决战前最后一个补给点"定位);必经位由冒烟断言承载、图合法性六检零新增;③ 兑付定稿——打开商店覆盖层、购买即时生效写统一冒险加算区(与关卡大奖励同账,归冒险结构)、离开后兑付确认;商店房不携带战斗行;④ 节点兑付表 / 纪律表 / 依赖表 / P0 闭环 / 验收标准 / P2「事件类节点候选」注记同步 |
|
||||
@@ -21,6 +21,7 @@
|
||||
| 冒险生命周期 | 待机 / 进行中 / 结算中三态,与完成、失败、放弃三种结局的判定和产出 |
|
||||
| 关卡序列 | 关卡表(逐关一行的配置表)行集内容的责任方;推进规则:线性、无跳关 |
|
||||
| 生命账本 | 跨战斗生命持久性的冒险侧账本:每战交出、战后收回、两个恢复口 |
|
||||
| GP 冒险账本 | 战斗掉落货币的局内账本(显示名「金币」):战斗完成统一入账、商店购买出账、冒险收束清零;统一冒险加算区(商店购买与大奖励同账) |
|
||||
| 关卡大奖励 | 通关一关固定发放的三件套的兑付 |
|
||||
| 失败重开与放弃 | 倒下即冒险终;重开等于全新一轮;放弃与失败同型结算 |
|
||||
| 主种子接线 | 一场冒险一个主种子,冒险内一切随机数派生自它 |
|
||||
@@ -30,6 +31,7 @@
|
||||
| 不负责 | 归谁 |
|
||||
|---|---|
|
||||
| 单关内节点行进与兑付触发 | [关卡节点地图](关卡节点地图.md) |
|
||||
| 商店房节点的开闭交互与商品陈列 | [关卡节点地图](关卡节点地图.md) |
|
||||
| 轮末结算编排与关卡清库接口 | [战斗结算](../战斗循环/战斗结算.md) |
|
||||
| 节点奖励内容与库存语义 | [地图战斗资源](../战斗循环/地图战斗资源.md) |
|
||||
| 通关率带、敌我数值与 Boss 设计 | [难度曲线与Boss](难度曲线与Boss.md) |
|
||||
@@ -114,7 +116,7 @@ MVP 的序列是两关线性:第一关是地牢基线,第二关在基线上
|
||||
|
||||
| 变量 | 含义 | 行值出处 |
|
||||
|---|---|---|
|
||||
| 生效上限 | 基础上限 + 大奖励累加提升 | 基础上限读英雄表 |
|
||||
| 生效上限 | 基础上限 + 统一冒险加算区累加(大奖励 + 商店购买,见 GP 冒险账本节) | 基础上限读英雄表 |
|
||||
| 恢复比例 | 恢复点回复生效上限的百分比,逐关配置 | 读关卡表 rest_heal_pct(provisional) |
|
||||
| 上限提升 | 每次大奖励的生命上限加成 | 读配置表(provisional) |
|
||||
|
||||
@@ -130,13 +132,31 @@ MVP 的序列是两关线性:第一关是地牢基线,第二关在基线上
|
||||
|
||||
冒险内没有其他跨关遗留物:增益随战斗结束清空(归战斗结算)、道具库存随关卡结束清空(由关卡节点地图在收束点编排)、储备行动点随战斗结束清零(归[行动经济](../排雷循环/行动经济.md))。冒险层真正跨关留下的,只有这本生命账与冒险摘要。
|
||||
|
||||
### GP 冒险账本:战斗掉落的局内钱包与统一冒险加算区
|
||||
|
||||
GP(显示名「金币」)是冒险内的一次性属性提升货币:战斗胜利掉落、商店房消费、冒险结束清零。它与钥石记账完全分离——钥石跨局持久、入存档;GP 局内有效、不入存档、不入冒险摘要五件事清单。账本语义归本文,商店房的开闭交互归[关卡节点地图](关卡节点地图.md)。
|
||||
|
||||
钱包只有三个收口:
|
||||
|
||||
| 收口 | 方向 | 规则 |
|
||||
|---|---|---|
|
||||
| 战斗完成 | 入 | 每完成一场战斗统一到账一次:金额 = 掉落掷定 + 关级掉落加值 + 本战战斗内缓冲,随战斗结束信号携带的 gp_dropped 一笔入账 |
|
||||
| 商店购买 | 出 | 商店房购买按现行价扣减钱包,余额不足拒绝;价格 = 基价 + 关级价格加成 + 步长 × 已购档数 |
|
||||
| 冒险收束 | 清零 | 完成 / 失败 / 放弃三种结局都清零;重开后钱包从零开始 |
|
||||
|
||||
**战斗内缓冲隔离**:战斗进行中从格面兑付拿到的 GP(金币袋正向、破财负向——字典归[地图战斗资源](../战斗循环/地图战斗资源.md))不直接进钱包,而是记入战斗域的中间态缓冲;战斗完成时缓冲随掉落掷定与关级加值一并折进 gp_dropped 统一到账。钱包只在战斗边界变动,战斗域自身零钱包状态——这条隔离让战斗结算保持"折算归我、账本归冒险结构"的既有边界。
|
||||
|
||||
**统一冒险加算区**:商店购买的属性提升与关卡大奖励的上限提升记**同一本账**——加算区三轴 {攻击, 生命上限, 护甲等级},入战参数 = 基础表值 + 加算区累加,经战斗开始入参的覆盖值通道生效(与生命账本的上参同一条通道)。购买生命上限时,当前生命与上限同步 +Δ(生命账本的连带写入口)。攻击轴现行只有商店一个累加源——MVP 大奖励不含攻击件。
|
||||
|
||||
掉落掷定消费 gp_drop 封闭随机流(主种子派生、可回放)。掉落与价格的现行行值读商店表(registry 为准),数值所有权归[难度曲线与Boss](难度曲线与Boss.md)曲线调参批——本批骨架先行,全部数值 provisional。
|
||||
|
||||
### 关卡大奖励:通关一关发三件
|
||||
|
||||
关卡大奖励(本文含义:通关一关时固定发放的三件套)是非末关通关的固定兑付。它不进任何背包、不占任何库存域,收益面只朝"下一战"生效。兑付时点固定:关卡节点地图先调战斗结算的关卡收束接口清空关卡域库存,再向本文宣布关卡结束——大奖励在这之后、下一关开始之前兑付,顺序不可换。上限已有加成时,后续大奖励累加;两关冒险至多兑付一次。
|
||||
|
||||
| 件 | 内容 | 生效方式 |
|
||||
|---|---|---|
|
||||
| 生命上限提升(行值读表) | 生效上限累加提升 | 经战斗开始入参的覆盖值通道,对后续每战生效 |
|
||||
| 生命上限提升(行值读表) | 生效上限累加提升(记入统一冒险加算区——与商店购买同一本账) | 经战斗开始入参的覆盖值通道,对后续每战生效 |
|
||||
| 血回满 | 生命 = 提升后的生效上限 | 直接写生命账本 |
|
||||
| 钥石入账(行值读表) | 在冒险摘要里记一笔 | 数值进摘要,消费方归元进度 |
|
||||
|
||||
@@ -152,7 +172,7 @@ MVP 的序列是两关线性:第一关是地牢基线,第二关在基线上
|
||||
|
||||
主动放弃按与失败同型结算:已获得的大奖励不追回、已到手的保留,摘要标记未打穿。放弃请求只在非战斗期受理,战斗进行中一律拒绝(战斗域单事务在飞)。MVP 没有放弃的界面入口,语义先行预留,入口呈现后置。
|
||||
|
||||
主种子(本文含义:冒险开始时注入的一个随机种子,本局全部随机性都派生自它)在冒险开始时注入恰一次。冒险内的雷图生成、奖励抽取、遭遇掷骰全部派生自它,冒险内不重置、不逐关播种——早期切片"每关开局各自播种"是没有冒险层时的历史简化,已改写为全冒险单一种子。冒险的可复现域 = 主种子 + 各随机流计数器,供复盘与快照(见[存档序列化](../表现与支撑/存档序列化.md))。重开抽到的种子与上一局相同是允许的(随机源范围小,撞种子不算错误),但生成新种子不得复用玩法里的随机数。
|
||||
主种子(本文含义:冒险开始时注入的一个随机种子,本局全部随机性都派生自它)在冒险开始时注入恰一次。冒险内的雷图生成、奖励抽取、遭遇掷骰与 GP 掉落掷定(消费 gp_drop 封闭流)全部派生自它,冒险内不重置、不逐关播种——早期切片"每关开局各自播种"是没有冒险层时的历史简化,已改写为全冒险单一种子。冒险的可复现域 = 主种子 + 各随机流计数器,供复盘与快照(见[存档序列化](../表现与支撑/存档序列化.md))。重开抽到的种子与上一局相同是允许的(随机源范围小,撞种子不算错误),但生成新种子不得复用玩法里的随机数。
|
||||
|
||||
### 与关卡节点地图的边界:收束点与接口四件套
|
||||
|
||||
@@ -190,11 +210,11 @@ MVP 的序列是两关线性:第一关是地牢基线,第二关在基线上
|
||||
|---|---|---|
|
||||
| 上游 | [难度曲线与Boss](难度曲线与Boss.md) | 逐关通关率带是序列难度排布的输入;第二关深度轴行值填充批是两关序列的生效前提;大奖励与恢复点数值的联动复验归它 |
|
||||
| 上游 | [数据配置](../表现与支撑/数据配置.md) | 关卡表与冒险表(单行全局)的承载与统一读取,本文是两表的内容与回填责任方;主种子的注入入口(随机服务) |
|
||||
| 上游 | [战斗结算](../战斗循环/战斗结算.md) | 主角战斗入参覆盖值通道(本文供生命与上限);关卡收束接口由关卡节点地图收束点调用,本文零直调;战后生命终值经上报读回 |
|
||||
| 上游 | [战斗结算](../战斗循环/战斗结算.md) | 主角战斗入参覆盖值通道(本文供生命与上限);关卡收束接口由关卡节点地图收束点调用,本文零直调;战后生命终值与本战 GP 掉落(gp_dropped)经上报读回 |
|
||||
| 下游 | [关卡节点地图](关卡节点地图.md) | 接口契约四件套 + 收束点语义,本文是定义源,也是其文档的强制输入 |
|
||||
| 下游 | [元进度](元进度.md) | 冒险摘要承载钥石入账合计(元进度是消费方);冒险开始参数面预留(未来跨局加成在此注入,MVP 空参数) |
|
||||
| 下游 | [存档序列化](../表现与支撑/存档序列化.md) | 快照契约域单位 = 冒险(主种子 + 随机流计数器);摘要含主种子供复盘 |
|
||||
| 下游 | [HUD框架](../表现与支撑/HUD框架.md) | 摘要结算画面、关卡推进指示、放弃入口(非战斗期)的数据源 = 冒险摘要与状态投影 |
|
||||
| 下游 | [HUD框架](../表现与支撑/HUD框架.md) | 摘要结算画面、关卡推进指示、放弃入口(非战斗期)的数据源 = 冒险摘要与状态投影;商店覆盖层与 GP 余额常驻读数的钱包数据源 |
|
||||
|
||||
## P0 最小闭环
|
||||
|
||||
@@ -226,6 +246,7 @@ MVP 的序列是两关线性:第一关是地牢基线,第二关在基线上
|
||||
| 恢复比例 | int [0,100],逐关行 | 见 level.tres rest_heal_pct(provisional) | 恢复点回复生效上限的百分比;随上限加成同步放大;100 = 恢复点变全满泉,0 = 死节点 |
|
||||
| 上限提升 | int [0,999],冒险表单行全局 | 见 adventure_table level_clear_hp_bonus(provisional) | 大奖励的生命上限加成;值锚与上调约束归难度曲线与Boss |
|
||||
| 每关钥石入账 | int [0,999],冒险表单行全局 | 见 adventure_table keystone_per_level(provisional) | 每通关一关在摘要记的值;消费方归元进度 |
|
||||
| GP 经济参数 | 商店表逐行 | 见 shop.tres gp_drops / level_gp_bonus / products / price_level_bonus(provisional,registry 为准) | 掉落均值与区间(普通/精英/Boss)、关级掉落加值、商品首价与步长、关级价格加成;公式语义归本文,数值所有权归难度曲线与Boss曲线调参批 |
|
||||
|
||||
结构冻结(非调参项):序列线性无跳关、关底 Boss 恒为末节点、倒下即冒险终、大奖励三件构成、一冒险一主种子、收束点先清库后宣布的次序。
|
||||
|
||||
@@ -245,7 +266,10 @@ MVP 的序列是两关线性:第一关是地牢基线,第二关在基线上
|
||||
- 覆盖义务:冒烟断言——行集内每个战斗与关底节点(关、深)在敌人与棋盘生成的数值表中有精确行;序列关数 = 行集关数。
|
||||
- 生效前提未兑现(第二关行缺行):以单关冒险运行(合法态),两关断言显式跳过而非失败。
|
||||
- 放弃:战斗中的放弃请求被拒绝;非战斗期放弃按失败同型结算,已获大奖励不追回。
|
||||
- 种子域:冒险内零随机流自开,一切随机派生自主种子;同种子同操作序列两局逐位可复现。
|
||||
- 种子域:冒险内零随机流自开,一切随机派生自主种子(含 GP 掉落掷定的 gp_drop 封闭流);同种子同操作序列两局逐位可复现。
|
||||
- GP 账本封闭:钱包只有三个收口——战斗完成入账恰一次(掉落掷定 + 关级加值 + 战斗内缓冲合计)、商店购买出账、冒险收束清零;战斗内缓冲与钱包隔离,战斗进行中钱包零变动。
|
||||
- GP 不留痕:GP 不入存档、不入冒险摘要;重开后钱包从零开始。
|
||||
- 加算区同账:商店购买与大奖励上限提升读同一本统一冒险加算区;购买生命上限后当前生命与上限同步增加等量;入战参数 = 基础表值 + 加算区累加(覆盖值通道)。
|
||||
|
||||
## 待办
|
||||
|
||||
@@ -259,3 +283,4 @@ MVP 的序列是两关线性:第一关是地牢基线,第二关在基线上
|
||||
| v0.2 | 2026-09-15 | 雾格残留清理:第二关"解锁新元素"条目改引用「已从设计中移除的机制」(见不做清单),并明确第二关层名未定稿、随第二关内容批重定。关卡序列与节点模板不变 |
|
||||
| v0.3 | 2026-09-17 | 代币→钥石统一拍板(用户裁决):大奖励第三件「代币记账 +10」改「钥石入账 +10」——与元进度钥石同一资源(渠道 = 关卡大奖励入账与 Boss 掉落),「代币」措辞退役;调参表字段名、验收标准、依赖表同步。数值与机制不变 |
|
||||
| v0.4 | 2026-09-17 | 数值纪律收口批:正文与调参表 knob 现行值改指路存根(事实源 data/tables/*.tres 与 registry),3 处失同步修正(示例轨迹改纯符号〔旧 50/50 生命轴示例作废〕、大奖励 +10/+10 改读 adventure_table 行值并删换算率数字锚、调参表 30/10/10 三格改读 level.tres / adventure_table) |
|
||||
| v0.5 | 2026-09-20 | **GP+商店房批(用户拍板——与关卡节点地图 v1.1 / 战斗结算 v0.23 / 地图战斗资源 v0.15 / 难度曲线与Boss v0.18 / registry / 切片同批)**:① 新增「GP 冒险账本」节——钱包三收口(战斗完成统一入账 / 商店购买出账 / 冒险收束清零)+ 战斗内缓冲隔离语义(格面兑付入缓冲、战斗完成统一到账、战斗域零钱包状态);② 统一冒险加算区成文——商店购买与关卡大奖励上限提升同一本账(三轴 {攻击, 生命上限, 护甲等级},经覆盖值通道生效;购血 = 上限与当前生命同步 +Δ);③ GP 掉落掷定消费 gp_drop 封闭随机流(主种子派生)登记进主种子节与调参表;④ GP 不入存档、不入冒险摘要;范围表 / 大奖励表 / 依赖表 / 验收标准同步。数值全 provisional-pending-曲线调参批,行值读 shop.tres |
|
||||
@@ -118,7 +118,31 @@ MVP 行值并不逐式现算,而是按此式量级直填经切片验证过的
|
||||
|
||||
更高加成档(攻击翻倍、三倍)的通关率带随档位放宽(翻倍档第一关 ≥95%),其机制承载是生存链区间的平移而不是另拍一套数字(平移换算见调参联动一节)。此外另有一条期望承伤占比带(普通场承伤不超过生命上限两成、Boss 场不超过五成,provisional),复验义务随最近的减伤调整批执行。
|
||||
|
||||
第三关口径(第三关内容批):L3 全部行值 provisional-pending-growth,上述各带对 L3 暂不适用——零成长基线下的红档属预期(主角成长侧前置 = L3-A 玩家成长批,GP + 商店房),现行冒烟的首遇绿档断言按「成长代理档 atk+200 / hp+100」口径执行;正式通关率带与首遇绿档口径随 L3-A 玩家成长批落地换装,归曲线调参批统一收带。
|
||||
第三关口径(第三关内容批):L3 全部行值 provisional-pending-growth,上述各带对 L3 暂不适用——零成长基线下的红档属预期(主角成长侧前置 = GP+商店房批,已于 2026-09-20 落地:GP 掉落 + 商店房 + 格面金币),现行冒烟的首遇绿档断言按「成长代理档 atk+200 / hp+100」口径执行;正式通关率带与首遇绿档口径随玩家成长落地换装,归曲线调参批统一收带。
|
||||
|
||||
### 商店与 GP 经济杠杆:玩家侧供给 × 定价的第三族杠杆
|
||||
|
||||
GP(显示名「金币」)与商店房随 2026-09-20 GP+商店房批落地,给难度引入第三族玩家侧杠杆:**GP 供给 × 商品定价 = 入关属性参数**——供给或定价单边移动,玩家入关时的攻击 / 生命 / 护甲与通关率随之漂移,与血量锚走廊、变异 effect_value 同列进禁独立调参网络。
|
||||
|
||||
**结论**:掉落与价格的全部现行行值 provisional-pending-曲线调参批——本批骨架先行、数值整体后置(用户 2026-09-20 拍板"骨架先行、数值是骨架上的肌肉"),分层验收与带平移全部随调参批执行,本文只登记杠杆位不投正式值。
|
||||
|
||||
```
|
||||
每战 GP 掉落 = 掷定(均值 ± 方差,按战斗节点身份查 gp_drops 行) + 关级掉落加值 + 战斗内缓冲
|
||||
商品价格 = 基价 + 关级价格加成 + 步长 × 已购档数
|
||||
```
|
||||
|
||||
定价锚定口径:商品价格按"正常运气可购档数 / 运气极好可购档数"两读数的数学期望制定(期望账进商店表批注)——正常运气的玩家买得到成长保底档,运气极好也不至于全表买穿,两个失速态都不出现。
|
||||
|
||||
| 杠杆位 | 口径 | 说明 |
|
||||
|---|---|---|
|
||||
| GP 掉落均值与方差 | 普通 / 精英(预留行)/ Boss 三行掷定区间 | 掉落量级与战斗节奏同族;格面 gp 型(金币袋,低概率)是"惊喜意外获取"的补充供给 |
|
||||
| 关级掉落加值 | 逐关一行 | 关间供给抬升的半边 |
|
||||
| 关级价格加成 | 逐关一行 | 关间价格上涨对应关间掉落上升的另一半——两轴同向联动的对子 |
|
||||
| 商品首价与步长 | 商品三行(攻击 / 生命上限 / 护甲等级 flat 档) | 重复购买逐档递增,堆单一属性自然衰减 |
|
||||
|
||||
公式语义与钱包账本归[冒险结构](冒险结构.md)(GP 冒险账本节),商店房编排归[关卡节点地图](关卡节点地图.md);行值读 `data/tables/shop.tres`(registry 为准),全部 provisional 归本文曲线调参批。
|
||||
|
||||
**分层验收 defer(2026-09-20 用户拍板成文)**:按"商店存在与否"分层的通关率验收——MC 双格(shop=off 现行基线 / shop=on 购买画像)、shop=on 的带平移量、商店属性提升的换算率登记(换算率强条目纪律同类)——**全部随曲线调参批执行**,与「基线通关率越带」「L2 Boss 承伤微越带」「大奖励数值随难度曲线联动」同窗。执行前 shop=off 续用现行带判读,shop=on 读数只登记不判读、不得用 shop=off 读数外推 shop=on。L3 首遇绿档冒烟断言仍按成长代理档口径,不因商店批改判。
|
||||
|
||||
### Boss 设计:食人魔酋长与关底血量锚
|
||||
|
||||
@@ -159,6 +183,7 @@ Boss 战的棋盘用本关最大板形(板形行值读棋盘参数表),板
|
||||
| 削弱强度 ↔ 当量锚 | [增益状态](../战斗循环/增益状态.md)、[战斗结算](../战斗循环/战斗结算.md) | 联动值禁独立调参,修订须重跑锚对照 |
|
||||
| 护甲类型 ↔ 克制矩阵 | [战斗结算](../战斗循环/战斗结算.md) | 敌种护甲变更须复算击杀区间(2026-09-19 充能退役——原"充能投桶与量"联动半边废止) |
|
||||
| 护甲等级 ↔ 攻击基线 ↔ 区间带宽 | [战斗结算](../战斗循环/战斗结算.md) | 敌种与主角的护甲等级行值变更须复算生存链区间(掷点带宽随折扣档位与攻击基线同向放大);掷点形式与档位已定稿(d20 三档——档位读 `settlement.tres`;nat20 暴击形式留档,倍率同表),护甲等级行值变更仍须复算生存链区间并同步 registry——现行行值读 `hero.tres` / `enemy.tres` 的 armor_class 列(均暂定),敌方正式行值随怪物设定批投值、与加难杠杆扫描合并调 |
|
||||
| GP 供给 ↔ 商品定价 | [冒险结构](冒险结构.md)、[关卡节点地图](关卡节点地图.md) | 供给 × 定价 = 入关属性参数,单边移动须复算分层通关率带(商店与 GP 经济杠杆节)——动供给必复算分层带,禁独立调参 |
|
||||
|
||||
五条经济契约的对账由[战斗结算](../战斗循环/战斗结算.md)闭合验收,其中挖掘风险不等式现行验算破线(现状见其「待办」);清图支配性已于 2026-09-19 按现行值链重跑、裁决②"非支配设计态"维持(回执见 design/research/audit-clear-dominance-2026-09-19.md)——两型的重跑义务都落在本系统的下一次调参批随批执行,这是本系统当前最优先的调参义务。完美排雷乘数的调参权在本系统。
|
||||
|
||||
@@ -248,6 +273,7 @@ Boss 战的棋盘用本关最大板形(板形行值读棋盘参数表),板
|
||||
| 注入概率初值 / 衰减 | 读表 | 注入机制归[地图战斗资源](../战斗循环/地图战斗资源.md) |
|
||||
| 清障炸药威力区间 | 读表 | 清图槽当场引爆 |
|
||||
| 状态强度 | 读 `status_effects.tres`(掷点轴——2026-09-16 换装,原减伤退役) | 联动评估归本文,禁独立调参 |
|
||||
| GP 掉落与商店价格参数 | 读 `shop.tres`(gp_drops 均值±方差 / level_gp_bonus / products 首价与步长 / price_level_bonus) | GP+商店房批新增;公式语义归[冒险结构](冒险结构.md),数值全部 provisional-pending-曲线调参批,与分层带联动禁独立调参 |
|
||||
|
||||
## 验收标准
|
||||
|
||||
@@ -260,6 +286,7 @@ Boss 战的棋盘用本关最大板形(板形行值读棋盘参数表),板
|
||||
- 恒等式闭合:关级通关恒等式逐项对账闭合,残差 <5%(provisional 阈值);奖励输出占比 ∈ [15%, 40%]。
|
||||
- Boss 锚在案:每个 Boss 行的血量锚比率记录在案且落在现行带内;越带须登记,不静默变更。
|
||||
- 联动可审计:任何行值修订在调参日志留有对应联动组的联合检算记录;越带必须回滚或登记——禁止静默出带、禁止带病前进。
|
||||
- 商店 / GP 杠杆在案:GP 掉落与价格行值读商店表且在 registry 有条目;shop=off/on 双格分层读数与带平移登记随曲线调参批执行——执行前 shop=on 读数不判读、不混入现行带。
|
||||
- 零运行时公式:运行时对难度零计算——同一种子同坐标必读同表值,难度差异全部来自表行。
|
||||
|
||||
## 待办
|
||||
@@ -287,3 +314,4 @@ Boss 战的棋盘用本关最大板形(板形行值读棋盘参数表),板
|
||||
| v0.15 | 2026-09-20 | **格面资源八型批同步(零行值改动——与敌人系统 / 战斗结算 / 地图战斗资源 / 行动经济 / registry / 切片代码同批)**:① 受伤峰值构成去"伏击附加"项(伏击随八型批退役);② 关键投值节伏击上限措辞改述为群伤负向 = 警觉(敌方护甲等级 +1),原 advantage_per_round_cap 对账供给面维持同日先行作废;③ 警觉与行动点修正两新词的当量对账义务归本系统调参批(随 06 清单「调参批数值点评登记」条执行) |
|
||||
| v0.16 | 2026-09-20 | **基线通关率带分带批 + MC 重投基线(用户拍板方案 A;台账 105〔登记时撞并行会话 103 号,顺延让号〕)**:① 基线通关率带由单一 [10%, 25%] 改为分带——第 1 关 [35%, 55%] / 第 2 关 [25%, 45%]:旧带锚定随机点击画像,普通画像换装基础定式后失锚;推导链 = 真人体验换算(模拟 47%/34% 对应真人新手约六成/五成)+ 与完美档带 [35,65] 错开一档保留元进度爬升空间;② MC 重投基线(N=300/画像/关 · unique · seed0=7000):完美档 55.7%/39.7% 落 [35,65]、普通档 46.7%/34.3% 落新带——双画像双关全落带,加难紧迫性解除;③ 残留越带唯一项 = L2 完美 Boss 承伤 51.6%(微越 1.6 点,归调参批);④ 报告 production/qa/mc-reinvest-baseline-2026-09-20.md;工具 level_monte_carlo.gd 增 baseline 参数 + F5 脚注随带同步 |
|
||||
| v0.17 | 2026-09-20 | **第三关内容批(灰矮人矿城——与切片代码 / registry / 03-内容规模 / 棋盘生成 / 地形变异 同批,存量行值零改动)**:调参表新增第三关图版注记——敌种生态(灰矮人 duergar 单体重甲中坚 + 钩爪恐魔 hook_horror 群行复用撕咬 + Boss 灰矮人石君王 duergar_king 专属池独占妨害技大地脉动 = 乏力 sapped 第二妨害词汇首发)、首遇位节点 0/1 固定数量不加骰、克制面重轻错开(重甲首次登台,教学梯度轻→魔→重在 L3 补完)、Boss 血量锚 75 = Σ91 × r_boss≈0.82、棋盘 13×13 / Boss 14×14 密度 0.25 维持、地形落石 3 点 + 矿尘、armor_class 恒 0(正式投值仍归在案条目全表同批);期望值带节补 L3 口径——行值 provisional-pending-growth,零成长基线红档属预期、冒烟首遇绿档按成长代理档(atk+200 / hp+100)口径,正式通关率带与首遇绿档口径随 L3-A 玩家成长批(GP + 商店房)换装,归曲线调参批统一收带 |
|
||||
| v0.18 | 2026-09-20 | **GP+商店房批(用户拍板"骨架先行、数值整体后置"——与 关卡节点地图 v1.1 / 冒险结构 v0.5 / 战斗结算 v0.23 / 地图战斗资源 v0.15 / registry / 切片同批,行值零改动)**:① 新增「商店与 GP 经济杠杆」节——GP 供给 × 商品定价 = 第三族玩家侧杠杆(掉落均值±方差 / 关级掉落加值 / 关级价格加成 / 首价与步长,全部读 shop.tres provisional-pending-曲线调参批),定价锚定口径 = 正常运气与运气极好可购档数的数学期望(期望账进商店表批注);② 分层验收 defer 成文——shop=off/on 双格 MC、shop=on 带平移、换算率登记全部随曲线调参批执行(与「基线通关率越带」「L2 Boss 承伤微越带」「大奖励数值联动」同窗),执行前 shop=on 只登记不判读;③ L3 首遇绿档成长代理档口径不变;④ 调参联动表 +GP 供给↔商品定价行、调参表 +shop.tres 行、验收标准 +杠杆在案条目 |
|
||||
@@ -18,9 +18,9 @@
|
||||
|
||||
| 负责 | 说明 |
|
||||
|---|---|
|
||||
| 格面资源类型封闭枚举 | 八型(attack / defense / heal / blast / weaken / pierce / magic / action——2026-09-18 伤害道具批五→七、2026-09-20 格面资源八型批七→八);表里出现清单外的类型,数据加载直接拒绝 |
|
||||
| 格面资源类型封闭枚举 | 九型(attack / defense / heal / blast / weaken / pierce / magic / action / gp——2026-09-18 伤害道具批五→七、2026-09-20 格面资源八型批七→八、同日 GP+商店房批八→九);表里出现清单外的类型,数据加载直接拒绝 |
|
||||
| 装板期身份预分配 | 每张图装上时一次摇定全部非封锁格的资源类型身份与清图奖励对象身份;只读、无重摇 |
|
||||
| 极性矩阵与八型字典 | 「动作 × 格型 → 正负号」的定义,以及每型正向 / 负向各兑付成什么效果 |
|
||||
| 极性矩阵与九型字典 | 「动作 × 格型 → 正负号」的定义,以及每型正向 / 负向各兑付成什么效果 |
|
||||
| 清图奖励对象 | 板级作用域的独立奖励封装,可配置可替换,按图类型差异化 |
|
||||
| 未来功能道具的预留结构位 | 长周期、更强力的功能性道具经其他渠道加入;渠道设计未定,本系统只留结构位不预写 |
|
||||
|
||||
@@ -73,17 +73,17 @@
|
||||
|
||||
| 格型 | 候选集合(cell 池正向行,weight>0) | 类型频次合计 |
|
||||
|---|---|---|
|
||||
| 安全格 | attack_up(attack)· ac_up(defense)· spark_aoe(blast)· heal_minor(heal)· weaken(weaken)· pierce_strike(pierce)· magic_bolt(magic)· ap_up(action) | 见 `data/tables/reward.tres` |
|
||||
| 雷格 | 同上(雷安同池——八行正向行全集,权重分布相同) | 见 `data/tables/reward.tres` |
|
||||
| 安全格 | attack_up(attack)· ac_up(defense)· spark_aoe(blast)· heal_minor(heal)· weaken(weaken)· pierce_strike(pierce)· magic_bolt(magic)· ap_up(action)· gp_gain(gp) | 见 `data/tables/reward.tres` |
|
||||
| 雷格 | 同上(雷安同池——九行正向行全集,权重分布相同) | 见 `data/tables/reward.tres` |
|
||||
|
||||
清图槽(clear_default 池,clear_blast)独立于格面资源——作用域为整张板,不挂任何一格。
|
||||
|
||||
引用(代码事实源):
|
||||
- `prototypes/dungeon-sweeper-vertical-slice/core/reward_economy.gd#roll_board`:`_rollable_rows()` 第 207-213 行收 `pool_tag=cell & polarity=positive & weight>0` 全集;循环第 65-67 行对 `eligible` 清单(全部非封锁格)一视同仁。
|
||||
- `prototypes/dungeon-sweeper-vertical-slice/core/minesweeper_rules.gd#_do_dig` 第 256-264 行:chain/dig 通道 `cell_revealed` 事件 `resource=true` 字段对安全格与雷格一致——注释明确"安全格与雷格一视同仁(雷格亦携带,负向兑付归 战斗结算 极性路由)"。
|
||||
- `prototypes/dungeon-sweeper-vertical-slice/data/tables/reward.tres`:8 行正向行 weight 现行值读 `data/tables/reward.tres`。
|
||||
- `prototypes/dungeon-sweeper-vertical-slice/data/tables/reward.tres`:9 行正向行 weight 现行值读 `data/tables/reward.tres`。
|
||||
|
||||
候选集合具体行值(power / power_min / power_max / stacks / effect_id)随行更新——以 `data/tables/reward.tres` 为现行值源;GDD 表格只锁行集与权重分布语义。负向 8 行(enrage / off_guard / heal_backlash / enemy_alert / ap_drain / sapped / pierce_snare / magic_recoil——2026-09-20 八型批 ambush_double→enemy_alert 换装 + ap_drain 新增)weight=0 不参与摇号,按(类型, 极性)查表取用,不在候选集合内。
|
||||
候选集合具体行值(power / power_min / power_max / stacks / effect_id)随行更新——以 `data/tables/reward.tres` 为现行值源;GDD 表格只锁行集与权重分布语义。负向 9 行(enrage / off_guard / heal_backlash / enemy_alert / ap_drain / sapped / pierce_snare / magic_recoil / gp_loss——2026-09-20 八型批 ambush_double→enemy_alert 换装 + ap_drain 新增、同日 GP+商店房批 +gp_loss)weight=0 不参与摇号,按(类型, 极性)查表取用,不在候选集合内。
|
||||
|
||||
举证:本节为候选集合枚举,无计算。
|
||||
|
||||
@@ -101,9 +101,9 @@
|
||||
|
||||
举证:本节为枚举与路由定义,无计算。
|
||||
|
||||
### 八型字典:每型一行正向一行负向
|
||||
### 九型字典:每型一行正向一行负向
|
||||
|
||||
格面资源的一切兑付从八个类型里出,每个类型恰好一行正向、一行负向(五型 + 2026-09-18 伤害道具批增补 pierce / magic 两型 + 2026-09-20 格面资源八型批增补 action 一型)——正向是玩家操作正确时拿到的东西,负向是操作失误或踩雷时吃到的东西:
|
||||
格面资源的一切兑付从九个类型里出,每个类型恰好一行正向、一行负向(五型 + 2026-09-18 伤害道具批增补 pierce / magic 两型 + 2026-09-20 格面资源八型批增补 action 一型 + 同日 GP+商店房批增补 gp 一型)——正向是玩家操作正确时拿到的东西,负向是操作失误或踩雷时吃到的东西:
|
||||
|
||||
| 类型 | 正向(奖励) | 负向(惩罚) |
|
||||
|---|---|---|
|
||||
@@ -113,10 +113,11 @@
|
||||
| blast 群伤 | spark_aoe 全体存活敌各吃一笔——威力/权重见 `reward.tres` | enemy_alert 警觉:敌方全体护甲等级 +1/层(`status_effects.tres` alert 行)——见 `reward.tres` |
|
||||
| weaken 削弱 | weaken(enemy_output 池;同名重复授予只刷新剩余次数——2026-09-18 续时定案)——幅度见 `status_effects.tres` weaken 行 | sapped(hero_offense 池)——幅度见 `status_effects.tres` sapped 行 |
|
||||
| action 行动点 | ap_up:下轮行动点 +1——见 `reward.tres` | ap_drain 迟滞:下轮行动点 −1——见 `reward.tres` |
|
||||
| gp 金币 | gp_gain 金币袋:战斗内 GP 缓冲 +power,战斗完成随 gp_dropped 统一入钱包账本(兑付编排归[战斗结算](战斗结算.md)、账本归[冒险结构](../冒险循环/冒险结构.md))——威力/权重见 `reward.tres` | gp_loss 破财:战斗内 GP 缓冲 −power,weight=0 纯字典不参与摇号——见 `reward.tres` |
|
||||
| pierce 穿刺 | pierce_strike 穿刺弹:单目标克制直伤(attack_type=pierce 查克制矩阵,必中全额不掷点;目标 = 固定顺位最前存活敌)——威力/权重见 `reward.tres` | pierce_snare 穿刺陷阱:主角直伤,不经增益状态——威力见 `reward.tres` |
|
||||
| magic 魔法 | magic_bolt 魔法矢:单目标克制直伤(attack_type=magic,同上)——威力/权重见 `reward.tres` | magic_recoil 魔法反噬:主角直伤,不经增益状态——威力见 `reward.tres` |
|
||||
|
||||
设计意图:读敌拍板的路线规划——敌方要放大招时抢护甲强化、怪重叠时抢削弱,惩罚侧则镜像同一批类型。类型清单是封闭的:加新类型等于修订本文档,不是调参数(2026-09-18 伤害道具批即按此修订五→七)。
|
||||
设计意图:读敌拍板的路线规划——敌方要放大招时抢护甲强化、怪重叠时抢削弱,惩罚侧则镜像同一批类型。类型清单是封闭的:加新类型等于修订本文档,不是调参数(2026-09-18 伤害道具批即按此修订五→七;2026-09-20 GP+商店房批按同一流程修订八→九——格面 GP 由用户拍板入池,定位"低概率惊喜获取"、量与战斗掉落相当,兑付进战斗内缓冲而非当场生效)。
|
||||
|
||||
凡"该兑付但没生效"——增益挂满层、满血捡到 heal 正向、场上无敌可打——一律在结算明细里明写一行"奖励未生效",不给折算补偿,也不静默消失。把兑付改成跳过轮末结算直接生效,同样在禁止之列。
|
||||
|
||||
@@ -124,7 +125,7 @@
|
||||
|
||||
### 负向恒即时:惩罚没有库存语义
|
||||
|
||||
负向兑付全部在授予段当场执行,不入库、不出窗口——惩罚不设库存、不留存量。正向里的 percent 类(增益行)进[增益状态](增益状态.md)的容器;flat 类(群伤、治疗、直伤)当场结算。叠层饱和由战斗结算既有池 clamp 承载;blast 负向(警觉)与 action 负向(迟滞)直授无上限。负向直伤使主角生命归零时,结算按战斗结算的致死短路规则处理。
|
||||
负向兑付全部在授予段当场执行,不入库、不出窗口——惩罚不设库存、不留存量。正向里的 percent 类(增益行)进[增益状态](增益状态.md)的容器;flat 类(群伤、治疗、直伤)当场结算;gp 正负两向(金币袋 / 破财)同样当场执行——记入战斗内 GP 缓冲,战斗完成随 gp_dropped 与钱包结算(账本归[冒险结构](../冒险循环/冒险结构.md)),同样无库存语义。叠层饱和由战斗结算既有池 clamp 承载;blast 负向(警觉)与 action 负向(迟滞)直授无上限。负向直伤使主角生命归零时,结算按战斗结算的致死短路规则处理。
|
||||
|
||||
举证:本节为兑付语义,无计算。
|
||||
|
||||
@@ -204,19 +205,21 @@
|
||||
| enemy_alert | blast | 负向 | weight=0 不参与摇号·effect_id=alert(敌方护甲等级 +1——effect 引用行,unit=ac) | 同上 | 同上 |
|
||||
| ap_up | action | 正向 | weight=6 参与摇号 | 同上 | 同上 |
|
||||
| ap_drain | action | 负向 | weight=0 不参与摇号 | 同上 | 同上 |
|
||||
| gp_gain | gp | 正向 | weight [0,999] 参与摇号 · power [0,999]·power_min/power_max 可选区间(flat 行 effect_id 恒空) | 同上 | 同上 |
|
||||
| gp_loss | gp | 负向 | weight=0 不参与摇号 · power [0,999](flat 行 effect_id 恒空) | 同上 | 同上 |
|
||||
| sapped | weaken | 负向 | weight=0 不参与摇号·power 不消费(percent 行 effect_id → StatusEffectTable sapped 引用) | 同上 | 同上 |
|
||||
| clear_blast | blast | 正向 | clear_default 池·power [0,999]·power_min/power_max 可选区间·weight [0,999] | 同上 | 清图槽 |
|
||||
|
||||
摇号只消费正向格面池的权重(类型频次由此涌现——具体权重读 `reward.tres`);负向行是纯兑付字典,按(类型,极性)查表取用,不消费权重。clear_blast 对全体存活敌各吃一笔当场兑付的 flat 伤害。RewardConfigTable 已随可选类删除下线——注入概率、按板保底等参数全部失义。
|
||||
|
||||
结构冻结(改动走本文修订流程,不走调参):八型封闭枚举(伤害道具批 2026-09-18 修订五→七、格面资源八型批 2026-09-20 修订七→八)、雷安同池、极性矩阵、装板期预分配且无重摇、负向恒即时无库存、清图恒非空、除摇号外零随机。
|
||||
结构冻结(改动走本文修订流程,不走调参):九型封闭枚举(伤害道具批 2026-09-18 修订五→七、格面资源八型批 2026-09-20 修订七→八、同日 GP+商店房批修订八→九)、雷安同池、极性矩阵、装板期预分配且无重摇、负向恒即时无库存、清图恒非空、除摇号外零随机。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 每格必有货:装板摇号后,每个非封锁格恰有一个资源类型、清图槽恰有一个对象身份;封锁格恒无资源;同图跨轮查询结果逐字相同。
|
||||
- 雷安同池:安全格与雷格从同一类型池、同一权重分布摇定;任何"按格型差异化分配"的分支不存在。
|
||||
- 恰一次与确定性:摇号对每张新图恰发生一次,重复调用被拒绝且映射不变;同一条随机流轨迹驱动两套实例,映射与兑付回执逐位相同。
|
||||
- 类型封闭与字典完整:表里出现八型之外的类型、或任一型缺正向/负向行,加载拒绝。
|
||||
- 类型封闭与字典完整:表里出现九型之外的类型、或任一型缺正向/负向行,加载拒绝。
|
||||
- 未生效可见:满层增益、满血正向治疗、无存活敌的群伤,各自在结算明细记"奖励未生效"行,零折算、零补偿。
|
||||
- 加载护栏:池空、清图池无正当量、增益引用悬空——任一命中即加载报错中止;默认内容加载通过。
|
||||
- 表实一致:数据表字段与 registry 的 reward_table 条目逐字段一致。
|
||||
@@ -243,3 +246,4 @@
|
||||
| v0.12 | 2026-09-19 | **插旗不可逆批·极性矩阵同步(用户拍板全套落地,与战斗结算 v0.18 / 排雷玩法 v0.5 / registry / 切片代码同批)**:矩阵「标旗后当轮撤旗」行删除(撤旗动作退役)、「插旗格直接挖开」行新增——安全格零兑付(四情况④零惩罚零奖励)、雷格负向照常(四情况②只受挖雷一层惩罚、标中不发);路由描述「抵消过滤」随战斗结算改「判定基线」 |
|
||||
| v0.13 | 2026-09-20 | **伏击单轮上限删除批同步(与战斗结算 v0.19 / 敌人系统 v0.11 等同批)**:负向即时节 blast 负向的伏击上限护栏与溢出行协议措辞删除,改直授无上限 |
|
||||
| v0.14 | 2026-09-20 | **格面资源八型批(用户拍板——与敌人系统 / 战斗结算 / 行动经济 / registry / 切片代码同批)**:① 七型→八型(+action 行动点——正向 ap_up 下轮行动点 +1、负向 ap_drain 迟滞 −1,经 action_economy 排队、下一轮兑现);② blast 负向 ambush_double 退役换装 enemy_alert 警觉(敌方全体护甲等级 +1/层,`status_effects.tres` alert 行,side-wide);③ 极性矩阵/八型字典/负向即时节/候选集合/调参表/结构冻结/验收标准同步;正向权重合计 67→74(attack 14 / defense 11 / blast 16 / heal 2 / weaken 8 / pierce 8 / magic 8 / 清障炸药 1 / action 6) |
|
||||
| v0.15 | 2026-09-20 | **格面资源九型批(GP+商店房批,用户拍板——格面 GP 获取途径推翻提案"格面池零修订"推荐:定位"低概率惊喜获取"、量与战斗掉落相当;与战斗结算 v0.23 / 冒险结构 v0.5 / 关卡节点地图 v1.1 / registry / 切片代码同批)**:① 八型→九型(+gp 金币——正向 gp_gain 金币袋 weight 2、负向 gp_loss 破财 weight=0 纯字典,power 语义 = 战斗内 GP 缓冲 ±,兑付编排归战斗结算、钱包账本归冒险结构);② 负向恒即时节补 gp 两向缓冲语义(战斗完成随 gp_dropped 统一到账,无库存语义不变);③ 候选集合/调参表/结构冻结(九型封闭枚举)/验收标准同步——正向权重合计 74→76;字典 16 格→18 格 |
|
||||
@@ -65,7 +65,7 @@
|
||||
| ④ 衰减段 | 移除到期的临时效果;每轮恰好一次,含末轮——清图奖励先兑付、效果后衰减 |
|
||||
| ⑤ 判定段 | 先判主角倒下(负),再判敌人清空(胜),都不满足则继续下一轮 |
|
||||
|
||||
结算本体是纯逻辑步骤流:零界面依赖、禁止动画等待,随机消费只走封闭随机流。触发只读行动经济的相位读数与轮末标记,这条消息有棋盘路径和停手路径两个发射源,到达先后不承载语义。这些性质共同保证结算可回放:同样的种子、同样的事件序列,跑出的步骤流逐位复现——录像回放与断言复测都建立在它上面。随机消费恰六处(2026-09-16 敌人技能池批由两处扩为三处;2026-09-17 分支节点图批换板入编排后扩为五处;2026-09-20 地形变异首批类型定稿批扩为六处——变异布置):战斗开始掷骰定敌方实际数量与攻击基线浮动,属于初始化随机、发生在结算步骤流之外;技能摇号属于回合内随机,但时点前置到每轮开轮、同样发生在结算步骤流之外(消费专用 `behavior_roll` 封闭流,每敌按生成序派生独立子流,单敌出招轨迹可独立复放、互不穿插);护甲等级掷点属于回合内随机、发生在步骤流之内(消费专用 `armor_roll` 封闭流,逐次掷逐次取号,可逐位回放);奖励格摇号(消费 `reward_roll` 封闭流,装板期格面资源批派发)、变异布置(消费 `variant_place` 封闭流,装板与资源预分配之后——棋盘设置顺序第 4 步)与换板棋盘生成(消费 `board_gen` 封闭流,清图轮换板时点)均属初始化随机、发生在结算步骤流之外。六处皆封闭随机流,同种子同输入逐位复现。
|
||||
结算本体是纯逻辑步骤流:零界面依赖、禁止动画等待,随机消费只走封闭随机流。触发只读行动经济的相位读数与轮末标记,这条消息有棋盘路径和停手路径两个发射源,到达先后不承载语义。这些性质共同保证结算可回放:同样的种子、同样的事件序列,跑出的步骤流逐位复现——录像回放与断言复测都建立在它上面。随机消费恰七处(2026-09-16 敌人技能池批由两处扩为三处;2026-09-17 分支节点图批换板入编排后扩为五处;2026-09-20 地形变异首批类型定稿批扩为六处——变异布置;同日 GP+商店房批扩为七处——战斗结束掉落掷定):战斗开始掷骰定敌方实际数量与攻击基线浮动,属于初始化随机、发生在结算步骤流之外;技能摇号属于回合内随机,但时点前置到每轮开轮、同样发生在结算步骤流之外(消费专用 `behavior_roll` 封闭流,每敌按生成序派生独立子流,单敌出招轨迹可独立复放、互不穿插);护甲等级掷点属于回合内随机、发生在步骤流之内(消费专用 `armor_roll` 封闭流,逐次掷逐次取号,可逐位回放);奖励格摇号(消费 `reward_roll` 封闭流,装板期格面资源批派发)、变异布置(消费 `variant_place` 封闭流,装板与资源预分配之后——棋盘设置顺序第 4 步)与换板棋盘生成(消费 `board_gen` 封闭流,清图轮换板时点)均属初始化随机、发生在结算步骤流之外;战斗结束掉落掷定属出口随机(消费 `gp_drop` 封闭流,按战斗节点身份查商店表掷定 GP 掉落,与关级加值、战斗内缓冲合成本战 gp_dropped),发生在结算步骤流之外。七处皆封闭随机流,同种子同输入逐位复现。
|
||||
|
||||
举证:五段为流程编排,无计算;段序常量存配置表,加载时校验与定稿顺序完全一致,不一致拒绝启动。
|
||||
|
||||
@@ -120,7 +120,7 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 ×
|
||||
|
||||
授予段消费结算队列。**队列与判定基线**:挖掘期玩家对格子的动作——开格、标旗——都进入结算队列;结算逐批兑付,不走逐动作出栈直算。旗判定以轮末在场旗位为基线:被直接挖开的旗已随挖掘消失、不在基线(零兑付——标中 / 标空让位于挖掘本体结局,见下文插旗不可逆四情况);开格事实不可撤(已开格无法回退)。过滤后按触发源分三批兑换,每格受理、每旗判定恰好一次。
|
||||
|
||||
兑付走**极性矩阵**(定义归[地图战斗资源](地图战斗资源.md)):开安全格 → 该格格面资源的正向;开雷格 → 负向;标中旗 → 该雷格资源的正向;标错旗 → 该安全格资源的负向。八型字典里每型各有一行正向、一行负向——伏击随 2026-09-20 八型批退役,blast 型负向现况 = 警觉(敌方护甲等级 +1)。
|
||||
兑付走**极性矩阵**(定义归[地图战斗资源](地图战斗资源.md)):开安全格 → 该格格面资源的正向;开雷格 → 负向;标中旗 → 该雷格资源的正向;标错旗 → 该安全格资源的负向。九型字典里每型各有一行正向、一行负向(gp 型随 2026-09-20 GP+商店房批增补——gp 正负两向兑付 = 战斗内 GP 缓冲,见下方兑付形态表);伏击随 2026-09-20 八型批退役,blast 型负向现况 = 警觉(敌方护甲等级 +1)。
|
||||
|
||||
**插旗不可逆四情况(2026-09-19 用户拍板全套落地,2026-09-18 提案转正)**:旗是一次性承诺、拔旗动作退役、挖旗格放行——兑付按四情况改判:
|
||||
|
||||
@@ -146,6 +146,7 @@ percent 类(每结算、每层): E = |层数 × 行值| ÷ 100 ×
|
||||
| 即时治疗(flat) | heal 正向 | 主角按表值回复并封顶上限 | 满血时零兑付,记"奖励未生效"行 |
|
||||
| 直伤(flat) | heal 负向 | 主角当场扣表值生命,不经增益状态 | 生命归零即走致死短路 |
|
||||
| 警觉加层 | blast 负向 | 敌方全体护甲等级 +1/层(读 `reward.tres` enemy_alert 行 → `status_effects.tres` alert 行),入增益状态容器 | 直授无上限;战斗段由防守方护甲等级读数消费 |
|
||||
| GP 缓冲(flat) | gp 正 / 负向 | 金币袋 / 破财按 power 记入战斗内 GP 缓冲(+power / −power);缓冲随战斗完成折进 battle_finished 的 gp_dropped 统一到账(钱包账本归[冒险结构](../冒险循环/冒险结构.md)) | 无拒收态——缓冲只在战斗边界与钱包结算,战斗域零钱包状态 |
|
||||
| 行动点修正 | action 正/负向 | 下轮行动点 ±power(action_economy 排队,下一轮开始兑现后清零;下限 0) | 排队净值不跨轮保留;明细记效果行(effect_id=ap_delta) |
|
||||
|
||||
警觉(blast 负向——2026-09-20 八型批伏击退役换装)为敌方 side-wide 状态:多个敌人共享同一状态值、互不消耗,只活一轮、轮末清零;战斗段由防守方护甲等级读数消费(我打敌 = 敌方 armor_class + Σ敌方侧 ac 型状态)。其余各型负向受池 clamp 与叠层上限承载。
|
||||
@@ -264,7 +265,7 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
|
||||
| 战斗进行中谓词 `is_battle_live` | 只读函数,布尔 | 战斗未出结果且主角在场——组合根高频相位判断的低成本面 |
|
||||
| 奖励身份查询 `query_reward_identity` | 只读函数,单格 | 已开格奖励身份解析的代理,转[地图战斗资源](地图战斗资源.md)的身份查询(就绪相位守卫归对方)——组合根注入棋盘徽章回调 |
|
||||
| 结算快照信号 `settlement_snapshot` | 信号,每次结算末 | 对象销毁前组装的全量状态快照(终局含胜负结果)——测试记录与数值分析消费 |
|
||||
| 战斗结束信号 `battle_finished` | 信号,战斗结束时 | 载荷 = 胜负结果与主角余量生命(销毁前捕获)——呈现层在核心对象销毁后读状态的时序防护 |
|
||||
| 战斗结束信号 `battle_finished` | 信号,战斗结束时 | 载荷 = 胜负结果、主角余量生命与本战 GP 掉落 `gp_dropped`(掉落掷定 + 关级加值 + 战斗内缓冲的合计;销毁前捕获)——呈现层在核心对象销毁后读状态的时序防护,钱包入账归[冒险结构](../冒险循环/冒险结构.md) |
|
||||
|
||||
另设一个诊断观测面(诊断快照 `diagnostic_snapshot`)供组合根诊断转储与行为探针消费:它暴露编排内部状态,不属于呈现契约,不作其他用途。
|
||||
|
||||
@@ -323,7 +324,7 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
|
||||
- 数值边界:伤害下限保底后全程无零值/负值伤害;溢杀丢弃不转移;治疗封顶不溢出。
|
||||
- 出口分发:先判主角倒下(负)再判敌清空(胜);停手与耗尽轮恒同图,清图轮才允许换图。
|
||||
- 关卡收束:关卡结束清空魔法道具背包恰一次并返回清空前计数;战斗结束不清背包。
|
||||
- 可回放(封闭随机流口径):同种子同输入两遍步骤流逐位复现;随机消费仅限六处封闭随机流——开战掷骰(初始化随机)、技能摇号 `behavior_roll`(回合内、开轮时点、步骤流之外)、护甲等级掷点 `armor_roll`(回合内、步骤流之内)、奖励格摇号 `reward_roll`(初始化、装板期)、变异布置 `variant_place`(初始化、装板与资源预分配之后)、换板棋盘生成 `board_gen`(初始化、清图轮换板时点),无未登记随机源、零界面等待;单敌出招轨迹可独立复放、互不穿插。
|
||||
- 可回放(封闭随机流口径):同种子同输入两遍步骤流逐位复现;随机消费仅限七处封闭随机流——开战掷骰(初始化随机)、技能摇号 `behavior_roll`(回合内、开轮时点、步骤流之外)、护甲等级掷点 `armor_roll`(回合内、步骤流之内)、奖励格摇号 `reward_roll`(初始化、装板期)、变异布置 `variant_place`(初始化、装板与资源预分配之后)、换板棋盘生成 `board_gen`(初始化、清图轮换板时点)、战斗结束掉落掷定 `gp_drop`(出口随机、战斗结束分支),无未登记随机源、零界面等待;单敌出招轨迹可独立复放、互不穿插。
|
||||
- 无来源即炸:每行明细都能指回一个来源事实(棋盘格、单位、清图或段级结论);找不到来源即断言失败,行型与来源类别两层豁免清单见明细流水一节,豁免行仍受其它契约约束。
|
||||
- 行型封闭:流水只出现本文登记的 9 类行型;新增或下线行型必须走本文修订并同步呈现层行格式化,未登记行型不得落行。
|
||||
|
||||
@@ -357,3 +358,4 @@ EV 参考数不入正文(派生读数,需要时按判定式与现行表值
|
||||
| v0.19 | 2026-09-20 | **伤害下限全来源批(用户拍板「任何伤害取值域 [1,+∞),最小值一定是 1」;与 06 清单「零伤害/无威胁组合的跨字段护栏」收口 / registry / 切片代码同批,台账 100)**:① 下限语义从"任何攻击落地伤害"扩为"任何来源的伤害落地"——攻击双方(原有)+棋盘克制直伤(counter_component 增 landed_floor 参数,空桶不吃修正恒 0 边界不变)+爆裂正向+负向反噬(穿刺陷阱/魔法反噬/heal 反噬)+地形变异 flat_damage,全部收进 damage_floor 闸内;② 取值域成文 [1, +∞) **不设上限**——「零伤害/无威胁组合」条目的跨表上界护栏半边随拍板裁决为不设(上不封顶),敌种攻击基线 0 的退化态经下限自然消解(0 基线攻击落地仍 ≥1,"无威胁敌"结构性不存在);③ 现行表值下零行为变更(棋盘直伤贴底值 2−1=1 本就在闸内),本批=结构保证+调参防误配;④ 判读口径/约束表/调参表/验收四处措辞同步;冒烟 352→355 项 0 失败(+贴底/穿透/空桶三断言)。版本号注记:v0.19 在伏击上限删除批(未合并分支)中已被占用,本条与之合并时以先落籍者为准顺延避让 |
|
||||
| v0.20 | 2026-09-20 | **伏击单轮上限删除批(用户拍板——与 敌人系统 / 地图战斗资源 / 难度曲线与Boss / registry / 切片代码同批)**:① 行型协议 10 类→9 类——溢出未生效行下线(唯一发射源 = 伏击上限截流,上限删除后零发射面);② 授予段伏击加层改直授无上限(反灾难护栏退役——一轮踩雷数由行动点预算天然封顶);③ 核验清单伏击守恒条改伏击池共享;④ 切片同批:enemy.tres 12 行字段删除、敌表校验/编排器截流/单位容器 F3 计划函数/日志视图 overflow 格式化删除、冒烟 blast 负向断言改直授累加(330 项 0 失败) |
|
||||
| v0.21 | 2026-09-20 | **格面资源八型批(用户拍板——与 敌人系统 / 地图战斗资源 / 行动经济 / registry / 切片代码同批)**:① 三乘法链与敌方技能攻击公式删伏击附加项及其"后置加法不进乘区"解释短语(rider 保留);② 授予段兑付表 blast 负向换装警觉加层(enemy_alert → alert 行)+ 新增 action 型行动点修正行(queue_ap_delta 排队、下一轮开始兑现、下限 0、明细记 effect_id=ap_delta);③ EV flat 类枚举伏击→行动点;④ 呈现快照两处伏击层数拆除(警觉并入敌方增益 effects 投影);⑤ 棋盘来源必中枚举去伏击(两处);⑥ 核验清单伏击池共享改警觉共享 |
|
||||
| v0.23 | 2026-09-20 | **GP+商店房批(用户拍板——与 关卡节点地图 v1.1 / 冒险结构 v0.5 / 地图战斗资源 v0.15 / 难度曲线与Boss v0.18 / registry / 切片代码同批;版本号注记:v0.22 已被充能退役批占用,表尾顺延)**:① 呈现只读面表 battle_finished 载荷 +gp_dropped(掉落掷定 + 关级加值 + 战斗内缓冲的合计——字段增删走本文修订条款兑现);② 授予段兑付形态表新增 GP 缓冲行:gp 正/负向(金币袋 / 破财——九型批)兑付 = 战斗内 GP 缓冲 ±power,战斗完成统一上抛入钱包账本(账本归冒险结构),战斗域零钱包状态;③ 随机口径六处→七处:战斗结束掉落掷定消费 `gp_drop` 封闭流(出口随机、步骤流之外);九型字典措辞与验收标准可回放条目同步 |
|
||||
@@ -20,10 +20,10 @@ entities:
|
||||
owner: design/gdd/systems/表现与支撑/数据配置.md
|
||||
attributes:
|
||||
kind: autoload
|
||||
streams: [board_gen, reward_roll, encounter, armor_roll, behavior_roll, variant_place]
|
||||
stream_names: 当前封闭清单(增流需改代码 + P1 支柱评审;combat_var 已于 2026-09-07 full 评审删除——玩法随机不预置基础设施;encounter 增流 2026-09-12 用户拍板——遭遇随机化批,scope_event_binding = 每次战斗实例化恰一 scope;**armor_roll 增流 2026-09-16 用户拍板**——护甲等级掷点批,回合内随机首次入流:scope_event_binding = 每次掷点恰一 scope〔逐次掷逐次取号、可逐位回放〕,消费面 = 战斗结算 吃掷点分量判定;**behavior_roll 增流 2026-09-16 用户拍板**——敌人技能池批(开轮摇号):回合内随机、时点前置到每轮开轮〔结算步骤流之外〕,scope_event_binding = 每敌每摇号恰一 scope,**派生向量 = spawn_index × 1000000 + 单敌摇号序**〔单敌出招轨迹可独立复放、敌互不穿插;取号门 = open_scope(stream, scope) 显式 scope 取号——计数器仍单调 +1 供观测〕,消费面 = 战斗结算 开轮摇号换算层 + 技能摇号器 SkillLottery;**variant_place 增流 2026-09-20 用户拍板**——地形变异首批类型定稿批:scope_event_binding = 每次装板恰一 scope〔常规换图/轮内刷新图各取号一次〕,消费面 = 战斗结算 布置段 _place_variants——变异类型选取 + 连通片生长取数)
|
||||
streams: [board_gen, reward_roll, encounter, armor_roll, behavior_roll, variant_place, gp_drop]
|
||||
stream_names: 当前封闭清单(增流需改代码 + P1 支柱评审;combat_var 已于 2026-09-07 full 评审删除——玩法随机不预置基础设施;encounter 增流 2026-09-12 用户拍板——遭遇随机化批,scope_event_binding = 每次战斗实例化恰一 scope;**armor_roll 增流 2026-09-16 用户拍板**——护甲等级掷点批,回合内随机首次入流:scope_event_binding = 每次掷点恰一 scope〔逐次掷逐次取号、可逐位回放〕,消费面 = 战斗结算 吃掷点分量判定;**behavior_roll 增流 2026-09-16 用户拍板**——敌人技能池批(开轮摇号):回合内随机、时点前置到每轮开轮〔结算步骤流之外〕,scope_event_binding = 每敌每摇号恰一 scope,**派生向量 = spawn_index × 1000000 + 单敌摇号序**〔单敌出招轨迹可独立复放、敌互不穿插;取号门 = open_scope(stream, scope) 显式 scope 取号——计数器仍单调 +1 供观测〕,消费面 = 战斗结算 开轮摇号换算层 + 技能摇号器 SkillLottery;**variant_place 增流 2026-09-20 用户拍板**——地形变异首批类型定稿批:scope_event_binding = 每次装板恰一 scope〔常规换图/轮内刷新图各取号一次〕,消费面 = 战斗结算 布置段 _place_variants——变异类型选取 + 连通片生长取数;**gp_drop 增流 2026-09-20 用户拍板**——GP+商店房批:scope_event_binding = 每场战斗结束的掉落掷定恰一 scope,消费面 = 战斗结算 战斗结束分支——按战斗节点身份查商店表掷定 GP 掉落,掷定 + 关级加值 + 战斗内缓冲合成本战 gp_dropped〔shop_roll 流随"无三选一摇号"裁决作废、从未入册〕)
|
||||
stream_facade: open_stream(stream) 取号(计数器+1→派生→返回已播种实例);derive_at(stream, scope) 纯函数供测试/预览(不推计数器)
|
||||
scope_event_binding: board_gen=每次生成新扫雷图(棋盘生成);reward_roll=每次 board install(格奖励批 + 清图奖励批)(地图战斗资源——2026-09-09 接口漂移改写,原"每次奖励结算 roll 批次";地图战斗资源 Rule 2/AC-1 为契约源);encounter=每次战斗实例化恰一 scope(战斗结算 begin_battle 实例化期、扫雷开始前——unit_count 区间均匀骰,挖掘决策域之外 P1 合规);armor_roll=每次掷点恰一 scope(战斗结算 战斗段吃掷点分量逐次判定——1d20,护甲等级掷点批 2026-09-16);variant_place=每次装板恰一 scope(战斗结算 布置段——地形变异首批类型定稿批 2026-09-20)
|
||||
scope_event_binding: board_gen=每次生成新扫雷图(棋盘生成);reward_roll=每次 board install(格奖励批 + 清图奖励批)(地图战斗资源——2026-09-09 接口漂移改写,原"每次奖励结算 roll 批次";地图战斗资源 Rule 2/AC-1 为契约源);encounter=每次战斗实例化恰一 scope(战斗结算 begin_battle 实例化期、扫雷开始前——unit_count 区间均匀骰,挖掘决策域之外 P1 合规);armor_roll=每次掷点恰一 scope(战斗结算 战斗段吃掷点分量逐次判定——1d20,护甲等级掷点批 2026-09-16);variant_place=每次装板恰一 scope(战斗结算 布置段——地形变异首批类型定稿批 2026-09-20);gp_drop=每场战斗结束的掉落掷定恰一 scope(战斗结算 战斗结束分支——GP+商店房批 2026-09-20)
|
||||
unseeded_access: 致命中止
|
||||
snapshot_contract: 续玩/复盘快照 = master_seed + 各流 scope 计数器(消费方 存档序列化)
|
||||
referenced_by: [棋盘生成, 战斗结算, 地图战斗资源, 冒险结构, 存档序列化]
|
||||
@@ -34,19 +34,20 @@ entities:
|
||||
attributes:
|
||||
variables: [SALT_s, master_seed, scope]
|
||||
output_range: "[0, 2^63)"
|
||||
golden_vectors: 见《数据配置》Formulas-R3 向量表(8 常量——2026-09-19 掷点测量修复批 B 案 R2→R3 改版全量重登记,Python 参考实现逐位交叉验证:board_gen m=42 scope0=8672514112661456564 / scope1=8834291164406037215 / scope2=7714373262647006282 / scope3=1280102845971471347 · reward_roll m=42 scope0=8363815839763232861 · encounter m=42 scope0=5906263231575069166 · armor_roll m=42 scope0=1459964581993244806 · behavior_roll m=42 scope0=1791557311233092846 · variant_place m=42 scope0=5785725196205023206——2026-09-20 地形变异首批类型定稿批 variant_place 增流按 R3 公式补向量;R2 旧向量随批作废)
|
||||
golden_vectors: 见《数据配置》Formulas-R3 向量表(8 常量——2026-09-19 掷点测量修复批 B 案 R2→R3 改版全量重登记,Python 参考实现逐位交叉验证:board_gen m=42 scope0=8672514112661456564 / scope1=8834291164406037215 / scope2=7714373262647006282 / scope3=1280102845971471347 · reward_roll m=42 scope0=8363815839763232861 · encounter m=42 scope0=5906263231575069166 · armor_roll m=42 scope0=1459964581993244806 · behavior_roll m=42 scope0=1791557311233092846 · variant_place m=42 scope0=5785725196205023206——2026-09-20 地形变异首批类型定稿批 variant_place 增流按 R3 公式补向量;R2 旧向量随批作废;**2026-09-21 GP+商店房实施批 gp_drop 增流向量补登**:gp_drop m=42 scope0=6226476397888000011 / scope1=5031558177015478025——按 R3 公式登记,冒烟同批断言)
|
||||
salt_board_gen: 0xA0761D6478BD642F
|
||||
salt_reward_roll: 0xE7037ED1A0B428DB
|
||||
salt_encounter: 0x8EBC6AF09C88C6E3(2026-09-14 encounter 增流——互异奇常数 ≠ 乘数)
|
||||
salt_armor_roll: 0xC2B2AE3D27D4EB2F(2026-09-16 armor_roll 增流——互异奇常数 ≠ 乘数)
|
||||
salt_behavior_roll: 0x85EBCA7758C24E15(2026-09-16 behavior_roll 增流——互异奇常数 ≠ 乘数)
|
||||
salt_variant_place: 0x9AE16A3F2FAD6C39(2026-09-20 variant_place 增流——互异奇常数 ≠ 乘数)
|
||||
salt_gp_drop: 0xD6E8FEB8665FD7F1(2026-09-21 gp_drop 增流——互异奇常数 ≠ 乘数;GP+商店房实施批随黄金向量同批补登)
|
||||
multiplier: 0x9E3779B97F4A7C15
|
||||
finalizer_mix: splitmix64 finalizer 变体(2026-09-19 B 案新增——derive = mix64(SALT XOR wrap64(master + scope×GOLDEN));乘数 0xBF58476D1CE4E5B9 / 0x94D049BB133111EB、加数同 GOLDEN_RATIO;全程 63 位掩码;动机=窄种子窗×短局复用会把裸派生种子头部窗口的面值彩票放大进聚合统计,混淆使任何窄种子窗表现如随机抽样)
|
||||
impl_spec: wrap64=二补数自然回绕;禁 float 中间量、禁对中间量用 %、禁"主动修复溢出";常量用分段表达式书写;掩码收尾;mix64 内全程 OUT_MASK 63 位掩码(有符号 int64 算术移位在 63 位正值域与逻辑移位逐位一致——参考实现对齐关键)
|
||||
reproducibility_claim: 同冒险内同流各 scope 无碰撞 + 跨版本恒定(不做跨 master_seed 全局单射声明)
|
||||
master_seed_int64_min: 致命(abs 恒等回绕→与 0 静默别名)
|
||||
set_master_seed_precondition: 仅全流计数器全零时受理(6 流——2026-09-14 数据配置 漂移批口径 + 2026-09-16 护甲等级掷点批 armor_roll 增流 + 同日敌人技能池批 behavior_roll 增流 + 2026-09-20 地形变异首批类型定稿批 variant_place 增流)
|
||||
set_master_seed_precondition: 仅全流计数器全零时受理(7 流——2026-09-14 数据配置 漂移批口径 + 2026-09-16 护甲等级掷点批 armor_roll 增流 + 同日敌人技能池批 behavior_roll 增流 + 2026-09-20 地形变异首批类型定稿批 variant_place 增流 + 同日 GP+商店房批 gp_drop 增流)
|
||||
referenced_by: [棋盘生成, 战斗结算, 地图战斗资源, 冒险结构, 存档序列化]
|
||||
|
||||
- name: difficulty_row_resolution
|
||||
@@ -220,7 +221,7 @@ entities:
|
||||
伏击池: 【2026-09-20 格面资源八型批退役——本词条留档】敌方侧共享的伏击层数——本轮踩几颗雷就几层,轮末清零(side-wide 语义,敌人系统 首次定义;继任 = alert 警觉 side-wide 状态,由增益状态容器承载)
|
||||
死亡即不动手: 轮到某敌人行动时它已死 → 这一下不打、招式轮转停格(2026-09-09 用户拍板,敌人系统 首次定义)
|
||||
封锁格: 初始展开白送的那批已开格——恒无格面资源、不再有任何加成,但计入清图进度(排雷玩法 首次定义;install 期可精确枚举为 R₀——格面资源摇号清单的排除项;取代旧术语"资格格",2026-09-15 格面资源批)
|
||||
格面资源(CellResource): 关联到格子上的资源对象——装板期摇定八型类型身份(attack/defense/heal/blast/weaken/pierce/magic/action——2026-09-18 伤害道具批五→七、2026-09-20 格面资源八型批七→八),安全格与雷格一视同仁、同池同分布;极性不在资源内,由「动作 × 格型」矩阵在结算期决定正向(奖励)或负向(惩罚)(地图战斗资源 首次定义;取代"安全格奖励"旧名)
|
||||
格面资源(CellResource): 关联到格子上的资源对象——装板期摇定九型类型身份(attack/defense/heal/blast/weaken/pierce/magic/action/gp——2026-09-18 伤害道具批五→七、2026-09-20 格面资源八型批七→八、同日 GP+商店房批八→九 +gp),安全格与雷格一视同仁、同池同分布;极性不在资源内,由「动作 × 格型」矩阵在结算期决定正向(奖励)或负向(惩罚)(地图战斗资源 首次定义;取代"安全格奖励"旧名)
|
||||
批尾: 同一批取号中最后消费的一个号——地图战斗资源 清图奖励身份 = install 批的批尾取号(地图战斗资源 F1 首次定义)
|
||||
死行: 被当前内容态判定为零消费的表行——加载不致命、不入参考池,复活走 GDD 修订连锁(地图战斗资源 F4 首次定义;历史首例 attack_up〔hero atk=0 时代〕已于 2026-09-11 随"基础攻击恒存在"用户裁决复活,当前内容态暂无在册死行)
|
||||
预推进: 测试中为到达 scope=σ 而先行丢弃式消费 σ 个 scope 的构造——RngService 全新实例首个 open_stream 恒为 scope 0,derive_at 不推计数器(地图战斗资源 AC 前言首次定义)
|
||||
@@ -238,7 +239,10 @@ entities:
|
||||
真视窄域护栏: 替代推理的道具效果(雷位真视)目标域 ≤3×3、全图级仅限恢复推理原料类(显影/预读)——信息层级护栏,防"扫雷退化为路径优化"(魔法道具 Core Rule 3 首次定义,schema 校验强制)
|
||||
渗透率: aux 道具期望件数占奖励池的份额——E[N_aux/板] = |U|×Σw_aux/W_total(魔法道具 F1 首次定义;供给面天然限流,P1 护栏的量化形态)
|
||||
行动: 轮次内挖掘期的原子决策单位——同一事件的三个视角:玩家决策层叫行动、进 排雷玩法 的数据形态叫意图(单意图帧整批提交)、行动经济 计价类别叫动作(action_kind 统一价目);有价行动(挖/插旗;拔旗随插旗不可逆批 2026-09-19 退役)1 AP/次、受每轮 AP 预算约束(现行表值 8),移动/停手免费不计 AP(game-concept 术语层级 2026-09-14 收录)
|
||||
节点: 关卡节点地图上的一个格——关卡内移动的步进单位;层名定稿(2026-09-14 用户裁决:节点 胜 遭遇——成员中立 + 关卡节点地图 注册名/切片 node_* 接口零改动 + 遭遇既有语义 = 战斗不挪用);类型分类学与图形态已展开定稿(2026-09-17 关卡节点地图 v1.0:四类封闭枚举 {battle, reward, rest, boss} + 分支节点图〔连线只朝前进方向、唯一入口、Boss 恒唯一终点、无回头路〕;节点编号 = 关卡内唯一定位地址,数据键 node_depth 沿用)
|
||||
节点: 关卡节点地图上的一个格——关卡内移动的步进单位;层名定稿(2026-09-14 用户裁决:节点 胜 遭遇——成员中立 + 关卡节点地图 注册名/切片 node_* 接口零改动 + 遭遇既有语义 = 战斗不挪用);类型分类学与图形态已展开定稿(2026-09-17 关卡节点地图 v1.0:四类封闭枚举 {battle, reward, rest, boss} + 分支节点图〔连线只朝前进方向、唯一入口、Boss 恒唯一终点、无回头路〕;节点编号 = 关卡内唯一定位地址,数据键 node_depth 沿用;2026-09-20 GP+商店房批扩五类 +shop 商店房——每关恰 1、Boss 前一位必经位,兑付 = 商店覆盖层开闭,见商店房词条)
|
||||
商店房: 关卡节点图第五类节点——每关恰 1、关底 Boss 前一位必经位(哈迪斯式"决战前最后一个补给点",必经位由冒烟断言承载、图合法性六检零新增);兑付 = 打开商店覆盖层,用金币(GP)购买三属性 flat 档商品(无刷新无三选一、全陈列),购买即时生效、离开后确认回选路,不携带战斗行(关卡节点地图 首次定义,2026-09-20 GP+商店房批)
|
||||
金币(GP): 冒险内一次性的属性提升货币——战斗掉落(掷定 + 关级加值 + 战斗内缓冲,完成一次战斗后统一到账)与格面金币袋(低概率惊喜获取)进钱包、商店房消费、冒险结束清零;不入存档、不入冒险摘要;玩家侧显示名「金币」,与跨局持久的钥石记账完全分离、永不互兑(冒险结构 首次定义,2026-09-20 GP+商店房批)
|
||||
统一冒险加算区: 商店购买与关卡大奖励上限提升共用的同一本属性加账——三轴 {攻击, 生命上限, 护甲等级},入战参数 = 基础表值 + 加算区累加,经战斗开始入参的覆盖值通道生效;购血 = 上限与当前生命同步 +Δ(冒险结构 首次定义,2026-09-20 GP+商店房批)
|
||||
遭遇: 一次完整战斗事件的同义词——战斗节点触发的那一场仗(既有用法:战斗结算"两场遭遇" / HUD框架"双敌种遭遇""Boss 遭遇变体");不作地图层名(2026-09-14 裁决)
|
||||
节点奖励: 奖励点节点给的奖励(关卡节点地图 域)——与安全格奖励(地图战斗资源,格内开出)区分;机制定稿(2026-09-17 关卡节点地图 v1.0:节点奖励 = **功能道具的正式渠道**,兑付 = 获得一件功能道具入持有域;内容/持有/使用语义归 魔法道具 内容重定批,切片期零兑付直通为合法空态);节点奖励覆盖层 = 兑付反馈覆盖层(与行进确认同面,视觉规格归 HUD框架;spec design/ux/node-reward.md 未来件〔battle.md OQ9〕)——三个"reward"各归各位(2026-09-14 拆名注记)
|
||||
HP 账本: 冒险域主角血量的唯一记录——冒险结构 持有,每战开始交出、战斗结束收回、恢复点与大奖励入账;关卡切换血条不清零(冒险结构 首次定义;跨战斗 HP 持久性归 关卡节点地图/冒险结构 已锁事实的 冒险结构 半边兑现)
|
||||
@@ -311,7 +315,7 @@ entities:
|
||||
table: RewardTable(数据配置 承载,键 = reward_id;地图战斗资源 回填 owner;**2026-09-15 格面资源批结构性重写**——旧 category / use_timing / 库存 / 供给轴随可选类删除整批下线,五型 × 极性字典取代〔历史结构见底部修订记录〕)
|
||||
fields:
|
||||
reward_id: String(表内主键)
|
||||
cell_type: enum {attack, defense, heal, blast, weaken, pierce, magic, action}——八型封闭(2026-09-18 伤害道具批五→七、2026-09-20 格面资源八型批七→八 +action;格面资源类型身份;表里出现清单外值加载拒绝 fail-fast) # 2026-09-15 新增,取代 category;2026-09-18 扩七型;2026-09-20 八型批扩八型
|
||||
cell_type: enum {attack, defense, heal, blast, weaken, pierce, magic, action, gp}——九型封闭(2026-09-18 伤害道具批五→七、2026-09-20 格面资源八型批七→八 +action、同日 GP+商店房批八→九 +gp;格面资源类型身份;CELL_TYPE_KEYS 词表十八格 = 九型 × 正负,表里出现清单外值加载拒绝 fail-fast) # 2026-09-15 新增,取代 category;2026-09-18 扩七型;2026-09-20 八型批扩八型;同日 GP+商店房批扩九型
|
||||
polarity: enum {positive, negative}——同型两行:正向行参与摇号(权重 = 类型频次),负向行纯兑付字典、不消费权重 # 2026-09-15 新增
|
||||
effect_id: String 可空——percent 类行(attack/defense/weaken 正负两向)必填(StatusEffectTable 精确引用,悬空校验已激活);flat 直付行(直伤/群伤/治疗)恒空
|
||||
stacks: int [1,16] 可省——多层授予行(原 blast 负向 ambush_double ×2 层——该行随 2026-09-20 八型批退役,现行表无 stacks 行、字段保留 schema 位);缺省 1
|
||||
@@ -321,10 +325,10 @@ entities:
|
||||
weight: int [0, 999](knob)——正向行 = 类型频次;0 = 该型不出(合法极端,取号前剔除);负向行不消费
|
||||
pool_tag: enum {cell, clear_default}(cell = 格面资源单一池——安全格与雷格一视同仁同池同分布;clear_<biome> 命名空间预留、校验拒绝——棋盘生成 elite_ 同款) # 2026-09-15 safe_cell→cell 更名 + active_inject 删除
|
||||
tier: int [1, 8](knob)——强度档位序数(与当量零耦合)
|
||||
cross_field_validation: 加载期致命族——池空(cell 池 ≥1 正向行 weight>0)· 八型正向行齐备 · 八型负向行齐备(字典完整性——缺任一极性行致命——2026-09-20 八型批)· clear_default 恒正当量(≥1 活行 ∧ power≥1 ∨ effect_id 非空——行动经济 条款④"清图奖励>0"的行级兑现口径)· percent 行 effect_id 必填/flat 行恒空 · flat 行 power≥1 # 2026-09-15 重写——旧 floor_supply_missing / no_damage_row 随可选类删除失义退役
|
||||
cross_field_validation: 加载期致命族——池空(cell 池 ≥1 正向行 weight>0)· 九型正向行齐备 · 九型负向行齐备(字典完整性——缺任一极性行致命——2026-09-20 八型批、同日 GP+商店房批随九型同步)· clear_default 恒正当量(≥1 活行 ∧ power≥1 ∨ effect_id 非空——行动经济 条款④"清图奖励>0"的行级兑现口径)· percent 行 effect_id 必填/flat 行恒空 · flat 行 power≥1 # 2026-09-15 重写——旧 floor_supply_missing / no_damage_row 随可选类删除失义退役
|
||||
mechanics: 格面资源身份 install 期预分配(roll_board 恰一次、行主序、消费 reward_roll 封闭流恰一 scope 批〔格批 + 清图批尾一号〕;全部非封锁格逐一摇定类型——安全格与雷格同池同分布、封锁格不入摇号清单;Ready 后只读、无重掷 API)· **极性不在资源内**——「动作 × 格型」矩阵(开安全格/标中雷 = 正向、开雷格/标错安全格 = 负向、插旗安全格直挖 = 零兑付〔四情况④〕;撤旗动作随插旗不可逆批 2026-09-19 退役)字典定义归 地图战斗资源、路由执行归 战斗结算 授予段(结算队列 + 轮末在场旗判定基线——被挖开的旗不入基线)· 授予期零随机(身份恒定跨轮;换图映射消亡)· 负向恒即时(不入库、无库存语义——库存已随可选类删除下线)· 满层零兑付 = 合法例外 + "奖励未生效"行 · 重复受理幂等(同格重复 grant / 同批次重复 grant_clear → 同一快照/身份 + 诊断;受理/兑付恰一次归 战斗结算×②③)
|
||||
api: 封闭枚举入口(provisional,实现期 ADR 定名)——roll_board / query_identity(返回 {reward_id, cell_type, tier} 引用级投影;未开格恒可查——魔法道具 桥接结构前提;清单外坐标 → null + 诊断)/ grant(同格重复受理幂等 + 诊断)/ grant_clear(同批次重调幂等 + 诊断)/ retire_mapping / equivalence_supply(全活行投影 + tier_min_buff)+ weighted_pick 纯函数面;新增入口 = 接口漂移,新增入口须随附全输入域语义表 # 2026-09-15 删 stock/use/query_inventory/clear_inventory(库存轴下线)
|
||||
mvp_content: 十六行(2026-09-15 格面资源批十行;2026-09-18 伤害道具批增补四行;2026-09-20 格面资源八型批 ambush_double→enemy_alert 换装 + ap_up/ap_drain 两行新增)——格面池正向八行(attack_up / ac_up〔2026-09-16 护甲等级掷点批换装,原 defense_up 减伤 −30% 退役;cell_type defense/权重/档位零改动〕/ spark_aoe / heal_minor / weaken / pierce_strike / magic_bolt〔attack_type 字段载攻击类型轴,查战斗结算克制矩阵、单目标必中全额〕/ ap_up action〔下轮行动点 +1——2026-09-20 八型批新增〕)+ 负向八行纯字典(enrage / off_guard / heal_backlash 直伤〔不经增益状态〕/ enemy_alert blast〔effect_id=alert,敌方护甲等级 +1——2026-09-20 八型批 ambush_double 退役换装〕/ ap_drain action〔下轮行动点 −1〕/ sapped / pierce_snare / magic_recoil〔均主角直伤,不经增益状态〕)+ clear_blast(clear_default);类型频次 = 正向行权重合计;tier_min_buff = 2。**2026-09-17 数值压缩批·文档同步——具体数值列(power/weight/power_min/power_max)改指路存根**:现行行值读 `data/tables/reward.tres`(压缩后锚行:spark_aoe power=1 / heal_minor power=[1,1] 定值(原 [2,6] 区间压缩)/ heal_backlash power=1 / clear_blast power_min=8 power_max=18 power=13 中点 / ambush_double stacks=2;类型频次合计 79→67〔heal 权重 15→3〕;2026-09-20 八型批后 67→74——attack 14 / defense 11 / blast 16 / heal 2 / weaken 8 / pierce 8 / magic 8 / 清障炸药 1 / action 6)
|
||||
mvp_content: 十八行(2026-09-15 格面资源批十行;2026-09-18 伤害道具批增补四行;2026-09-20 格面资源八型批 ambush_double→enemy_alert 换装 + ap_up/ap_drain 两行新增;同日 GP+商店房批 +gp_gain/gp_loss 两行)——格面池正向九行(attack_up / ac_up〔2026-09-16 护甲等级掷点批换装,原 defense_up 减伤 −30% 退役;cell_type defense/权重/档位零改动〕/ spark_aoe / heal_minor / weaken / pierce_strike / magic_bolt〔attack_type 字段载攻击类型轴,查战斗结算克制矩阵、单目标必中全额〕/ ap_up action〔下轮行动点 +1——2026-09-20 八型批新增〕/ gp_gain gp 金币袋〔战斗内 GP 缓冲 +power,兑付编排归 战斗结算、钱包账本归 冒险结构——GP+商店房批新增〕)+ 负向九行纯字典(enrage / off_guard / heal_backlash 直伤〔不经增益状态〕/ enemy_alert blast〔effect_id=alert,敌方护甲等级 +1——2026-09-20 八型批 ambush_double 退役换装〕/ ap_drain action〔下轮行动点 −1〕/ sapped / pierce_snare / magic_recoil〔均主角直伤,不经增益状态〕/ gp_loss gp 破财〔战斗内 GP 缓冲 −power——GP+商店房批新增〕)+ clear_blast(clear_default);类型频次 = 正向行权重合计;tier_min_buff = 2。**2026-09-17 数值压缩批·文档同步——具体数值列(power/weight/power_min/power_max)改指路存根**:现行行值读 `data/tables/reward.tres`(压缩后锚行:spark_aoe power=1 / heal_minor power=[1,1] 定值(原 [2,6] 区间压缩)/ heal_backlash power=1 / clear_blast power_min=8 power_max=18 power=13 中点 / ambush_double stacks=2;类型频次合计 79→67〔heal 权重 15→3〕;2026-09-20 八型批后 67→74——attack 14 / defense 11 / blast 16 / heal 2 / weaken 8 / pierce 8 / magic 8 / 清障炸药 1 / action 6;同日 GP+商店房批九型后 74→76——+gp_gain weight 2)
|
||||
direction_precheck: **刀供给方向预检随可选类删除失义退役(2026-09-15)**——历史读数见底部修订记录;新对账锚 = 行动经济 条款②③ 左臂(正向格面资源当量供给)随格面资源批重推,义务归 难度曲线与Boss 调参批
|
||||
settlement_obligations: 四项(战斗结算 GDD 门禁评审缺项即 blocking,清单全文见 地图战斗资源 GDD Dependencies)——① 结算段序骨架(授予→增益/减益→战斗→衰减→判定)② 极性路由受理 + 每格恰一次 + 满层零兑付"奖励未生效"行(含聚合计数字段)③ clear_reward 授予路径(增益状态 OQ4 关闭条件)④ 生命周期编排(battle_over retire 映射消亡)+ roll_board 恰一次 # 2026-09-15 由六项收拢(窗口/道具段与库存编排随可选类删除失义)
|
||||
hud_obligations: 三项(缺项即 blocking)——① 已开格资源身份恒定可读(挖掘期规划面)+ 未开格类型可查面的呈现深度(预读边界待办)② 结算明细行(效果行/"奖励未生效"〔聚合计数〕/标旗未中行/直伤行)③ H2 清图奖励行强调呈现 # 2026-09-15 由五项收拢(库存常读条/道具窗口随可选类删除失义)
|
||||
@@ -532,10 +536,10 @@ entities:
|
||||
level_index: int 行主键(冒险序列序号,从 1 连续递增——线性序列无跳关)
|
||||
display_name: String(玩家可读层名——L1"第一层 · 地精洞窟"、L2"第二层 · 幽暗地域"切片现行〔L2 随 2026-09-17 分支节点图批落名;L1 随 2026-09-20 层名改名批落名——原「苔藓回廊」退役,同日二改「裂颚洞窟」→「地精洞窟」定名(不绑 DND 专名部族,规避 IP 指涉)〕)
|
||||
rest_heal_pct: int [0,100](knob 默认 30——恢复点回复 max_hp_eff 的百分比,随上限加成同步放大防高上限档恢复失效;数值复验归 难度曲线与Boss)
|
||||
nodes: 节点图结构——节点数组 + 连线(node_kind ∈ {battle, reward, rest, boss},切片字段名 type;每节点领关卡内唯一**节点编号**,battle/boss 数据键 node_depth 沿用〔v1.0 语义升级:深度 → 节点编号〕;连线 = 出边节点编号表,只朝前进方向、无回头路);单链特例 = 隐式后继(数组序即链,切片现行形态零漂移)
|
||||
rowset_rule: 图合法性校验(2026-09-17 关卡节点地图 v1.0)——节点编号关卡内唯一、入口唯一、Boss 恒唯一终点(编号最大,Dmax 语义平移)、连线无环、所有节点沿连线可达 Boss,违规加载期致命;实现批(2026-09-17 分支节点图批):校验落 LevelTable.validate(单链形态经同一套算法——编号解析单链 = 数组下标 / 图 = node_depth),L1 单链与 L2 图双行集加载全绿断言
|
||||
nodes: 节点图结构——节点数组 + 连线(node_kind ∈ {battle, reward, rest, boss, shop},切片字段名 type;+shop 随 2026-09-20 GP+商店房批——商店房兑付 = 打开商店覆盖层、离开后确认回选路,不携带战斗行〔零敌人、零棋盘配置行〕;每节点领关卡内唯一**节点编号**,battle/boss 数据键 node_depth 沿用〔v1.0 语义升级:深度 → 节点编号〕;连线 = 出边节点编号表,只朝前进方向、无回头路);单链特例 = 隐式后继(数组序即链,切片现行形态零漂移)
|
||||
rowset_rule: 图合法性校验(2026-09-17 关卡节点地图 v1.0)——节点编号关卡内唯一、入口唯一、Boss 恒唯一终点(编号最大,Dmax 语义平移)、连线无环、所有节点沿连线可达 Boss,违规加载期致命;实现批(2026-09-17 分支节点图批):校验落 LevelTable.validate(单链形态经同一套算法——编号解析单链 = 数组下标 / 图 = node_depth),L1 单链与 L2 图双行集加载全绿断言;商店房编排(每关恰 1、Boss 前一位必经位)由冒烟断言承载、非加载校验——图合法性六检零新增(GP+商店房批 2026-09-20,关卡节点地图 v1.1)
|
||||
cross_table_validation: battle/boss 节点 (L,D) 在 board_gen/enemy 两表存在精确行(数据配置 跨表校验⑧复用 + 冒险结构 AC-7 冒烟断言——序列关数 ⊆ 内容覆盖,越界关卡 = 内容生产缺口)
|
||||
mvp_content: L1 全行(切片转正——7 节点链〔单链特例,合法图形态〕、rest_heal_pct=30);L2 全行已落地(2026-09-17 分支节点图批——首张分支图:9 节点〔入口 0 → 中间三层 2/3/2 → Boss 8〕、真实岔路 4 处、奖励点+恢复点各 1、战斗节点 6 + Boss;行值 provisional 归 难度曲线与Boss v0.7;首页直入口试玩 ≠ 两关序列生效,正式序列推进仍登记待办);**L3 全行已落地(2026-09-20 第三关内容批——「第三层 · 灰矮人矿城」:九节点图复用 L2 同模板〔6 战 + Boss + 奖励 + 恢复、真实岔路 4 处〕,行值全 provisional 归 难度曲线与Boss;测试直入口 ≠ 序列生效——正式三关序列推进仍登记 Alpha 验收条件)**
|
||||
mvp_content: L1 全行(切片转正——7 节点链〔单链特例,合法图形态〕、rest_heal_pct=30);L2 全行已落地(2026-09-17 分支节点图批——首张分支图:9 节点〔入口 0 → 中间三层 2/3/2 → Boss 8〕、真实岔路 4 处、奖励点+恢复点各 1、战斗节点 6 + Boss;行值 provisional 归 难度曲线与Boss v0.7;首页直入口试玩 ≠ 两关序列生效,正式序列推进仍登记待办);**L3 全行已落地(2026-09-20 第三关内容批——「第三层 · 灰矮人矿城」:九节点图复用 L2 同模板〔6 战 + Boss + 奖励 + 恢复、真实岔路 4 处〕,行值全 provisional 归 难度曲线与Boss;测试直入口 ≠ 序列生效——正式三关序列推进仍登记 Alpha 验收条件);2026-09-20 GP+商店房批 rewiring:L2/L3 图版 Boss 重编号 8→9、各新增 (8, shop, next[9]) 商店房节点(Boss 前一位必经位),L1 零改动,图合法性六检零新增——必经位由冒烟断言承载**
|
||||
referenced_by: [冒险结构, 关卡节点地图, 难度曲线与Boss, 棋盘生成, 数据配置]
|
||||
|
||||
- name: adventure_table
|
||||
@@ -550,6 +554,26 @@ entities:
|
||||
extension_reserve: 大奖励形态扩列位(三选一 / 攻击型 / 通关加成——冒险结构 OQ2,随元进度设计)
|
||||
referenced_by: [冒险结构, 元进度, 数据配置]
|
||||
|
||||
- name: shop_table
|
||||
type: schema_data
|
||||
owner: design/gdd/systems/冒险循环/冒险结构.md
|
||||
attributes:
|
||||
table: ShopTable(数据配置 承载;**2026-09-20 GP+商店房批新建**——商店房商品与 GP 经济的行集;冒险结构 回填 owner,公式语义归其 GP 冒险账本节,数值所有权归 难度曲线与Boss 曲线调参批)
|
||||
fields: gp_drops 三行(键 = 战斗节点身份 {normal, elite, boss}——普通 均值 3 区间 [2,4] / 精英 均值 5 区间 [4,6] 预留行 / Boss 均值 8 区间 [6,10],掷定 + 均值方差口径)· level_gp_bonus(逐关——L1 0 / L2 1 / L3 2)· products 三行(atk_up 基础攻击 +1 首价 4 / hp_up 生命上限 +3 首价 2 / armor_up 护甲等级 +1 首价 4,步长均 2)· price_level_bonus(逐关——L1 0 / L2 2 / L3 4)
|
||||
formulas: 每战 GP 掉落 = 掷定 + 关级掉落加值 + 战斗内缓冲(战斗完成随 battle_finished.gp_dropped 统一入钱包——冒险结构 GP 冒险账本三收口);商品价格 = 基价 + 关级价格加成 + 步长 × 已购档数;定价锚 = 「正常运气可购档数 / 运气极好可购档数」两读数的数学期望(期望账进表批注)
|
||||
value_status: 全部行值 provisional-pending-曲线调参批(用户 2026-09-20 拍板「骨架先行、数值整体后置」)——shop=off/on 分层 MC、shop=on 带平移、换算率登记全部随曲线调参批执行(难度曲线与Boss v0.18 商店与 GP 经济杠杆节)
|
||||
rule4_status: 进度成长表(数值随关卡与商品档变化,无 Rule 4 豁免)
|
||||
referenced_by: [冒险结构, 关卡节点地图, 难度曲线与Boss, 战斗结算, 数据配置]
|
||||
|
||||
- name: battle_finished
|
||||
type: convention
|
||||
owner: design/gdd/systems/战斗循环/战斗结算.md
|
||||
attributes:
|
||||
kind: 呈现只读面战斗结束信号(战斗结算 呈现只读面表登记的对外契约——销毁前捕获,字段增删走战斗结算 GDD 修订)
|
||||
payload: 胜负结果 · 主角余量生命 · gp_dropped(本战 GP 掉落合计 = 掉落掷定 + 关级加值 + 战斗内缓冲——**2026-09-20 GP+商店房批新增**;钱包入账归 冒险结构 GP 冒险账本,战斗域零钱包状态)
|
||||
consumers: 呈现层(核心对象销毁后读状态的时序防护)/ 行进器钱包入账(战斗完成收口——关卡节点地图)/ 试玩日志 battle_end 行(gp_dropped 列,验收取数面)
|
||||
referenced_by: [冒险结构, 关卡节点地图, HUD框架]
|
||||
|
||||
- name: hero_override_parameter_passing
|
||||
type: convention
|
||||
owner: design/gdd/systems/战斗循环/战斗结算.md
|
||||
|
||||
@@ -5,6 +5,19 @@
|
||||
> 依据:06-待办清单「冒险循环内成长提案(临时资源 GP + 商店房节点)」条目(2026-09-18 登记,含六开放问题原文);2026-09-20 用户拍板立项提前、与第三关内容批捆绑;地图战斗资源 v0.14 八型字典与雷安同池冻结面;关卡节点地图 v1.0 四类节点封闭枚举;战斗结算轮次生命周期与呈现只读面(表尾 v0.22 已被充能退役批占用,本批文档版本顺延);元进度三柜台与覆盖值通道;01-核心循环数值语言条款;第三关内容批预案(proposal-level3-content-2026-09-20.md)的敌值锚
|
||||
> 行值纪律:本文只给**候选锚点与取值域**供拍板;正式行值一律随批落表(shop 新表 / level 表 / 冒险表),数值所有权归 难度曲线与Boss,不在此双写。
|
||||
|
||||
## 裁决记录(2026-09-20)
|
||||
|
||||
用户对六开放问题及附加点逐条裁决如下,要点按裁决原文记录。**本节是实施批的设计权威:§2/§5 中与本节冲突的推荐方案一律以本节为准。**
|
||||
|
||||
- **问题①(GP 格面渠道)——九型进**:推翻 §2.1"格面池零修订"推荐,拍板 GP 进格面第九型(正负两行齐全);获取来源 = 战斗结束掉落 + 格面低概率获取,量与战斗掉落相当,定位"惊喜意外获取"。
|
||||
- **问题②(掉落量级与记账)——随机掉落 + 统一到账**:推翻固定值锚——普通 / 精英 / Boss 各设均值 + 方差掷定,关间设倍率上升对应商店价格上涨;记账 = 完成一次战斗后 GP 统一到账,战斗内获取(格面兑付)走战斗域中间态缓冲、与钱包隔离。
|
||||
- **问题③(商店房结构)——扩类通过,哈迪斯式定位**:四类→五类扩类通过;位置参考哈迪斯 = 固定 Boss 房前一位;交互通过;冒险类附加属性由统一加算区管理、与关卡大奖励同一本账。
|
||||
- **问题④(商品与定价)——三件全陈列,无刷新无三选一**:商品按推荐三属性 flat 档,但只有 3 个商品 = 无刷新、无三选一(全陈列);定价按"正常运气可购档数 / 运气极好可购档数"的数学期望制定(期望账进表批注)。随本裁决,§2.4 的三选一 + 刷新设计与新随机流 `shop_roll` 作废(无摇号即无此流;新随机流 = `gp_drop`,承载掉落掷定)。
|
||||
- **问题⑤(双币分工)——维持**:GP = 冒险内一次性属性提升资源、钥石 = 跨局持久,分工维持。
|
||||
- **问题⑥(成长与验收)——数值整体后置**:数值 / 验收系统整体后置,用户拍板"骨架先行、数值是骨架上的肌肉"——shop=off/on 分层 MC、带平移、换算率登记全部归曲线调参批,本批不做任何 MC 分层基线。
|
||||
- **附加点**:关卡内结构随机生成(哈迪斯 / 哥布林弹珠式布局变化)经用户方向性提及——现行切片关卡为固定流程,登记为方向观察条目(归 06-待办清单,关卡节点地图名下),未立项未设计。
|
||||
- **落地范围注记**:商店房行集落地范围 = L2 / L3 两关图版(Boss 重编号 8→9、新增 Boss 前一位商店房节点;L1 零改动)——较 §2.3"L2 行集首落地"的推荐扩一关。
|
||||
|
||||
## 0. 一句话结论
|
||||
|
||||
建议按「战斗掉落单渠道 + 商店房节点 + 三属性整数档商品」的最小修订面落地冒险内成长:**格面资源池零结构改动**(雷安同池与八型封闭冻结零触碰)、GP 走战斗胜利掉落(普通战 2 / Boss 5,固定值)、关卡节点图四类扩五类加商店房(每关恰 1、中段必经位、L2 行集首落地)、商品与永久强化属性锻造同族 flat 整数档(攻 +1 首价 4 / 血上限 +3 首价 2 / 护甲等级 +1 首价 4,重复购买每档 +2 递增)。按此锚,"通关 L1+L2 + 两次大奖励 + 途中商店购买"后的入关第三关参数 ≈ 攻 5–6 / 血 28–34 / 护甲等级 10–11,对齐**成长代理档**(攻 6 / 血 34,即冒烟满配通关路径的属性覆盖档 `atk+200%/hp+100%` 的量级),第三关零成长红档由此解除。
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
## 当前状态快照(收口批先更新本节,再追加台账;≤10 行)
|
||||
|
||||
- **更新于**:2026-09-20(**GP+商店房实施批(L3-A 玩家成长批)**,worktree gp-shop-20260920,台账 116):用户六问裁决落地——①GP 进格面第九型(金币袋 weight 2 power 3 低概率惊喜+破财字典负向行,正向合计 76);②掉落=均值±方差掷定(普通 3[2,4]/Boss 8[6,10]/精英 5[4,6] 预留)+关级加值(L2 +1/L3 +2),gp_drop 新封闭流(黄金向量已登);③记账=战斗完成统一到账,格面兑付走战斗内缓冲 escrow 与钱包隔离;④商店房 Boss 前一位必经位(哈迪斯式,L2/L3 行集 Boss 重编号 8→9);⑤统一冒险加算区(商店购买与大奖励同账,购血上限当前同步);⑥三商品全陈列无刷新无三选一,价格=基价+关级加成+步长×已购档;数值/分层验收整体后置归曲线调参批(骨架先行)。冒烟 437 项 0 失败+三探针 PASS;GDD 八文档(关卡节点地图 v1.1 五类枚举/冒险结构 v0.5 GP 账本/战斗结算 v0.23/地图战斗资源 v0.15 九型/曲线 v0.18 杠杆登记/元进度 v1.2/03 v0.7/05 红线例外成文)+06 GP 条目收口+「关卡结构随机生成」方向观察登记
|
||||
- **更新于**:2026-09-20(**第三关内容批 + GP 定稿提案批**,worktree l3-content-20260920,台账 115〔113/114 号让给并行在飞会话两批,顺延让号〕):L3-B 落地——第三层「灰矮人矿城」行集(enemy 七行 duergar/hook_horror/duergar_king + board 13×13/Boss 14×14 词表值 deep_underdark + 九节点图 + 落石/矿尘 + 大地脉动=sapped 首发)+ 关卡 3 测试直入口 + 冒烟 375 项全绿 + registry 五条目 + GDD 五文档(03 v0.6/曲线 v0.17/棋盘生成 v0.4/地形变异 v0.4/06 GP 条目立项提前);板形验算 13/14×14 推理率 0.94–0.96 全达标零降档;MC 读数:零成长基线普通 16%/完美 20%(N=300,死亡 75–80% 集 Boss——预案「必红」预判被棋盘武器库部分证伪,成长批必要性不变)+ 成长代理档 40%/80%;**GP+商店房定稿提案已交付**(proposal-gp-shop-room-2026-09-20.md,10 拍板点:格面池零修订/商店房必经位/三属性 flat 档 攻+1=4 血+3=2 甲+1=4),待用户逐条拍板后进波次 2 实施批
|
||||
- **更新于**:2026-09-20(**第一层定名「地精洞窟」二改批**,worktree rename-l1-goblin-20260920,台账 111):用户拍板「不用这么 DND,就叫地精洞窟」——终名不绑定 DND 专名部族、规避 IP 指涉;「裂颚」出典作废仅留档,8 处消费面与提案层名行/沿革注记随批同步(03-内容规模 补 v0.5);纯字面变更零行值零机制改动,冒烟 355 项 0 失败
|
||||
- **更新于**:2026-09-20(**第一层改名「裂颚洞窟」批**,独立 worktree 分支 rename-l1-20260920,台账 110〔登记时撞并行会话 109 号,顺延让号〕):用户拍板「苔藓回廊与地精不契合」作为过期词语全仓退役——新名致敬 DND 5e《凡戴尔的失落矿坑》地精部族「裂颚」(Cragmaw)洞窟巢穴,洞窟纵深连通幽暗地域、与第三关预案「灰矮人矿城」构成三层纵深叙事线;八处面同步(level.tres display_name+表尾批注 / ui_assembler 首页按钮 / 切片 README×2 / 00-游戏概念 / 03-内容规模 v0.4 / registry level_table / 第三关提案层名行+蕈林理由重写+两处旧读数顺手校正);纯字面变更零行值零机制改动,冒烟 355 项 0 失败
|
||||
@@ -288,3 +289,4 @@
|
||||
110. **第一层改名「裂颚洞窟」批(2026-09-20,本会话——用户拍板「苔藓回廊跟地精不太契合……直接把苔藓回廊作为过期词语删除替换掉」,新名授权本会话选定;worktree rename-l1-20260920)**:全仓「苔藓回廊」现行用字清零(8 处消费面全替换,仅 5 处退役审计注记有意保留——版本历史/registry 注记/表尾批注/提案沿革,符合文档纪律)——①切片 `level.tres` display_name →「第一层 · 裂颚洞窟」+表尾改名批注;②切片首页按钮 `ui_assembler.gd`;③切片 README 试玩指引×2;④`00-游戏概念` 首关层名行;⑤`03-内容规模` 层名表+版本历史 v0.4;⑥registry `level_table` display_name 行。**命名出典**:新名致敬 DND 5e《凡戴尔的失落矿坑》(Lost Mine of Phandelver)地精部族「裂颚」(Cragmaw)洞窟巢穴——地精盘踞的浅层洞窟、通道向下延伸天然连通幽暗地域,且 LMOP 反主即卓尔「黑蜘蛛」,与 L2 蛛化卓尔 Boss 同谱;三层纵深叙事线贯通:裂颚洞窟(地精部族)→ 幽暗地域(蛛群·卓尔裔)→ 灰矮人矿城(第三关预案方向,未拍板)。第三关提案随批同步:层名行 + 蕈林备选理由重写(原理由绑「苔藓」已失义)+ 层名沿革注记 + 两处旧读数顺手校正(L2 完美 Boss 承伤改 51.6% 重投基线口径〔原 55.8% 系台账 96 地形批读数、已被台账 105 重投翻页〕;通关率带改分带后 L1 [35,55]/L2 [25,45])。仅字面变更:display_name 无代码/探针消费方(grep 证实),节点结构/行值/机制零改动。**验证**:冒烟 355 项 0 失败(tools/run_smoke.py 三闸全过;全新 worktree 首跑撞 class_name 缓存坑,对切片目录 `--editor --quit` 刷缓存后过——十二坑清单第 1 条再验证);全仓 grep 苔藓仅剩审计注记。
|
||||
111. **第一层定名「地精洞窟」二改批(2026-09-20,本会话——用户拍板「不用这么 DND,就叫地精洞窟就行了,不用明确说明是某个部族,毕竟我们没有 DND 的相应版权」;worktree rename-l1-goblin-20260920,纯字面零机制)**:台账 110 的「裂颚洞窟」(DND 5e《凡戴尔的失落矿坑》Cragmaw 致敬名)全仓退役改定「地精洞窟」——理由 = 终名不绑定 DND 专名部族、规避 IP 指涉。8 处消费面同步(level.tres display_name+表尾二改批注 / ui_assembler 首页按钮 / 切片 README×2 / 00-游戏概念 / 03-内容规模 层名表+版本历史 v0.5〔v0.4 出典作废留档〕 / registry level_table / 第三关提案层名行+沿革注记改写);「裂颚」字样仅存于版本历史/批注/沿革等审计记录(有意保留)。洞窟纵深连通幽暗地域的三层纵深叙事线取向不变(地精洞窟→幽暗地域→灰矮人矿城)。**验证**:冒烟 355 项 0 失败三闸全过(worktree 建起即对切片目录预刷类名缓存——十二坑清单第 9 条路径坑的规避姿势);grep 裂颚非审计命中 0。
|
||||
115. **第三关内容批 + GP 定稿提案批(2026-09-20,本会话——用户对 proposal-level3-content-2026-09-20.md 五拍板点全按推荐通过(灰矮人矿城/GP+商店房提前捆绑/乏力首发/板面+地形杠杆/测试直入口),plan 模式三子代理并行执行;worktree l3-content-20260920)**:**L3-B 第三关内容批**——①数据表:enemy.tres 七行(duergar 灰矮人重甲 15 血/灰矮人重装手 16/小队 8、hook_horror 钩爪恐魔轻甲 5–6 血群行〔复用 bite 共享行〕、Boss duergar_king 灰矮人石君王 75 血重甲=Σ91×r_boss≈0.82 锚;两敌种首遇位节点 0/1 固定数量不加骰;armor_class 恒 0 归在案投值条目);board_gen.tres 七行(战斗 13×13/Boss 14×14、d0.25、t0.88、词表第三值 deep_underdark);level.tres 第三关九节点图(与 L2 同模板);variation.tres 落石(flat_damage 3,1→2→3 递进)+矿尘(hide_number);skills.tres earth_pulse 大地脉动(妨害型=sapped 乏力第二词汇首发,字段口径同 web_snare);②代码:board_gen_table.gd 词表 v1.1+1、关卡 3 测试直入口(ui_assembler lv3btn+main 接线)、smoke_all.gd L3 断言段(图结构/跨表 7 坐标/定点/物种集八敌种/首遇绿档成长代理档口径 atk+200·hp+100 十二种子);③registry 五条目(enemy/board_gen_table/skill/variation_table/level_table);④GDD 五文档:03 v0.6(三关已落地/棋盘按关配置口径)/难度曲线与Boss v0.17(第三关图版注记+L3 provisional-pending-growth 口径)/棋盘生成 v0.4(词表 v1.1)/地形变异 v0.4(六类型两轴×三关)/06(GP 条目立项提前);**验证**——冒烟 375 项 0 失败三闸全过+三 UI 探针 PASS;板形验算 13×13 推理率 0.950–0.959/14×14 0.941 全达标零降档(降档梯子未触发);Boss max_gen_attempts 36→32(越 schema 域 [1,32],自决取域上限并批注登记);**MC 读数(unique)**——零成长基线 N=300:普通 16%/完美 20%,死亡 75–80% 集中 Boss(8.boss 败北 188/194 次);成长代理档 N=30:普通 40%/完美 80%;**预案「零成长必红档」预判被部分证伪**(棋盘武器库的克制输出〔魔法矢+1 克重甲/爆裂/攻击增益〕是预案生存链估算未计入的供给面),但成长批必要性判读不变(零成长读数低于 L1/L2 分带下缘、Boss 死亡占比过高、带域需成长后才可判读)——L3 行值维持 provisional-pending-growth,正式带归曲线调参批与 L3-A 同窗。**GP+商店房定稿提案交付**(design/research/proposal-gp-shop-room-2026-09-20.md,196 行 10 拍板点):格面池零修订(GP 走战斗掉落单渠道 普通 2/Boss 5 固定值)、节点图四类→五类加商店房(每关恰 1 中段必经位 L2 行集首落地)、三属性 flat 同族档(攻+1 首价 4/血上限+3 首价 2/护甲+1 首价 4,重复购买每档+2,三选一+刷新 2 GP 限 2 次,shop_roll 新随机流)、入关第三关参数账 攻 5–6/血 28–34/AC11 对照成长代理档、「抽象货币不做」红线判读不拦(GP=战斗之果非扫雷夹层)、验收 shop=off/on 双格分层——**十拍板点待用户逐条表态后进波次 2 实施批(A 表/B 代码/C 文档/D 验证四互斥文件集拆批清单已附)**。
|
||||
116. **GP+商店房实施批(L3-A 玩家成长批)(2026-09-20,本会话——用户对 proposal-gp-shop-room-2026-09-20.md 六开放问题逐条裁决(关键推翻:GP 进第九型格面,格面池零修订推荐被否);plan 批四子代理接力(A 表/B 代码/C 文档/D 冒烟);worktree gp-shop-20260920)**:**裁决要点**——①GP 进九型:金币袋(gp_gain,power 3,weight 2,低概率惊喜获取、量与战斗掉落相当)+破财(gp_loss,power 2,weight 0 字典行,钱包 clamp ≥0);②掉落非固定值=均值±方差掷定(普通 3[2,4]/Boss 8[6,10]/精英 5[4,6] 预留行)+关级加值(L1 0/L2 1/L3 2)+关级价格加成(L1 0/L2 2/L3 4);③记账=完成战斗统一到账(battle_finished 载荷 +gp_dropped),战斗内格面兑付走战斗域缓冲 escrow(战斗开始清零随载荷上抛即弃)与钱包隔离;④商店房=Boss 前一位必经位(哈迪斯式,L2/L3 行集 Boss 重编号 8→9、(8,shop,next[9])、L1 零改动、必经位冒烟断言承载);⑤统一冒险加算区 {atk,max_hp,armor_class}——商店购买与关卡大奖励同账、经覆盖值通道生效、购血上限与当前生命同步 +Δ;⑥三商品全陈列**无刷新无三选一**(shop_roll 作废),定价按期望账制定(L2 商店前可得 ≈35–40 GP vs 三件首购 16/全购 26–34,取舍空间成立,账进 shop.tres 批注);⑦**数值与分层验收整体后置**(用户拍板"骨架先行、数值是肌肉":shop=off/on 双格 MC、带平移、换算率全归曲线调参批与既有三条目同窗);⑧「关卡内结构随机生成(哈迪斯/弹珠式)」登记 06 方向观察条目未立项。**落地面**——新表 shop.tres+ShopTable schema(四行族+域校验)、reward 九型批(CELL_TYPES+gp、字典十八格、正向合计 76)、level_table 词表五类+L2/L3 rewiring(陈旧 (·,8) Boss 行清理)、代码四件(rng_service gp_drop 流〔黄金向量 m=42 scope0=6226476397888000011/scope1=5031558177015478025 已登 registry〕、battle_orchestrator escrow+gp_dropped、level_runner 钱包三收口+商店兑付+加算区、config_db shop 加载+gp 效应豁免)、呈现五件(ShopOverlay 商店覆盖层/顶栏金币常驻/格面徽标 gp 金色键〔避开紫赤禁入带〕/选路图商店房词表/battle_narrator 台词)、playtest_logger(battle_end.gp_dropped+wallet 事件行)、GDD 八文档(关卡节点地图 v1.1 五类枚举+编排规则/冒险结构 v0.5 GP 冒险账本+统一加算区/战斗结算 v0.23 载荷+缓冲/地图战斗资源 v0.15 九型批/难度曲线 v0.18 杠杆登记+分层 defer 成文/元进度 v1.2 双币分工/03 v0.7/05 红线显式例外成文)、registry 六处(reward/shop_table 新条目/level_table/载荷/随机流+黄金向量/术语三词条)、06 GP 提案条目解决移除、提案文首裁决记录块。**验证**——冒烟 437 项 0 失败三闸全过(+60 项断言:GP 到账区间/escrow 隔离+破财 clamp/商店三收口+价格递增+余额拒/图不变式含 DFS 全路径必经穿店/九型字典+黄金向量);ui_probe/click_probe(商店层适配)/tune_probe 三探针 PASS。
|
||||
@@ -8,6 +8,9 @@
|
||||
# behavior_roll〔回合内、开轮时点、步骤流之外——敌人技能池批 2026-09-16〕+ 护甲等级掷点〔回合内、
|
||||
# 步骤流之内〕;同种子同输入逐位复现)· 高光双落(完美排雷 ×2 / 标中旗 highlight)·
|
||||
# Waiting 相位结算触发(不依赖事件到达序)。
|
||||
# GP+商店房实施批(2026-09-21):授予段极性路由增 GP 型兑付(effect_id gp_gain/gp_loss → 战斗内缓冲
|
||||
# escrow 记账,不入钱包不入增益容器);战斗结束分支掷 gp_drop 封闭流(每战斗末恰一 scope)出
|
||||
# gp_dropped = 掷定 + level_gp_bonus + escrow 随 battle_finished 上抛——战斗域零 GP 状态持久。
|
||||
# 切片登记:① 事件总线载体 = 直连信号(实现期 ADR 项,provisional)② Waiting 相位刷新通道 2026-09-15 用户拍板整体
|
||||
# 拆除(仅 Active 挖掘期受理)③ 效果行协议 = 授予段逐 grant 行 + 增益/减益段续存实例组合行。
|
||||
# 实现 GDD:design/gdd/systems/战斗循环/战斗结算.md(v0.7 格面资源批——registry 追溯锚)
|
||||
@@ -15,7 +18,7 @@ class_name BattleOrchestrator
|
||||
extends RefCounted
|
||||
|
||||
signal step_row(row: Dictionary)
|
||||
signal battle_finished(payload: Dictionary) # {result, hero_hp}——hero 在 despawn 前捕获(呈现层销毁后读状态的时序防护)
|
||||
signal battle_finished(payload: Dictionary) # {result, hero_hp, gp_dropped}——hero 在 despawn 前捕获(呈现层销毁后读状态的时序防护);gp_dropped = 战斗掉落合计(GP+商店房实施批 2026-09-21)
|
||||
signal round_started(round_index: int)
|
||||
signal board_changed()
|
||||
signal settlement_snapshot(snap: Dictionary) # 每结算末尾的全量状态快照(playtest_logger 消费——JSONL 数值分析)
|
||||
@@ -46,6 +49,7 @@ var _reward_economy: RewardEconomy
|
||||
var encounter: Dictionary = {}
|
||||
var round_index := 0
|
||||
var battle_result := "" # "" / "victory" / "defeat"
|
||||
var escrow := 0 # 战斗内 GP 缓冲(战斗域中间态——begin_battle 清零;gp_gain/gp_loss 兑付累记;随 gp_dropped 上抛即弃,不入钱包)
|
||||
|
||||
var _facts_reveals: Array = []
|
||||
var _facts_flags: Array = []
|
||||
@@ -83,6 +87,7 @@ func begin_battle(enc: Dictionary) -> bool:
|
||||
push_warning("[战斗结算] begin_battle 重复受理")
|
||||
return false
|
||||
encounter = enc
|
||||
escrow = 0 # 战斗域零 GP 持久(GP+商店房实施批——每场战斗缓冲从零起算)
|
||||
db.set_reload_gate(true)
|
||||
if not _status_effects.notify_battle_start():
|
||||
_fatal("增益状态 开域被拒(编排违例)")
|
||||
@@ -352,9 +357,12 @@ func _dispatch_row(row: Dictionary, source_class: String, cell: Vector2i, ri: in
|
||||
return false
|
||||
|
||||
|
||||
# percent 行兑付:apply_effect 入容器;满层拒收 = "奖励未生效"行(零静默)
|
||||
# percent 行兑付:GP 型行走战斗内缓冲记账(GP+商店房实施批);其余 apply_effect 入容器;
|
||||
# 满层拒收 = "奖励未生效"行(零静默)
|
||||
func _apply_percent_row(row: Dictionary, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
|
||||
var eff := String(row.effect_id)
|
||||
if eff == "gp_gain" or eff == "gp_loss":
|
||||
return _apply_gp_row(row, source_class, cell, ri, highlight, label_suffix)
|
||||
var ok: bool = _status_effects.apply_effect(eff, {"source_class": source_class, "origin_cell": cell, "round_index": ri})
|
||||
if ok:
|
||||
var srow: Dictionary = db.status_effect_row(eff)
|
||||
@@ -364,6 +372,17 @@ func _apply_percent_row(row: Dictionary, source_class: String, cell: Vector2i, r
|
||||
return false
|
||||
|
||||
|
||||
# GP 型行兑付(GP+商店房实施批 2026-09-21):战斗内缓冲 escrow 记账——不入钱包不入增益容器。
|
||||
# 正向(gp_gain)= 挖开金币安全格/标中旗;负向(gp_loss)= 挖雷格/标错旗。兑付行经既有 effect 行
|
||||
# 通道外发(escrow 随行——归因可读),战斗结束随 gp_dropped 一次上抛后即弃。
|
||||
func _apply_gp_row(row: Dictionary, source_class: String, cell: Vector2i, ri: int, highlight: String = "", label_suffix: String = "") -> bool:
|
||||
var eff := String(row.effect_id)
|
||||
var delta: int = int(row.power) if eff == "gp_gain" else -int(row.power)
|
||||
escrow += delta
|
||||
_emit_row("effect", source_class, cell, ri, "%s %+d%s" % [String(row.display_name), delta, label_suffix], delta, {"effect_id": eff, "escrow": escrow}, highlight)
|
||||
return true
|
||||
|
||||
|
||||
# flat 行兑付(负向恒即时,无库存语义):
|
||||
# blast 正向 = 全体存活敌各 power 点(无敌 → 奖励未生效行);blast 负向 = 警觉 percent 行(effect_id=alert,走 _apply_percent_row 不入本函数);
|
||||
# heal 正向 = 主角回复 clamp(满血 → 奖励未生效行);heal 负向 = 主角直伤 power 点(不经增益状态;归零 → 战斗段短路/判定段判负)。
|
||||
@@ -770,13 +789,29 @@ func _battle_over(result: String) -> void:
|
||||
battle_result = result
|
||||
# 映射随战斗结束退役(地图战斗资源 义务④——下一场战斗 = 新板新 scope 批,roll_board 相位守卫的前提)
|
||||
var hero_hp_left := int(_enemy_system.hero_unit().get("hp", 0)) # despawn 前捕获(信号载荷供呈现层"HP 带入下一层")
|
||||
var gp_dropped := _roll_gp_drop() # 战斗掉落(GP+商店房实施批——escrow 随此值上抛即弃)
|
||||
_reward_economy.retire_mapping()
|
||||
_enemy_system.despawn_all()
|
||||
_status_effects.notify_battle_end()
|
||||
action_economy.notify_combat_end() # → Idle,RAP 清零
|
||||
db.set_reload_gate(false)
|
||||
_reset_round_facts()
|
||||
battle_finished.emit({"result": result, "hero_hp": hero_hp_left})
|
||||
battle_finished.emit({"result": result, "hero_hp": hero_hp_left, "gp_dropped": gp_dropped})
|
||||
|
||||
|
||||
# 战斗掉落掷定(GP+商店房实施批 2026-09-21):tier = Boss 节点 ? "boss" : "normal"(elite 留分支位);
|
||||
# gp_dropped = gp_drop 封闭流掷定(tier 行的 roll_min..roll_max 均匀整数——每战斗末恰一 scope)
|
||||
# + level_gp_bonus(level_index) + escrow。掷定行缺位 = 数据面缺行(fail 报错按 0 兑付——不哑掉)。
|
||||
func _roll_gp_drop() -> int:
|
||||
var tier := "boss" if String(encounter.get("node_type", "")) == "boss" else "normal"
|
||||
var drops: Dictionary = db.shop_gp_drop_row(tier)
|
||||
var roll := 0
|
||||
if drops.is_empty():
|
||||
push_error("[战斗结算] gp_drops 缺行 tier=%s——掉落掷定按 0 兑付" % tier)
|
||||
else:
|
||||
var stream: RandomNumberGenerator = rng.open_stream("gp_drop")
|
||||
roll = stream.randi_range(int(drops.get("roll_min", 0)), int(drops.get("roll_max", 0)))
|
||||
return roll + db.shop_level_gp_bonus(int(encounter.get("level_index", 0))) + escrow
|
||||
|
||||
|
||||
# ---- 呈现状态面(E1 封装收口 2026-09-13):呈现层 / headless 自动玩家的唯一读取出口——
|
||||
|
||||
@@ -20,6 +20,7 @@ const TABLE_MANIFEST := {
|
||||
"settlement": "settlement.tres",
|
||||
"level": "level.tres",
|
||||
"difficulty": "difficulty.tres",
|
||||
"shop": "shop.tres", # GP+商店房实施批 2026-09-21(表与 schema 落籍归并行数据面——本批只接线加载与消费)
|
||||
}
|
||||
|
||||
var action_economy: ActionEconomyTable
|
||||
@@ -33,6 +34,9 @@ var reward: RewardTable
|
||||
var settlement: SettlementTable
|
||||
var level: LevelTable
|
||||
var difficulty: DifficultyTable
|
||||
# GP+商店房实施批(2026-09-21):ShopTable 暂持无类型注记——schema class_name 落籍归并行数据面,
|
||||
# 落籍后可收紧为 `var shop: ShopTable`(消费面一律走下方 shop_* 访问器,禁直读行集)
|
||||
var shop = null
|
||||
|
||||
var is_ready := false
|
||||
var _diagnostics: Array = [] # {severity, category, message}
|
||||
@@ -114,6 +118,7 @@ func load_tables(root: String, collect_only: bool) -> bool:
|
||||
settlement = loaded.get("settlement")
|
||||
level = loaded.get("level")
|
||||
difficulty = loaded.get("difficulty")
|
||||
shop = loaded.get("shop")
|
||||
is_ready = not has_errors
|
||||
return not has_errors
|
||||
|
||||
@@ -127,10 +132,12 @@ func _cross_validate(loaded: Dictionary) -> void:
|
||||
var effect_ids := {}
|
||||
for row in st.rows:
|
||||
effect_ids[row.get("effect_id", "")] = true
|
||||
# ① RewardTable effect_id 悬空(percent 行引用 StatusEffectTable——悬空加载致命)
|
||||
# ① RewardTable effect_id 悬空(percent 行引用 StatusEffectTable——悬空加载致命)。
|
||||
# GP+商店房实施批(2026-09-21)例外:gp_gain/gp_loss = GP 战斗内缓冲(escrow)通道的路由键
|
||||
# (战斗结算 _apply_gp_row 兑付——不入增益状态容器),词表封闭按字面豁免悬空引用校验。
|
||||
for row in rt.rows:
|
||||
var eff: String = row.get("effect_id", "")
|
||||
if eff != "" and not effect_ids.has(eff):
|
||||
if eff != "" and eff != "gp_gain" and eff != "gp_loss" and not effect_ids.has(eff):
|
||||
push_diag("error", "dangling_effect", "RewardTable %s 引用不存在的 effect_id: %s" % [row.get("reward_id"), eff])
|
||||
# ② SettlementTable.effect_pool_map 悬空
|
||||
for eff in st_tab.effect_pool_map.keys():
|
||||
@@ -322,6 +329,52 @@ func status_effect_row(effect_id: String) -> Dictionary:
|
||||
return status_effects.row_by_id(effect_id)
|
||||
|
||||
|
||||
# ---- GP+商店房实施批(2026-09-21):ShopTable 消费面(行集解析单点——runner/战斗编排禁自扫表)----
|
||||
# 行族四轴(数据契约,行集归 shop.tres):gp_drops {tier, mean, roll_min, roll_max} ·
|
||||
# level_gp_bonus {level_index, bonus} · products {product_id, display_name, attr, flat_value,
|
||||
# base_price, price_increment} · price_level_bonus {level_index, bonus}。
|
||||
# 行族经 ShopTable 同名数组属性读取(.get 容缺——表落籍前查询恒空,掉落按 0 兑付的兜底在调用方)。
|
||||
|
||||
func shop_gp_drop_row(tier: String) -> Dictionary:
|
||||
for row in _shop_rows("gp_drops"):
|
||||
if String(row.get("tier", "")) == tier:
|
||||
return row
|
||||
return {}
|
||||
|
||||
|
||||
func shop_level_gp_bonus(level_index: int) -> int:
|
||||
for row in _shop_rows("level_gp_bonus"):
|
||||
if int(row.get("level_index", -1)) == level_index:
|
||||
return int(row.get("bonus", 0))
|
||||
return 0
|
||||
|
||||
|
||||
func shop_price_level_bonus(level_index: int) -> int:
|
||||
for row in _shop_rows("price_level_bonus"):
|
||||
if int(row.get("level_index", -1)) == level_index:
|
||||
return int(row.get("bonus", 0))
|
||||
return 0
|
||||
|
||||
|
||||
func shop_products() -> Array:
|
||||
return _shop_rows("products")
|
||||
|
||||
|
||||
func shop_product_row(product_id: String) -> Dictionary:
|
||||
for row in _shop_rows("products"):
|
||||
if String(row.get("product_id", "")) == product_id:
|
||||
return row
|
||||
return {}
|
||||
|
||||
|
||||
# 行族数组读取(ShopTable 属性名 = 行族名;表未落籍/属性缺失 = 空集非错误态)
|
||||
func _shop_rows(family: String) -> Array:
|
||||
if shop == null:
|
||||
return []
|
||||
var arr = shop.get(family)
|
||||
return arr if arr is Array else []
|
||||
|
||||
|
||||
# 技能行单点读取(敌人技能池批——单位容器执行上报 / 战斗结算兑现的消费面)
|
||||
func skill_row(skill_id: String) -> Dictionary:
|
||||
return skill.row_by_id(skill_id)
|
||||
|
||||
@@ -13,21 +13,29 @@
|
||||
# E4 收口(2026-09-13):覆盖值经 encounter 显式传入战斗(不再写 db.hero 表——表值运行期不可变,
|
||||
# 双基线同步/双写叠加 6→18→54 整类隐患随表写一并移除)。
|
||||
# 对应 GDD:关卡节点地图 v1.0(图版结构——本文件为 P1 批实现位;命名规范见 CLAUDE.md)
|
||||
# GP+商店房实施批(2026-09-21):GP 冒险钱包(战斗完成入账 clamp ≥0 · 冒险收束清零)+ 统一冒险
|
||||
# 加算区 bonus{atk,max_hp,armor_class}(商店购买与关卡大奖励共用账本——战斗开始参数 = 基础表值 +
|
||||
# 加算区,TunePanel 百分比在加算之后乘)+ 商店房节点兑付(开覆盖层/购买出账/离开回选路确认——
|
||||
# 复用 continue_after_node 兑付确认纪律,零自动推进)。
|
||||
class_name LevelRunner
|
||||
extends RefCounted
|
||||
|
||||
signal node_entered(node: Dictionary, info: Dictionary) # info 按 type:battle/boss={} · reward={}(内容待拍板) · rest={healed,hp,max_hp}
|
||||
signal node_entered(node: Dictionary, info: Dictionary) # info 按 type:battle/boss={} · reward={}(内容待拍板) · rest={healed,hp,max_hp} · shop={gp}
|
||||
signal choices_presented(candidates: Array) # 选路口(兑付确认后——候选节点引用数组,零自动推进)
|
||||
signal step_row(row: Dictionary) # 转发当前 战斗结算 结算行
|
||||
signal round_started(round_index: int)
|
||||
signal board_changed()
|
||||
signal level_finished(payload: Dictionary) # {result, hp, node_index, node_id, node_type}
|
||||
signal wallet_changed(event: String, gp: int, extra: Dictionary) # GP 钱包事件投影:gain{amount}/buy{product,price}/clear——试玩日志与商店覆盖层刷新源(GP+商店房实施批)
|
||||
|
||||
var db
|
||||
var rng
|
||||
var level_row: Dictionary = {}
|
||||
var node_index := -1
|
||||
var hero_hp := 0
|
||||
var gp := 0 # GP 冒险钱包(局内货币——冒险开始 = 0、收束清零;不入存档不入摘要)
|
||||
var bonus := {"atk": 0, "max_hp": 0, "armor_class": 0} # 统一冒险加算区(冒险开始全 0)——商店购买与关卡大奖励 hp 上限提升写同一本账(大奖励在切片序列未生效,契约位:大奖励 hp 上限提升写本加算区,随正式序列批接线)
|
||||
var _shop_purchased := {} # product_id → 已购档数(现价重算输入;冒险开始清零)
|
||||
# 协作对象私有(E1/E3 封装收口 2026-09-13):settlement 读取经 current_battle() 成文出口
|
||||
var _settlement = null # 当前战斗编排器(关卡结束置 null)
|
||||
|
||||
@@ -53,12 +61,21 @@ func set_hero_override(atk_pct: int, hp_pct: int) -> void:
|
||||
hero_hp_pct = hp_pct
|
||||
|
||||
|
||||
# 叠加次序(GP+商店房实施批):基础表值 + 加算区(商店/大奖励 flat 先加)→ TunePanel 百分比后乘
|
||||
# (调整板 = 测试工具按现状保持——百分比恒乘在加算生效值上)
|
||||
func effective_atk() -> int:
|
||||
return maxi(1, int(round(float(db.hero.atk) * (100.0 + float(hero_atk_pct)) / 100.0)))
|
||||
var base: int = int(db.hero.atk) + int(bonus.get("atk", 0))
|
||||
return maxi(1, int(round(float(base) * (100.0 + float(hero_atk_pct)) / 100.0)))
|
||||
|
||||
|
||||
func effective_max_hp() -> int:
|
||||
return maxi(1, int(round(float(db.hero.max_hp) * (100.0 + float(hero_hp_pct)) / 100.0)))
|
||||
var base: int = int(db.hero.max_hp) + int(bonus.get("max_hp", 0))
|
||||
return maxi(1, int(round(float(base) * (100.0 + float(hero_hp_pct)) / 100.0)))
|
||||
|
||||
|
||||
# 护甲等级生效值(加算区 armor_class 轴——无 TunePanel 百分比通道,纯 flat)
|
||||
func effective_ac() -> int:
|
||||
return maxi(0, int(db.hero.armor_class) + int(bonus.get("armor_class", 0)))
|
||||
|
||||
|
||||
# 覆盖复位(关卡结束归零——表值不可变,仅清百分比;调用方兼容保留)
|
||||
@@ -81,6 +98,9 @@ func start_level(level_index: int, seed_val: int) -> bool:
|
||||
_candidates = []
|
||||
visited = []
|
||||
_graph_mode = false
|
||||
gp = 0 # 冒险钱包清零开局(GP+商店房实施批——局内货币生命周期起点)
|
||||
bonus = {"atk": 0, "max_hp": 0, "armor_class": 0} # 加算区冒险开始全 0
|
||||
_shop_purchased = {} # 已购档数清零(跨冒险不延续——递增定价按冒险内累计)
|
||||
for node in nodes():
|
||||
if node.has("next"):
|
||||
_graph_mode = true
|
||||
@@ -214,6 +234,11 @@ func _enter_node(node: Dictionary) -> bool:
|
||||
var healed := _apply_rest()
|
||||
node_entered.emit(node, {"healed": healed, "hp": hero_hp, "max_hp": effective_max_hp()})
|
||||
return true
|
||||
"shop":
|
||||
# 商店房兑付(GP+商店房实施批 2026-09-21):到达即开店(覆盖层归呈现层),购买出账/
|
||||
# 离开归玩家意图——零自动推进;离开确认经 continue_after_node 回选路口
|
||||
node_entered.emit(node, {"gp": gp})
|
||||
return true
|
||||
push_error("[关卡节点地图] 未知节点类型: %s" % str(node))
|
||||
return false
|
||||
|
||||
@@ -230,7 +255,8 @@ func _start_battle(node: Dictionary) -> bool:
|
||||
# board_changed(首板安装)并重建棋盘——后发会导致首战棋盘不渲染(2026-09-11 playtest 缺陷①)
|
||||
node_entered.emit(node, {})
|
||||
if not _settlement.begin_battle({"level_index": int(level_row.level_index), "node_depth": int(node.get("node_depth", 0)),
|
||||
"hero_hp": hero_hp, "hero_atk": effective_atk(), "hero_max_hp": effective_max_hp()}):
|
||||
"hero_hp": hero_hp, "hero_atk": effective_atk(), "hero_max_hp": effective_max_hp(),
|
||||
"hero_armor_class": effective_ac(), "node_type": String(node.get("type", ""))}):
|
||||
push_error("[关卡节点地图] 战斗启动失败(depth %s)" % str(node.get("node_depth", -1)))
|
||||
return false
|
||||
return true
|
||||
@@ -253,10 +279,12 @@ func _on_battle_finished(payload: Dictionary) -> void:
|
||||
var result: String = String(payload.get("result", ""))
|
||||
if result == "victory":
|
||||
hero_hp = maxi(1, int(payload.get("hero_hp", 0))) # despawn 前捕获值(载荷自包含)
|
||||
_gain_wallet(int(payload.get("gp_dropped", 0))) # 战斗完成入账(clamp ≥0——破财可把 escrow 打负,钱包入账时钳制)
|
||||
if String(node.get("type", "")) == "boss" or result == "defeat":
|
||||
# Boss 胜利 = 关卡通关;任意战斗败北 = 关卡失败(Roguelike——冒险即终,重开归 冒险结构/元进度)
|
||||
level_result = "victory" if result == "victory" else "defeat"
|
||||
var hp_left := hero_hp if result == "victory" else 0
|
||||
_close_wallet() # 冒险收束(通关/倒下)→ GP 清零(局内货币生命周期终点)
|
||||
_running = false
|
||||
_settlement = null
|
||||
level_finished.emit({"result": level_result, "hp": hp_left, "node_index": node_index,
|
||||
@@ -310,15 +338,80 @@ func current_battle():
|
||||
|
||||
# ---- 玩家输入转发(呈现层只与 runner 对话)----
|
||||
|
||||
# 奖励/恢复点兑付确认(呈现层在覆盖层上点击"继续"后调用;自动模拟在 node_entered 回调内立即调用)
|
||||
# 奖励/恢复/商店房兑付确认(呈现层在覆盖层上点击"继续/离开"后调用;自动模拟在 node_entered 回调内立即调用)
|
||||
# 泛化语义(接口漂移清偿):确认 ≠ 推进——确认后进选路口,下一跳归 pick_next_node
|
||||
func continue_after_node() -> bool:
|
||||
var t := String(current_node().get("type", ""))
|
||||
if t != "reward" and t != "rest":
|
||||
if t != "reward" and t != "rest" and t != "shop":
|
||||
return false
|
||||
return _present_choices()
|
||||
|
||||
|
||||
# ---- GP 冒险钱包(GP+商店房实施批 2026-09-21:入账/出账/收束清零三收口)----
|
||||
|
||||
# 战斗完成入账(gp_dropped 载荷——clamp ≥0;破财可把战斗域 escrow 打负,钱包不透支)
|
||||
func _gain_wallet(amount: int) -> void:
|
||||
gp = maxi(0, gp + amount)
|
||||
wallet_changed.emit("gain", gp, {"amount": amount})
|
||||
|
||||
|
||||
# 冒险收束清零(通关/倒下——局内货币生命周期终点,不入存档不入摘要)
|
||||
func _close_wallet() -> void:
|
||||
gp = 0
|
||||
wallet_changed.emit("clear", 0, {})
|
||||
|
||||
|
||||
# ---- 商店房兑付(GP+商店房实施批:呈现层只读投影 + 购买出账)----
|
||||
|
||||
# 商店覆盖层数据源(只读投影——现价/已购档数在此现算,呈现层禁复算定价公式)
|
||||
func shop_products() -> Array:
|
||||
var out: Array = []
|
||||
for prow in db.shop_products():
|
||||
out.append({
|
||||
"product_id": String(prow.get("product_id", "")),
|
||||
"display_name": String(prow.get("display_name", "")),
|
||||
"attr": String(prow.get("attr", "")),
|
||||
"flat_value": int(prow.get("flat_value", 0)),
|
||||
"price": current_price(prow),
|
||||
"purchased": purchased_tier(String(prow.get("product_id", ""))),
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
# 现价 = base_price + price_level_bonus(level_index) + price_increment × 该商品已购档数
|
||||
func current_price(prow: Dictionary) -> int:
|
||||
return int(prow.get("base_price", 0)) + db.shop_price_level_bonus(int(level_row.level_index)) \
|
||||
+ int(prow.get("price_increment", 0)) * purchased_tier(String(prow.get("product_id", "")))
|
||||
|
||||
|
||||
func purchased_tier(product_id: String) -> int:
|
||||
return int(_shop_purchased.get(product_id, 0))
|
||||
|
||||
|
||||
# 购买(未知商品/属性键出域/余额不足拒绝——返回 false 零副作用):
|
||||
# 出账 → 加算区 += flat_value → 购血同步当前生命(封顶新上限)→ 已购档数 +1
|
||||
func buy_product(product_id: String) -> bool:
|
||||
var prow: Dictionary = db.shop_product_row(product_id)
|
||||
if prow.is_empty():
|
||||
push_error("[关卡节点地图] 商店房未知商品: %s" % product_id)
|
||||
return false
|
||||
var attr := String(prow.get("attr", ""))
|
||||
if not bonus.has(attr):
|
||||
push_error("[关卡节点地图] 商品属性键出域: %s(attr=%s)" % [product_id, attr])
|
||||
return false
|
||||
var price := current_price(prow)
|
||||
if gp < price:
|
||||
return false # 余额不足(覆盖层按钮禁用为常态防线——此处兜底拒绝)
|
||||
gp -= price
|
||||
var flat := int(prow.get("flat_value", 0))
|
||||
bonus[attr] = int(bonus[attr]) + flat
|
||||
if attr == "max_hp":
|
||||
hero_hp = mini(hero_hp + flat, effective_max_hp()) # 购血:上限与当前生命同步 +Δ(封顶新上限)
|
||||
_shop_purchased[product_id] = purchased_tier(product_id) + 1
|
||||
wallet_changed.emit("buy", gp, {"product": product_id, "price": price})
|
||||
return true
|
||||
|
||||
|
||||
func player_request_refresh() -> bool:
|
||||
if _settlement == null:
|
||||
return false
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# 取号语义:全新实例首个 open_stream = scope 0(地图战斗资源 预推进术语的登记口径)。
|
||||
extends Node
|
||||
|
||||
const STREAM_NAMES := ["board_gen", "reward_roll", "encounter", "armor_roll", "behavior_roll", "variant_place"] # 2026-09-12 增 encounter(遭遇随机化批);2026-09-16 增 armor_roll(护甲等级掷点批)+ behavior_roll(敌人技能池批——开轮摇号,每敌派生独立子流,registry rng_service 条目同步);2026-09-19 增 variant_place(地形变异首批类型定稿批——变异布置流,一板恰一 scope)
|
||||
const STREAM_NAMES := ["board_gen", "reward_roll", "encounter", "armor_roll", "behavior_roll", "variant_place", "gp_drop"] # 2026-09-12 增 encounter(遭遇随机化批);2026-09-16 增 armor_roll(护甲等级掷点批)+ behavior_roll(敌人技能池批——开轮摇号,每敌派生独立子流,registry rng_service 条目同步);2026-09-19 增 variant_place(地形变异首批类型定稿批——变异布置流,一板恰一 scope);2026-09-21 增 gp_drop(GP+商店房实施批——战斗掉落掷定流,每战斗末恰一 scope,registry rng_service 条目随批同步)
|
||||
|
||||
# 分段表达式书写(数据配置 R3 实现级规约——禁 ≥2^63 完整字面量)
|
||||
const GOLDEN_RATIO := (0x9E3779B9 << 32) | 0x7F4A7C15 # 0x9E3779B97F4A7C15
|
||||
@@ -21,6 +21,7 @@ const SALT_BEHAVIOR_ROLL := (0x85EBCA77 << 32) | 0x58C24E15 # 0x85EBCA7758C24E
|
||||
const MIX_MUL_A := (0xBF58476D << 32) | 0x1CE4E5B9 # 0xBF58476D1CE4E5B9
|
||||
const MIX_MUL_B := (0x94D049BB << 32) | 0x133111EB # 0x94D049BB133111EB
|
||||
const SALT_VARIANT_PLACE := (0x9AE16A3F << 32) | 0x2FAD6C39 # 0x9AE16A3F2FAD6C39(变异布置流——互异奇常数 ≠ 乘数;地形变异首批类型定稿批 2026-09-20,黄金向量按 R3 公式登记)
|
||||
const SALT_GP_DROP := (0xD6E8FEB8 << 32) | 0x665FD7F1 # 0xD6E8FEB8665FD7F1(战斗掉落流——互异奇常数 ≠ 乘数;GP+商店房实施批 2026-09-21,黄金向量按 R3 公式登记归冒烟/registry 同批)
|
||||
# behavior_roll 派生向量(registry 同批登记):子流 scope = spawn_index × STRIDE + 单敌摇号序(0 起)。
|
||||
# 每敌消费自己的 scope 段 → 单敌出招轨迹可独立复放、敌间互不穿插(proposal 约束二)。
|
||||
const BEHAVIOR_SUBSTREAM_STRIDE := 1000000
|
||||
@@ -32,7 +33,7 @@ const INT64_MIN := -9223372036854775808
|
||||
var _master_seed: int = -1 # -1 = 未播种(取数即致命,EC7)
|
||||
var master_seed: int:
|
||||
get: return _master_seed
|
||||
var _counters := {"board_gen": 0, "reward_roll": 0, "encounter": 0, "armor_roll": 0, "behavior_roll": 0, "variant_place": 0}
|
||||
var _counters := {"board_gen": 0, "reward_roll": 0, "encounter": 0, "armor_roll": 0, "behavior_roll": 0, "variant_place": 0, "gp_drop": 0}
|
||||
|
||||
|
||||
func _salt_for(stream: String) -> int:
|
||||
@@ -49,6 +50,8 @@ func _salt_for(stream: String) -> int:
|
||||
return SALT_BEHAVIOR_ROLL
|
||||
"variant_place":
|
||||
return SALT_VARIANT_PLACE
|
||||
"gp_drop":
|
||||
return SALT_GP_DROP
|
||||
push_error("[RngService] 未知名流: %s" % stream)
|
||||
assert(false, "未知名流(流集合封闭,EC12)")
|
||||
return 0
|
||||
@@ -82,11 +85,12 @@ func set_master_seed(seed_val: int) -> void:
|
||||
if seed_val < 0:
|
||||
seed_val = -seed_val
|
||||
push_warning("[RngService] master_seed 负输入 abs 归一化: %d" % seed_val)
|
||||
assert(_counters["board_gen"] == 0 and _counters["reward_roll"] == 0 and _counters["encounter"] == 0 and _counters["armor_roll"] == 0 and _counters["behavior_roll"] == 0 and _counters["variant_place"] == 0, "冒险中途重播种会击穿同冒险无碰撞前提(仅许全部流计数器全零时调用)")
|
||||
assert(_counters["board_gen"] == 0 and _counters["reward_roll"] == 0 and _counters["encounter"] == 0 and _counters["armor_roll"] == 0 and _counters["behavior_roll"] == 0 and _counters["variant_place"] == 0 and _counters["gp_drop"] == 0, "冒险中途重播种会击穿同冒险无碰撞前提(仅许全部流计数器全零时调用)")
|
||||
_master_seed = seed_val
|
||||
_counters["board_gen"] = 0
|
||||
_counters["reward_roll"] = 0
|
||||
_counters["variant_place"] = 0
|
||||
_counters["gp_drop"] = 0
|
||||
|
||||
|
||||
# 切片登记:冒险开始重播种入口(正式侧调用权归 冒险结构;切片主菜单 = 冒险起点)
|
||||
@@ -97,6 +101,7 @@ func new_adventure(seed_val: int) -> void:
|
||||
_counters["armor_roll"] = 0
|
||||
_counters["behavior_roll"] = 0
|
||||
_counters["variant_place"] = 0
|
||||
_counters["gp_drop"] = 0
|
||||
set_master_seed(seed_val)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
# Validation Question: 线性第一关(战×2→道具→战×2→恢复→Boss)能否承载"数值提升→通关期望改善"的元进度验证
|
||||
# Date: 2026-09-11(2026-09-17 分支节点图批:nodes 升级图结构——单链=出边恰一的退化形态)
|
||||
# 数据配置 LevelTable schema(关卡节点地图 v1.0 承载面)。
|
||||
# 节点类型 closed enum:battle(战斗,须带 node_depth)/ reward(直通占位点)/ rest(血量恢复点)/ boss(关底 Boss,须带 node_depth)。
|
||||
# 节点类型 closed enum:battle(战斗,须带 node_depth)/ reward(直通占位点)/ rest(血量恢复点)/ boss(关底 Boss,须带 node_depth)
|
||||
# / shop(商店房——GP+商店房实施批 2026-09-21 五类落地:同 reward/rest 无战斗行、无 node_depth 要求,
|
||||
# 图形态仍须编号化;零新增图合法性检查,必经位由冒烟断言承载非加载校验)。
|
||||
# 图形态(行集内任一节点带 "next" 出边表即整行按图校验):节点编号 = node_depth(关卡内唯一定位地址,
|
||||
# 全节点须带,reward/rest 亦然);next = [出边节点编号...](连线只朝前进方向:目标编号严格大于自身)。
|
||||
# 单链形态(零 "next" 键——第一关现状零迁移):隐式出边 = 数组顺序下一项,节点编号 = 数组下标
|
||||
@@ -15,7 +17,7 @@ extends Resource
|
||||
|
||||
@export var rows: Array = [] # Array[Dictionary]
|
||||
|
||||
const NODE_TYPES := ["battle", "reward", "rest", "boss"]
|
||||
const NODE_TYPES := ["battle", "reward", "rest", "boss", "shop"] # 商店房批 2026-09-21 四→五类(关卡节点地图 v1.1)
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
|
||||
@@ -3,17 +3,19 @@
|
||||
# Date: 2026-09-10(2026-09-15 格面资源批结构性重写:category/use_timing/库存/供给轴下线,五型 × 极性字典取代)
|
||||
# 数据配置 RewardTable schema(地图战斗资源 回填值)。
|
||||
# 行 = Dictionary,键 reward_id(Rule 4 豁免:定义表);跨字段校验族 + 池护栏。
|
||||
# 八型封闭枚举(attack/defense/heal/blast/weaken/pierce/magic/action)× 极性(positive/negative):
|
||||
# 九型封闭枚举(attack/defense/heal/blast/weaken/pierce/magic/action/gp)× 极性(positive/negative):
|
||||
# 正向行 = 摇号池(权重 = 类型频次);负向行 = 纯兑付字典(不消费权重)。
|
||||
# 九型批(2026-09-20 词表 v1.1 注记——用户拍板 GP 进第九型):gp 型正负两行入池,
|
||||
# effect_id gp_gain/gp_loss = 战斗内缓冲通道(战斗结算 escrow 记账,不入增益容器)。
|
||||
class_name RewardTable
|
||||
extends Resource
|
||||
|
||||
@export var rows: Array = [] # Array[Dictionary]
|
||||
|
||||
const CELL_TYPES := ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic", "action"] # 八型封闭(格面资源八型批 2026-09-20 七→八)——清单外加载拒绝
|
||||
const CELL_TYPES := ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic", "action", "gp"] # 九型封闭(格面资源九型批 2026-09-20 八→九,词表 v1.1)——清单外加载拒绝
|
||||
const POLARITIES := ["positive", "negative"]
|
||||
const POOL_TAGS := ["cell", "clear_default"] # cell = 格面资源单一池;clear_<biome> 命名空间预留拒绝
|
||||
const CELL_TYPE_KEYS := ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic", "action"] # 字典完整性校验轴(类型 × 极性 = 十六格)
|
||||
const CELL_TYPE_KEYS := ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic", "action", "gp"] # 字典完整性校验轴(类型 × 极性 = 十八格)
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
@@ -93,7 +95,7 @@ func validate() -> Array:
|
||||
for ctype in CELL_TYPE_KEYS:
|
||||
for pol in POLARITIES:
|
||||
if not cell_dict.has("%s|%s" % [ctype, pol]):
|
||||
errs.append({"category": "cross_field", "message": "五型字典缺行: %s %s——(类型, 极性) 齐备为加载前提" % [ctype, pol]})
|
||||
errs.append({"category": "cross_field", "message": "九型字典缺行: %s %s——(类型, 极性) 齐备为加载前提" % [ctype, pol]})
|
||||
# ② 池空护栏:cell 池 ≥1 正向行 weight>0(摇号池非空)
|
||||
if cell_positive_live < 1:
|
||||
errs.append({"category": "cross_field", "message": "cell 池零活正向行(weight>0)——摇号池空"})
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Validation Question: 玩家能否在 Boss 前商店房用战斗掉落 GP 购买三属性锻造档,兑现「数值提升→后续战斗改善」的局内成长闭环(P1)
|
||||
# Date: 2026-09-21(GP+商店房实施批——2026-09-20 用户拍板,设计权威 design/research/proposal-gp-shop-room-2026-09-20.md 裁决记录)
|
||||
# 数据配置 ShopTable schema(GP 冒险钱包 × 商店房 行族四轴)。
|
||||
# 行族四轴(数据契约对齐 ConfigDB.shop_* 访问器——消费面一律走访问器,禁直读行集):
|
||||
# gp_drops = 战斗掉落掷定行(键 tier:normal/elite/boss 封闭词表;掷定 = roll_min..roll_max 均匀整数,
|
||||
# mean = 期望锚——roll_min ≤ mean ≤ roll_max,行动经济 条款①期望当量锚同款纪律);elite 为预留行
|
||||
#(当前无消费方,随精英节点内容批启用)。level_gp_bonus = 关级掉落加值行(level_index 连续从 1 递增)。
|
||||
# products = 商品行(键 product_id;三件全陈列无刷新无摇号——2026-09-20 拍板);
|
||||
# 现价公式 = base_price + price_level_bonus(关卡) + price_increment × 该商品已购档数(已购档数在 冒险结构)。
|
||||
# price_level_bonus = 关级定价加值行(level_index 连续从 1 递增)。
|
||||
# 行值全部 provisional 归 难度曲线与Boss(用户拍板骨架先行、数值整体后置)。
|
||||
class_name ShopTable
|
||||
extends Resource
|
||||
|
||||
@export var gp_drops: Array = [] # Array[Dictionary] 键 tier
|
||||
@export var level_gp_bonus: Array = [] # Array[Dictionary] 键 level_index
|
||||
@export var products: Array = [] # Array[Dictionary] 键 product_id
|
||||
@export var price_level_bonus: Array = [] # Array[Dictionary] 键 level_index
|
||||
|
||||
const GP_TIERS := ["normal", "elite", "boss"]
|
||||
const PRODUCT_ATTRS := ["atk", "max_hp", "armor_class"]
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
var errs: Array = []
|
||||
# 行族空护栏(四轴皆为 ConfigDB 消费面——缺族即致命)
|
||||
for family in ["gp_drops", "level_gp_bonus", "products", "price_level_bonus"]:
|
||||
if get(family).is_empty():
|
||||
errs.append({"category": "empty_table", "message": "ShopTable 行族为空: %s" % family})
|
||||
# gp_drops:tier 词表封闭 + 掷定域 + 均值锚 + tier 唯一
|
||||
var seen_tier := {}
|
||||
for row in gp_drops:
|
||||
var tier: String = row.get("tier", "")
|
||||
if not GP_TIERS.has(tier):
|
||||
errs.append({"category": "enum", "message": "gp_drops tier 出域 {normal, elite, boss}: %s" % tier})
|
||||
if seen_tier.has(tier):
|
||||
errs.append({"category": "dup_key", "message": "gp_drops tier 重复: %s" % tier})
|
||||
seen_tier[tier] = true
|
||||
if String(row.get("display_name", "")) == "":
|
||||
errs.append({"category": "domain", "message": "gp_drops %s display_name 空" % tier})
|
||||
var rmin: int = int(row.get("roll_min", -1))
|
||||
var mean: int = int(row.get("mean", -1))
|
||||
var rmax: int = int(row.get("roll_max", -1))
|
||||
if rmin < 0 or mean < 0 or rmax < 0 or rmax > 999:
|
||||
errs.append({"category": "domain", "message": "gp_drops %s 掷定域越域 [0,999]: %d..%d..%d" % [tier, rmin, mean, rmax]})
|
||||
if rmin > mean or mean > rmax:
|
||||
errs.append({"category": "cross_field", "message": "gp_drops %s 违反 roll_min(%d) ≤ mean(%d) ≤ roll_max(%d)——均值恒等期望锚" % [tier, rmin, mean, rmax]})
|
||||
# level_gp_bonus / price_level_bonus:level_index 连续从 1 递增 + 加值域
|
||||
for family in ["level_gp_bonus", "price_level_bonus"]:
|
||||
var arr: Array = get(family)
|
||||
var seen := {}
|
||||
for row in arr:
|
||||
var li: int = int(row.get("level_index", 0))
|
||||
if seen.has(li):
|
||||
errs.append({"category": "dup_key", "message": "%s level_index 重复: %d" % [family, li]})
|
||||
seen[li] = true
|
||||
var bonus: int = int(row.get("bonus", -1))
|
||||
if bonus < 0 or bonus > 999:
|
||||
errs.append({"category": "domain", "message": "%s level %d bonus 越域 [0,999]: %d" % [family, li, bonus]})
|
||||
for i in arr.size():
|
||||
var li: int = int(arr[i].get("level_index", 0))
|
||||
if li != i + 1:
|
||||
errs.append({"category": "structural", "message": "%s level_index 不连续(须从 1 连续递增): 位 %d = %d" % [family, i + 1, li]})
|
||||
break
|
||||
# products:product_id 唯一 + attr 词表 + 价格域 [1,999]
|
||||
var seen_pid := {}
|
||||
for row in products:
|
||||
var pid: String = row.get("product_id", "")
|
||||
if pid == "":
|
||||
errs.append({"category": "domain", "message": "products product_id 空"})
|
||||
continue
|
||||
if seen_pid.has(pid):
|
||||
errs.append({"category": "dup_key", "message": "products product_id 重复: %s" % pid})
|
||||
seen_pid[pid] = true
|
||||
if String(row.get("display_name", "")) == "":
|
||||
errs.append({"category": "domain", "message": "%s display_name 空" % pid})
|
||||
var attr: String = row.get("attr", "")
|
||||
if not PRODUCT_ATTRS.has(attr):
|
||||
errs.append({"category": "enum", "message": "%s attr 出域 {atk, max_hp, armor_class}: %s" % [pid, attr]})
|
||||
var flat: int = int(row.get("flat_value", 0))
|
||||
if flat < 1 or flat > 999:
|
||||
errs.append({"category": "domain", "message": "%s flat_value 越域 [1,999]: %d" % [pid, flat]})
|
||||
var base: int = int(row.get("base_price", 0))
|
||||
if base < 1 or base > 999:
|
||||
errs.append({"category": "domain", "message": "%s base_price 越域 [1,999]: %d" % [pid, base]})
|
||||
var inc: int = int(row.get("price_increment", -1))
|
||||
if inc < 0 or inc > 999:
|
||||
errs.append({"category": "domain", "message": "%s price_increment 越域 [0,999]: %d" % [pid, inc]})
|
||||
return errs
|
||||
@@ -127,7 +127,7 @@ rows = [{
|
||||
"board_type": "underdark"
|
||||
}, {
|
||||
"level_index": 2,
|
||||
"node_depth": 8,
|
||||
"node_depth": 9,
|
||||
"board_width": 12,
|
||||
"board_height": 12,
|
||||
"mine_density": 0.25,
|
||||
@@ -204,7 +204,7 @@ rows = [{
|
||||
"board_type": "deep_underdark"
|
||||
}, {
|
||||
"level_index": 3,
|
||||
"node_depth": 8,
|
||||
"node_depth": 9,
|
||||
"board_width": 14,
|
||||
"board_height": 14,
|
||||
"mine_density": 0.25,
|
||||
@@ -236,3 +236,10 @@ rows = [{
|
||||
; min_safe_count 按既有 ~0.42 格占比外推(13×13 → 70 / 14×14 → 82)。
|
||||
; 降档登记:Boss max_gen_attempts 批内拟值 36 越 schema 域 [1,32](config_fatal 修行值纪律——不许放宽 schema),
|
||||
; 按域上限取 32(批内降档梯子「Boss 回 13×13 时 attempts 32」同款值)。行值全 provisional 归 难度曲线与Boss。
|
||||
;
|
||||
; 商店房批连带(2026-09-21——level.tres 商店房批 Boss 重编号 8→9):新增 (2,9)/(3,9) 精确行
|
||||
; (跨表校验强制——ConfigDB ④ level_node_dangling),行值照抄原 Boss (L,8) 行;
|
||||
; 商店房 (L,8) 不开战斗、无板行(跨表校验只查 battle/boss 节点)。行值 provisional 归 难度曲线与Boss。
|
||||
; 陈旧行清理(GP+商店房实施批收尾 2026-09-21):并行表代理曾把 Boss 板行照抄到 (2,8)/(3,8)——节点 8 现为
|
||||
; 商店房、无板行需求,原 (2,8)/(3,8) 行成陈旧冗余已删(删前核 (·,9) 行在位且内容与被删行逐字段一致);
|
||||
; 本行集迁移 = Boss 重编号 8→9 的商店房批连带。
|
||||
@@ -248,7 +248,7 @@ rows = [{
|
||||
"armor_class": 0
|
||||
}, {
|
||||
"level_index": 2,
|
||||
"node_depth": 8,
|
||||
"node_depth": 9,
|
||||
"enemy_id": "drider",
|
||||
"display_name": "蛛化卓尔精灵",
|
||||
"unit_count": 1,
|
||||
@@ -408,7 +408,7 @@ rows = [{
|
||||
"armor_class": 0
|
||||
}, {
|
||||
"level_index": 3,
|
||||
"node_depth": 8,
|
||||
"node_depth": 9,
|
||||
"enemy_id": "duergar_king",
|
||||
"display_name": "灰矮人石君王",
|
||||
"unit_count": 1,
|
||||
@@ -507,3 +507,10 @@ rows = [{
|
||||
; (provisional 锚,域 [0.5,1.1]);Boss 专属池独占技 earth_pulse(第二妨害词汇 sapped 首发);
|
||||
; armor_class 恒 0(正式投值仍归「敌方护甲等级正式行值」在案条目全表同批)。行值全 provisional
|
||||
; 归 难度曲线与Boss。
|
||||
;
|
||||
; 商店房批连带(2026-09-21——level.tres 商店房批 Boss 重编号 8→9):新增 (2,9)/(3,9) 精确行
|
||||
; (跨表校验强制——ConfigDB ④ level_node_dangling),行值照抄原 Boss (L,8) 行(drider / duergar_king);
|
||||
; 商店房 (L,8) 不开战斗、无行集。行值 provisional 归 难度曲线与Boss。
|
||||
; 陈旧行清理(GP+商店房实施批收尾 2026-09-21):并行表代理曾把 Boss 行照抄到 (2,8)/(3,8)——节点 8 现为
|
||||
; 商店房、无战斗行需求,原 (2,8)/(3,8) 行成陈旧冗余已删(删前核 (·,9) 行在位且内容与被删行逐字段一致);
|
||||
; 本行集迁移 = Boss 重编号 8→9 的商店房批连带。
|
||||
@@ -66,6 +66,10 @@ rows = [{
|
||||
"next": [8]
|
||||
}, {
|
||||
"node_depth": 8,
|
||||
"type": "shop",
|
||||
"next": [9]
|
||||
}, {
|
||||
"node_depth": 9,
|
||||
"type": "boss"
|
||||
}]
|
||||
}, {
|
||||
@@ -106,6 +110,10 @@ rows = [{
|
||||
"next": [8]
|
||||
}, {
|
||||
"node_depth": 8,
|
||||
"type": "shop",
|
||||
"next": [9]
|
||||
}, {
|
||||
"node_depth": 9,
|
||||
"type": "boss"
|
||||
}]
|
||||
}]
|
||||
@@ -140,3 +148,11 @@ rows = [{
|
||||
; L3 行集复用 L2 九节点图模板(6 战 + Boss + 奖励 + 恢复,真实岔路 4 处);灰矮人矿城主题
|
||||
; (DND 幽暗地域最深层方向)。仅行集落地,正式三关序列推进仍登记 Alpha 验收条件
|
||||
; (测试直入口 ≠ 序列生效)。行值全 provisional 归 难度曲线与Boss。
|
||||
;
|
||||
; 商店房批(2026-09-20 用户拍板——GP+商店房实施批,关卡节点地图 v1.1 四类→五类):编排规则锚 =
|
||||
; 每关恰 1 商店房、Boss 前一位必经位(参考哈迪斯商店房位置);L2/L3 行集 rewiring:原 Boss 行 (8, boss)
|
||||
; 改为 (8, shop, next [9])、新增 (9, boss)——图六检全绿(Boss 恒最大编号 9、无出边恒终点;节点 6/7 出边
|
||||
; [8] 字面不变、指向现为商店房,全路径穿商店房 8);L1 行集零改动(教学期最小修订面,L1 掉落攒进 L2 首店)。
|
||||
; 必经位(全路径穿 8)由冒烟断言承载、非加载校验——level_table.gd 零新增图合法性检查。
|
||||
; 连带:board_gen/enemy 补 (2,9)/(3,9) 精确行(跨表校验 ④ 强制,行值照抄原 Boss (L,8) 行)。
|
||||
; 行值全 provisional 归 难度曲线与Boss。
|
||||
@@ -180,7 +180,27 @@ rows = [{
|
||||
"weight": 0,
|
||||
"pool_tag": "cell",
|
||||
"tier": 1
|
||||
}]
|
||||
}, {
|
||||
"reward_id": "gp_coin",
|
||||
"cell_type": "gp",
|
||||
"polarity": "positive",
|
||||
"effect_id": "gp_gain",
|
||||
"display_name": "金币袋",
|
||||
"power": 3,
|
||||
"weight": 2,
|
||||
"pool_tag": "cell",
|
||||
"tier": 1
|
||||
}, {
|
||||
"reward_id": "gp_lose",
|
||||
"cell_type": "gp",
|
||||
"polarity": "negative",
|
||||
"effect_id": "gp_loss",
|
||||
"display_name": "破财",
|
||||
"power": 2,
|
||||
"weight": 0,
|
||||
"pool_tag": "cell",
|
||||
"tier": 1
|
||||
}]
|
||||
|
||||
; 格面资源批(2026-09-15 用户拍板——registry reward_table 条目为现行 schema 事实源):
|
||||
; 十行格面池(五型 × 正负各一行)+ 清图槽一行。安全格与雷格同池同分布(雷安同池),
|
||||
@@ -215,3 +235,9 @@ rows = [{
|
||||
; ② 权重再平衡(保 spark_aoe 16 不动、总 67 不变):attack_up 20→14 · ac_up 16→11 · heal_minor 3→2 · weaken 12→8;
|
||||
; 新行各 8。等比基准 = 其余四行 51→35(×0.686),整取后 14/11/2/8,微差登记已知偏差。
|
||||
; 类型频次合计仍 67:attack 14 / defense 11 / blast 16 / heal 2 / weaken 8 / pierce 8 / magic 8。
|
||||
;
|
||||
; 九型批(2026-09-20 用户拍板——GP 进第九型,获取来源=战斗结束掉落+雷图低概率惊喜获取〔量与战斗掉落相当〕;
|
||||
; 金币袋=挖开安全格入战斗内缓冲、战斗完成统一入账;破财=雷格摇到 gp 面的字典负向行〔weight 0 不入池,
|
||||
; 挖雷格兑付=缓冲 -2 钱包侧 clamp ≥0〕;正向权重合计 74→76);行值 provisional 归 难度曲线与Boss。
|
||||
; gp 两行 tier=1(provisional 占位);effect_id gp_gain/gp_loss = 战斗内缓冲(escrow)通道路由键
|
||||
;(战斗结算 _apply_percent_row 分流,不入增益容器——ConfigDB ① status_effects 悬空豁免归 core 面)。
|
||||
@@ -0,0 +1,75 @@
|
||||
[gd_resource type="Resource" script_class="ShopTable" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://data/schemas/shop_table.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
gp_drops = [{
|
||||
"tier": "normal",
|
||||
"display_name": "普通战斗",
|
||||
"mean": 3,
|
||||
"roll_min": 2,
|
||||
"roll_max": 4
|
||||
}, {
|
||||
"tier": "elite",
|
||||
"display_name": "精英战斗",
|
||||
"mean": 5,
|
||||
"roll_min": 4,
|
||||
"roll_max": 6
|
||||
}, {
|
||||
"tier": "boss",
|
||||
"display_name": "Boss 战斗",
|
||||
"mean": 8,
|
||||
"roll_min": 6,
|
||||
"roll_max": 10
|
||||
}]
|
||||
level_gp_bonus = [{
|
||||
"level_index": 1,
|
||||
"bonus": 0
|
||||
}, {
|
||||
"level_index": 2,
|
||||
"bonus": 1
|
||||
}, {
|
||||
"level_index": 3,
|
||||
"bonus": 2
|
||||
}]
|
||||
products = [{
|
||||
"product_id": "atk_up",
|
||||
"display_name": "攻击锻升",
|
||||
"attr": "atk",
|
||||
"flat_value": 1,
|
||||
"base_price": 4,
|
||||
"price_increment": 2
|
||||
}, {
|
||||
"product_id": "hp_up",
|
||||
"display_name": "生命锻升",
|
||||
"attr": "max_hp",
|
||||
"flat_value": 3,
|
||||
"base_price": 2,
|
||||
"price_increment": 2
|
||||
}, {
|
||||
"product_id": "armor_up",
|
||||
"display_name": "护甲锻升",
|
||||
"attr": "armor_class",
|
||||
"flat_value": 1,
|
||||
"base_price": 4,
|
||||
"price_increment": 2
|
||||
}]
|
||||
price_level_bonus = [{
|
||||
"level_index": 1,
|
||||
"bonus": 0
|
||||
}, {
|
||||
"level_index": 2,
|
||||
"bonus": 2
|
||||
}, {
|
||||
"level_index": 3,
|
||||
"bonus": 4
|
||||
}]
|
||||
|
||||
; GP+商店房实施批(2026-09-20 用户拍板,设计权威 design/research/proposal-gp-shop-room-2026-09-20.md 裁决记录):
|
||||
; 掉落=均值±方差掷定(gp_drop 封闭流)+关级加值;精英行为预留行(当前无消费方,随精英节点内容批启用);
|
||||
; 价格公式 = base_price + price_level_bonus(关卡) + price_increment × 该商品已购档数(已购档数在 冒险结构——
|
||||
; 冒险内累计、跨冒险清零);商品三件全陈列无刷新无摇号(2026-09-20 拍板,提案 §2.4 的三选一+刷新方案退役)。
|
||||
; 期望账:L2 商店前可得 GP 期望 ≈ 35–40(L1 全程 22 ± 惊喜 + L2 商店前五战 21 ± ),三件首购总价 L2 =
|
||||
; (4+2)+(2+2)+(4+2)=16、全购欲望 ≈ 26–34——取舍空间成立;量级与方差全部 provisional-pending-曲线调参批
|
||||
;(用户拍板骨架先行、数值整体后置,行值归 难度曲线与Boss)。
|
||||
@@ -21,6 +21,11 @@ static func rest_line(healed: int, hp: int, max_hp: int) -> String:
|
||||
return "[color=#7cc47c]恢复点:回复 %d HP(→ %d/%d)[/color]" % [healed, hp, max_hp]
|
||||
|
||||
|
||||
# 商店房行(GP+商店房实施批——到达即开店;购买/离开归玩家意图)
|
||||
static func shop_line(gp: int) -> String:
|
||||
return "[color=#e0b860]商店房:金币 %d——购买即时生效,离开后选下一跳[/color]" % gp
|
||||
|
||||
|
||||
# 选路口行(零自动推进停点——战斗胜利/兑付确认后出现)
|
||||
static func route_line(candidates: Array, runner) -> String:
|
||||
var names: Array = []
|
||||
|
||||
@@ -16,7 +16,10 @@ extends RefCounted
|
||||
|
||||
# 每次结算行/轮次/节点变化/动作返回后调用(组合根全量重查装配)
|
||||
static func refresh(stage: BattleStageView, info: HeroInfoPanelView, ap_bar: ApBarView,
|
||||
settlement, runner, level_active: bool, db, refresh_remaining: int) -> void:
|
||||
settlement, runner, level_active: bool, db, refresh_remaining: int, gp_label: Label = null) -> void:
|
||||
# GP 余额常驻(GP+商店房实施批 2026-09-21——A 区顶栏邻接位;runner 缺位 = 非冒险态按 0 读数)
|
||||
if gp_label != null:
|
||||
gp_label.text = "金币 %d" % (int(runner.gp) if runner != null else 0)
|
||||
if settlement == null:
|
||||
_refresh_out_of_battle(stage, info, runner, level_active, db)
|
||||
return
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
# 以 encounter 参数入战斗,db.hero 运行期只读)
|
||||
# ④ 全屏 States 状态机(挖掘/结算/终局)与一次性提示去重。
|
||||
# 2026-09-15 格面资源批:道具窗口与库存面板随可选类道具删除下线——E 区移除,窗口接线/选择序列删除。
|
||||
# GP+商店房实施批(2026-09-21):商店覆盖层挂接与信号接线(装配器零信号连接纪律——句柄返回、
|
||||
# 本类连接,仿 lv2btn 模式)+ runner.wallet_changed 转发试玩日志(wallet 行族)+ A 区顶栏金币余额。
|
||||
# 渲染与文案归 views/,核心状态→文案装配归 hud_presenter.gd 与 battle_narrator.gd,节点构建归
|
||||
# ui_assembler.gd,模态层级归 modal_stack.gd,批量模拟编排归 tune_sim_controller.gd,调试模式编排归
|
||||
# debug_session.gd——本类不拼呈现字符串。
|
||||
@@ -49,10 +51,12 @@ var board_zone: BoardZoneView # D 区(含 ap_bar/board_view/toast/
|
||||
var board_view: BoardView # 探针兼容别名(= board_zone.board_view)
|
||||
var ap_bar: ApBarView # 探针兼容别名(= board_zone.ap_bar)
|
||||
var settlement_fx: SettlementFxView # 结算演出覆盖层(跨区弹道/光带——A–D 之上、模态之下)
|
||||
var gp_label: Label # A 区顶栏 GP 余额常驻位(GP+商店房实施批——文案装配归 HudPresenter)
|
||||
# 模态 overlay(z 序 = 登记序;pattern 6 层级纪律)
|
||||
var legend_overlay: LegendOverlay
|
||||
var review_overlay: SettlementReviewOverlay
|
||||
var node_overlay: NodeOverlay
|
||||
var shop_overlay: ShopOverlay # 商店覆盖层(GP+商店房实施批——商店房节点兑付呈现)
|
||||
var tune_panel: TunePanel
|
||||
|
||||
var _modals = null # 模态层级状态机(modal_stack.gd——缺陷⑤纪律载体)
|
||||
@@ -85,6 +89,7 @@ func _build_ui() -> void:
|
||||
add_child(root)
|
||||
var battle_ui: Dictionary = UiAssembler.build_battle_ui(root, db)
|
||||
strip_view = battle_ui.strip_view
|
||||
gp_label = battle_ui.gp_label
|
||||
stage = battle_ui.stage
|
||||
log_view = battle_ui.log_view
|
||||
info_panel = battle_ui.info_panel
|
||||
@@ -108,28 +113,31 @@ func _build_ui() -> void:
|
||||
logger = PlaytestLogger.new()
|
||||
logger.ap_per_round = int(db.action_economy.ap_per_round)
|
||||
|
||||
# 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 选路 < 终局 < 调整板 < 开始)
|
||||
# 模态覆盖层(登记序 = 树序 = z 遮挡序:图例 < 明细回看 < 节点 < 选路 < 终局 < 商店 < 调整板 < 开始)
|
||||
var overlays: Dictionary = UiAssembler.build_overlays(self, db, int(db.hero.atk), int(db.hero.max_hp))
|
||||
legend_overlay = overlays.legend_overlay
|
||||
review_overlay = overlays.review_overlay
|
||||
node_overlay = overlays.node_overlay
|
||||
_end_overlay = overlays.end_overlay
|
||||
shop_overlay = overlays.shop_overlay
|
||||
tune_panel = overlays.tune_panel
|
||||
route_overlay = RouteOverlay.new()
|
||||
add_child(route_overlay)
|
||||
# 树序 = 模态登记序 = z 遮挡序(缺陷⑤纪律)——选路层须夹在节点层与终局层之间
|
||||
# 树序 = 模态登记序 = z 遮挡序(缺陷⑤纪律)——选路层须夹在节点层与商店层之间
|
||||
move_child(route_overlay, node_overlay.get_index() + 1)
|
||||
legend_overlay.closed.connect(_on_modal_closed.bind(legend_overlay))
|
||||
review_overlay.closed.connect(_on_modal_closed.bind(review_overlay))
|
||||
node_overlay.continue_pressed.connect(_on_node_continue)
|
||||
route_overlay.route_chosen.connect(_on_route_chosen)
|
||||
shop_overlay.buy_requested.connect(_on_shop_buy)
|
||||
shop_overlay.leave_pressed.connect(_on_shop_leave)
|
||||
_end_overlay.review_pressed.connect(_on_end_review)
|
||||
_end_overlay.continue_pressed.connect(_on_battle_end_continue)
|
||||
tune_panel.closed.connect(_close_tune_panel)
|
||||
tune_panel.apply_requested.connect(_on_tune_apply)
|
||||
tune_panel.simulate_requested.connect(_on_tune_simulate)
|
||||
_modals = ModalStack.new()
|
||||
for m: Control in [legend_overlay, review_overlay, node_overlay, route_overlay, _end_overlay, tune_panel]:
|
||||
for m: Control in [legend_overlay, review_overlay, node_overlay, route_overlay, _end_overlay, shop_overlay, tune_panel]:
|
||||
_register_modal(m)
|
||||
_tune_sim = TuneSimController.new(db, tune_panel, int(db.hero.max_hp))
|
||||
|
||||
@@ -184,8 +192,8 @@ func _on_modal_closed(m: Control) -> void:
|
||||
func _input_locked() -> bool:
|
||||
# 模态/非战斗期棋盘输入锁(各 overlay 自身可见即锁定)
|
||||
return legend_overlay.is_open() or review_overlay.is_open() or _end_overlay.is_open() \
|
||||
or node_overlay.visible or route_overlay.visible or tune_panel.visible or _start_overlay.visible \
|
||||
or _inter_overlay.visible or settlement == null
|
||||
or node_overlay.visible or route_overlay.visible or shop_overlay.visible or tune_panel.visible \
|
||||
or _start_overlay.visible or _inter_overlay.visible or settlement == null
|
||||
|
||||
|
||||
# ==================== 完整关卡模式(关卡节点地图 图版推进器)====================
|
||||
@@ -201,6 +209,7 @@ func _start_full_level(level_index: int = 1) -> void:
|
||||
runner.node_entered.connect(_on_runner_node)
|
||||
runner.choices_presented.connect(_on_choices_presented)
|
||||
runner.level_finished.connect(_on_level_finished)
|
||||
runner.wallet_changed.connect(_on_wallet_changed)
|
||||
_level_active = true
|
||||
_reset_run_scope()
|
||||
log_view.clear()
|
||||
@@ -238,6 +247,12 @@ func _on_runner_node(node: Dictionary, info: Dictionary) -> void:
|
||||
_open_modal(node_overlay)
|
||||
node_overlay.show_rest(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0)))
|
||||
log_view.append_bb(Narrator.rest_line(healed, int(info.get("hp", 0)), int(info.get("max_hp", 0))))
|
||||
elif t == "shop":
|
||||
# 商店房兑付(GP+商店房实施批):到达即开店——商品快照/余额经 runner 只读投影喂入;
|
||||
# 购买/离开归玩家意图(buy_requested/leave_pressed 接线),零自动推进
|
||||
_open_modal(shop_overlay)
|
||||
shop_overlay.open_shop(runner.shop_products(), int(runner.gp))
|
||||
log_view.append_bb(Narrator.shop_line(int(runner.gp)))
|
||||
logger.log_node(node, info, runner.node_index)
|
||||
strip_view.refresh(runner)
|
||||
_refresh_presentations()
|
||||
@@ -499,6 +514,35 @@ func _on_route_chosen(node_id: int) -> void:
|
||||
push_error("[组合根] 选路被拒(编号 %d 不在候选)" % node_id)
|
||||
|
||||
|
||||
# ---- 商店覆盖层与 GP 钱包(GP+商店房实施批——写调用唯一归组合根)----
|
||||
|
||||
func _on_shop_buy(product_id: String) -> void:
|
||||
if runner == null:
|
||||
return
|
||||
if not runner.buy_product(product_id):
|
||||
# 余额不足(按钮禁用为常态防线——兜底拒绝可见,pattern 1)
|
||||
logger.log_ux("ux_reject", {"source": "wallet", "sub_reason": "insufficient_gp"})
|
||||
board_zone.toast.show_toast("金币不足——买不起这件商品", "warn")
|
||||
return
|
||||
shop_overlay.refresh_state(runner.shop_products(), int(runner.gp))
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
func _on_shop_leave() -> void:
|
||||
_close_modal(shop_overlay)
|
||||
if runner != null:
|
||||
# 复用兑付确认纪律(接口漂移清偿):离开 ≠ 推进——确认后经 choices_presented 弹出选路口
|
||||
runner.continue_after_node()
|
||||
|
||||
|
||||
func _on_wallet_changed(event: String, gp: int, extra: Dictionary) -> void:
|
||||
# wallet 行族转发(到账/购买/清零——试玩日志验收取数面)+ 常驻余额与在开商店覆盖层重刷
|
||||
logger.log_wallet(event, gp, extra)
|
||||
if shop_overlay.visible and runner != null:
|
||||
shop_overlay.refresh_state(runner.shop_products(), int(runner.gp))
|
||||
_refresh_presentations()
|
||||
|
||||
|
||||
# ==================== 玩家意图 → 核心写调用(唯一出口)====================
|
||||
|
||||
func _on_board_action(cell: Vector2i, kind: String) -> void:
|
||||
@@ -576,7 +620,7 @@ func _handle_outcome_feedback(outcome: Dictionary) -> void:
|
||||
func _refresh_presentations() -> void:
|
||||
# 每个核心信号/写调用返回后的全量重查装配(更新触发注记——无推送信号,命令式重查)
|
||||
var refresh_remaining: int = maxi(0, int(db.action_economy.refresh_cap_per_round) - _refresh_used_round)
|
||||
HudPresenter.refresh(stage, info_panel, ap_bar, settlement, runner, _level_active, db, refresh_remaining)
|
||||
HudPresenter.refresh(stage, info_panel, ap_bar, settlement, runner, _level_active, db, refresh_remaining, gp_label)
|
||||
_sync_settlement_visuals()
|
||||
|
||||
|
||||
|
||||
@@ -55,7 +55,17 @@ func attach_battle(settlement) -> void:
|
||||
settlement.settlement_snapshot.connect(_on_settlement_snapshot)
|
||||
settlement.battle_finished.connect(func(p): _append({
|
||||
"kind": "battle_end", "result": String(p.get("result", "")),
|
||||
"hero_hp": int(p.get("hero_hp", 0))}))
|
||||
"hero_hp": int(p.get("hero_hp", 0)),
|
||||
"gp_dropped": int(p.get("gp_dropped", 0))})) # 战斗掉落列(GP+商店房实施批 2026-09-21——验收取数面)
|
||||
|
||||
|
||||
# ---- GP 钱包行(GP+商店房实施批——到账/购买/清零事件;runner wallet_changed 投影经组合根转发)----
|
||||
|
||||
func log_wallet(event: String, gp: int, extra: Dictionary = {}) -> void:
|
||||
var line := {"kind": "wallet", "event": event, "gp": gp}
|
||||
for k in extra.keys():
|
||||
line[k] = extra[k]
|
||||
_append(line)
|
||||
|
||||
|
||||
# ---- ux 遥测(battle.md §Events Fired——呈现层观测行族,不改 core) ----
|
||||
@@ -98,6 +108,8 @@ func log_node(node: Dictionary, info: Dictionary, node_index: int) -> void:
|
||||
line["depth"] = int(node.get("node_depth", 0))
|
||||
elif t == "rest":
|
||||
line["healed"] = int(info.get("healed", 0))
|
||||
elif t == "shop":
|
||||
line["gp"] = int(info.get("gp", 0)) # 进店时点余额(GP+商店房实施批)
|
||||
# reward 节点:内容待拍板(格面资源批 2026-09-15 道具删除)——零载荷
|
||||
_append(line)
|
||||
|
||||
|
||||
@@ -9,21 +9,28 @@ extends RefCounted
|
||||
|
||||
# 战斗屏四区(A 进度条 / B 战斗画面 / C·D 两列——E 区道具面板已随可选类删除下线,2026-09-15 格面资源批)
|
||||
# + 结算演出覆盖层(A–D 之上、模态之下——跨区弹道/光带载体,结算演出链批 2026-09-20)。
|
||||
# 返回句柄字典:{strip_view, stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx}
|
||||
# 返回句柄字典:{strip_view, gp_label, stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx}
|
||||
static func build_battle_ui(root: Control, db) -> Dictionary:
|
||||
# set_anchors_and_offsets_preset(非 set_anchors_preset):后者会把控件重置回最小内容尺寸
|
||||
# (实测 927×613——缩顶、棋盘 45px 格 ×10 行超出 D 区 333px 高被裁,2026-09-13 用户报告回归)
|
||||
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
root.add_theme_constant_override("separation", 0)
|
||||
|
||||
# A 区:关卡进度条(全宽 × 28px 顶栏)
|
||||
# A 区:关卡进度条(全宽 × 28px 顶栏)+ GP 余额常驻位(GP+商店房实施批 2026-09-21——
|
||||
# 顶栏邻接位,显示名「金币」;文案装配归 HudPresenter,此处只建节点)
|
||||
var strip_panel := PanelContainer.new()
|
||||
strip_panel.custom_minimum_size = Vector2(0, 28)
|
||||
var sb := UiKit.panel_flat(UiKit.BG_ROOT, UiKit.BG_ROOT, 0, 0)
|
||||
strip_panel.add_theme_stylebox_override("panel", sb)
|
||||
root.add_child(strip_panel)
|
||||
var strip_row := HBoxContainer.new()
|
||||
strip_panel.add_child(strip_row)
|
||||
var strip_view := LevelStripView.new()
|
||||
strip_panel.add_child(strip_view)
|
||||
strip_view.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
strip_row.add_child(strip_view)
|
||||
var gp_label := UiKit.label("金币 0", 15, true)
|
||||
gp_label.add_theme_color_override("font_color", UiKit.GOLD_DEEP)
|
||||
strip_row.add_child(gp_label)
|
||||
|
||||
# B 区:战斗画面(全宽 × 252px——常驻双舞台,布局决策 D1)
|
||||
var stage := BattleStageView.new(db)
|
||||
@@ -48,6 +55,7 @@ static func build_battle_ui(root: Control, db) -> Dictionary:
|
||||
|
||||
return {
|
||||
"strip_view": strip_view,
|
||||
"gp_label": gp_label,
|
||||
"stage": stage,
|
||||
"log_view": stage.log_view,
|
||||
"info_panel": info_panel,
|
||||
@@ -58,8 +66,8 @@ static func build_battle_ui(root: Control, db) -> Dictionary:
|
||||
}
|
||||
|
||||
|
||||
# 模态覆盖层组(z 序 = 登记序:图例 < 明细回看 < 节点 < 终局 < 调整板)。
|
||||
# 返回 {legend_overlay, review_overlay, node_overlay, end_overlay, tune_panel};调用方按本序 addChild。
|
||||
# 模态覆盖层组(z 序 = 登记序:图例 < 明细回看 < 节点 < 终局 < 商店 < 调整板)。
|
||||
# 返回 {legend_overlay, review_overlay, node_overlay, end_overlay, shop_overlay, tune_panel};调用方按本序 addChild。
|
||||
static func build_overlays(host: Control, db, base_atk: int, base_hp: int) -> Dictionary:
|
||||
var legend_overlay := LegendOverlay.new()
|
||||
host.add_child(legend_overlay)
|
||||
@@ -73,6 +81,10 @@ static func build_overlays(host: Control, db, base_atk: int, base_hp: int) -> Di
|
||||
var end_overlay := BattleEndOverlay.new()
|
||||
host.add_child(end_overlay)
|
||||
|
||||
# 商店覆盖层(GP+商店房实施批 2026-09-21——商店房节点兑付呈现;按钮接线归组合根 lv2btn 模式)
|
||||
var shop_overlay := ShopOverlay.new()
|
||||
host.add_child(shop_overlay)
|
||||
|
||||
var tune_panel := TunePanel.new(db, base_atk, base_hp)
|
||||
tune_panel.visible = false # 显式初始化——原版依赖"被不透明开始层遮蔽"的树序巧合(缺陷⑤家族);模态锁输入以 visible 为准
|
||||
host.add_child(tune_panel)
|
||||
@@ -82,6 +94,7 @@ static func build_overlays(host: Control, db, base_atk: int, base_hp: int) -> Di
|
||||
"review_overlay": review_overlay,
|
||||
"node_overlay": node_overlay,
|
||||
"end_overlay": end_overlay,
|
||||
"shop_overlay": shop_overlay,
|
||||
"tune_panel": tune_panel,
|
||||
}
|
||||
|
||||
|
||||
@@ -168,6 +168,12 @@ func _format_row(row: Dictionary, agg_count := -1) -> String:
|
||||
var suffix := "(%s)" % src if src != "" else ""
|
||||
match String(row.get("type", "")):
|
||||
"effect":
|
||||
# GP 型效果行(GP+商店房实施批 2026-09-21):「金币袋 +3」兑付反馈——value = 本行 ±power,
|
||||
# escrow = 战斗内缓冲现值(入钱包在战斗末一次上抛——呈现语义与增益池分账)
|
||||
if String(row.get("effect_id", "")) in ["gp_gain", "gp_loss"]:
|
||||
var gp_col := "#e0b860" if v > 0 else "#c47c7c"
|
||||
var gp_body := "%s %+d(战斗暂存 %+d)%s" % [name_, v, int(row.get("escrow", 0)), suffix]
|
||||
return prefix + (_gold_frame(gp_body) if hl != "" else "[color=%s]%s[/color]" % [gp_col, gp_body])
|
||||
# 效果行 value = 单次贡献值,pool_value = 池合成值——禁相加呈现(战斗结算 呈现细则)
|
||||
var pool: int = int(row.get("pool_value", 0))
|
||||
var col := "#7cc47c" if v > 0 else "#c47c7c"
|
||||
|
||||
@@ -24,9 +24,10 @@ const COL_GOLD := UiKit.GOLD # 奖励金 #F0A828
|
||||
const COL_PATH := UiKit.HERO_BLUE # 路径预览点迹——小人蓝细线
|
||||
const COL_DIG_EDGE := UiKit.DIG_EDGE # 可挖域呼吸微光基色(绿相,明度贴格底)
|
||||
const COL_HL_TARGET := UiKit.GOLD # 悬停目标格边框(金)
|
||||
const REWARD_BADGE := { # 已开格格面资源身份 icon(cell_type 八型键;极性视觉归批 2)
|
||||
const REWARD_BADGE := { # 已开格格面资源身份 icon(cell_type 九型键;极性视觉归批 2)
|
||||
"attack": "攻", "defense": "守", "heal": "医", "blast": "爆", "weaken": "弱", "pierce": "穿", "magic": "魔", # 穿/魔 = 伤害道具批 2026-09-18
|
||||
"action": "行", # 行 = 行动点型(八型批 2026-09-20——ap_up/ap_drain 两行;徽章键漏网补齐,ui_click_probe 91 断言修复)
|
||||
"gp": "金", # 金 = GP 型(GP+商店房实施批 2026-09-21——gp_gain/gp_loss 两行;金色系 ~40° 相位,禁入带紫赤 300–345° 外,取色 UiKit.GOLD 徽标色单点)
|
||||
}
|
||||
const BADGE_PLACEHOLDER := "?" # 未知图标键占位纹样(HUD框架:禁静默空图标)
|
||||
const CURSOR_DIG := Input.CURSOR_CROSS # 挖——镐形自绘纹理载体
|
||||
@@ -290,7 +291,7 @@ func _update_badge(c: Vector2i, btn: Button) -> void:
|
||||
badge.visible = true
|
||||
|
||||
|
||||
# 奖励图标键解析(HUD框架:查不到的键立即断言 + ? 占位纹样 + 诊断;键集 = 格面资源 cell_type 七值枚举——伤害道具批 2026-09-18 五→七)
|
||||
# 奖励图标键解析(HUD框架:查不到的键立即断言 + ? 占位纹样 + 诊断;键集 = 格面资源 cell_type 九值枚举——GP+商店房实施批 2026-09-21 八→九)
|
||||
func _badge_glyph(category: String) -> String:
|
||||
if REWARD_BADGE.has(category):
|
||||
return REWARD_BADGE[category]
|
||||
|
||||
@@ -12,7 +12,7 @@ const COL_CURRENT := UiKit.GOLD # 当前——奖励金高亮
|
||||
const COL_UPCOMING := UiKit.DARK_UPCOMING
|
||||
const COL_CANDIDATE := UiKit.GOLD_LIT # 候选下一跳——亮金边框
|
||||
const COL_BOSS_BORDER := UiKit.FLAG_RED # Boss 边框——旗朱
|
||||
const NODE_LABELS := {"battle": "战", "reward": "赏", "rest": "疗", "boss": "王"}
|
||||
const NODE_LABELS := {"battle": "战", "reward": "赏", "rest": "疗", "boss": "王", "shop": "商"} # 商 = GP+商店房实施批 2026-09-21
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
|
||||
@@ -16,7 +16,7 @@ const COL_ROOM_FUTURE := UiKit.DARK_UPCOMING # 未达——暗
|
||||
const COL_PASSAGE := UiKit.STONE # 通道——石灰
|
||||
const COL_PASSAGE_LIT := UiKit.GOLD_LIT # 候选通道——亮金
|
||||
const COL_BOSS_BORDER := UiKit.FLAG_RED # Boss 边框——旗朱
|
||||
const NODE_LABELS := {"battle": "战", "reward": "赏", "rest": "疗", "boss": "王"}
|
||||
const NODE_LABELS := {"battle": "战", "reward": "赏", "rest": "疗", "boss": "王", "shop": "商"} # 商 = GP+商店房实施批 2026-09-21
|
||||
|
||||
var _runner = null
|
||||
var _btn_box: VBoxContainer
|
||||
@@ -87,6 +87,7 @@ func _type_name(t: String) -> String:
|
||||
"reward": return "奖励点"
|
||||
"rest": return "恢复点"
|
||||
"boss": return "Boss 房"
|
||||
"shop": return "商店房"
|
||||
return t
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# Date: 2026-09-21(GP+商店房实施批——商店房覆盖层呈现)
|
||||
# 商店覆盖层(模态):标题「商店」/ 金币余额 / 三商品卡全陈列(显示名·档位文案·现价·已购档数)/
|
||||
# 购买按钮(余额不足禁用置灰)/ 离开按钮。模态挂接方式仿既有覆盖层(route_overlay 现成参照——
|
||||
# dim + CenterContainer + box,组合根 _register_modal 收口层级)。
|
||||
# 高内聚边界:纯视图 setter 面——商品快照与余额经组合根喂入(runner.shop_products() 只读投影,
|
||||
# 现价/已购档数由 runner 现算),购买/离开意图经信号外发——零核心写调用、零 runner 持有。
|
||||
class_name ShopOverlay
|
||||
extends Control
|
||||
|
||||
signal buy_requested(product_id: String)
|
||||
signal leave_pressed
|
||||
|
||||
const ATTR_LABELS := {"atk": "攻击", "max_hp": "生命上限", "armor_class": "护甲"} # 档位文案轴(数据契约 attr 词表)
|
||||
|
||||
var _gp_label: Label
|
||||
var _cards_box: VBoxContainer
|
||||
var _card_rows: Array = [] # [{product_id, price, bought, btn}]——refresh_state 就地重刷(零重建回流)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
var dim := ColorRect.new()
|
||||
dim.color = Color(0, 0, 0, 0.6)
|
||||
dim.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
add_child(dim)
|
||||
var center := CenterContainer.new()
|
||||
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
dim.add_child(center)
|
||||
var box := VBoxContainer.new()
|
||||
box.add_theme_constant_override("separation", 12)
|
||||
center.add_child(box)
|
||||
var title := UiKit.label("商店", 26, true)
|
||||
title.add_theme_color_override("font_color", UiKit.WARM)
|
||||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(title)
|
||||
_gp_label = UiKit.label("金币 0", 18, true)
|
||||
_gp_label.add_theme_color_override("font_color", UiKit.GOLD)
|
||||
_gp_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
box.add_child(_gp_label)
|
||||
_cards_box = VBoxContainer.new()
|
||||
_cards_box.add_theme_constant_override("separation", 8)
|
||||
box.add_child(_cards_box)
|
||||
var leave_btn := UiKit.button("离开商店")
|
||||
leave_btn.pressed.connect(func(): leave_pressed.emit())
|
||||
box.add_child(leave_btn)
|
||||
visible = false
|
||||
|
||||
|
||||
# 打开商店(products = runner.shop_products() 投影——[{product_id, display_name, attr, flat_value, price, purchased}])
|
||||
func open_shop(products: Array, gp: int) -> void:
|
||||
for child in _cards_box.get_children():
|
||||
child.queue_free()
|
||||
_card_rows.clear()
|
||||
for p in products:
|
||||
_card_rows.append(_build_card(p))
|
||||
refresh_state(products, gp)
|
||||
visible = true
|
||||
|
||||
|
||||
# 状态重刷(购买/钱包变化后调用——就地改文案与按钮禁用态,零重建)
|
||||
func refresh_state(products: Array, gp: int) -> void:
|
||||
_gp_label.text = "金币 %d" % gp
|
||||
var by_id := {}
|
||||
for p in products:
|
||||
by_id[String(p.product_id)] = p
|
||||
for card in _card_rows:
|
||||
var p: Dictionary = by_id.get(String(card.product_id), {})
|
||||
if p.is_empty():
|
||||
continue
|
||||
card.price.text = "现价 %d 金币" % int(p.price)
|
||||
card.bought.text = "已购 %d 档" % int(p.purchased)
|
||||
card.btn.disabled = gp < int(p.price) # 余额不足禁用置灰
|
||||
|
||||
|
||||
# 商品卡(显示名 + 档位文案 | 现价 | 已购档数 | 购买按钮)
|
||||
func _build_card(p: Dictionary) -> Dictionary:
|
||||
var card := PanelContainer.new()
|
||||
card.add_theme_stylebox_override("panel", UiKit.panel_flat(UiKit.BG_OVERLAY, UiKit.GOLD_DIM, 4, 1, Vector4(12, 6, 12, 6)))
|
||||
var row := HBoxContainer.new()
|
||||
row.add_theme_constant_override("separation", 14)
|
||||
card.add_child(row)
|
||||
var name_l := UiKit.label(_card_title(p), 15, true)
|
||||
name_l.custom_minimum_size = Vector2(190, 0)
|
||||
row.add_child(name_l)
|
||||
var price_l := UiKit.label("", 14, false)
|
||||
price_l.add_theme_color_override("font_color", UiKit.WARM_SOFT)
|
||||
price_l.custom_minimum_size = Vector2(110, 0)
|
||||
row.add_child(price_l)
|
||||
var bought_l := UiKit.label("", 14, false)
|
||||
bought_l.add_theme_color_override("font_color", UiKit.WARM_FAINT)
|
||||
row.add_child(bought_l)
|
||||
var btn := UiKit.button("购买")
|
||||
btn.pressed.connect(func(): buy_requested.emit(String(p.product_id)))
|
||||
row.add_child(btn)
|
||||
_cards_box.add_child(card)
|
||||
return {"product_id": String(p.product_id), "price": price_l, "bought": bought_l, "btn": btn}
|
||||
|
||||
|
||||
# 卡题 = 显示名 + 档位文案(attr →「攻击 +1」形)
|
||||
func _card_title(p: Dictionary) -> String:
|
||||
var attr := String(p.get("attr", ""))
|
||||
return "%s(%s %+d)" % [String(p.get("display_name", "")), String(ATTR_LABELS.get(attr, attr)), int(p.get("flat_value", 0))]
|
||||
@@ -0,0 +1 @@
|
||||
uid://bv0bs2ase1m5s
|
||||
@@ -106,6 +106,8 @@ static func _on_node_entered(node: Dictionary, info: Dictionary, runner, st: Dic
|
||||
st.rest_healed = int(info.get("healed", 0))
|
||||
st.hp_before_rest = int(info.get("hp", 0)) - st.rest_healed
|
||||
runner.continue_after_node()
|
||||
elif t == "shop":
|
||||
runner.continue_after_node() # 商店房零购离开(GP+商店房批——自动路径直通 Boss;购买行为归冒烟商店专项断言/真人试玩)
|
||||
if logger != null:
|
||||
logger.log_node(node, info, runner.node_index)
|
||||
|
||||
|
||||
@@ -93,6 +93,11 @@ func _run_core_smoke(db, rng) -> void:
|
||||
_smoke_settlement_segments()
|
||||
_smoke_skill_pool()
|
||||
_smoke_level_graph()
|
||||
_smoke_gp_wallet()
|
||||
_smoke_gp_escrow()
|
||||
_smoke_shop_room()
|
||||
_smoke_shop_graph()
|
||||
_smoke_nine_types_gp_stream()
|
||||
|
||||
|
||||
# ---- 战斗屏棋盘布局·格边长公式回归(2026-09-19 真人试玩 bug⑥:L2 12×12 板下缘被裁约 1/6 格高——
|
||||
@@ -1400,10 +1405,11 @@ func _smoke_refresh_provenance_guard() -> void:
|
||||
# ==================== 格面资源批新增覆盖(极性路由 / 负向字典 / 资源面 / 五段全序) ====================
|
||||
# 极性路由断言(战斗结算 验收):挖安全格/标中旗只出正向、挖雷格/标错旗只出负向、兑付行指回来源格。
|
||||
# 正向字典效果集 = {attack_up, ac_up, weaken}(ac_up = 2026-09-16 护甲等级掷点批换装,原 defense_up)
|
||||
# + spark_aoe/heal_minor(flat);
|
||||
# 负向字典效果集 = {enrage, off_guard, sapped}(percent)+ 直伤(heal flat)/ alert 警觉(blast percent——2026-09-20 换装)。
|
||||
const POSITIVE_EFFECTS := ["attack_up", "ac_up", "weaken"]
|
||||
const NEGATIVE_EFFECTS := ["enrage", "off_guard", "sapped", "alert"]
|
||||
# + spark_aoe/heal_minor(flat)+ gp_gain(九型批 2026-09-20——金币袋,escrow 缓冲路由键非增益容器行);
|
||||
# 负向字典效果集 = {enrage, off_guard, sapped}(percent)+ 直伤(heal flat)/ alert 警觉(blast percent——2026-09-20 换装)
|
||||
# + gp_loss(九型批——破财,escrow 缓冲路由键)。
|
||||
const POSITIVE_EFFECTS := ["attack_up", "ac_up", "weaken", "gp_gain"]
|
||||
const NEGATIVE_EFFECTS := ["enrage", "off_guard", "sapped", "alert", "gp_loss"]
|
||||
|
||||
|
||||
func _smoke_polarity_routing() -> void:
|
||||
@@ -1413,6 +1419,7 @@ func _smoke_polarity_routing() -> void:
|
||||
check(int(probe["mine_events"]) >= 1, "构造触达:挖到雷格 ≥1(%d)" % int(probe["mine_events"]))
|
||||
check(int(probe["safe_events"]) >= 1, "构造触达:挖到安全格 ≥1(%d)" % int(probe["safe_events"]))
|
||||
# 挖到的安全格:只出正向——其全部来源行必存在且 effect 行 ∈ 正向集
|
||||
# (action 型排队行 effect_id=ap_delta 正负两极共用路由键——按行值符号辨极性:正向格恒 +power)
|
||||
var safe_all_pos := true
|
||||
for e in probe["safe_cells"].keys():
|
||||
var cell_arr: Array = [e.x, e.y]
|
||||
@@ -1421,10 +1428,15 @@ func _smoke_polarity_routing() -> void:
|
||||
safe_all_pos = false # 每个资源格必有 ≥1 兑付行(每格必有货 → 每格必有行)
|
||||
for r in rows:
|
||||
var eff := String(r.get("effect_id", ""))
|
||||
if eff == "ap_delta":
|
||||
if int(r.get("value", 0)) <= 0:
|
||||
safe_all_pos = false
|
||||
continue
|
||||
if eff != "" and not POSITIVE_EFFECTS.has(eff):
|
||||
safe_all_pos = false
|
||||
check(safe_all_pos, "挖安全格 → 只出正向兑付(效果行 ∈ 正向字典 + 每格 ≥1 行——%d 格)" % probe["safe_cells"].size())
|
||||
# 挖到的雷格:只出负向——来源行必存在且 effect 行 ∈ 负向集(alert = blast 负向;heal 负向 = damage 行)
|
||||
# 挖到的雷格:只出负向——来源行必存在且 effect 行 ∈ 负向集(alert = blast 负向;heal 负向 = damage 行;
|
||||
# action 型排队行同按行值符号辨极性:雷格恒 −power)
|
||||
var mine_all_neg := true
|
||||
for e in probe["mine_cells"].keys():
|
||||
var cell_arr: Array = [e.x, e.y]
|
||||
@@ -1433,6 +1445,10 @@ func _smoke_polarity_routing() -> void:
|
||||
mine_all_neg = false
|
||||
for r in rows:
|
||||
var eff := String(r.get("effect_id", ""))
|
||||
if eff == "ap_delta":
|
||||
if int(r.get("value", 0)) >= 0:
|
||||
mine_all_neg = false
|
||||
continue
|
||||
if eff != "" and not NEGATIVE_EFFECTS.has(eff):
|
||||
mine_all_neg = false
|
||||
check(mine_all_neg, "挖雷格 → 只出负向兑付(效果行 ∈ 负向字典 + 每格 ≥1 行——%d 格)" % probe["mine_cells"].size())
|
||||
@@ -1860,14 +1876,14 @@ func _smoke_skill_pool_data() -> void:
|
||||
"2_5": {"attack": 5, "lunge": 2, "heavy": 2},
|
||||
"2_6": {"attack": 6, "bite": 2, "charge": 1},
|
||||
"2_7": {"attack": 6, "bite": 2, "charge": 1},
|
||||
"2_8": {"attack": 6, "heavy": 2, "venom_burst": 2, "web_snare": 2},
|
||||
"2_9": {"attack": 6, "heavy": 2, "venom_burst": 2, "web_snare": 2},
|
||||
"3_0": {"attack": 6, "charge": 2, "heavy": 2},
|
||||
"3_1": {"attack": 6, "bite": 2, "charge": 1},
|
||||
"3_4": {"attack": 6, "bite": 2, "charge": 1},
|
||||
"3_5": {"attack": 5, "lunge": 2, "heavy": 2},
|
||||
"3_6": {"attack": 6, "bite": 2, "charge": 1},
|
||||
"3_7": {"attack": 6, "charge": 2, "heavy": 2},
|
||||
"3_8": {"attack": 6, "heavy": 2, "earth_pulse": 2},
|
||||
"3_9": {"attack": 6, "heavy": 2, "earth_pulse": 2},
|
||||
}
|
||||
var pools_ok := true
|
||||
for row in _db.enemy.rows:
|
||||
@@ -1880,7 +1896,7 @@ func _smoke_skill_pool_data() -> void:
|
||||
got[String(entry.skill_id)] = int(entry.weight)
|
||||
if got != expect_pools[rk]:
|
||||
pools_ok = false
|
||||
check(pools_ok, "十九行池模板 = 建议池行值(L1 地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2;L2 兄弟精 6/1/2 · 蜘蛛 6/2/1 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2;L3 灰矮人 6/2/2 · 钩爪恐魔 6/2/1 · 重装手 5/2/2 · 石君王 6/2/2〔earth_pulse〕——全部 provisional)")
|
||||
check(pools_ok, "十九行池模板 = 建议池行值(L1 地精 9/1 · 熊地精 6/2/2 · 酋长 6/2/2;L2 兄弟精 6/1/2 · 蜘蛛 6/2/1 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2〔(2,9)——商店房批 Boss 重编号〕;L3 灰矮人 6/2/2 · 钩爪恐魔 6/2/1 · 重装手 5/2/2 · 石君王 6/2/2〔earth_pulse·(3,9)〕——全部 provisional)")
|
||||
var basic_count_ok := true
|
||||
for row in _db.enemy.rows:
|
||||
var n := 0
|
||||
@@ -2149,8 +2165,9 @@ func _has_err_category(errs: Array, category: String) -> bool:
|
||||
|
||||
|
||||
# ⑦ 新效应词汇批实装探针(2026-09-19——妨害技蛛网束缚 + 毒爆必中附加分量,实装面 = L2 Boss 卓尔):
|
||||
# 停手轮驱动 (2,8)——零挖掘零旗 → 零池,伤害行可逐拍精确复算;种子 hunt 至少各命一粒毒爆伤害行
|
||||
# 停手轮驱动 (2,9)——零挖掘零旗 → 零池,伤害行可逐拍精确复算;种子 hunt 至少各命一粒毒爆伤害行
|
||||
# 与蛛网束缚施放行;毒分量随开战掷定的攻击基线缩放(atk 区间 2–4 → rider 1–3)。
|
||||
# (2026-09-21 商店房批连带:Boss 重编号 8→9——(2,8) 现为商店房无战斗行,驱动点随行迁移。)
|
||||
func _smoke_debuff_rider() -> void:
|
||||
var venom_seed := -1
|
||||
var venom_check_ok := true
|
||||
@@ -2167,7 +2184,7 @@ func _smoke_debuff_rider() -> void:
|
||||
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rg)
|
||||
var rows: Array = []
|
||||
settlement.step_row.connect(func(row): rows.append(row))
|
||||
if not settlement.begin_battle({"level_index": 2, "node_depth": 8, "hero_hp": int(_db.hero.max_hp)}):
|
||||
if not settlement.begin_battle({"level_index": 2, "node_depth": 9, "hero_hp": int(_db.hero.max_hp)}):
|
||||
continue
|
||||
var boss_atk: int = int(settlement._enemy_system.all_enemies()[0].atk) # 开战掷定攻击基线(毒分量随其缩放)
|
||||
for i in range(6):
|
||||
@@ -2228,15 +2245,15 @@ func _smoke_level_graph() -> void:
|
||||
for row in _db.level.rows:
|
||||
if int(row.level_index) == 2:
|
||||
l2 = row
|
||||
check(not l2.is_empty() and l2.nodes.size() == 9, "L2 节点数 = 9(入口 1 + 中间三层 2/3/2 + Boss)")
|
||||
var tcount := {"battle": 0, "reward": 0, "rest": 0, "boss": 0}
|
||||
check(not l2.is_empty() and l2.nodes.size() == 10, "L2 节点数 = 10(入口 1 + 中间三层 2/3/2 + 商店房 + Boss——商店房批 Boss 重编号 8→9)")
|
||||
var tcount := {"battle": 0, "reward": 0, "rest": 0, "boss": 0, "shop": 0}
|
||||
var branch := 0
|
||||
for node in l2.nodes:
|
||||
tcount[String(node.get("type", ""))] = int(tcount[String(node.get("type", ""))]) + 1
|
||||
if node.get("next", []).size() >= 2:
|
||||
branch += 1
|
||||
check(int(tcount.battle) == 6 and int(tcount.boss) == 1, "L2 普通战斗节点 = 6 + Boss 恒一(编号最大恒终点)")
|
||||
check(int(tcount.reward) == 1 and int(tcount.rest) == 1, "L2 恢复节点 1(rest_heal_pct 行字段)+ 奖励节点 1(直通空态)")
|
||||
check(int(tcount.reward) == 1 and int(tcount.rest) == 1 and int(tcount.shop) == 1, "L2 恢复节点 1(rest_heal_pct 行字段)+ 奖励节点 1(直通空态)+ 商店房节点 1(商店房批)")
|
||||
check(branch >= 2, "L2 真实岔路 = %d 处(≥2)" % branch)
|
||||
# ③ 图校验正反断言(违规行集定位到类别——加载期致命的同款算法)
|
||||
var lt = load("res://data/schemas/level_table.gd").new()
|
||||
@@ -2275,12 +2292,12 @@ func _smoke_level_graph() -> void:
|
||||
# ④ L2 选路推进:min / max 双路径全通(确定性路径轴——零随机流自开)
|
||||
var win_min = autoplay.run_level(_db, 42, 2, 200, 100)
|
||||
check(win_min.result == "victory", "L2·满配 min 路径通关(实测:%s)" % win_min.result)
|
||||
check(win_min.boss_depth == 8, "L2 Boss 以编号 8 解析(终点编号最大)")
|
||||
check(win_min.visited_ids == [0, 1, 3, 6, 8], "min 路径 = 0→1→3(恢复)→6→8(实测 %s)" % str(win_min.visited_ids))
|
||||
check(win_min.boss_depth == 9, "L2 Boss 以编号 9 解析(终点编号最大——商店房批重编号 8→9)")
|
||||
check(win_min.visited_ids == [0, 1, 3, 6, 8, 9], "min 路径 = 0→1→3(恢复)→6→8(商店零购离开)→9(实测 %s)" % str(win_min.visited_ids))
|
||||
check(win_min.choices >= 3, "选路口发生 %d 次(每节点兑付后——零自动推进)" % win_min.choices)
|
||||
var win_max = autoplay.run_level(_db, 42, 2, 200, 100, false, null, "normal", "max")
|
||||
check(win_max.result == "victory" and win_max.visited_ids == [0, 2, 5, 7, 8],
|
||||
"L2·满配 max 路径(经奖励点 2)通关 = 0→2→5→7→8(实测 %s %s)" % [win_max.result, str(win_max.visited_ids)])
|
||||
check(win_max.result == "victory" and win_max.visited_ids == [0, 2, 5, 7, 8, 9],
|
||||
"L2·满配 max 路径(经奖励点 2)通关 = 0→2→5→7→8(商店零购离开)→9(实测 %s %s)" % [win_max.result, str(win_max.visited_ids)])
|
||||
# ⑤ 零自动推进状态机(手动驱动——兑付确认 / 选路拒绝语义逐拍断言)
|
||||
var runner = load("res://core/level_runner.gd").new(_db, _fresh_rng(42))
|
||||
runner.set_hero_override(200, 100)
|
||||
@@ -2343,24 +2360,24 @@ func _smoke_level_graph() -> void:
|
||||
check(cross_ok, "跨表行完整:L2 全部战斗/Boss 节点在 board_gen + enemy 精确行(7 节点)")
|
||||
var l2_spider: Dictionary = _db.enemy.resolve_rowset(2, 4)
|
||||
check(String(l2_spider.get("enemy_id", "")) == "giant_spider", "巨蜘蛛首遇行集 (2,4) = giant_spider(固定 2 只——首遇教学位不加骰)")
|
||||
var l2_boss: Dictionary = _db.enemy.resolve_rowset(2, 8)
|
||||
var l2_boss: Dictionary = _db.enemy.resolve_rowset(2, 9)
|
||||
check(String(l2_boss.get("enemy_id", "")) == "drider" and String(l2_boss.get("armor_type", "")) == "ward",
|
||||
"蛛化卓尔精灵 Boss 行集 (2,8) = drider(魔法甲——护甲类型重配批 2026-09-18:关卡 2 唯一非轻甲)")
|
||||
"蛛化卓尔精灵 Boss 行集 (2,9) = drider(魔法甲——护甲类型重配批 2026-09-18:关卡 2 唯一非轻甲;商店房批重编号 (2,8)→(2,9))")
|
||||
# ---- 第三关内容批(2026-09-20)L3 灰矮人矿城断言段 ----
|
||||
# ⑧ L3 行集结构(复用 L2 九节点图模板——6 战 + Boss + 奖励 + 恢复,真实岔路 4 处)
|
||||
var l3 := {}
|
||||
for row in _db.level.rows:
|
||||
if int(row.level_index) == 3:
|
||||
l3 = row
|
||||
check(not l3.is_empty() and l3.nodes.size() == 9, "L3 节点数 = 9(入口 1 + 中间三层 2/3/2 + Boss——L2 同模板)")
|
||||
var tcount3 := {"battle": 0, "reward": 0, "rest": 0, "boss": 0}
|
||||
check(not l3.is_empty() and l3.nodes.size() == 10, "L3 节点数 = 10(入口 1 + 中间三层 2/3/2 + 商店房 + Boss——L2 同模板 + 商店房批)")
|
||||
var tcount3 := {"battle": 0, "reward": 0, "rest": 0, "boss": 0, "shop": 0}
|
||||
var branch3 := 0
|
||||
for node in l3.nodes:
|
||||
tcount3[String(node.get("type", ""))] = int(tcount3[String(node.get("type", ""))]) + 1
|
||||
if node.get("next", []).size() >= 2:
|
||||
branch3 += 1
|
||||
check(int(tcount3.battle) == 6 and int(tcount3.boss) == 1, "L3 普通战斗节点 = 6 + Boss 恒一(编号最大恒终点)")
|
||||
check(int(tcount3.reward) == 1 and int(tcount3.rest) == 1, "L3 恢复节点 1(rest_heal_pct 行字段)+ 奖励节点 1(直通空态)")
|
||||
check(int(tcount3.reward) == 1 and int(tcount3.rest) == 1 and int(tcount3.shop) == 1, "L3 恢复节点 1(rest_heal_pct 行字段)+ 奖励节点 1(直通空态)+ 商店房节点 1(商店房批)")
|
||||
check(branch3 == 4, "L3 真实岔路 = %d 处(0→[1,2] · 1→[3,4] · 2→[4,5] · 4→[6,7])" % branch3)
|
||||
# ⑨ 跨表行完整:L3 每战斗/Boss 节点 (3, 编号) 在 board_gen 与 enemy 精确行(⑦ 的 bg_keys/en_keys 复用)
|
||||
var cross_ok3 := true
|
||||
@@ -2376,9 +2393,9 @@ func _smoke_level_graph() -> void:
|
||||
var l3_lead: Dictionary = _db.enemy.resolve_rowset(3, 0)
|
||||
check(String(l3_lead.get("enemy_id", "")) == "duergar" and String(l3_lead.get("armor_type", "")) == "heavy",
|
||||
"灰矮人首遇行集 (3,0) = duergar(重甲首次登台——固定 1 只,首遇教学位不加骰)")
|
||||
var l3_king: Dictionary = _db.enemy.resolve_rowset(3, 8)
|
||||
var l3_king: Dictionary = _db.enemy.resolve_rowset(3, 9)
|
||||
check(String(l3_king.get("enemy_id", "")) == "duergar_king" and String(l3_king.get("armor_type", "")) == "heavy",
|
||||
"灰矮人石君王 Boss 行集 (3,8) = duergar_king(重甲收束「轻甲→魔法甲→重甲」教学梯度)")
|
||||
"灰矮人石君王 Boss 行集 (3,9) = duergar_king(重甲收束「轻甲→魔法甲→重甲」教学梯度;商店房批重编号 (3,8)→(3,9))")
|
||||
# ⑪ L3 成长代理档首遇绿档:模拟 GP 商店成长后的入关参数(atk_pct=200 / hp_pct=100)· 完美档多种子首遇战稳过
|
||||
# (零加成基线红档属预期——断言口径 = 成长代理档,非基线;跑法同 ⑥)
|
||||
var green3_ok := true
|
||||
@@ -2412,6 +2429,397 @@ func _fresh_rng(seed_val: int):
|
||||
return rng
|
||||
|
||||
|
||||
# ==================== GP+商店房实施批冒烟(2026-09-21——接 L3 断言段后) ====================
|
||||
# ① GP 到账(战斗完成入账恰一次 + Boss 关级加值)② escrow 隔离(格面兑付入缓冲/破财不透支)
|
||||
# ③ 商店三收口(购买出账/加算区/余额不足拒/离开/收束清零)④ 图不变式(商店必经位/跨表行集迁移)
|
||||
# ⑤ 九型与 gp_drop 流(奖励字典 18 格 + 黄金向量)。
|
||||
|
||||
# ---- ① GP 冒险钱包·到账 ----
|
||||
|
||||
func _smoke_gp_wallet() -> void:
|
||||
print("--- GP 冒险钱包·到账(战斗完成入账恰一次 + Boss 加值)---")
|
||||
var autoplay = load("res://smoke/level_autoplay.gd")
|
||||
# ① L1 首战(普通战档——掷定 [2,4] + 关级加值 0):runner 驱动,收集 battle_finished 与钱包事件
|
||||
var runner = load("res://core/level_runner.gd").new(_db, _fresh_rng(42))
|
||||
runner.set_hero_override(200, 100)
|
||||
var fin := {}
|
||||
var wallet_events: Array = []
|
||||
runner.wallet_changed.connect(func(ev, gp_val, extra): wallet_events.append({"event": String(ev), "gp": int(gp_val), "amount": int(extra.get("amount", 0))}))
|
||||
check(runner.start_level(1, 42), "GP 到账:L1 启动(普通战档)")
|
||||
var battle = runner.current_battle()
|
||||
var rows: Array = []
|
||||
battle.step_row.connect(func(row): rows.append(row))
|
||||
battle.battle_finished.connect(func(p): fin["p"] = p)
|
||||
var st := {"result": "", "rounds": 0, "refreshes": 0, "mine_hits": 0, "hero_dmg": 0, "policy": "normal"}
|
||||
var guard := 30
|
||||
while runner.current_battle() == battle and not fin.has("p") and guard > 0:
|
||||
guard -= 1
|
||||
autoplay._autoplay_battle(runner, battle, st, null)
|
||||
check(fin.has("p") and String(fin["p"].get("result", "")) == "victory", "L1 首战胜利(battle_finished gp_dropped 载荷在案)")
|
||||
var escrow_sum := _escrow_sum_of(rows)
|
||||
var gp_dropped: int = int(fin["p"].get("gp_dropped", -999))
|
||||
check(gp_dropped - escrow_sum >= 2 and gp_dropped - escrow_sum <= 4,
|
||||
"L1 普通战 gp_dropped − escrow ∈ [2+0, 4+0](掷定+关级加值 %d,escrow %+d)" % [gp_dropped - escrow_sum, escrow_sum])
|
||||
var gains: Array = []
|
||||
for ev in wallet_events:
|
||||
if ev["event"] == "gain":
|
||||
gains.append(ev)
|
||||
check(gains.size() == 1, "钱包恰入账一次(gain 事件 ×%d)" % gains.size())
|
||||
if gains.size() == 1:
|
||||
check(int(gains[0]["amount"]) == gp_dropped, "入账金额 = gp_dropped 载荷(%d)" % gp_dropped)
|
||||
check(runner.gp == maxi(0, gp_dropped) and runner.gp == int(gains[0]["gp"]),
|
||||
"钱包读数 = 入账后投影(gp=%d,破财负值场景经 clamp ≥0)" % runner.gp)
|
||||
# ② L2 Boss 战(节点 9——商店房批重编号):掷定 [6,10] + 关级加值 1
|
||||
var boss = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(42))
|
||||
var rows_b: Array = []
|
||||
var fin_b := {}
|
||||
boss.step_row.connect(func(row): rows_b.append(row))
|
||||
boss.battle_finished.connect(func(p): fin_b["p"] = p)
|
||||
check(boss.begin_battle({"level_index": 2, "node_depth": 9, "node_type": "boss", "hero_hp": int(_db.hero.max_hp)}),
|
||||
"L2 Boss 战启动((2,9)——Boss 档掷定 [6,10] + 关级加值 1)")
|
||||
var guard_b := 60
|
||||
while boss.battle_result == "" and guard_b > 0:
|
||||
guard_b -= 1
|
||||
boss.minesweeper.settle_batch([{"action": "stop"}])
|
||||
check(fin_b.has("p"), "L2 Boss 战终局(%s——gp_dropped 于战斗末掷定上抛)" % String(boss.battle_result))
|
||||
var escrow_b := _escrow_sum_of(rows_b)
|
||||
var gp_b: int = int(fin_b.get("p", {}).get("gp_dropped", -999)) if fin_b.has("p") else -999
|
||||
check(gp_b - escrow_b >= 7 and gp_b - escrow_b <= 11,
|
||||
"L2 Boss 战 gp_dropped − escrow ∈ [6+1, 10+1](掷定+关级加值 %d,escrow %+d)" % [gp_b - escrow_b, escrow_b])
|
||||
|
||||
|
||||
# 步骤流中 GP 型兑付行(effect_id gp_gain/gp_loss)的 value 代数和 = escrow 会计算(归因行随行携带)
|
||||
func _escrow_sum_of(rows: Array) -> int:
|
||||
var sum := 0
|
||||
for row in rows:
|
||||
if String(row.get("effect_id", "")) == "gp_gain" or String(row.get("effect_id", "")) == "gp_loss":
|
||||
sum += int(row.get("value", 0))
|
||||
return sum
|
||||
|
||||
|
||||
# ---- ② escrow 隔离(金币安全格兑付 / 破财直验 / 钱包 clamp)----
|
||||
|
||||
func _smoke_gp_escrow() -> void:
|
||||
print("--- GP escrow 隔离(金币安全格兑付入缓冲 · 破财不透支)---")
|
||||
# ① 种子扫描:找金币袋(gp 型安全格)可挖掘局——挖开兑付 +3 入 escrow,战斗末随 gp_dropped 一次上抛
|
||||
var scan := {}
|
||||
for s in range(1, 121):
|
||||
var rng = _fresh_rng(s)
|
||||
var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng)
|
||||
var rows: Array = []
|
||||
settlement.step_row.connect(func(row): rows.append(row))
|
||||
var fin := {}
|
||||
settlement.battle_finished.connect(func(p): fin["p"] = p)
|
||||
if not settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999}):
|
||||
continue
|
||||
var target := _find_gp_dig(settlement.minesweeper, settlement)
|
||||
if target.is_empty():
|
||||
continue
|
||||
scan = {"seed": s, "settlement": settlement, "target": target, "rows": rows, "fin": fin}
|
||||
break
|
||||
check(not scan.is_empty(), "金币袋(gp 型安全格)可挖掘局种子可达(seed=%s)" % str(scan.get("seed", -1)))
|
||||
if not scan.is_empty():
|
||||
var settlement2 = scan["settlement"]
|
||||
var ms = settlement2.minesweeper
|
||||
var t: Dictionary = scan["target"]
|
||||
var dig_arr: Array = [t["dig_cell"].x, t["dig_cell"].y]
|
||||
if t["hero_cell"] != ms.hero_pos():
|
||||
ms.settle_batch([{"action": "move", "cell": t["hero_cell"]}])
|
||||
ms.settle_batch([{"action": "dig", "cell": t["dig_cell"]}])
|
||||
var guard_stop := 12
|
||||
while settlement2.battle_result == "" and guard_stop > 0:
|
||||
guard_stop -= 1
|
||||
ms.settle_batch([{"action": "stop"}]) # 收轮结算:授予段兑付 +3 → 战斗段先手 999 清场 → 胜利上抛
|
||||
check(settlement2.battle_result == "victory", "金币局战斗终局(先手 999 收束)")
|
||||
var gain_ok := false
|
||||
for row in scan["rows"]:
|
||||
if String(row.get("effect_id", "")) == "gp_gain" and row.get("origin_cell", []) == dig_arr and int(row.get("value", 0)) == 3:
|
||||
gain_ok = true
|
||||
check(gain_ok, "金币袋兑付行在案(金币袋 +3 → 战斗内缓冲,origin 指回来源格)")
|
||||
check(int(settlement2.escrow) >= 3, "escrow 累计缓冲 ≥3(实测 %d——战斗域中间态,不入钱包不入增益容器)" % int(settlement2.escrow))
|
||||
var gp_d: int = int(scan["fin"].get("p", {}).get("gp_dropped", -999))
|
||||
check(gp_d - int(settlement2.escrow) >= 2 and gp_d - int(settlement2.escrow) <= 4,
|
||||
"格面兑付计入 gp_dropped(%d − escrow %d ∈ [2,4]——战斗末一次到账,随上抛即弃)" % [gp_d, int(settlement2.escrow)])
|
||||
# ② 破财(gp_loss)直验:兑付入口直调(weight 0 不入池——字典负向行单测通道,负向字典冒烟前例)
|
||||
var rng_l = _fresh_rng(20260921)
|
||||
var settle_l = load("res://core/battle_orchestrator.gd").new(_db, rng_l)
|
||||
var fin_l := {}
|
||||
settle_l.battle_finished.connect(func(p): fin_l["p"] = p)
|
||||
check(settle_l.begin_battle({"level_index": 1, "node_depth": 4, "hero_hp": int(_db.hero.max_hp), "hero_atk": 999}),
|
||||
"破财直验战斗启动")
|
||||
for i in range(3):
|
||||
settle_l._grant_routed({"reward_id": "gp_lose", "cell_type": "gp", "polarity": "negative", "display_name": "破财", "power": 2, "effect_id": "gp_loss", "stacks": 1},
|
||||
"negative", "mine_hit", Vector2i(0, 0), 1)
|
||||
check(int(settle_l.escrow) == -6, "破财 ×3 → escrow −6(负向缓冲记账,零钱包触达)")
|
||||
settle_l.minesweeper.settle_batch([{"action": "stop"}])
|
||||
var gp_l: int = int(fin_l.get("p", {}).get("gp_dropped", 999))
|
||||
check(gp_l + 6 >= 2 and gp_l + 6 <= 4, "破财压制 gp_dropped 负值上抛(%d + 6 ∈ [2,4]——掷定不受破财污染)" % gp_l)
|
||||
# ③ 钱包不透支(clamp ≥0):runner 驱动 L1 首战 + 战斗中注入破财 → gp_dropped 负值 → 钱包钳制归 0
|
||||
var runner = load("res://core/level_runner.gd").new(_db, _fresh_rng(4242))
|
||||
runner.set_hero_override(49850, 0) # atk 2 → 999(快速终局;hp 基线)
|
||||
var ev_box := {"gains": [], "min_gp": 999999}
|
||||
runner.wallet_changed.connect(func(ev, gp_val, extra):
|
||||
ev_box["gains"].append({"event": String(ev), "gp": int(gp_val), "amount": int(extra.get("amount", 0))})
|
||||
ev_box["min_gp"] = mini(ev_box["min_gp"], int(gp_val)))
|
||||
check(runner.start_level(1, 4242), "clamp 场景:L1 启动(冒险钱包开局 0)")
|
||||
var b3 = runner.current_battle()
|
||||
var fin3 := {}
|
||||
b3.battle_finished.connect(func(p): fin3["p"] = p)
|
||||
for i in range(3):
|
||||
b3._grant_routed({"reward_id": "gp_lose", "cell_type": "gp", "polarity": "negative", "display_name": "破财", "power": 2, "effect_id": "gp_loss", "stacks": 1},
|
||||
"negative", "mine_hit", Vector2i(0, 0), 1)
|
||||
var guard3 := 12
|
||||
while runner.current_battle() == b3 and not fin3.has("p") and guard3 > 0:
|
||||
guard3 -= 1
|
||||
b3.minesweeper.settle_batch([{"action": "stop"}])
|
||||
var gp3: int = int(fin3.get("p", {}).get("gp_dropped", 999))
|
||||
check(gp3 < 0, "破财压制入账载荷为负(gp_dropped=%d)" % gp3)
|
||||
var gain3: Array = []
|
||||
for ev in ev_box["gains"]:
|
||||
if ev["event"] == "gain":
|
||||
gain3.append(ev)
|
||||
check(gain3.size() == 1 and int(gain3[0]["amount"]) == gp3 and int(gain3[0]["gp"]) == 0,
|
||||
"破财入账恰一次且投影读数 0(amount=%d,clamp 后 gp=0)" % gp3)
|
||||
check(runner.gp == 0 and int(ev_box["min_gp"]) >= 0, "钱包全程不透支(gp=%d,事件投影最小读数 %d ≥ 0)" % [runner.gp, int(ev_box["min_gp"])])
|
||||
|
||||
|
||||
# 板真相直读找金币袋挖掘目标:未开安全格 + gp 型资源身份 + 存在可达已开邻格(_find_flag_target 同款口径)
|
||||
func _find_gp_dig(minesweeper, settlement) -> Dictionary:
|
||||
var board = minesweeper.current_board()
|
||||
var revealed := {}
|
||||
for c in minesweeper.revealed_cells():
|
||||
revealed[c] = true
|
||||
for y in board.height:
|
||||
for x in board.width:
|
||||
var c := Vector2i(x, y)
|
||||
if revealed.has(c) or board.is_mine(c):
|
||||
continue
|
||||
var ident = settlement.query_reward_identity(c)
|
||||
if ident == null or String(ident.get("cell_type", "")) != "gp":
|
||||
continue
|
||||
for n in board.neighbors8(c):
|
||||
if revealed.has(n) and (minesweeper.compute_move_path(n).size() > 0 or n == minesweeper.hero_pos()):
|
||||
return {"dig_cell": c, "hero_cell": n}
|
||||
return {}
|
||||
|
||||
|
||||
# ---- ③ 商店房三收口 ----
|
||||
|
||||
func _smoke_shop_room() -> void:
|
||||
print("--- 商店房三收口(现价/加算区/余额不足拒/离开回选路/收束清零)---")
|
||||
var autoplay = load("res://smoke/level_autoplay.gd")
|
||||
# 行进到商店房(min 路径:0→1→3(恢复)→6→8)——种子扫描保证进店余额 ≥ 三件首购总价 16(6+4+6)
|
||||
var shop_run := {}
|
||||
for s in range(42, 92):
|
||||
var runner = load("res://core/level_runner.gd").new(_db, _fresh_rng(s))
|
||||
runner.set_hero_override(400, 200) # 行进段快速通关档(购买断言与百分比轴正交——加算区单独断言)
|
||||
runner.start_level(2, s)
|
||||
var st := {"result": "", "rounds": 0, "refreshes": 0, "mine_hits": 0, "hero_dmg": 0, "policy": "normal"}
|
||||
var guard := 40
|
||||
while String(runner.level_result) == "" and int(runner.current_node_id()) != 8 and guard > 0:
|
||||
guard -= 1
|
||||
if runner.awaiting_choice():
|
||||
var ids: Array = runner.next_candidate_ids()
|
||||
ids.sort()
|
||||
runner.pick_next_node(int(ids[0]))
|
||||
continue
|
||||
var t := String(runner.current_node().get("type", ""))
|
||||
if t == "reward" or t == "rest":
|
||||
runner.continue_after_node()
|
||||
continue
|
||||
if t == "shop":
|
||||
break
|
||||
var b = runner.current_battle()
|
||||
if b == null:
|
||||
break
|
||||
var fin := {}
|
||||
b.battle_finished.connect(func(p): fin["p"] = p)
|
||||
var g2 := 80
|
||||
while runner.current_battle() == b and not fin.has("p") and g2 > 0:
|
||||
g2 -= 1
|
||||
autoplay._autoplay_battle(runner, b, st, null)
|
||||
if String(runner.level_result) == "" and int(runner.current_node_id()) == 8 and runner.gp >= 16:
|
||||
shop_run = {"seed": s, "runner": runner}
|
||||
break
|
||||
check(not shop_run.is_empty(), "商店房到达种子可达(seed=%s——进店 GP ≥ 16)" % str(shop_run.get("seed", -1)))
|
||||
if shop_run.is_empty():
|
||||
return
|
||||
var runner = shop_run["runner"]
|
||||
check(String(runner.current_node().get("type", "")) == "shop" and int(runner.current_node_id()) == 8,
|
||||
"L2 行进到商店房(节点 8,next=[9]——Boss 前必经位)")
|
||||
check(not runner.awaiting_choice() and runner.next_candidate_ids().is_empty(),
|
||||
"到店开店态(覆盖层数据源就位——离开前不出选路口,零自动推进)")
|
||||
# 现价 = 基价 + price_level_bonus(L2)=2 + 步长 × 已购档数
|
||||
var prods: Array = runner.shop_products()
|
||||
check(prods.size() == 3, "三商品全陈列(atk_up/hp_up/armor_up——无刷新无摇号)")
|
||||
var by_id := _shop_products_by_id(prods)
|
||||
check(int(by_id["atk_up"]["price"]) == 6 and int(by_id["hp_up"]["price"]) == 4 and int(by_id["armor_up"]["price"]) == 6,
|
||||
"L2 首购现价 = 基价 + 关级加值 2(atk_up 6 / hp_up 4 / armor_up 6)")
|
||||
# 购买 atk_up:钱包 −6 · 加算区 atk +1 · 二购现价 8
|
||||
var gp0: int = runner.gp
|
||||
check(runner.buy_product("atk_up"), "购买 atk_up 受理(现价 6)")
|
||||
check(runner.gp == gp0 - 6, "钱包扣现价 6(%d → %d)" % [gp0, runner.gp])
|
||||
check(int(runner.bonus.get("atk", -1)) == 1 and int(runner.bonus.get("max_hp", -1)) == 0 and int(runner.bonus.get("armor_class", -1)) == 0,
|
||||
"统一冒险加算区 atk +1(bonus={atk,max_hp,armor_class} 账本)")
|
||||
by_id = _shop_products_by_id(runner.shop_products())
|
||||
check(int(by_id["atk_up"]["price"]) == 8 and int(by_id["atk_up"]["purchased"]) == 1,
|
||||
"atk_up 二购现价 = 4+2+2=8(步长 2 × 已购 1 档,现价现算)")
|
||||
# 行进段百分比归零(购买/生效断言按加算区裸值口径——TunePanel 百分比后乘;hp 快照钳到新口径上限防 spawn 越界,测试通道)
|
||||
runner.set_hero_override(0, 0)
|
||||
runner.hero_hp = mini(runner.hero_hp, runner.effective_max_hp())
|
||||
# hp_up:上限与当前生命同步 +3(封顶新上限)
|
||||
var hp_before: int = runner.hero_hp
|
||||
var mhp_before: int = runner.effective_max_hp()
|
||||
check(runner.buy_product("hp_up"), "购买 hp_up 受理(现价 4)")
|
||||
check(runner.gp == gp0 - 6 - 4, "钱包扣 4(%d)" % runner.gp)
|
||||
check(runner.effective_max_hp() == mhp_before + 3, "生命上限 +3(%d → %d——加算区 max_hp 轴)" % [mhp_before, runner.effective_max_hp()])
|
||||
check(runner.hero_hp == mini(hp_before + 3, runner.effective_max_hp()), "当前生命同步 +3 封顶新上限(%d → %d)" % [hp_before, runner.hero_hp])
|
||||
# armor_up:AC 10+1
|
||||
check(runner.buy_product("armor_up"), "购买 armor_up 受理(现价 6)")
|
||||
check(runner.effective_ac() == int(_db.hero.armor_class) + 1, "AC = 10+1(armor_class 加算区轴,无百分比通道)")
|
||||
# 余额不足购买被拒(零副作用):hp_up 连购至不可负担后兜底拒绝
|
||||
var guard_drain := 20
|
||||
while guard_drain > 0:
|
||||
guard_drain -= 1
|
||||
var hp_price: int = int(_shop_products_by_id(runner.shop_products())["hp_up"]["price"])
|
||||
if runner.gp < hp_price:
|
||||
break
|
||||
if not runner.buy_product("hp_up"):
|
||||
break
|
||||
var gp_dry: int = runner.gp
|
||||
var tier_dry: int = runner.purchased_tier("hp_up")
|
||||
var hp_dry: int = int(_shop_products_by_id(runner.shop_products())["hp_up"]["price"])
|
||||
check(not runner.buy_product("hp_up") and runner.gp == gp_dry and runner.purchased_tier("hp_up") == tier_dry,
|
||||
"余额不足购买被拒(gp=%d < 现价 %d——零扣费零档位变动)" % [gp_dry, hp_dry])
|
||||
# 离开 → 回选路口(候选 [9])
|
||||
check(runner.continue_after_node() and runner.awaiting_choice() and runner.next_candidate_ids() == [9],
|
||||
"离开商店 → 回选路口(候选 [9]——零自动推进收口)")
|
||||
# 下一战参数 = 基础表值 + 加算区(百分比已归零——加算先乘后乘序的前半在案)
|
||||
check(runner.effective_atk() == int(_db.hero.atk) + 1, "主角攻击生效值 = 2+1(基础表值 + 加算区)")
|
||||
check(runner.pick_next_node(9) and runner.current_battle() != null, "选 Boss(节点 9)开战")
|
||||
var snap: Dictionary = runner.current_battle().presentation_snapshot()
|
||||
check(int(snap["hero"].get("atk", -1)) == int(_db.hero.atk) + 1 and int(snap["hero"].get("max_hp", -1)) == int(_db.hero.max_hp) + 3,
|
||||
"战斗开始参数 = 基础表值 + 加算区(atk %d = 2+1 / max_hp %d = 17+3)" % [int(snap["hero"].get("atk", -1)), int(snap["hero"].get("max_hp", -1))])
|
||||
# 冒险收束(Boss 胜或败北——基线裸装口径任一结局)→ 钱包清零
|
||||
var fin_end := {}
|
||||
runner.level_finished.connect(func(p): fin_end["p"] = p)
|
||||
var g3 := 60
|
||||
while not fin_end.has("p") and g3 > 0:
|
||||
g3 -= 1
|
||||
var b_end = runner.current_battle()
|
||||
if b_end == null:
|
||||
break
|
||||
autoplay._autoplay_battle(runner, b_end, {"result": "", "rounds": 0, "refreshes": 0, "mine_hits": 0, "hero_dmg": 0, "policy": "normal"}, null)
|
||||
check(fin_end.has("p") and String(fin_end["p"].get("result", "")) in ["victory", "defeat"],
|
||||
"冒险收束(Boss 战终局:%s)" % String(fin_end.get("p", {}).get("result", "")))
|
||||
check(runner.gp == 0, "冒险收束钱包清零(局内货币生命周期终点——不入存档不入摘要)")
|
||||
|
||||
|
||||
func _shop_products_by_id(prods: Array) -> Dictionary:
|
||||
var out := {}
|
||||
for p in prods:
|
||||
out[String(p["product_id"])] = p
|
||||
return out
|
||||
|
||||
|
||||
# ---- ④ 商店房图不变式(恰 1 商店 · 必经位 · 跨表行集迁移)----
|
||||
|
||||
func _smoke_shop_graph() -> void:
|
||||
print("--- 商店房图不变式(恰 1 商店房 · 全路径必经 · 跨表行集迁移)---")
|
||||
for L in [2, 3]:
|
||||
var row := {}
|
||||
for r in _db.level.rows:
|
||||
if int(r.level_index) == L:
|
||||
row = r
|
||||
var shops: Array = []
|
||||
var boss_id := -1
|
||||
var max_id := -1
|
||||
for n in row.nodes:
|
||||
var t := String(n.get("type", ""))
|
||||
if t == "shop":
|
||||
shops.append(int(n.get("node_depth", -1)))
|
||||
if t == "boss":
|
||||
boss_id = int(n.get("node_depth", -1))
|
||||
max_id = maxi(max_id, int(n.get("node_depth", -1)))
|
||||
check(shops.size() == 1 and int(shops[0]) == 8, "L%d 商店房恰 1 且 node_depth=8(next=[9])" % L)
|
||||
check(boss_id == 9 and boss_id == max_id, "L%d Boss = 9 = 全图最大编号(商店房批重编号 8→9)" % L)
|
||||
var paths: Array = _all_entry_boss_paths(row.nodes)
|
||||
var via_shop := true
|
||||
for p in paths:
|
||||
if not p.has(8):
|
||||
via_shop = false
|
||||
check(paths.size() >= 2 and via_shop, "L%d 全部 %d 条入口→Boss 路径穿商店房 8(必经位——DFS 枚举)" % [L, paths.size()])
|
||||
# 跨表校验:(·,9) Boss 行精确存在 · (·,8) 无战斗行残留(陈旧行清理确认)
|
||||
var bg_keys := {}
|
||||
for row in _db.board_gen.rows:
|
||||
bg_keys["%d_%d" % [int(row.level_index), int(row.node_depth)]] = true
|
||||
var en_keys := {}
|
||||
for row in _db.enemy.rows:
|
||||
en_keys["%d_%d" % [int(row.level_index), int(row.node_depth)]] = true
|
||||
for L in [2, 3]:
|
||||
check(bg_keys.has("%d_9" % L) and en_keys.has("%d_9" % L), "(%d,9) Boss 行在 board_gen + enemy 精确存在" % L)
|
||||
check(not bg_keys.has("%d_8" % L) and not en_keys.has("%d_8" % L), "(%d,8) 商店房无板/行集残留(陈旧行已清理)" % L)
|
||||
check(bg_keys.has("1_4") and en_keys.has("1_4") and not bg_keys.has("1_8") and not en_keys.has("1_8"),
|
||||
"L1 行集零改动(单链 Boss (1,4)——教学期最小修订面)")
|
||||
|
||||
|
||||
# 入口→Boss 全路径枚举(DFS 迭代栈——图合法性校验保证单入口无环,路径有限)
|
||||
func _all_entry_boss_paths(nodes: Array) -> Array:
|
||||
var by_id := {}
|
||||
var inbound := {}
|
||||
for n in nodes:
|
||||
by_id[int(n.get("node_depth", -1))] = n
|
||||
for n in nodes:
|
||||
for tid in n.get("next", []):
|
||||
inbound[int(tid)] = true
|
||||
var entry := -1
|
||||
for n in nodes:
|
||||
if not inbound.has(int(n.get("node_depth", -1))):
|
||||
entry = int(n.get("node_depth", -1))
|
||||
var paths: Array = []
|
||||
var stack: Array = [[entry]]
|
||||
while not stack.is_empty():
|
||||
var path: Array = stack.pop_back()
|
||||
var cur: Dictionary = by_id[path[path.size() - 1]]
|
||||
var outs: Array = cur.get("next", [])
|
||||
if outs.is_empty():
|
||||
paths.append(path)
|
||||
continue
|
||||
for t in outs:
|
||||
var np := path.duplicate()
|
||||
np.append(int(t))
|
||||
stack.append(np)
|
||||
return paths
|
||||
|
||||
|
||||
# ---- ⑤ 格面资源九型 + gp_drop 流(黄金向量)----
|
||||
|
||||
func _smoke_nine_types_gp_stream() -> void:
|
||||
print("--- 格面资源九型 + gp_drop 流(奖励字典 18 格 + 黄金向量)---")
|
||||
check(RewardTable.CELL_TYPES.size() == 9 and RewardTable.CELL_TYPES.has("gp"),
|
||||
"格面资源九型封闭(第九型 gp——九型批 2026-09-20,词表 v1.1)")
|
||||
var cell_rows := 0
|
||||
var pos_weight := 0
|
||||
var gp_pos := -1
|
||||
var gp_neg := -1
|
||||
for row in _db.reward.rows:
|
||||
if String(row.get("pool_tag", "")) == "cell":
|
||||
cell_rows += 1
|
||||
if String(row.get("polarity", "")) == "positive":
|
||||
pos_weight += int(row.get("weight", 0))
|
||||
if String(row.get("reward_id", "")) == "gp_coin":
|
||||
gp_pos = int(row.weight)
|
||||
if String(row.get("reward_id", "")) == "gp_lose":
|
||||
gp_neg = int(row.weight)
|
||||
check(cell_rows == 18, "奖励字典 18 格(九型 × 正负——清图槽 clear_default 独立批尾不计)")
|
||||
check(pos_weight == 76, "正向权重合计 76(gp 正向 weight 2 入池后 74→76——九型批)")
|
||||
check(gp_pos == 2 and gp_neg == 0, "gp 型两行(金币袋 gp_gain 正向 weight 2 / 破财 gp_loss 负向 weight 0 不入池)")
|
||||
var rng = _fresh_rng(42)
|
||||
check(rng.derive_at("gp_drop", 0) == 6226476397888000011, "R3 向量 gp_drop m=42 scope=0(GP+商店房实施批——registry salt_gp_drop=0xD6E8FEB8665FD7F1 权威值)")
|
||||
check(rng.derive_at("gp_drop", 1) == 5031558177015478025, "R3 向量 gp_drop m=42 scope=1(每战斗末恰一 scope 的派生复放锚)")
|
||||
check(rng.counter("gp_drop") == 0, "derive_at 纯函数不推计数器(gp_drop 流计数器恒 0)")
|
||||
|
||||
|
||||
# ---- 战斗日志视图·行格式化呈现层同步闸(2026-09-20 真人试玩卡断 bug 回归:八型批伏击退役拆除
|
||||
# 伤害行 attach 字段后本视图拆行分支漏网未同步——敌方首笔攻击行带 baseline 无 attach,
|
||||
# _format_row Invalid access 使结算段中途夭折、界面卡死。行协议变更必须同步呈现层〔战斗结算 §行协议〕,
|
||||
|
||||
@@ -66,6 +66,11 @@ func _ready() -> void:
|
||||
_route_clicks += 1
|
||||
_flags_placed.clear()
|
||||
continue
|
||||
if main.shop_overlay.visible:
|
||||
# 商店房覆盖层(GP+商店房批——L2 商店为 Boss 前必经位):零购离开(真实 leave 信号路径)放行选路
|
||||
main.shop_overlay.leave_pressed.emit()
|
||||
_flags_placed.clear()
|
||||
continue
|
||||
if main._end_overlay.is_open():
|
||||
# 终局 overlay 模态断言(pattern 6:打开时输入锁定)
|
||||
if not main._input_locked():
|
||||
|
||||
@@ -85,12 +85,12 @@ func _ready() -> void:
|
||||
slots_row.set_effects([])
|
||||
if not slots_row._slots.is_empty():
|
||||
problems.append("BUG-0001 回归:空态未清槽(%d)" % slots_row._slots.size())
|
||||
# ---- 图标键回归(HUD框架 图标键契约:状态表七行全映射、格面资源 cell_type 五值全映射;
|
||||
# ---- 图标键回归(HUD框架 图标键契约:状态表七行全映射、格面资源 cell_type 九型全映射〔gp 键 = 九型批 2026-09-20〕;
|
||||
# 查不到的键 fail-fast + ? 占位——此处钉死映射不残缺,未知键断言由 _icon_cfg/_badge_glyph 承担)----
|
||||
if not StatusSlotRow.EFFECT_ICON.has("enrage") or not StatusSlotRow.EFFECT_ICON.has("off_guard") \
|
||||
or not StatusSlotRow.EFFECT_ICON.has("sapped"):
|
||||
problems.append("状态图标表缺暴走/失衡/乏力键(states 表七行须全映射)")
|
||||
for cat in ["attack", "defense", "heal", "blast", "weaken"]:
|
||||
for cat in ["attack", "defense", "heal", "blast", "weaken", "pierce", "magic", "action", "gp"]:
|
||||
if not BoardView.REWARD_BADGE.has(cat):
|
||||
problems.append("奖励图标表缺键(%s)" % cat)
|
||||
var probe_kb: Array = [
|
||||
|
||||
Reference in new issue
Block a user