Aetheria/Assets/Scripts/Gameplay/Weapons/Projectile.cs
Meta Vens 39ad4bfc80 Fire control Cut 6b: a shot's dice belong to the shot, and airburst resolves in the simulation
6.1 (Soul finding 6): Commit builds a local CultMath.Random from (Zone.CombatSeed,
shot.ShotId) instead of reading and writing the shared ItemManager.Random stream.
Zone exposes CombatSeed (the same expression that already seeds its own _random),
so a shot's outcome is a pure function of zone identity and shot id -- immune to
whatever else drew from the shared stream in between.

6.2 (Soul finding 5): airburst is wired into the simulation instead of back into
Projectile.cs. PendingShot gains BurstPosition/BurstRadius, frozen at Fire time
from PredictedIntercept and the weapon's authored WeaponItemData.AirburstRange
when WeaponModifiers carries Airburst. FireControl.Step resolves such a shot via
Splash instead of Apply, never both. Projectile.cs/ProjectileManager.cs drop the
dead AirburstDistance/AirburstRange fields and write-back.

Tests: FireControlCut6Tests.cs pins both rules (208 baseline + 5 new, all green).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 18:34:04 +02:00

62 lines
2.1 KiB
C#

using System.Collections;
using UnityEngine;
using static CultMath.math;
// Cut 3 (docs/fire-control-cut.md): the raycast, shield branch and SendHit are deleted -- FireControl already
// decided this shot's fate before this object was ever spawned (R8). What is left is pure flight-path
// presentation: fly, and disappear at Range or ShotId's resolution, whichever comes first.
// Cut 6b, 6.2: airburst resolves in the simulation now (FireControl.Step calls Splash instead of Apply at
// arrival) -- a MonoBehaviour calling Splash would be exactly the authority Cut 3 spent itself deleting, so
// AirburstDistance/AirburstRange are gone. This object's job stays fly-and-disappear.
public class Projectile : MonoBehaviour
{
public TrailRenderer Trail;
public float Gravity;
public float Drag = .1f;
public Prototype HitEffect;
private bool _alive;
// Cut 3: FireControl.Fire's ShotId -- this projectile's handle onto Zone.ShotCommitted/ShotResolved.
public int ShotId { get; set; }
public Zone Zone { get; set; }
public Vector3 StartPosition { get; set; }
public Vector3 Velocity { get; set; }
public Entity SourceEntity { get; set; }
public float Range { get; set; }
private void OnEnable()
{
_alive = true;
}
// Update is called once per frame
void Update()
{
if (SourceEntity == null) return;
if(_alive)
{
var t = transform;
Velocity -= Vector3.up * (Gravity * Time.deltaTime);
Velocity *= max(0, 1 - Drag * Time.deltaTime);
t.forward = Velocity.normalized;
transform.position += Velocity * Time.deltaTime;
var distanceTraveled = (transform.position - StartPosition).magnitude;
if(distanceTraveled > Range)
StartCoroutine(Kill());
}
}
IEnumerator Kill()
{
_alive = false;
var startTime = Time.time;
var lifetime = Trail.time;
while (Time.time - startTime < lifetime)
yield return null;
GetComponent<Prototype>().ReturnToPool();
}
}