# VERTICAL SLICE - NOT FOR PRODUCTION # Validation Question: 玩家能否无指导在一场完整战斗中经历「开战→挖掘规划→轮末结算逐行兑现→胜/负」并逐行归因(P2) # Date: 2026-09-16 # 敌人系统·技能摇号器(proposal-skill-pool D9 封装纪律):独立服务类——输入 实例池可摇条目 + 随机子流, # 输出 skill_id。权重份额归一:中签率 = 该技能权重 ÷ 可摇权重合计(排除谁,谁的份额按比例摊给其余技能)。 # 不碰血量、不碰表、不碰容器——过滤与冷却状态由调用方(战斗结算换算层)注入确定值。 # 零随机纪律:随机消费只在子流参数(behavior_roll 派生流),本类无自建随机源。 # 实现 GDD:design/gdd/systems/战斗循环/敌人系统.md(registry 追溯锚,命名规范见 CLAUDE.md) class_name SkillLottery extends RefCounted # 权重摇号(整数份额,行序累计走查——同份额前行优先,确定性);entries = [{skill_id, weight}](非空) static func draw(entries: Array, stream: RandomNumberGenerator) -> String: assert(not entries.is_empty(), "[技能摇号] 空可摇集合(普攻冷却恒 0 → 池恒非空——结构违例即致命)") var total := 0 for e in entries: var w: int = int(e.get("weight", 0)) assert(w > 0, "[技能摇号] 池条目权重须 > 0: %s" % str(e)) total += w assert(total > 0, "[技能摇号] 可摇权重合计为 0") var r: int = stream.randi_range(1, total) var acc := 0 for e in entries: acc += int(e.weight) if r <= acc: return String(e.skill_id) return String(entries[entries.size() - 1].skill_id) # 数值防御分支(不可达——r ≤ total 恒有落点)