全局作用域变异首发批 T1-T4 核心面(迭代14③号卡):variation_table 词表扩 resource_face/always+互锁三条(resource_face⇔global、global⇒any+always)+effect_value 域[0,50](不适用 flat_damage[1,9]);variation.tres +skewed_veins 贫矿脉行(scope=global/unlock_level=2/board_types∅通配/投值30 provisional);config_db variant_pool 扩节点身份第三参(global行仅boss/elite入池,缺省双参跳过零扰动)+global_variant_row 决议访问器;board_data +board_variants 板级承载;battle_orchestrator 装板序 _resolve_global_variant 前置于 roll_board(数据依赖:偏斜旋钮先于摇号定型;全局取用确定性零取号)+_place_variants 替换制早退(boss图恰一行板级变异+零region格;池空回落region=兜底;普通图路径零diff);reward_economy +board_skew 旋钮+_skewed_cell_rows 整数最大余数投影(生存族按v%下调·进攻族等比上浮·白名单豁免·合计守恒77·off态直通零扰动)+_biased_cell_rows 基座换源=合成序图级先件级后

This commit is contained in:
DKChan committed 2026-10-04 17:52:01 +08:00
1 parent 9f64456fcd
commit fcb95f2d5a
6 files changed
+182 -5

No files matched your search

