Skip to content

Commit 8c93d1f

Browse files
pdbethkeclaude
andcommitted
fix(modifiers): a template option's cost overwrites a stated BASECOST, as HD's restore does
GenericObject.java:3665 reads the stated BASECOST, then the OPTIONID restore (:3747) ends in setSelectedOption() -> setBaseCost(option.getBaseCost()) -- unconditional, mutation-proved (a lying AOE BASECOST=9.75 moved HD's answer not at all). The engine's guard (opt.base_cost != 0 and orig_base_cost == 0) let the stated value win for modifiers. Now unconditional for Modifier instances; powers keep the guarded rule the corpus fought for. Cost parity 695/695 (incl. the authored characters); sink cost ledger 23 -> 10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PrVDXgpDfQpzEGErZTLjEs
1 parent 14b79fc commit 8c93d1f

2 files changed

Lines changed: 18 additions & 21 deletions

File tree

kirby_cost/objects/base.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,17 @@ def apply_template(self, tmpl: "TemplateData", option_id: str = None) -> None:
444444
self.level_power = opt.level_power
445445
self.level_multiplier = opt.level_multiplier
446446
option_set_lm = True
447-
if opt.base_cost != 0 and self.orig_base_cost == 0:
447+
# HD assigns the option's cost UNCONDITIONALLY for modifiers:
448+
# restoreFromSave reads the stated BASECOST (GenericObject.java
449+
# :3665) and then the OPTIONID restore (:3747) ends in
450+
# setSelectedOption() -> setBaseCost(option.getBaseCost()) --
451+
# mutation-proved 2026-08-30 (a lying AOE BASECOST=9.75 moved
452+
# HD's answer not at all). Powers keep the guarded rule the
453+
# corpus fought for (32 characters broke without it).
454+
from kirby_cost.objects.modifier import Modifier as _Modifier
455+
if isinstance(self, _Modifier):
456+
self._base_cost = opt.base_cost
457+
elif opt.base_cost != 0 and self.orig_base_cost == 0:
448458
self._base_cost = opt.base_cost
449459

450460
# Record the option as an object, not just its numbers. Java
Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,16 @@
11
{
22
"_comment": "Shrink-only ledger for tests/test_validation_sink_costs.py. Regenerate the fixture: venv/bin/python -c 'from tests.validation_sink import write; write(\"/tmp/ValidationSink.hdc\")' ; (cd ../kirby-hd-oracle && ./hd6cli.sh /tmp/ValidationSink.hdc) > tests/fixtures/authored/ValidationSink.json. Seeded 2026-08-30: the corpus is HD-written and cannot see these; the hand-written sink can.",
3-
"_baseline": "23 sink cost values disagree with HD (2026-08-30)",
3+
"_baseline": "23 sink cost values disagree with HD (2026-08-30); option-cost fix 2026-08-30: 13 closed, now 10 remain",
44
"gaps": {
5-
"20260830000009:real_cost": "modifier value: option overwrites stated BASECOST in HD; EIGHT is -1/2 -- HD 7.0 engine 8 (FLIGHT 'Flight Charges')",
6-
"20260830000027:active_cost": "modifier value: engine computes AOE at base+levels progression where HD's AreaEffect computes the radius value (+1/4 at 4m, +1/2 at 8m); stated BASECOST is ignored by HD (mutation-proved) -- HD 80.0 engine 100 (ENERGYBLAST 'Blast AOE')",
7-
"20260830000027:real_cost": "modifier value: engine computes AOE at base+levels progression where HD's AreaEffect computes the radius value (+1/4 at 4m, +1/2 at 8m); stated BASECOST is ignored by HD (mutation-proved) -- HD 80.0 engine 100.0 (ENERGYBLAST 'Blast AOE')",
8-
"20260830000043:active_cost": "framework: slot/pool cost rule differs on this hand-written shape (HD zeroes or redistributes where the engine costs the slot standalone); needs its own read of List/Multipower/VPP cost paths -- HD 2.0 engine 3 (GENERIC_OBJECT 'Multipower')",
95
"20260830000055:real_cost": "framework: slot/pool cost rule differs on this hand-written shape (HD zeroes or redistributes where the engine costs the slot standalone); needs its own read of List/Multipower/VPP cost paths -- HD 27.0 engine 40.0 (ENERGYBLAST 'VPP Blast Slot')",
10-
"20260830000057:active_cost": "framework: slot/pool cost rule differs on this hand-written shape (HD zeroes or redistributes where the engine costs the slot standalone); needs its own read of List/Multipower/VPP cost paths -- HD 11.0 engine 16 (FORCEFIELD 'VPP Force Field Slot')",
11-
"20260830000057:real_cost": "framework: slot/pool cost rule differs on this hand-written shape (HD zeroes or redistributes where the engine costs the slot standalone); needs its own read of List/Multipower/VPP cost paths -- HD 7.0 engine 16.0 (FORCEFIELD 'VPP Force Field Slot')",
12-
"20260830000060:active_cost": "modifier value: engine computes AOE at base+levels progression where HD's AreaEffect computes the radius value (+1/4 at 4m, +1/2 at 8m); stated BASECOST is ignored by HD (mutation-proved) -- HD 15.0 engine 20 (NAKEDMODIFIER 'Naked Advantage')",
13-
"20260830000060:real_cost": "modifier value: engine computes AOE at base+levels progression where HD's AreaEffect computes the radius value (+1/4 at 4m, +1/2 at 8m); stated BASECOST is ignored by HD (mutation-proved) -- HD 15.0 engine 20 (NAKEDMODIFIER 'Naked Advantage')",
6+
"20260830000057:real_cost": "framework: slot/pool cost rule differs on this hand-written shape (HD zeroes or redistributes where the engine costs the slot standalone); needs its own read of List/Multipower/VPP cost paths -- HD 7.0 engine 11.0 (FORCEFIELD 'VPP Force Field Slot')",
7+
"20260830000060:active_cost": "carrier math: Absorption's identical AOE closed with the option-cost fix, so the residual is the CARRIER's own cost rule on this shape (Images' +4m pricing / NakedModifier's base), not the AOE value -- HD 15.0 engine 10 (NAKEDMODIFIER 'Naked Advantage')",
8+
"20260830000060:real_cost": "carrier math: Absorption's identical AOE closed with the option-cost fix, so the residual is the CARRIER's own cost rule on this shape (Images' +4m pricing / NakedModifier's base), not the AOE value -- HD 15.0 engine 10 (NAKEDMODIFIER 'Naked Advantage')",
149
"20260830000061:real_cost": "CustomPower real-cost rounding on a hand-written shape -- HD 0.0 engine 1.0 (CUSTOMPOWER 'Custom Power With END')",
15-
"20260830000064:active_cost": "modifier value: option overwrites stated BASECOST in HD (setSelectedOption -> setBaseCost(option)); NONMENTALOMCV is +0 -- HD 40.0 engine 50 (ENERGYBLAST 'Blast Alternate Combat Value')",
16-
"20260830000064:real_cost": "modifier value: option overwrites stated BASECOST in HD (setSelectedOption -> setBaseCost(option)); NONMENTALOMCV is +0 -- HD 40.0 engine 50.0 (ENERGYBLAST 'Blast Alternate Combat Value')",
17-
"20260830000066:active_cost": "modifier value: option overwrites stated BASECOST in HD (setSelectedOption -> setBaseCost(option)); engine lets _base_cost_from_xml win -- HD 50.0 engine 60 (ENERGYBLAST 'Blast Autofire')",
18-
"20260830000066:real_cost": "modifier value: option overwrites stated BASECOST in HD (setSelectedOption -> setBaseCost(option)); engine lets _base_cost_from_xml win -- HD 50.0 engine 60.0 (ENERGYBLAST 'Blast Autofire')",
1910
"20260830000072:active_cost": "modifier value: HD's DoT prototype carries template adders (damage increments, frequency) the sink never stated; engine sums neither -- HD 170.0 engine 80 (ENERGYBLAST 'Blast Damage Over Time')",
2011
"20260830000072:real_cost": "modifier value: HD's DoT prototype carries template adders (damage increments, frequency) the sink never stated; engine sums neither -- HD 170.0 engine 80.0 (ENERGYBLAST 'Blast Damage Over Time')",
21-
"20260830000077:active_cost": "modifier value: option overwrites stated BASECOST in HD; DELAYEDRETURNRATE HOUR is +1 3/4 -- HD 55.0 engine 25 (DRAIN 'Slow Drain')",
22-
"20260830000077:real_cost": "modifier value: option overwrites stated BASECOST in HD; DELAYEDRETURNRATE HOUR is +1 3/4 -- HD 37.0 engine 17 (DRAIN 'Slow Drain')",
23-
"20260830000122:active_cost": "modifier value: engine computes AOE at base+levels progression where HD's AreaEffect computes the radius value (+1/4 at 4m, +1/2 at 8m); stated BASECOST is ignored by HD (mutation-proved) -- HD 12.0 engine 28 (IMAGES 'Images')",
24-
"20260830000122:real_cost": "modifier value: engine computes AOE at base+levels progression where HD's AreaEffect computes the radius value (+1/4 at 4m, +1/2 at 8m); stated BASECOST is ignored by HD (mutation-proved) -- HD 12.0 engine 28.0 (IMAGES 'Images')",
25-
"20260830000122:total_cost": "modifier value: engine computes AOE at base+levels progression where HD's AreaEffect computes the radius value (+1/4 at 4m, +1/2 at 8m); stated BASECOST is ignored by HD (mutation-proved) -- HD 10.0 engine 16.0 (IMAGES 'Images')",
26-
"20260830000124:active_cost": "modifier value: engine computes AOE at base+levels progression where HD's AreaEffect computes the radius value (+1/4 at 4m, +1/2 at 8m); stated BASECOST is ignored by HD (mutation-proved) -- HD 4.0 engine 5 (ABSORPTION 'Absorption')",
27-
"20260830000124:real_cost": "modifier value: engine computes AOE at base+levels progression where HD's AreaEffect computes the radius value (+1/4 at 4m, +1/2 at 8m); stated BASECOST is ignored by HD (mutation-proved) -- HD 4.0 engine 5.0 (ABSORPTION 'Absorption')"
12+
"20260830000122:active_cost": "carrier math: Absorption's identical AOE closed with the option-cost fix, so the residual is the CARRIER's own cost rule on this shape (Images' +4m pricing / NakedModifier's base), not the AOE value -- HD 12.0 engine 20 (IMAGES 'Images')",
13+
"20260830000122:real_cost": "carrier math: Absorption's identical AOE closed with the option-cost fix, so the residual is the CARRIER's own cost rule on this shape (Images' +4m pricing / NakedModifier's base), not the AOE value -- HD 12.0 engine 20.0 (IMAGES 'Images')",
14+
"20260830000122:total_cost": "carrier math: Absorption's identical AOE closed with the option-cost fix, so the residual is the CARRIER's own cost rule on this shape (Images' +4m pricing / NakedModifier's base), not the AOE value -- HD 10.0 engine 16.0 (IMAGES 'Images')"
2815
}
2916
}

0 commit comments

Comments
 (0)