一、里程碑语义立规:M3=Alpha 完整实现/M4=Beta 完整实现,未明令关闭 M3 前只排 M3 需求(roadmap M-4 节头立规注+M-3 节头口径指针+迭代 21+ 行注+06 倍率差条注);迭代 20/21/22 规划冻结(roadmap 迭代 20 行注——先解决眼前问题,重启=用户直接发令) 二、文档正确性审查批三波:全 GDD 29 份过审——状态记录按代码证据订正/里程碑删前缀(M-x+迭代N→迭代N,裸 M-4→Beta 阶段(未排期))/退役词退役件当现役清零/在役旧实现节加隔离横幅;重件=秘宝.md v1.7 老正文深层清算(横幅重写消「过渡形态已退役」误读/沿革节换代/六件旧件登记摘除/验收与 charges 矛盾订正)+奇物 v3.8+冒险结构 v0.18+关卡节点地图 v1.7+图生成 v0.7+难度曲线与Boss v0.26+战斗结算 v0.27+敌人系统 v0.16+地图战斗资源 v0.21+增益状态 v0.13+战斗表现 v0.6+元进度 v1.9+生涯 v0.3+00 v1.3+02 v1.16+03 v1.1+ux 八份(battle 23 处/camp/shop/node-reward/level-clear/interaction-patterns/settlement-summary/level-map) 三、去毒批:奇物 v3.7(魔法道具→秘宝/档名三档→四级/门票与刷新「尚未入表」假话订正/验收第 3 条改令牌口径/资源成员指路凭证件表)+秘宝 v1.6(速览旧目录退役标注/退役件示例换现役) 四、道具退役素材库新建(design/research/道具退役素材库.md,案乙已追认):秘宝退役 9 件含去向+专长退役 27 件名单+待核 4 行;魔法提灯(fog_lantern)裁退役(06 条目已裁+秘宝.md 注记换代+素材库迁表一;拆线归实装批) 五、B2/B3/B4 裁决落文:元进度.md 道具工坊行删旧写新(v1.10——旧六行登记作废、指路秘宝新目录+解锁门槛谓词分级);奇物.md P1「两项合成高阶项」注销(v3.9——用户定性脏信息);§3 刷新句按最新口径换代(刷新券替代、不进持有集) 六、天赋树独立成文(B1 裁决):新建 design/gdd/systems/跨局循环/天赋树.md v0.1(33 节点机制全按 meta_tree.tres 实态+迭代 12 落籍裁决+被删底稿 git 取证——脊柱行门槛/钥石单币/Fork A/B 面/存档 v5 域/注入双通道)+元进度.md 定名节换代(v1.11)+02-系统清单跨局循环区加行(v1.17) 七、A 类卫生包:版本历史表理排四份(秘宝/奇物含 v2.7 断裂行修复/敌人系统/图生成,只调行序)+缺号注记三处(增益状态 v0.6 未考/冒险结构 v0.15/关卡节点地图 v1.6=MC-1 批)+node-reward v2.2 双号改 a/b+07 补建版本历史表+06 删「设置与本地化」空节+「文档/结构闸存量欠账」块挪文末「文档与结构」节+「L1 商店编排」两悬空指针改指 GP 换算失衡条+难度曲线两处过期指向核销注记(v0.27)+B5「现役 UI/文案两处自相冲突」登记 06 待办(battle_narrator.gd:160 旧话/hero_info_panel 两段同标「特性」——随后续迭代修) 八、命名统一(用户令「任务书」全库改「需求卡片」):167 文件约 800 处(design/ 全部文档+roadmap+CLAUDE.md+active.md+entities.yaml+game/ 注释与数据批注);两文件改名(需求卡片-M4数值批/需求卡片-冒险前资源配比预设批);残留=自造短语全量清单(黑话证据文档故意跳过) 九、CLAUDE.md:批次工作流标准清单收口段新增第 12 条「文档同步闸」(需求卡片必列关联文档同步面、合并前同步完毕方算收口,原 12/13 顺延);呈报六问铁规适用范围补立(前序批随批落籍) 十、active.md 快照节+工作日志 414-421(并行会话 416 黑话清扫条目按共享工作日志惯例一并入库);06 中并行会话「active.md 定性与裁剪」条目未提交,按零吞并纪律留在工作区未入库 零测试(文档批两入口铁规);主区前序脏件(.gitignore/.zcode/config.json/playnote/project.godot/.zcodeignore/美术审计报告 html 等)未带
119 lines
7.7 KiB
GDScript
119 lines
7.7 KiB
GDScript
# 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 递增)。
|
||
# pool_shelf = 共享池货架行(M-4 迭代 11 第②批 T1——需求卡片 v1.3 P1 共享池/P4 ceil):键 kind_id 词表
|
||
# 封闭 = 四切边后种(SHELF_KINDS:attack/max_hp/armor/ap——GP 袋负交易切边、规则改写型特性三轴 doctrine 切边);
|
||
# 库存=上限即库存(账本在 冒险结构 _curio_taken 单点共享,行族不记库存);price_a/price_b = 乘法价格
|
||
# 因子(公式 ceil(base × b^(关序−1) × A^(本次进店已购次数)),provisional 投值——P5 甲研究拍板后置追认)。
|
||
# products 行族 + 加法定价随 legacy 模式(shop=off 对照承载)保留,退役归 off 锚换代批。
|
||
# 行值全部 provisional 归 难度曲线与Boss(用户拍板骨架先行、数值整体后置)。
|
||
class_name ShopTable
|
||
extends Resource
|
||
|
||
@export var gp_drops: Array[Dictionary] = [] # Array[Dictionary] 键 tier
|
||
@export var level_gp_bonus: Array[Dictionary] = [] # Array[Dictionary] 键 level_index
|
||
@export var products: Array[Dictionary] = [] # Array[Dictionary] 键 product_id
|
||
@export var price_level_bonus: Array[Dictionary] = [] # Array[Dictionary] 键 level_index
|
||
@export var pool_shelf: Array[Dictionary] = [] # Array[Dictionary] 键 kind_id(迭代 11 第②批 T1)
|
||
@export var price_a: float = 0.0 # 进店已购次数爬价因子(provisional——0 = 未投值即致命域)
|
||
@export var price_b: float = 0.0 # 跨关递增因子(provisional)
|
||
|
||
const GP_TIERS := ["normal", "elite", "boss"]
|
||
const PRODUCT_ATTRS := ["atk", "max_hp", "armor_class"]
|
||
const SHELF_KINDS := ["attack", "max_hp", "armor", "ap", "feat_ticket", "heal"] # 货架候选词表(四切边后种 + T5 门票行 + T6 heal 补回复〔默认原则:分类内成员都入该渠道池〕——奇物批迭代15②)
|
||
|
||
|
||
func validate() -> Array:
|
||
var errs: Array[Dictionary] = []
|
||
# 行族空护栏(legacy 四轴 + pool_shelf 皆为 ConfigDB 消费面——缺族即致命)
|
||
for family in ["gp_drops", "level_gp_bonus", "products", "price_level_bonus", "pool_shelf"]:
|
||
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]})
|
||
# pool_shelf:kind_id 词表封闭(四切边后种)+ 唯一 + display_name + base_price 域 [1,999](迭代 11 第②批 T1)
|
||
var seen_kind := {}
|
||
for row in pool_shelf:
|
||
var kind: String = row.get("kind_id", "")
|
||
if not SHELF_KINDS.has(kind):
|
||
errs.append({"category": "enum", "message": "pool_shelf kind_id 出域(四切边后种封闭词表): %s" % kind})
|
||
if seen_kind.has(kind):
|
||
errs.append({"category": "dup_key", "message": "pool_shelf kind_id 重复: %s" % kind})
|
||
seen_kind[kind] = true
|
||
if String(row.get("display_name", "")) == "":
|
||
errs.append({"category": "domain", "message": "pool_shelf %s display_name 空" % kind})
|
||
var base: int = int(row.get("base_price", 0))
|
||
if base < 1 or base > 999:
|
||
errs.append({"category": "domain", "message": "pool_shelf %s base_price 越域 [1,999]: %d" % [kind, base]})
|
||
# 价格因子域(乘法语义——A/b 均 ≥1 单调不降;上界防手滑投值;provisional 值本体归难度曲线与Boss)
|
||
if price_a < 1.0 or price_a > 3.0:
|
||
errs.append({"category": "domain", "message": "price_a 越域 [1.0,3.0]: %f" % price_a})
|
||
if price_b < 1.0 or price_b > 2.0:
|
||
errs.append({"category": "domain", "message": "price_b 越域 [1.0,2.0]: %f" % price_b})
|
||
return errs
|