+29
View File
@@ -285,6 +285,11 @@ func _install_new_board(during_round := false) -> bool:
var board: BoardData = _board_generator.generate(int(encounter.level_index), int(encounter.node_depth), stream, db)
if board == null:
return false
# 全局作用域变异决议(首发批 2026-10-04——装板序内前置于 roll_board:图级偏斜旋钮须在资源
# 摇号前定型;全局门控与板无关〔节点身份+unlock+board_types〕,确定性取用零取号)。
# boss/elite 图命中全局型 → 写 board_variants(_place_variants 见非空即跳 region=替换制);
# 全局池空 → 板级字段留空(_place_variants 原路走 region=兜底回落合法态)。
_resolve_global_variant(board)
var res: Dictionary = minesweeper.install_board(board, during_round)
if not res.accepted:
_fatal("排雷玩法 install 拒绝(不变式违例): %s" % str(res))
@@ -318,6 +323,22 @@ func _install_new_board(during_round := false) -> bool:
return true
# ---- 全局作用域变异 决议(首发批 2026-10-04——装板序内 roll_board 之前)----
# boss/elite 图命中全局型:写板级 board_variants(恰一行)+ 注入资源面偏斜旋钮(图级投影在
# RewardEconomy.roll_board 消费);不消费 variant_place 流(全局取用确定性——零取号合法态)。
# 全局池空(含 L1 Boss 免疫/unlock 门控未达):字段留空、旋钮归零——_place_variants 原路走
# region 布置 = 兜底回落(保守向后兼容,防配置缺行静默裸奔——provisional)。
func _resolve_global_variant(board: BoardData) -> void:
board.board_variants.clear()
_reward_economy.board_skew = 0
var row: Dictionary = db.global_variant_row(int(encounter.level_index), String(board.board_type),
String(encounter.get("node_type", "")))
if row.is_empty():
return
board.board_variants = [String(row.variant_id)]
_reward_economy.board_skew = int(row.get("effect_value", 0))
# ---- 地形变异 布置段(首批类型定稿批 2026-09-19——棋盘设置顺序第 4 步)----
# 候选池门控(unlock_level ≤ 关卡序号 + board_types 与本地形交集,∅ 通配)经 config_db.variant_pool;
# 池空 = 合法态,静默零布置(框架 P0 口径不变)。零特判:本段只写格子变异数据快照,不改扫雷规则本体。
@@ -327,7 +348,11 @@ func _install_new_board(during_round := false) -> bool:
# (关卡序号−1)×per_level_bonus——基数为候选池而非总格数(2026-09-21 用户拍板基数修正批:初始展开
# 占全板 56–67%,总格数基数使有效密度膨胀 4–6 倍、L1 小板极端全池覆盖;percent 语义 = 占可挖格比例,
# 关卡递增由 per_level_bonus 绝对格数承载);连通片生长(8 邻),单片 ≤ pack_max(2×2 / 1×4 / 1×3+1 等形)。
# 替换制(全局作用域变异首发批 2026-10-04):板级 board_variants 非空 = 本图已带全局型,跳过 region
# 布置(每图一型的板级推广:普通图=region,Boss/精英图=global);空 = 原路径(含 Boss 图全局池空回落)。
func _place_variants(board: BoardData) -> void:
if not board.board_variants.is_empty():
return
var pool: Array = db.variant_pool(int(encounter.level_index), String(board.board_type))
if pool.is_empty():
return
@@ -1155,6 +1180,10 @@ func presentation_snapshot() -> Dictionary:
"hero": hero,
"enemies": enemies,
"hero_effects": _status_effects.query_active("hero").duplicate(true), # 值拷贝出户(code-review 2026-09-29 P2-3——快照消费方直改不渗入状态容器)
# 板级变异投影(全局作用域变异首发批 2026-10-04——呈现横幅/常驻角标唯一读取面;
# 空数组 = 无全局型合法态:普通图与全局池空回落图恒空)
"board_variants": (minesweeper.current_board().board_variant_ids().duplicate()
if minesweeper.current_board() != null else ([] as Array[String])),
}
+7
View File
@@ -16,6 +16,8 @@ var spawn_center := Vector2i.ZERO
var mines := {} # Vector2i → true
var digits := {} # Vector2i → int(安全格恒有值;数字恒真 = 8 邻雷数)
var variants := {} # Vector2i → Array[String](生成期快照,抗表重载)
var board_variants: Array[String] = [] # 板级变异 id 集(scope=global 整图作用域——全局作用域变异批 2026-10-04:
# 战斗编排装板决议写入,随图生灭同 variants;普通图恒空)
var board_type := "" # 图类型标签(词表 v1.0:dungeon/underdark;玩家侧显示名「地图类型」)
var level_index: int = 1
var node_depth: int = 0
@@ -50,6 +52,11 @@ func variant_ids(c: Vector2i) -> Array:
return variants.get(c, [])
# 板级变异只读出口(scope=global 行集——呈现快照/断言消费;空 = 无全局型合法态)
func board_variant_ids() -> Array[String]:
return board_variants
# 中和格变异(迭代 12 T6 净化工兵——伤害型地形格变普通格;🔴 R4 甲仅限伤害型的过滤在调用方)
func clear_variants(c: Vector2i) -> void:
variants.erase(c)
+19 -1
View File
@@ -197,10 +197,16 @@ func resolve_difficulty_modifiers(L: int, D: int) -> Dictionary:
# ---- 地形变异 variant_pool_gate:候选池门控(∅ board_types = 通配;2026-09-19 用户裁定字段名对齐
# board_type 词表——biome_tags 系旧观念残留退役)----
# 门控第三维 = 节点身份(全局作用域变异首发批 2026-10-04):scope=global 行仅 boss/elite 节点入池,
# 其余节点(battle 等)静默跳过——门控兜底 P0 口径(零报错零警告);elite 系前向词位(首发零表行)。
# node_type 缺省 "" = 旧双参调用口径(global 行跳过)——既有测试/冒烟调用零扰动。
func variant_pool(level_index: int, board_type: String) -> Array:
func variant_pool(level_index: int, board_type: String, node_type: String = "") -> Array:
var pool: Array[Dictionary] = []
for row in variation.rows:
if String(row.get("scope", "region")) == "global" \
and node_type != "boss" and node_type != "elite":
continue
if int(row.get("unlock_level", 999)) > level_index:
continue
var tags: Array = row.get("board_types", [])
@@ -212,6 +218,18 @@ func variant_pool(level_index: int, board_type: String) -> Array:
return pool
# 全局型单行决议(战斗编排装板期消费——访问器纪律:消费面禁直扫表):
# boss/elite 节点取候选池内首个 scope=global 行(首发批全局池恒恰一行;多行为配置越界态,
# 按表序取首行确定性兜底);空 = 无全局型(调用方回落 region 布置——兜底回落合法态)
func global_variant_row(level_index: int, board_type: String, node_type: String) -> Dictionary:
if node_type != "boss" and node_type != "elite":
return {}
for row in variant_pool(level_index, board_type, node_type):
if String(row.get("scope", "region")) == "global":
return row
return {}
func status_effect_row(effect_id: String) -> Dictionary:
return status_effects.row_by_id(effect_id)
+89 -2
View File
@@ -179,13 +179,25 @@ var luck_bias: int = 0
# 稀有正向资源 kinds(底稿 §3——权重投影面,池结构零触碰)
const LUCK_BIAS_KINDS := ["heal", "gp", "pierce", "magic"]
# 图级偏斜旋钮(全局作用域变异首发批 2026-10-04——battle_orchestrator 装板决议注入,全局型行
# effect_value 语义=生存族权重下调百分比;0 = 中性 off 态。每板决议时显式重置/写入)
var board_skew: int = 0
# 偏斜族(任务书 §二:进攻族等比上浮/生存族按 v% 下调/白名单豁免恒不动——判定键=cell_type;
# 其余 cell_type(weaken/gp/action 白名单与未来扩型)= 豁免;负向行不入摇号已在 _rollable_rows 滤除)
const SKEW_DOWN_KINDS := ["heal", "defense"]
const SKEW_UP_KINDS := ["attack", "blast", "pierce", "magic"]
# 偏运硬币权重投影(底稿 §3:只动池内权重、雷安仍同池同分布;luck_bias=0 逐位返回原行集——off 态零扰动)
# 基座换源(首发批 2026-10-04):先经图级偏斜投影再叠件级加法——合成序锚=图级先、件级后
#(环境事实先定型、玩家构筑叠其上;两投影均为池内权重面,雷安同池与取号次数零变化)
func _biased_cell_rows() -> Array:
var base: Array = _skewed_cell_rows()
if luck_bias <= 0:
return _rollable_rows()
return base
var out: Array = []
for row in _rollable_rows():
for row in base:
if LUCK_BIAS_KINDS.has(String(row.get("cell_type", ""))):
var biased: Dictionary = row.duplicate()
biased["weight"] = int(row.weight) + luck_bias
@@ -195,6 +207,81 @@ func _biased_cell_rows() -> Array:
return out
# 图级资源面偏斜投影(首发批 T3——整数最大余数法:生存族按 board_skew% 下调,下调量按进攻族
# 原权重比例整数重分配,合计恒等于原池合计 77;负向行与白名单三行逐位不动)。
# board_skew<=0 逐位返回原行集 = off 态零扰动第一断言:不进投影域、行集/行序/取号域与基线逐位同。
func _skewed_cell_rows() -> Array:
if board_skew <= 0:
return _rollable_rows()
var base: Array = _rollable_rows()
var down: Array = []
var up: Array = []
for row in base:
var k := String(row.get("cell_type", ""))
if SKEW_DOWN_KINDS.has(k):
down.append(row)
elif SKEW_UP_KINDS.has(k):
up.append(row)
if down.is_empty() or up.is_empty():
return base # 结构缺族 = 无偏斜作用面(保守直通——池结构变更须走表加载校验另批)
var v := board_skew
# 下调总量 = round_half_up(Σ生存×v/100);逐行精确份额 w×v/100 最大余数取整(行序平局)
var drops := _largest_remainder_split(down, v, 100, (2 * _pool_weight(down) * v + 100) / 200)
# 上浮总量 = 下调总量(守恒),逐行精确份额 drop×w/Σ进攻 最大余数取整
var gains := _largest_remainder_split(up, drops_total(drops), _pool_weight(up), drops_total(drops))
var di := 0
var ui := 0
var out: Array = []
for row in base:
var k := String(row.get("cell_type", ""))
var w := int(row.weight)
if SKEW_DOWN_KINDS.has(k):
w -= drops[di]
di += 1
elif SKEW_UP_KINDS.has(k):
w += gains[ui]
ui += 1
assert(w > 0, "图级偏斜投影产生非正权重(池结构变更须走另批重审)")
var skewed: Dictionary = row.duplicate()
skewed["weight"] = w
out.append(skewed)
assert(_pool_weight(out) == _pool_weight(base), "图级偏斜守恒破(偏斜后合计须等于原池)")
return out
func drops_total(amounts: Array[int]) -> int:
var t := 0
for a in amounts:
t += a
return t
# 最大余数整数分摊(行序平局——确定性):把 total 单位按精确份额 w_i×numer/denom 分到 rows
# (floor 后余数单位依次给分数最大者,平局取行序在前)——Σ结果恒 = total
static func _largest_remainder_split(rows: Array, numer: int, denom: int, total: int) -> Array[int]:
var floors: Array[int] = []
var fracs: Array[int] = []
var used := 0
for row in rows:
var exact: int = int(row.weight) * numer
floors.append(int(exact / denom))
fracs.append(exact % denom)
used += int(exact / denom)
var order: Array[int] = []
for i in rows.size():
order.append(i)
order.sort_custom(func(a: int, b: int) -> bool:
if fracs[a] != fracs[b]:
return fracs[a] > fracs[b]
return a < b)
var out: Array[int] = floors
var rest: int = total - used
assert(rest >= 0 and rest <= order.size(), "最大余数分摊余数越界(不应可达)")
for j in rest:
out[order[j]] += 1
return out
func weighted_pick(rows: Array, r: int) -> String:
var total := _pool_weight(rows)
assert(r >= 0 and r < total, "weighted_pick 取号越域(纯函数域守卫)")