feat(m80-i10b): carry battle HP across rounds and raise enemy tempo
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
| 2026-09-15 | M79-F 区域 6 内容(数据/本地化/立绘/音乐/战场)+ 推进门到第十一份合同 + 注册表测试 R6 计修正 | `feat(m79-f)` `c728a49a`/`a4de1565` | `m79-region6` `20260915T050042Z-16542` | runtime_verified | 240 装备/45 遗物/74 敌人/12 Boss/12 合同/461 本地化;`contracts=11 region=RG-06`;15 marker 各 1 次 |
|
||||
| 2026-09-15 | M79-E 区域 5 内容(数据/本地化/立绘/音乐/战场)+ 推进门到第九份合同 + 注册表验证器修 13 敌人 | `feat(m79-e)` `aff0358b`/`6a9518e3` | `m79-region5` `20260915T023248Z-65213` | runtime_verified | 200 装备/37 遗物/61 敌人/10 Boss/10 合同/395 本地化;`contracts=9 region=RG-05`;15 marker 各 1 次 |
|
||||
| 2026-09-15 | M79-D 区域 4 美术+音乐+接线(含 R2/R3 姿态回填;risk 选项轮询去 flake) | `feat(m79-d)` `5f9723d2` + `test(m79-d)` `c7abcb97` | `m79-region4` `20260915T022134Z-55448` | runtime_verified | 28 张 R4 立绘+卷根战场+`battle_r4`/`boss_r4`;R2/R3 42 条姿态接线回填;15 marker 各 1 次 |
|
||||
| 2026-09-15 | M80-I10 (d)+(c):合同级 HP 延续与敌方行动频率 | `feat(m80-i10b)` | runtime_verified | 时长 28-35s、仍全胜;(a)/(b) 待 owner 定调 |
|
||||
| 2026-09-15 | M80-I10 第一版遭遇加强(rank HP/power 上调 + 规模对齐容量) | `tune(m80-i10a)` | runtime_verified | 时长 20-27s → 28-34s;仍全胜,缺口记为设计决策待定调 |
|
||||
| 2026-09-15 | M80-B4 切片 3:资源门/HP 带/邻位条件行启用 + 夹具双缺陷修复(recruit_id、敌人属性成长) | `feat(m80-b4c)` | runtime_verified | `ABG_M80_CONDITION_ROWS_OK enabled=14 pending=9 fired=true`;遭遇侧偏弱记为 M80-I10 |
|
||||
| 2026-09-15 | M80-B4 切片 2:§14.1 强制位移(push/pull/碰撞伤害) | `feat(m80-b4b)` | runtime_verified | `ABG_M80_MOVEMENT_OK push=swaps pull=swaps collision=formula immovable=blocks` |
|
||||
|
||||
@@ -84,7 +84,10 @@ reach/目标选择、Guard/护盾、伤害公式(含暴击与标签化随机
|
||||
(投射屏反射、标签吞噬、带位延迟、双足印旋转…)仍需 B3 后续把它实现为效果类而非仅声明 |
|
||||
| `M80-I05` | RESOLVED(2026-09-15)| 320/320 行全部声明 §17 合法钩子 + 效果类(230 行关键词分类 + 90 行逐行点名;
|
||||
233 行落在模拟已实现的效果类,其余按未实现效果类登记,绝不猜);后续把未实现效果类逐类落地即可 |
|
||||
| `M80-I10` | OPEN(数值设计决策待 owner 定调)| 第一版加强已落地(敌人规模上限=队伍人数;rank HP 150/260/520→260/420/780、power 10/16/22→13/20/26):
|
||||
| `M80-I10` | OPEN((d)+(c) 已落地,仍缺 (a)/(b))| (d) 回合间 HP 延续(有文档依据:§5"晋升保留 HP 百分比"、§9"MaxHP 变化保留 HP 百分比且不治疗")已进领域层
|
||||
(`ContractRun.apply_carry/carry_from/casualties`)+ 桥读取 + 扫描使用;(c) 敌人 tempo/initiative +4。实测:第 1/8 区时长 28-34 s、
|
||||
仍 100% 通关 → **(d)+(c) 不足以拉开难度**。剩余杠杆均为产品决策:(a) 敌方总量超容量;(b) 削弱满编职业组合的治疗/护卫/资源循环 |
|
||||
| `M80-I10a` | DONE(记录)| 第一版加强已落地(敌人规模上限=队伍人数;rank HP 150/260/520→260/420/780、power 10/16/22→13/20/26):
|
||||
时长 20-27 s → 28-34 s,仍 100% 通关、几乎无伤亡。结论:**满容量 12 职业队对同数敌人是碾压**,
|
||||
继续加数值收益递减。需要 owner 定调的是产品选择:(a) 遭遇敌方总量显著超过我方容量(可读性风险);
|
||||
(b) 削弱满编强度(职业/治疗/护卫的资源循环太强);(c) 提高敌方行动频率(tempo/initiative);
|
||||
|
||||
@@ -28,11 +28,11 @@ const PROFESSION_BASES := {
|
||||
const ENEMY_RANK := {
|
||||
# `level_offset` says how far the row sits ahead of the fastest party at its
|
||||
# region entry; attributes then slope with the encounter level.
|
||||
"ordinary": {"hp": 260, "power": 13, "guard": 8, "tempo": 26, "initiative": 10, "multiplier": 1.00,
|
||||
"ordinary": {"hp": 260, "power": 13, "guard": 8, "tempo": 30, "initiative": 14, "multiplier": 1.00,
|
||||
"level_offset": 0, "might": 5, "finesse": 5, "insight": 4, "vigor": 6, "resolve": 4},
|
||||
"elite": {"hp": 420, "power": 20, "guard": 14, "tempo": 30, "initiative": 16, "multiplier": 1.25,
|
||||
"elite": {"hp": 420, "power": 20, "guard": 14, "tempo": 34, "initiative": 20, "multiplier": 1.25,
|
||||
"level_offset": 1, "might": 7, "finesse": 7, "insight": 6, "vigor": 8, "resolve": 6},
|
||||
"boss": {"hp": 780, "power": 26, "guard": 20, "tempo": 28, "initiative": 12, "multiplier": 1.50,
|
||||
"boss": {"hp": 780, "power": 26, "guard": 20, "tempo": 32, "initiative": 16, "multiplier": 1.50,
|
||||
"level_offset": 3, "might": 9, "finesse": 9, "insight": 8, "vigor": 10, "resolve": 8},
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,42 @@ static func run_boss_with_escort(contract: Dictionary, region_index: int, allies
|
||||
return simulation.run()
|
||||
|
||||
|
||||
static func apply_carry(recruits: Array, carry: Dictionary) -> Array:
|
||||
# Returns the recruit rows with the carried current HP/guard folded in.
|
||||
var rows: Array = []
|
||||
for recruit in recruits:
|
||||
var entry: Dictionary = Dictionary(recruit).duplicate(true)
|
||||
var recruit_id := str(entry.get("recruit_id", entry.get("id", "")))
|
||||
if carry.has(recruit_id):
|
||||
var state: Dictionary = Dictionary(carry[recruit_id])
|
||||
entry["hp"] = int(state.get("hp", 0))
|
||||
entry["guard"] = int(state.get("guard", 0))
|
||||
rows.append(entry)
|
||||
return rows
|
||||
|
||||
|
||||
static func carry_from(result: Dictionary) -> Dictionary:
|
||||
# Current HP/guard of the living after a battle, keyed by recruit id.
|
||||
var carry := {}
|
||||
for ally in Array(result.get("allies", [])):
|
||||
var entry: Dictionary = Dictionary(ally)
|
||||
if bool(entry.get("fallen", false)):
|
||||
continue
|
||||
var ally_id := str(entry.get("id", ""))
|
||||
var recruit_id := ally_id.trim_prefix("ally-")
|
||||
carry[recruit_id] = {"hp": int(entry.get("hp", 0)), "guard": int(entry.get("guard", 0))}
|
||||
return carry
|
||||
|
||||
|
||||
static func casualties(result: Dictionary) -> Array[String]:
|
||||
var fallen: Array[String] = []
|
||||
for ally in Array(result.get("allies", [])):
|
||||
var entry: Dictionary = Dictionary(ally)
|
||||
if bool(entry.get("fallen", false)):
|
||||
fallen.append(str(entry.get("id", "")).trim_prefix("ally-"))
|
||||
return fallen
|
||||
|
||||
|
||||
static func advance(state: Dictionary, victory: bool) -> Dictionary:
|
||||
var next: Dictionary = state.duplicate(true)
|
||||
if victory:
|
||||
|
||||
@@ -159,6 +159,13 @@ static func _ally_row(recruit_id: String, position: int, recruit: Dictionary) ->
|
||||
"armor_guard": 2, "armor_ward": 1, "weapon_power": 3, "focus_power": 1,
|
||||
}
|
||||
row.merge(bases, false)
|
||||
# Section 9/5: HP persists between battles and a MaxHP change preserves the
|
||||
# HP percentage. A carried value therefore enters the battle as the current
|
||||
# HP rather than a full heal.
|
||||
if int(recruit.get("hp", 0)) > 0:
|
||||
row["hp"] = int(recruit["hp"])
|
||||
if int(recruit.get("guard", 0)) >= 0 and recruit.has("guard"):
|
||||
row["guard"] = int(recruit["guard"])
|
||||
var item_id := str(recruit.get("equipped_item_id", ""))
|
||||
if not item_id.is_empty():
|
||||
var support := Authoring.equipment_row(item_id, int(row.level), ContentRegistry.snapshot())
|
||||
|
||||
@@ -53,21 +53,36 @@ func _run() -> void:
|
||||
var clears := 0
|
||||
var ticks_total := 0
|
||||
var survivors_total := 0
|
||||
var carry := {}
|
||||
var deaths: Array[String] = []
|
||||
var active := recruits.duplicate(true)
|
||||
for attempt in range(attempts):
|
||||
var encounter := ContractRun.ordinary_encounter(region_index, enemy_ids, round_index, attempt, party_size)
|
||||
var ids: Array = []
|
||||
for entry in encounter:
|
||||
ids.append(str(Dictionary(entry).get("enemy_id", "")))
|
||||
var result := Bridge.run("sweep|r%d|rnd%d|a%d" % [region_index, round_index, attempt], formation, recruits, ids, region_index, _meta_at(enemy_meta, level + 2))
|
||||
# Section 9/5 carry: a cleared round keeps its current HP, so a
|
||||
# contract is a sequence of fights rather than twelve full heals.
|
||||
# A failed attempt is retired at the Market and retried at the
|
||||
# state it entered with, which keeps the retry rule simple here.
|
||||
var before := carry.duplicate(true)
|
||||
var result := Bridge.run("sweep|r%d|rnd%d|a%d" % [region_index, round_index, attempt], formation, active, ids, region_index, _meta_at(enemy_meta, level + 2))
|
||||
ticks_total += int(result.get("ticks", 0))
|
||||
survivors_total += _survivors(result)
|
||||
if str(result.get("outcome", "")) == "victory":
|
||||
clears += 1
|
||||
carry = ContractRun.carry_from(result)
|
||||
else:
|
||||
carry = before
|
||||
for fallen in ContractRun.casualties(result):
|
||||
if not deaths.has(fallen):
|
||||
deaths.append(fallen)
|
||||
var clear_rate := float(clears) / float(attempts)
|
||||
if clear_rate < 0.5:
|
||||
hard += 1
|
||||
print("%d %d %d %d %.2f %.2f %.0f %.1f %.1f" % [
|
||||
region_index, round_index, party_size, level, 1.0 - clear_rate, clear_rate,
|
||||
|
||||
float(ticks_total) / float(attempts),
|
||||
float(ticks_total) / float(attempts) / 10.0,
|
||||
float(survivors_total) / float(attempts)])
|
||||
|
||||
Reference in New Issue
Block a user