2026-08-10 14:57:50 +01:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using UniRx;
|
2026-09-14 17:39:12 +02:00
|
|
|
|
using CultMath;
|
Convert engine types through CultMath.UnityBridge
CultMath has no implicit conversions to or from Vector*, Quaternion or
Color, so Assembly-CSharp converts explicitly with ToCultMath/ToUnity at
each engine boundary: FieldDriver, GuidedProjectile(Manager),
EntityInstance, ActionGameManager, HullCollider, GridObject, ShipInstance,
ShieldManager, ItemPickup, FieldTester, PlaceUIElementWorldspace,
MapMenuInput, MapRenderer, SectorRenderer and ZoneRenderer. CultMath math
calls that were handed Unity vectors now take converted values;
Vector3.Scale in FieldDriver becomes a float3 product, and ItemPickup's
Vector3.Dot on float3s becomes dot. packages-lock.json records the
cultmath git package at f0605367 and demotes com.unity.mathematics to a
transitive dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:47:33 +02:00
|
|
|
|
using CultMath.UnityBridge;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
using UnityEngine;
|
2026-09-14 17:39:12 +02:00
|
|
|
|
using static CultMath.math;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
public class GuidedProjectileManager : InstantWeaponEffectManager
|
|
|
|
|
|
{
|
2026-09-29 17:05:15 +02:00
|
|
|
|
// Presentation follows the committed shot (FireControl): the round retargets to the burst point the commit
|
|
|
|
|
|
// names and bursts when the shot resolves, rather than predicting either from its own homing.
|
|
|
|
|
|
private static void Bind(GuidedProjectile p, Zone zone, int shotId)
|
|
|
|
|
|
{
|
|
|
|
|
|
Vector3 At(ShotOutcome o) => new Vector3(o.BurstPoint.x, p.transform.position.y, o.BurstPoint.y);
|
|
|
|
|
|
var bindings = new CompositeDisposable();
|
|
|
|
|
|
zone.ShotCommitted.Where(o => o.ShotId == shotId && o.HasBurstPoint).Subscribe(o => p.BurstAt(At(o))).AddTo(bindings);
|
2026-09-29 18:37:02 +02:00
|
|
|
|
zone.ShotResolved.Where(o => o.ShotId == shotId)
|
2026-10-13 07:13:55 +02:00
|
|
|
|
.Subscribe(o => p.Resolve(o.Result == ShotResult.Hit || o.Result == ShotResult.Burst, o.HasBurstPoint ? At(o) : (Vector3?) null,
|
|
|
|
|
|
ActionGameManager.DrawsAt(o.Target)))
|
2026-09-29 18:37:02 +02:00
|
|
|
|
.AddTo(bindings);
|
2026-09-29 21:16:52 +02:00
|
|
|
|
// The binding outlives a fade-out until the shot's own arrival (the moment the simulation resolves it), plus a
|
|
|
|
|
|
// second of margin; a shot no longer pending has nothing left to resolve.
|
|
|
|
|
|
p.BindingLifetime = zone.TryGetShot(shotId, out var shot) ? max(0f, shot.ArrivalTime - zone.Time) + 1f : 0f;
|
2026-09-29 18:37:02 +02:00
|
|
|
|
p.Binding = bindings; // replaces, and so disposes, any binding the projectile already had
|
2026-09-29 17:05:15 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
|
public Prototype ProjectilePrototype;
|
|
|
|
|
|
|
|
|
|
|
|
public Subject<(Entity source, Transform target, GuidedProjectile missile)> OnFireGuided = new Subject<(Entity source, Transform target, GuidedProjectile missile)>();
|
|
|
|
|
|
|
Fire control Cut 3: hit authority moves to FireControl
Deletes the Unity physics hit path (HullCollider.Hit/Splash, and the
raycast/shield/SendHit blocks in Projectile, GuidedProjectile, Laser and
Lightning) and replaces it with a single owner in the same cut: FireControl
now decides whether a shot hits, Zone owns the pending-shot lifecycle, and
Entity.DamageSchematic/ApplyHit (moved verbatim from EntityInstance) apply the
result. Presentation subscribes to Zone.ShotCommitted/ShotResolved and applies
nothing.
FireControl.HitProbability is the pure targeting solution (Accuracy * pSensor
* pSpread, gated by detection/range/lock/arc). FireControl.Fire freezes the
weapon's payload and the shooter's resolved targeting stats at the instant the
trigger is pulled (Q6), computes the predicted intercept through the one
shared PredictedIntercept function (CombatState now calls the same function
instead of duplicating first_order_intercept), and queues a PendingShot.
FireControl.Step commits each shot's outcome at ArrivalTime - CommitHorizon --
deviation from the predicted intercept, measured once, live, at that instant
-- and applies it at ArrivalTime. A shot whose flight is shorter than the
horizon commits and resolves in the same tick it fires. Rolls draw from the
shooter's own ItemManager.Random (Q5, seeded and reproducible); an out-of-arc
or otherwise gated shot draws nothing.
Player fire is arc-gated through the same predicate AI and turret fire use
(Weapon.ArcAllowsFire, checked in InstantWeapon.Trigger -- the one point every
shooter's trigger passes through), closing Q2.
Adds GameplaySettings.CommitHorizon, SchematicCellSize and
AgentMinHitProbability (first guesses; the headless suite is the tuning
harness, operator play smoke is the arbiter).
FORK reported, not worked around silently: HullCollider.SendHit/SendSplash
cannot be deleted yet without breaking ConstantLaser.cs, ConstantLightning.cs,
ConstantParticleWeapon.cs, HitscanEffect.cs and Mine.cs, which still call them
from their own live Physics queries -- that's Cut 4's migration. They fire
into the void today: nothing subscribes to Hit/Splash any more for the four
weapon kinds this cut owns.
tests/Aetheria.Shared.Tests/FireAuthorityTests.cs pins the roll's seeding, the
gate-consumes-no-draw rule, resolve-on-arrival, the commit horizon and its
evasion window, short-flight degradation, the frozen-payload snapshot, aimed
hits, the item-then-hull damage order, the planar penetration march and the
shield-absorbs branch. FireControlTests.cs and InputCapacitorTests.cs are
updated for the new AI gate and OnFire(int) signature.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:07:07 +02:00
|
|
|
|
public override void Fire(InstantWeapon weapon, EquippedItem item, EntityInstance source, EntityInstance target, int shotId)
|
2026-08-10 14:57:50 +01:00
|
|
|
|
{
|
|
|
|
|
|
if(weapon.Data is LauncherData launcher)
|
|
|
|
|
|
{
|
2026-09-29 16:27:54 +02:00
|
|
|
|
// A round the simulation fired at no target (FireControl.Fire: a fused round with no valid targeting
|
|
|
|
|
|
// data, or out of arc) flies to the point it bursts at; one with no target and no burst is not a shot.
|
|
|
|
|
|
PendingShot shot = default;
|
|
|
|
|
|
if (target == null && !(source.Entity.Zone.TryGetShot(shotId, out shot) && shot.Fuse != null)) return;
|
|
|
|
|
|
var burst = shot.BurstPosition.ToUnity();
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var p = ProjectilePrototype.Instantiate<GuidedProjectile>();
|
Fire control Cut 3: hit authority moves to FireControl
Deletes the Unity physics hit path (HullCollider.Hit/Splash, and the
raycast/shield/SendHit blocks in Projectile, GuidedProjectile, Laser and
Lightning) and replaces it with a single owner in the same cut: FireControl
now decides whether a shot hits, Zone owns the pending-shot lifecycle, and
Entity.DamageSchematic/ApplyHit (moved verbatim from EntityInstance) apply the
result. Presentation subscribes to Zone.ShotCommitted/ShotResolved and applies
nothing.
FireControl.HitProbability is the pure targeting solution (Accuracy * pSensor
* pSpread, gated by detection/range/lock/arc). FireControl.Fire freezes the
weapon's payload and the shooter's resolved targeting stats at the instant the
trigger is pulled (Q6), computes the predicted intercept through the one
shared PredictedIntercept function (CombatState now calls the same function
instead of duplicating first_order_intercept), and queues a PendingShot.
FireControl.Step commits each shot's outcome at ArrivalTime - CommitHorizon --
deviation from the predicted intercept, measured once, live, at that instant
-- and applies it at ArrivalTime. A shot whose flight is shorter than the
horizon commits and resolves in the same tick it fires. Rolls draw from the
shooter's own ItemManager.Random (Q5, seeded and reproducible); an out-of-arc
or otherwise gated shot draws nothing.
Player fire is arc-gated through the same predicate AI and turret fire use
(Weapon.ArcAllowsFire, checked in InstantWeapon.Trigger -- the one point every
shooter's trigger passes through), closing Q2.
Adds GameplaySettings.CommitHorizon, SchematicCellSize and
AgentMinHitProbability (first guesses; the headless suite is the tuning
harness, operator play smoke is the arbiter).
FORK reported, not worked around silently: HullCollider.SendHit/SendSplash
cannot be deleted yet without breaking ConstantLaser.cs, ConstantLightning.cs,
ConstantParticleWeapon.cs, HitscanEffect.cs and Mine.cs, which still call them
from their own live Physics queries -- that's Cut 4's migration. They fire
into the void today: nothing subscribes to Hit/Splash any more for the four
weapon kinds this cut owns.
tests/Aetheria.Shared.Tests/FireAuthorityTests.cs pins the roll's seeding, the
gate-consumes-no-draw rule, resolve-on-arrival, the commit horizon and its
evasion window, short-flight degradation, the frozen-payload snapshot, aimed
hits, the item-then-hull damage order, the planar penetration march and the
shield-absorbs branch. FireControlTests.cs and InputCapacitorTests.cs are
updated for the new AI gate and OnFire(int) signature.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:07:07 +02:00
|
|
|
|
p.ShotId = shotId;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
p.Source = source.transform;
|
|
|
|
|
|
p.SourceEntity = source.Entity;
|
2026-09-29 16:27:54 +02:00
|
|
|
|
if (target != null) p.Target = target.transform;
|
|
|
|
|
|
else p.TargetPosition = () => burst;
|
2026-10-12 04:02:32 +02:00
|
|
|
|
p.Frequency = item.Evaluate(MunitionParts.Of<MunitionControlData>(item).EvasionFrequency);
|
2026-10-03 14:57:27 +02:00
|
|
|
|
var hp = item.Hardpoint;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var barrel = source.GetBarrel(hp);
|
Convert engine types through CultMath.UnityBridge
CultMath has no implicit conversions to or from Vector*, Quaternion or
Color, so Assembly-CSharp converts explicitly with ToCultMath/ToUnity at
each engine boundary: FieldDriver, GuidedProjectile(Manager),
EntityInstance, ActionGameManager, HullCollider, GridObject, ShipInstance,
ShieldManager, ItemPickup, FieldTester, PlaceUIElementWorldspace,
MapMenuInput, MapRenderer, SectorRenderer and ZoneRenderer. CultMath math
calls that were handed Unity vectors now take converted values;
Vector3.Scale in FieldDriver becomes a float3 product, and ItemPickup's
Vector3.Dot on float3s becomes dot. packages-lock.json records the
cultmath git package at f0605367 and demotes com.unity.mathematics to a
transitive dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:47:33 +02:00
|
|
|
|
p.StartPosition = (p.transform.position = barrel.position).ToCultMath();
|
2026-08-10 14:57:50 +01:00
|
|
|
|
p.Range = weapon.Range;
|
|
|
|
|
|
p.GuidanceCurve = launcher.GuidanceCurve.ToCurve();
|
|
|
|
|
|
p.LiftCurve = launcher.LiftCurve.ToCurve();
|
|
|
|
|
|
p.ThrustCurve = launcher.ThrustCurve.ToCurve();
|
|
|
|
|
|
p.Velocity = barrel.forward * weapon.Velocity;
|
2026-10-12 04:02:32 +02:00
|
|
|
|
p.Thrust = item.Evaluate(MunitionParts.Of<MunitionThrusterData>(item).Acceleration);
|
|
|
|
|
|
p.TopSpeed = item.Evaluate(MunitionParts.Of<MunitionThrusterData>(item).MaxSpeed);
|
2026-09-29 17:05:15 +02:00
|
|
|
|
Bind(p, source.Entity.Zone, shotId);
|
|
|
|
|
|
// A round with no target (a fused round fired at none) has no transform to hand a listener.
|
|
|
|
|
|
OnFireGuided.OnNext((source.Entity, target != null ? target.transform : null, p));
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
else if(weapon.Data is GuidedWeaponData guidance)
|
|
|
|
|
|
{
|
|
|
|
|
|
var p = ProjectilePrototype.Instantiate<GuidedProjectile>();
|
Fire control Cut 3: hit authority moves to FireControl
Deletes the Unity physics hit path (HullCollider.Hit/Splash, and the
raycast/shield/SendHit blocks in Projectile, GuidedProjectile, Laser and
Lightning) and replaces it with a single owner in the same cut: FireControl
now decides whether a shot hits, Zone owns the pending-shot lifecycle, and
Entity.DamageSchematic/ApplyHit (moved verbatim from EntityInstance) apply the
result. Presentation subscribes to Zone.ShotCommitted/ShotResolved and applies
nothing.
FireControl.HitProbability is the pure targeting solution (Accuracy * pSensor
* pSpread, gated by detection/range/lock/arc). FireControl.Fire freezes the
weapon's payload and the shooter's resolved targeting stats at the instant the
trigger is pulled (Q6), computes the predicted intercept through the one
shared PredictedIntercept function (CombatState now calls the same function
instead of duplicating first_order_intercept), and queues a PendingShot.
FireControl.Step commits each shot's outcome at ArrivalTime - CommitHorizon --
deviation from the predicted intercept, measured once, live, at that instant
-- and applies it at ArrivalTime. A shot whose flight is shorter than the
horizon commits and resolves in the same tick it fires. Rolls draw from the
shooter's own ItemManager.Random (Q5, seeded and reproducible); an out-of-arc
or otherwise gated shot draws nothing.
Player fire is arc-gated through the same predicate AI and turret fire use
(Weapon.ArcAllowsFire, checked in InstantWeapon.Trigger -- the one point every
shooter's trigger passes through), closing Q2.
Adds GameplaySettings.CommitHorizon, SchematicCellSize and
AgentMinHitProbability (first guesses; the headless suite is the tuning
harness, operator play smoke is the arbiter).
FORK reported, not worked around silently: HullCollider.SendHit/SendSplash
cannot be deleted yet without breaking ConstantLaser.cs, ConstantLightning.cs,
ConstantParticleWeapon.cs, HitscanEffect.cs and Mine.cs, which still call them
from their own live Physics queries -- that's Cut 4's migration. They fire
into the void today: nothing subscribes to Hit/Splash any more for the four
weapon kinds this cut owns.
tests/Aetheria.Shared.Tests/FireAuthorityTests.cs pins the roll's seeding, the
gate-consumes-no-draw rule, resolve-on-arrival, the commit horizon and its
evasion window, short-flight degradation, the frozen-payload snapshot, aimed
hits, the item-then-hull damage order, the planar penetration march and the
shield-absorbs branch. FireControlTests.cs and InputCapacitorTests.cs are
updated for the new AI gate and OnFire(int) signature.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:07:07 +02:00
|
|
|
|
p.ShotId = shotId;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
p.Source = source.transform;
|
|
|
|
|
|
p.SourceEntity = source.Entity;
|
2026-10-12 04:02:32 +02:00
|
|
|
|
p.Frequency = item.Evaluate(MunitionParts.Of<MunitionControlData>(item).EvasionFrequency);
|
2026-10-03 14:57:27 +02:00
|
|
|
|
var hp = item.Hardpoint;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
var barrel = source.GetBarrel(hp);
|
Convert engine types through CultMath.UnityBridge
CultMath has no implicit conversions to or from Vector*, Quaternion or
Color, so Assembly-CSharp converts explicitly with ToCultMath/ToUnity at
each engine boundary: FieldDriver, GuidedProjectile(Manager),
EntityInstance, ActionGameManager, HullCollider, GridObject, ShipInstance,
ShieldManager, ItemPickup, FieldTester, PlaceUIElementWorldspace,
MapMenuInput, MapRenderer, SectorRenderer and ZoneRenderer. CultMath math
calls that were handed Unity vectors now take converted values;
Vector3.Scale in FieldDriver becomes a float3 product, and ItemPickup's
Vector3.Dot on float3s becomes dot. packages-lock.json records the
cultmath git package at f0605367 and demotes com.unity.mathematics to a
transitive dependency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:47:33 +02:00
|
|
|
|
p.StartPosition = (p.transform.position = barrel.position).ToCultMath();
|
2026-08-10 14:57:50 +01:00
|
|
|
|
p.Range = weapon.Range;
|
|
|
|
|
|
p.GuidanceCurve = guidance.GuidanceCurve.ToCurve();
|
|
|
|
|
|
p.LiftCurve = guidance.LiftCurve.ToCurve();
|
|
|
|
|
|
p.ThrustCurve = guidance.ThrustCurve.ToCurve();
|
|
|
|
|
|
p.Velocity = barrel.forward * weapon.Velocity;
|
2026-10-12 04:02:32 +02:00
|
|
|
|
p.Thrust = item.Evaluate(MunitionParts.Of<MunitionThrusterData>(item).Acceleration);
|
|
|
|
|
|
p.TopSpeed = item.Evaluate(MunitionParts.Of<MunitionThrusterData>(item).MaxSpeed);
|
2026-10-07 12:33:20 +02:00
|
|
|
|
p.TargetPosition = () => (source.Entity.Position + length(source.LookAtPoint.position.ToCultMath() - source.Entity.Position) * source.Entity.Aim).ToUnity();
|
2026-09-29 17:05:15 +02:00
|
|
|
|
Bind(p, source.Entity.Zone, shotId);
|
2026-08-10 14:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
else Debug.LogError($"Weapon {item.Data.Name} linked to {name} effect, but is not a Launcher!");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|