Fused rounds fire out of arc; safe fuse; proximity misses type as Burst

Operator ruling 2026-09-30: a fused weapon with a selected but out-of-arc
target fires. Its round flies along the arc-clamped aim and bursts at the
target's range. Non-fused weapons still refuse.

- FireControl.ArcPermitsFire is the one trigger arc gate (fused weapons are
  exempt); Fire reads the same InArc through PFire's new designated/inArc
  outs, which replace `bears`. No valid targeting data still bursts at Range.
- F3: a fused weapon whose effective Range does not exceed its BlastRadius
  stays safed at the trigger (FuseCanArm, read from the runtime Range). The
  catalog check on Range.Min is deleted; it read the authored floor, which
  Evaluate can scale below.
- F4: a targeted proximity burst is pulled back along its line to Range.
- F8: a proximity round that detonates commits as Burst (or Hit); Apply's
  Proximity case collapses into the Burst path via Outcome.BurstPoint.
- Tests: arc-only and LockWeapon gates, out-of-arc burst, trigger gate,
  runtime-Range refusal, Range clamp, Burst typing, commit horizon, and
  Soul's adopted probes (aim clamp edges, contact point, target leaves,
  shooter destroyed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-29 16:27:46 +02:00
commit 640fa96787
8 changed files with 609 additions and 145 deletions

View file

@ -752,21 +752,6 @@ public static class StatValidation
"not exceed them");
}
// Operator ruling 2026-09-30 (Soul): a round with a blast that could burst on its own shooter is refused at
// the catalog rather than policed at runtime. A weapon that detonates (a fuse and a blast radius above zero,
// the two FireControl.Fire freezes together) must reach farther than its blast, at the weakest value its Range
// stat can take: a no-lock round bursts at Range, so Range <= BlastRadius would put the shooter inside the
// burst, and Range <= 0 is a burst on the muzzle. A fuse without a radius is inert and needs no range.
public static void ValidateFuse(EquippableItemData data)
{
if (!(data is WeaponItemData weapon) || weapon.Fuse == null || !(weapon.BlastRadius > 0f)) return;
foreach (var behavior in weapon.Behaviors)
if (behavior is WeaponData weaponData && weaponData.Range.Min <= weapon.BlastRadius.Value)
throw new InvalidOperationException(
$"{weapon.Name}: fused weapon's Range {weaponData.Range.Min} does not exceed its BlastRadius " +
$"{weapon.BlastRadius.Value} -- a burst at max range would land on the shooter");
}
// Cut 2 (docs/stats-and-power-cut.md): a StatReference names its target by (type name, field name) so a
// modifier can point at any design or behaviour without a hard type reference -- but before this cut an
// unresolvable reference failed silently: StatModifier.Initialize left `_stats` null and the first