2022-08-08 22:43:55 +01:00
|
|
|
using System;
|
2026-08-10 14:57:50 +01:00
|
|
|
using System.Collections;
|
2022-08-08 22:43:55 +01:00
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEngine;
|
2026-08-10 14:57:50 +01:00
|
|
|
using UnityEngine.InputSystem;
|
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-09-14 17:39:12 +02:00
|
|
|
using static CultMath.math;
|
2022-08-08 22:43:55 +01:00
|
|
|
|
2026-09-17 14:36:40 +02:00
|
|
|
// Implements the capability presenter interfaces (Assets/Scripts/Gameplay/CapabilityPresenters.cs) by
|
|
|
|
|
// delegating to its own pre-existing methods/fields; it decides nothing about capability rules,
|
|
|
|
|
// per docs/headless-playground-cut.md fork L. CapabilityPresentationBinder is what actually
|
|
|
|
|
// subscribes these methods to a CapabilityEvents source -- FieldDriver has no reference to one.
|
|
|
|
|
public class FieldDriver : MonoBehaviour, IAbsorbPresenter, IGrabPresenter, IMeleePresenter, IThrustPresenter
|
2022-08-08 22:43:55 +01:00
|
|
|
{
|
2025-01-30 01:58:05 +00:00
|
|
|
public Camera Camera;
|
2026-08-10 14:57:50 +01:00
|
|
|
public float2 Push;
|
2025-01-30 01:58:05 +00:00
|
|
|
public float FrontTwist;
|
|
|
|
|
public float RearTwist;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public float TestMagnitude;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public float FlowSpeed;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public float FlowSpeedThrottleExponent;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2025-01-30 01:58:05 +00:00
|
|
|
public float WaveThrottleExponent;
|
|
|
|
|
|
2022-08-08 22:43:55 +01:00
|
|
|
public int MaxHits;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public ExponentialCurve MagnitudeTimeScaling;
|
2025-01-30 01:58:05 +00:00
|
|
|
|
Replace the Database Tools with the Studio and cut Unity over
Cut 8b. The Unity project compiles with 0 errors in 6000.3.24f1 batchmode.
Packages: manifest.json pins org.gamecult.cultlib at cultlib-unity-v1.0.57
and org.gamecult.caching.unity at caching-unity-v1.2.0, next to the 8a
CultMath tag. Aetheria.Shared.Unity references GameCult.CultLib and keeps
noEngineReferences.
The Database Tools (Assets/Scripts/CultCache/Editor) are deleted in this
commit, alongside their five ported drawers in
Assets/Scripts/Editor/CultCacheDrawers.cs, each an
[CultInspectorDrawer] ICultInspectorDrawer:
- InspectableType: popup of the attribute type's [Union] member names,
writing the string.
- InspectableColor: float3 and float4 through ColorField.
- InspectableAnimationCurve: float4[] and BezierCurve through CurveField.
- InspectableTemperature: Kelvin, Celsius and Fahrenheit fields over one
value.
- InspectableSchematicShape: size and cell toggles. The old drawer also
drew the item's schematic texture and tinted hull hardpoints, which need
the parent document. The tagged Studio hands a drawer only the value, so
those wait on the Studio exposing the open record.
Unity call sites:
- ActionGameManager opens the catalog through AetheriaStores; Cut 10 adds
the run and player stores.
- Refs resolve through ItemManager.GetData or Get(ref); orbit, planet and
belt maps key by CultRecordKey; identity is compared by reference or key.
- The PlayerSettings.msgpack reader and writer, which Cut 10 deletes, pass
CultDocumentMessagePackSerialization.OptionsFor, since the embedded run
reaches CultMath values and refs. Keyboard layouts need no resolver and
keep MessagePack's defaults until Cut 10.
- The input map uses the nested action to binding shape.
- PropertiesPanel reads CultInspectorRangeAttribute.
- NameTools loses its legacy name-file writer.
- Slime compares serialized settings instead of the deleted SHA1 helper.
- FieldDriver drops InspectorHeader.
- .gitignore covers the test project's build output.
Builds: Aetheria.Shared, AetherDb, Aetheria.Shared.Tests (6/6), Unity
batchmode (0 errors).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:15:20 +02:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public float MeleeRange;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public float MeleeRangeExponent;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public float MeleeFlatness;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public float MeleeShaping;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public float MeleeDuration;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public float MeleeAngle;
|
2026-08-10 14:57:50 +01:00
|
|
|
[Inspectable]
|
2022-08-08 22:43:55 +01:00
|
|
|
public float MeleeAngleExponent;
|
2025-01-30 01:58:05 +00:00
|
|
|
|
Replace the Database Tools with the Studio and cut Unity over
Cut 8b. The Unity project compiles with 0 errors in 6000.3.24f1 batchmode.
Packages: manifest.json pins org.gamecult.cultlib at cultlib-unity-v1.0.57
and org.gamecult.caching.unity at caching-unity-v1.2.0, next to the 8a
CultMath tag. Aetheria.Shared.Unity references GameCult.CultLib and keeps
noEngineReferences.
The Database Tools (Assets/Scripts/CultCache/Editor) are deleted in this
commit, alongside their five ported drawers in
Assets/Scripts/Editor/CultCacheDrawers.cs, each an
[CultInspectorDrawer] ICultInspectorDrawer:
- InspectableType: popup of the attribute type's [Union] member names,
writing the string.
- InspectableColor: float3 and float4 through ColorField.
- InspectableAnimationCurve: float4[] and BezierCurve through CurveField.
- InspectableTemperature: Kelvin, Celsius and Fahrenheit fields over one
value.
- InspectableSchematicShape: size and cell toggles. The old drawer also
drew the item's schematic texture and tinted hull hardpoints, which need
the parent document. The tagged Studio hands a drawer only the value, so
those wait on the Studio exposing the open record.
Unity call sites:
- ActionGameManager opens the catalog through AetheriaStores; Cut 10 adds
the run and player stores.
- Refs resolve through ItemManager.GetData or Get(ref); orbit, planet and
belt maps key by CultRecordKey; identity is compared by reference or key.
- The PlayerSettings.msgpack reader and writer, which Cut 10 deletes, pass
CultDocumentMessagePackSerialization.OptionsFor, since the embedded run
reaches CultMath values and refs. Keyboard layouts need no resolver and
keep MessagePack's defaults until Cut 10.
- The input map uses the nested action to binding shape.
- PropertiesPanel reads CultInspectorRangeAttribute.
- NameTools loses its legacy name-file writer.
- Slime compares serialized settings instead of the deleted SHA1 helper.
- FieldDriver drops InspectorHeader.
- .gitignore covers the test project's build output.
Builds: Aetheria.Shared, AetherDb, Aetheria.Shared.Tests (6/6), Unity
batchmode (0 errors).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:15:20 +02:00
|
|
|
[Inspectable]
|
2026-08-10 14:57:50 +01:00
|
|
|
public float TendrilBaseRadius = 3;
|
|
|
|
|
[Inspectable]
|
|
|
|
|
public float TendrilTipRadius = .1f;
|
|
|
|
|
[Inspectable]
|
|
|
|
|
public float TendrilExtensionExponent = .75f;
|
|
|
|
|
[Inspectable]
|
|
|
|
|
public float TendrilExtendBaseAnimationExponent = .5f;
|
|
|
|
|
[Inspectable]
|
|
|
|
|
public float TendrilBaseDamping = 2;
|
|
|
|
|
[Inspectable]
|
|
|
|
|
public float TendrilTipRadiusAnimationExponent = 2f;
|
|
|
|
|
[Inspectable]
|
|
|
|
|
public float TendrilFadeExponent = 2;
|
|
|
|
|
// [Inspectable]
|
|
|
|
|
// public float GrabScaleAnimationExponent = .5f;
|
|
|
|
|
|
Replace the Database Tools with the Studio and cut Unity over
Cut 8b. The Unity project compiles with 0 errors in 6000.3.24f1 batchmode.
Packages: manifest.json pins org.gamecult.cultlib at cultlib-unity-v1.0.57
and org.gamecult.caching.unity at caching-unity-v1.2.0, next to the 8a
CultMath tag. Aetheria.Shared.Unity references GameCult.CultLib and keeps
noEngineReferences.
The Database Tools (Assets/Scripts/CultCache/Editor) are deleted in this
commit, alongside their five ported drawers in
Assets/Scripts/Editor/CultCacheDrawers.cs, each an
[CultInspectorDrawer] ICultInspectorDrawer:
- InspectableType: popup of the attribute type's [Union] member names,
writing the string.
- InspectableColor: float3 and float4 through ColorField.
- InspectableAnimationCurve: float4[] and BezierCurve through CurveField.
- InspectableTemperature: Kelvin, Celsius and Fahrenheit fields over one
value.
- InspectableSchematicShape: size and cell toggles. The old drawer also
drew the item's schematic texture and tinted hull hardpoints, which need
the parent document. The tagged Studio hands a drawer only the value, so
those wait on the Studio exposing the open record.
Unity call sites:
- ActionGameManager opens the catalog through AetheriaStores; Cut 10 adds
the run and player stores.
- Refs resolve through ItemManager.GetData or Get(ref); orbit, planet and
belt maps key by CultRecordKey; identity is compared by reference or key.
- The PlayerSettings.msgpack reader and writer, which Cut 10 deletes, pass
CultDocumentMessagePackSerialization.OptionsFor, since the embedded run
reaches CultMath values and refs. Keyboard layouts need no resolver and
keep MessagePack's defaults until Cut 10.
- The input map uses the nested action to binding shape.
- PropertiesPanel reads CultInspectorRangeAttribute.
- NameTools loses its legacy name-file writer.
- Slime compares serialized settings instead of the deleted SHA1 helper.
- FieldDriver drops InspectorHeader.
- .gitignore covers the test project's build output.
Builds: Aetheria.Shared, AetherDb, Aetheria.Shared.Tests (6/6), Unity
batchmode (0 errors).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:15:20 +02:00
|
|
|
[Inspectable]
|
2026-10-13 18:43:15 +02:00
|
|
|
public float TendrilTipScale = 1;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
[Inspectable]
|
2025-01-30 01:58:05 +00:00
|
|
|
public RectTransform.Axis RefractionAxis = RectTransform.Axis.Horizontal;
|
2022-08-08 22:43:55 +01:00
|
|
|
|
|
|
|
|
private Material _field;
|
|
|
|
|
private ComputeBuffer _hitBuffer;
|
|
|
|
|
private List<FieldHit> _hits = new List<FieldHit>();
|
Collapse the two implicit shield ellipsoids into ShieldEnvelope (Cut 2)
FieldDriver derived the shield ellipsoid from its own transform.localScale;
ShieldManager derived it independently from the Shield prefab instance's
transform, doing the same normalise-and-project by hand against a different
object. Add ShieldEnvelope as the one reader of a transform's centre, radii,
surface projection, and true outward normal (derived, not authored, per R6 --
no second copy of numbers the physical MeshCollider already owns), and switch
both consumers onto it, deleting their hand-rolled projections.
Radii read the attached collider mesh's own bounds.extents per axis rather
than assuming a flat 0.5 or 1.0: Shield.prefab's Icosphere collider measures
(0.0095106, 0.01, 0.01), not a uniform round number, and the cubesphere's
collider measures exactly (1,1,1) -- confirmed by a throwaway batchmode probe
against the actual mesh assets, not guessed.
SurfaceNormal is not normalize(p): a non-uniform ellipsoid's true normal is
the gradient of the implicit surface, transformed by rotation and the INVERSE
of lossyScale (a normal is a covector). The first implementation used
TransformDirection (rotation and forward scale) and the batchmode probe's
finite-difference gradient check caught it immediately -- exactly the
plausible-looking-wrong-answer failure mode the cut map called out by name.
Both original consumers keep normalize(p)-equivalent behaviour via the new
SurfaceDirection method, since they only ever wanted a direction.
FieldDriver falls back to its pre-cut formulas (logged once) when no
ShieldEnvelope is present, since it has no guaranteed carrier yet.
ShieldManager does not: every object that carries it gets an envelope in this
cut, so a missing one logs an error and degrades to a fixed direction rather
than recomputing a second projection.
No prefab or scene file is touched here (Q5: the operator rigs prefabs and
scenes by hand); ShieldPanelCut2Verify's batchmode probe instead builds
synthetic transforms carrying Longinus's and Djinni's actual override values
plus the real Icosphere mesh, and proves ProjectToSurface lands exactly on the
ellipsoid and SurfaceNormal matches a finite-difference gradient to 1e-3.
mutation_tests_shield_panel_cut2.py mutates SurfaceNormal three ways (back to
normalize(p), back to TransformDirection, and a flattened 0.5 extents factor)
and confirms the probe goes red for each, naming the reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:33:38 +02:00
|
|
|
private ShieldEnvelope _envelope;
|
|
|
|
|
private bool _loggedMissingEnvelope;
|
2025-01-30 01:58:05 +00:00
|
|
|
private float _waveOffset;
|
2022-08-08 22:43:55 +01:00
|
|
|
private float _meleeTime;
|
|
|
|
|
private bool _meleeActive;
|
2026-08-10 14:57:50 +01:00
|
|
|
private MeshCollider _collider;
|
2026-10-13 18:43:15 +02:00
|
|
|
private bool _grabbing;
|
|
|
|
|
private GrabPhase _grabPhase;
|
2026-08-10 14:57:50 +01:00
|
|
|
private float _grabTime;
|
2026-10-13 18:43:15 +02:00
|
|
|
private float _phaseDuration;
|
|
|
|
|
private Vector3 _grabStart;
|
|
|
|
|
private Vector3 _grabStartTendrilBase;
|
2026-08-10 14:57:50 +01:00
|
|
|
private Vector3 _tendrilBasePos;
|
|
|
|
|
private Vector3 _tendrilBendTarget;
|
|
|
|
|
private Vector3 _tendrilTargetPos;
|
|
|
|
|
private float _fadePoint;
|
|
|
|
|
|
2022-08-08 22:43:55 +01:00
|
|
|
private struct FieldHit
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
public float3 Position;
|
|
|
|
|
public float3 Direction;
|
2022-08-08 22:43:55 +01:00
|
|
|
public float Magnitude;
|
|
|
|
|
public float Time;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Start()
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_collider = GetComponent<MeshCollider>();
|
2022-08-08 22:43:55 +01:00
|
|
|
_field = GetComponent<MeshRenderer>().material;
|
|
|
|
|
_hitBuffer = new ComputeBuffer(MaxHits, 32);
|
Collapse the two implicit shield ellipsoids into ShieldEnvelope (Cut 2)
FieldDriver derived the shield ellipsoid from its own transform.localScale;
ShieldManager derived it independently from the Shield prefab instance's
transform, doing the same normalise-and-project by hand against a different
object. Add ShieldEnvelope as the one reader of a transform's centre, radii,
surface projection, and true outward normal (derived, not authored, per R6 --
no second copy of numbers the physical MeshCollider already owns), and switch
both consumers onto it, deleting their hand-rolled projections.
Radii read the attached collider mesh's own bounds.extents per axis rather
than assuming a flat 0.5 or 1.0: Shield.prefab's Icosphere collider measures
(0.0095106, 0.01, 0.01), not a uniform round number, and the cubesphere's
collider measures exactly (1,1,1) -- confirmed by a throwaway batchmode probe
against the actual mesh assets, not guessed.
SurfaceNormal is not normalize(p): a non-uniform ellipsoid's true normal is
the gradient of the implicit surface, transformed by rotation and the INVERSE
of lossyScale (a normal is a covector). The first implementation used
TransformDirection (rotation and forward scale) and the batchmode probe's
finite-difference gradient check caught it immediately -- exactly the
plausible-looking-wrong-answer failure mode the cut map called out by name.
Both original consumers keep normalize(p)-equivalent behaviour via the new
SurfaceDirection method, since they only ever wanted a direction.
FieldDriver falls back to its pre-cut formulas (logged once) when no
ShieldEnvelope is present, since it has no guaranteed carrier yet.
ShieldManager does not: every object that carries it gets an envelope in this
cut, so a missing one logs an error and degrades to a fixed direction rather
than recomputing a second projection.
No prefab or scene file is touched here (Q5: the operator rigs prefabs and
scenes by hand); ShieldPanelCut2Verify's batchmode probe instead builds
synthetic transforms carrying Longinus's and Djinni's actual override values
plus the real Icosphere mesh, and proves ProjectToSurface lands exactly on the
ellipsoid and SurfaceNormal matches a finite-difference gradient to 1e-3.
mutation_tests_shield_panel_cut2.py mutates SurfaceNormal three ways (back to
normalize(p), back to TransformDirection, and a flattened 0.5 extents factor)
and confirms the probe goes red for each, naming the reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:33:38 +02:00
|
|
|
_envelope = GetComponent<ShieldEnvelope>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cut 2 of docs/shield-panel-cut.md: FieldDriver no longer owns the ellipsoid envelope --
|
|
|
|
|
// ShieldEnvelope does, and this transform's localScale is display geometry for the field mesh
|
|
|
|
|
// only now. Absent an envelope (compatibility path only; every real carrier gets one in this
|
|
|
|
|
// cut), fall back to the pre-Cut-2 formulas and log once rather than silently keeping a second
|
|
|
|
|
// opinion about the envelope.
|
|
|
|
|
private void WarnMissingEnvelopeOnce()
|
|
|
|
|
{
|
|
|
|
|
if (_loggedMissingEnvelope) return;
|
|
|
|
|
_loggedMissingEnvelope = true;
|
|
|
|
|
Debug.LogWarning("FieldDriver has no ShieldEnvelope; falling back to its own transform for the envelope.", this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Vector3 EnvelopeRadiiOrFallback()
|
|
|
|
|
{
|
|
|
|
|
if (_envelope != null) return _envelope.Radii;
|
|
|
|
|
WarnMissingEnvelopeOnce();
|
|
|
|
|
return transform.localScale;
|
2022-08-08 22:43:55 +01:00
|
|
|
}
|
|
|
|
|
|
Fix ShieldManager's missing-envelope fallback regression
ShieldManager's fallback (no ShieldEnvelope yet, e.g. every ship prefab
before the operator hand-rigs it per Q5) hard-failed to a fixed
Vector3.forward direction with a per-hit Debug.LogError. That silently
changed behaviour for every ship in the game until the operator edits
Longinus.prefab/Djinni.prefab on their own schedule -- worse than either
the deleted code or a loud one-time failure.
Extract ShieldEnvelope's local-direction primitive into two static,
no-component-required entry points -- SurfaceDirection(Transform, Vector3)
and LegacyLocalSurfacePoint(Transform, Vector3) -- so both FieldDriver's and
ShieldManager's fallbacks go through one owner instead of each re-deriving
the pre-Cut-2 formula by hand. ShieldManager's fallback now reproduces the
deleted normalize(shield.transform.InverseTransformPoint(point)) formula
exactly via the shared static, logging once (a warning, not a per-hit
error). FieldDriver's fallback is rewired onto the same static rather than
its own inline copy of the maths.
Documented, not silently assumed: FieldDriver's "envelope present" branch
(envelope.ProjectToSurface, world-space) and its fallback (the legacy local-
space formula) are only numerically identical when the carrier's transform
has zero position and identity rotation -- true of the one FieldDriver
carrier in the tree today, not true in general. This is a characteristic of
Cut 2 as scoped by the map (FieldDriver.cs:125 reads envelope.ProjectToSurface
directly, per the map's own text), not something this fix introduces or
resolves; flagged so nobody assumes the two branches are interchangeable for
a future non-origin, non-identity FieldDriver carrier.
ShieldPanelCut2Verify.CheckLegacyFallbackFormulas pins both static entry
points against an independent re-derivation of the exact pre-Cut-2 formulas,
on a carrier with a non-trivial transform (nonzero position, non-identity
rotation) so the check isn't vacuous. mutation_tests_shield_panel_cut2.py
gained two cases: SurfaceDirection collapsing back to a fixed Vector3.forward
(the exact regression fixed here), and LegacyLocalSurfacePoint collapsing to
the raw, untransformed world point -- both correctly fail the probe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:45:07 +02:00
|
|
|
// Cut 2 note on the two branches below NOT being the same computation shape: envelope.
|
|
|
|
|
// ProjectToSurface returns a WORLD-space point (it transforms back through rotation and
|
|
|
|
|
// translation -- Cut 3's panel presenter needs that). The fallback reproduces the pre-Cut-2
|
|
|
|
|
// formula exactly instead, which stayed in LOCAL space with no rotation/translation applied,
|
|
|
|
|
// because that is what the shader consumes here (_Hits is uploaded and read in the field
|
|
|
|
|
// mesh's own local space, not world space). The two branches are only numerically identical
|
|
|
|
|
// when the carrier's transform has zero position and identity rotation -- true of the one
|
|
|
|
|
// FieldDriver carrier in the tree today (FieldShieldTest.unity's cubesphere), NOT true in
|
|
|
|
|
// general. This is an existing characteristic of Cut 2 as scoped by the map (FieldDriver.cs:125
|
|
|
|
|
// reads envelope.ProjectToSurface(position) directly, per the map's own Cut 2 text), not
|
|
|
|
|
// something introduced by this fallback -- flagging it so nobody assumes the two branches are
|
|
|
|
|
// interchangeable for a future non-origin, non-identity FieldDriver carrier.
|
2026-08-10 14:57:50 +01:00
|
|
|
public void AddHit(float3 position, float3 direction, float magnitude)
|
2022-08-08 22:43:55 +01:00
|
|
|
{
|
|
|
|
|
if (_hits.Count >= MaxHits) return;
|
Collapse the two implicit shield ellipsoids into ShieldEnvelope (Cut 2)
FieldDriver derived the shield ellipsoid from its own transform.localScale;
ShieldManager derived it independently from the Shield prefab instance's
transform, doing the same normalise-and-project by hand against a different
object. Add ShieldEnvelope as the one reader of a transform's centre, radii,
surface projection, and true outward normal (derived, not authored, per R6 --
no second copy of numbers the physical MeshCollider already owns), and switch
both consumers onto it, deleting their hand-rolled projections.
Radii read the attached collider mesh's own bounds.extents per axis rather
than assuming a flat 0.5 or 1.0: Shield.prefab's Icosphere collider measures
(0.0095106, 0.01, 0.01), not a uniform round number, and the cubesphere's
collider measures exactly (1,1,1) -- confirmed by a throwaway batchmode probe
against the actual mesh assets, not guessed.
SurfaceNormal is not normalize(p): a non-uniform ellipsoid's true normal is
the gradient of the implicit surface, transformed by rotation and the INVERSE
of lossyScale (a normal is a covector). The first implementation used
TransformDirection (rotation and forward scale) and the batchmode probe's
finite-difference gradient check caught it immediately -- exactly the
plausible-looking-wrong-answer failure mode the cut map called out by name.
Both original consumers keep normalize(p)-equivalent behaviour via the new
SurfaceDirection method, since they only ever wanted a direction.
FieldDriver falls back to its pre-cut formulas (logged once) when no
ShieldEnvelope is present, since it has no guaranteed carrier yet.
ShieldManager does not: every object that carries it gets an envelope in this
cut, so a missing one logs an error and degrades to a fixed direction rather
than recomputing a second projection.
No prefab or scene file is touched here (Q5: the operator rigs prefabs and
scenes by hand); ShieldPanelCut2Verify's batchmode probe instead builds
synthetic transforms carrying Longinus's and Djinni's actual override values
plus the real Icosphere mesh, and proves ProjectToSurface lands exactly on the
ellipsoid and SurfaceNormal matches a finite-difference gradient to 1e-3.
mutation_tests_shield_panel_cut2.py mutates SurfaceNormal three ways (back to
normalize(p), back to TransformDirection, and a flattened 0.5 extents factor)
and confirms the probe goes red for each, naming the reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:33:38 +02:00
|
|
|
Vector3 surfacePosition;
|
|
|
|
|
if (_envelope != null)
|
|
|
|
|
surfacePosition = _envelope.ProjectToSurface(position.ToUnity());
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
WarnMissingEnvelopeOnce();
|
Fix ShieldManager's missing-envelope fallback regression
ShieldManager's fallback (no ShieldEnvelope yet, e.g. every ship prefab
before the operator hand-rigs it per Q5) hard-failed to a fixed
Vector3.forward direction with a per-hit Debug.LogError. That silently
changed behaviour for every ship in the game until the operator edits
Longinus.prefab/Djinni.prefab on their own schedule -- worse than either
the deleted code or a loud one-time failure.
Extract ShieldEnvelope's local-direction primitive into two static,
no-component-required entry points -- SurfaceDirection(Transform, Vector3)
and LegacyLocalSurfacePoint(Transform, Vector3) -- so both FieldDriver's and
ShieldManager's fallbacks go through one owner instead of each re-deriving
the pre-Cut-2 formula by hand. ShieldManager's fallback now reproduces the
deleted normalize(shield.transform.InverseTransformPoint(point)) formula
exactly via the shared static, logging once (a warning, not a per-hit
error). FieldDriver's fallback is rewired onto the same static rather than
its own inline copy of the maths.
Documented, not silently assumed: FieldDriver's "envelope present" branch
(envelope.ProjectToSurface, world-space) and its fallback (the legacy local-
space formula) are only numerically identical when the carrier's transform
has zero position and identity rotation -- true of the one FieldDriver
carrier in the tree today, not true in general. This is a characteristic of
Cut 2 as scoped by the map (FieldDriver.cs:125 reads envelope.ProjectToSurface
directly, per the map's own text), not something this fix introduces or
resolves; flagged so nobody assumes the two branches are interchangeable for
a future non-origin, non-identity FieldDriver carrier.
ShieldPanelCut2Verify.CheckLegacyFallbackFormulas pins both static entry
points against an independent re-derivation of the exact pre-Cut-2 formulas,
on a carrier with a non-trivial transform (nonzero position, non-identity
rotation) so the check isn't vacuous. mutation_tests_shield_panel_cut2.py
gained two cases: SurfaceDirection collapsing back to a fixed Vector3.forward
(the exact regression fixed here), and LegacyLocalSurfacePoint collapsing to
the raw, untransformed world point -- both correctly fail the probe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:45:07 +02:00
|
|
|
surfacePosition = ShieldEnvelope.LegacyLocalSurfacePoint(transform, position.ToUnity());
|
Collapse the two implicit shield ellipsoids into ShieldEnvelope (Cut 2)
FieldDriver derived the shield ellipsoid from its own transform.localScale;
ShieldManager derived it independently from the Shield prefab instance's
transform, doing the same normalise-and-project by hand against a different
object. Add ShieldEnvelope as the one reader of a transform's centre, radii,
surface projection, and true outward normal (derived, not authored, per R6 --
no second copy of numbers the physical MeshCollider already owns), and switch
both consumers onto it, deleting their hand-rolled projections.
Radii read the attached collider mesh's own bounds.extents per axis rather
than assuming a flat 0.5 or 1.0: Shield.prefab's Icosphere collider measures
(0.0095106, 0.01, 0.01), not a uniform round number, and the cubesphere's
collider measures exactly (1,1,1) -- confirmed by a throwaway batchmode probe
against the actual mesh assets, not guessed.
SurfaceNormal is not normalize(p): a non-uniform ellipsoid's true normal is
the gradient of the implicit surface, transformed by rotation and the INVERSE
of lossyScale (a normal is a covector). The first implementation used
TransformDirection (rotation and forward scale) and the batchmode probe's
finite-difference gradient check caught it immediately -- exactly the
plausible-looking-wrong-answer failure mode the cut map called out by name.
Both original consumers keep normalize(p)-equivalent behaviour via the new
SurfaceDirection method, since they only ever wanted a direction.
FieldDriver falls back to its pre-cut formulas (logged once) when no
ShieldEnvelope is present, since it has no guaranteed carrier yet.
ShieldManager does not: every object that carries it gets an envelope in this
cut, so a missing one logs an error and degrades to a fixed direction rather
than recomputing a second projection.
No prefab or scene file is touched here (Q5: the operator rigs prefabs and
scenes by hand); ShieldPanelCut2Verify's batchmode probe instead builds
synthetic transforms carrying Longinus's and Djinni's actual override values
plus the real Icosphere mesh, and proves ProjectToSurface lands exactly on the
ellipsoid and SurfaceNormal matches a finite-difference gradient to 1e-3.
mutation_tests_shield_panel_cut2.py mutates SurfaceNormal three ways (back to
normalize(p), back to TransformDirection, and a flattened 0.5 extents factor)
and confirms the probe goes red for each, naming the reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:33:38 +02:00
|
|
|
}
|
2022-08-08 22:43:55 +01:00
|
|
|
var hit = new FieldHit
|
|
|
|
|
{
|
Collapse the two implicit shield ellipsoids into ShieldEnvelope (Cut 2)
FieldDriver derived the shield ellipsoid from its own transform.localScale;
ShieldManager derived it independently from the Shield prefab instance's
transform, doing the same normalise-and-project by hand against a different
object. Add ShieldEnvelope as the one reader of a transform's centre, radii,
surface projection, and true outward normal (derived, not authored, per R6 --
no second copy of numbers the physical MeshCollider already owns), and switch
both consumers onto it, deleting their hand-rolled projections.
Radii read the attached collider mesh's own bounds.extents per axis rather
than assuming a flat 0.5 or 1.0: Shield.prefab's Icosphere collider measures
(0.0095106, 0.01, 0.01), not a uniform round number, and the cubesphere's
collider measures exactly (1,1,1) -- confirmed by a throwaway batchmode probe
against the actual mesh assets, not guessed.
SurfaceNormal is not normalize(p): a non-uniform ellipsoid's true normal is
the gradient of the implicit surface, transformed by rotation and the INVERSE
of lossyScale (a normal is a covector). The first implementation used
TransformDirection (rotation and forward scale) and the batchmode probe's
finite-difference gradient check caught it immediately -- exactly the
plausible-looking-wrong-answer failure mode the cut map called out by name.
Both original consumers keep normalize(p)-equivalent behaviour via the new
SurfaceDirection method, since they only ever wanted a direction.
FieldDriver falls back to its pre-cut formulas (logged once) when no
ShieldEnvelope is present, since it has no guaranteed carrier yet.
ShieldManager does not: every object that carries it gets an envelope in this
cut, so a missing one logs an error and degrades to a fixed direction rather
than recomputing a second projection.
No prefab or scene file is touched here (Q5: the operator rigs prefabs and
scenes by hand); ShieldPanelCut2Verify's batchmode probe instead builds
synthetic transforms carrying Longinus's and Djinni's actual override values
plus the real Icosphere mesh, and proves ProjectToSurface lands exactly on the
ellipsoid and SurfaceNormal matches a finite-difference gradient to 1e-3.
mutation_tests_shield_panel_cut2.py mutates SurfaceNormal three ways (back to
normalize(p), back to TransformDirection, and a flattened 0.5 extents factor)
and confirms the probe goes red for each, naming the reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:33:38 +02:00
|
|
|
Position = surfacePosition.ToCultMath(),
|
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
|
|
|
Direction = normalize((transform.rotation * direction.ToUnity()).ToCultMath()),
|
2022-08-08 22:43:55 +01:00
|
|
|
Magnitude = magnitude,
|
|
|
|
|
Time = 0
|
|
|
|
|
};
|
2025-01-30 01:58:05 +00:00
|
|
|
//Debug.Log($"Received hit: Position={hit.Position}, Direction={hit.Direction}");
|
2022-08-08 22:43:55 +01:00
|
|
|
_hits.Add(hit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int HitCount => _hits.Count;
|
|
|
|
|
|
|
|
|
|
public void Melee()
|
|
|
|
|
{
|
|
|
|
|
_meleeActive = true;
|
|
|
|
|
_meleeTime = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-17 14:36:40 +02:00
|
|
|
// IAbsorbPresenter: delegates straight to the existing hit-buffer mechanism. e.DamageType is not
|
|
|
|
|
// read -- the field shield has no per-damage-type visual today; it rides along in the event for
|
|
|
|
|
// presentations that do vary by type (the mask itself is absorb-capability data, out of scope here).
|
|
|
|
|
public void Absorb(AbsorbEvent e) => AddHit(e.Position, e.Direction, e.Magnitude);
|
|
|
|
|
|
2026-10-13 18:43:15 +02:00
|
|
|
// IGrabPresenter: the simulation owns the grab; this draws the tendril from the events alone. Started records
|
|
|
|
|
// where the item is held and where the tendril leaves the field; Extend, Envelop and Pull set the phase and
|
|
|
|
|
// restart the real-time phase clock over the event's duration (it clamps at 1 until the next event, so a
|
|
|
|
|
// slowed simulation stretches the wait, not the animation); Completed and Cancelled end it.
|
|
|
|
|
public void Grab(GrabEvent e)
|
2026-09-17 14:36:40 +02:00
|
|
|
{
|
2026-10-13 18:43:15 +02:00
|
|
|
switch (e.Phase)
|
|
|
|
|
{
|
|
|
|
|
case GrabPhase.Started:
|
|
|
|
|
_grabbing = true;
|
|
|
|
|
_grabPhase = GrabPhase.Started;
|
|
|
|
|
_grabStart = e.TargetPosition.ToUnity();
|
|
|
|
|
_tendrilBasePos = _grabStartTendrilBase = _collider.ClosestPoint(_grabStart);
|
|
|
|
|
_tendrilTargetPos = _grabStart;
|
|
|
|
|
_fadePoint = .999f;
|
|
|
|
|
break;
|
|
|
|
|
case GrabPhase.Extend:
|
|
|
|
|
case GrabPhase.Envelop:
|
|
|
|
|
case GrabPhase.Pull:
|
|
|
|
|
if (!_grabbing) break;
|
|
|
|
|
_grabPhase = e.Phase;
|
|
|
|
|
_grabTime = 0;
|
|
|
|
|
_phaseDuration = e.PhaseDuration;
|
|
|
|
|
break;
|
|
|
|
|
case GrabPhase.Completed:
|
|
|
|
|
case GrabPhase.Cancelled:
|
|
|
|
|
_grabbing = false;
|
|
|
|
|
_field.SetFloat("_TendrilInfluence", 0);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-09-17 14:36:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IMeleePresenter: the field shield's swing shape comes from its own authored curves
|
|
|
|
|
// (MeleeRange/MeleeAngle/MeleeDuration/etc, tuned in the inspector), not from the event's
|
|
|
|
|
// direction/range/arc/duration -- those exist for presentations that do read them.
|
|
|
|
|
public void Melee(MeleeEvent e) => Melee();
|
|
|
|
|
|
|
|
|
|
// IThrustPresenter: Throttle is not read; pushMag is derived from PlanarThrust in Update() below,
|
|
|
|
|
// matching the field shield's existing behaviour.
|
|
|
|
|
public void Thrust(ThrustEvent e)
|
|
|
|
|
{
|
|
|
|
|
Push = e.PlanarThrust;
|
|
|
|
|
FrontTwist = e.Twist.x;
|
|
|
|
|
RearTwist = e.Twist.y;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-08 22:43:55 +01:00
|
|
|
void Update()
|
|
|
|
|
{
|
2025-01-30 01:58:05 +00:00
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
var pushMag = min(length(Push), 1);
|
2025-01-30 01:58:05 +00:00
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
_waveOffset = frac(_waveOffset + Time.deltaTime * FlowSpeed * pow(max(pushMag, max(abs(FrontTwist), abs(RearTwist))), FlowSpeedThrottleExponent));
|
2025-01-30 01:58:05 +00:00
|
|
|
|
|
|
|
|
var refractionRotation = Matrix4x4.Rotate(Camera.transform.rotation).inverse;
|
|
|
|
|
if (RefractionAxis == RectTransform.Axis.Vertical) refractionRotation =
|
|
|
|
|
Matrix4x4.Rotate(Quaternion.Euler(90, 0, 0)) * refractionRotation;
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
_field.SetFloat("_WaveOffset", _waveOffset * PI * 2);
|
2025-01-30 01:58:05 +00:00
|
|
|
_field.SetFloat("_Push", pushMag);
|
|
|
|
|
_field.SetVector("_PushDirection", new Vector4(-Push.x, 0, -Push.y));
|
|
|
|
|
_field.SetFloat("_TwistFront", FrontTwist);
|
|
|
|
|
_field.SetFloat("_TwistRear", RearTwist);
|
|
|
|
|
|
Collapse the two implicit shield ellipsoids into ShieldEnvelope (Cut 2)
FieldDriver derived the shield ellipsoid from its own transform.localScale;
ShieldManager derived it independently from the Shield prefab instance's
transform, doing the same normalise-and-project by hand against a different
object. Add ShieldEnvelope as the one reader of a transform's centre, radii,
surface projection, and true outward normal (derived, not authored, per R6 --
no second copy of numbers the physical MeshCollider already owns), and switch
both consumers onto it, deleting their hand-rolled projections.
Radii read the attached collider mesh's own bounds.extents per axis rather
than assuming a flat 0.5 or 1.0: Shield.prefab's Icosphere collider measures
(0.0095106, 0.01, 0.01), not a uniform round number, and the cubesphere's
collider measures exactly (1,1,1) -- confirmed by a throwaway batchmode probe
against the actual mesh assets, not guessed.
SurfaceNormal is not normalize(p): a non-uniform ellipsoid's true normal is
the gradient of the implicit surface, transformed by rotation and the INVERSE
of lossyScale (a normal is a covector). The first implementation used
TransformDirection (rotation and forward scale) and the batchmode probe's
finite-difference gradient check caught it immediately -- exactly the
plausible-looking-wrong-answer failure mode the cut map called out by name.
Both original consumers keep normalize(p)-equivalent behaviour via the new
SurfaceDirection method, since they only ever wanted a direction.
FieldDriver falls back to its pre-cut formulas (logged once) when no
ShieldEnvelope is present, since it has no guaranteed carrier yet.
ShieldManager does not: every object that carries it gets an envelope in this
cut, so a missing one logs an error and degrades to a fixed direction rather
than recomputing a second projection.
No prefab or scene file is touched here (Q5: the operator rigs prefabs and
scenes by hand); ShieldPanelCut2Verify's batchmode probe instead builds
synthetic transforms carrying Longinus's and Djinni's actual override values
plus the real Icosphere mesh, and proves ProjectToSurface lands exactly on the
ellipsoid and SurfaceNormal matches a finite-difference gradient to 1e-3.
mutation_tests_shield_panel_cut2.py mutates SurfaceNormal three ways (back to
normalize(p), back to TransformDirection, and a flattened 0.5 extents factor)
and confirms the probe goes red for each, naming the reason.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:33:38 +02:00
|
|
|
var envelopeRadii = EnvelopeRadiiOrFallback();
|
|
|
|
|
_field.SetVector("_InverseScale", new Vector4(1/envelopeRadii.x,1/envelopeRadii.y,1/envelopeRadii.z));
|
2025-01-30 01:58:05 +00:00
|
|
|
_field.SetMatrix("_ReflRotate", refractionRotation);
|
|
|
|
|
|
2022-08-08 22:43:55 +01:00
|
|
|
for (int i = 0; i < _hits.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
var hit = _hits[i];
|
|
|
|
|
hit.Time += Time.deltaTime / MagnitudeTimeScaling.Evaluate(_hits[i].Magnitude);
|
|
|
|
|
_hits[i] = hit;
|
|
|
|
|
if (hit.Time > 1)
|
|
|
|
|
{
|
|
|
|
|
_hits.RemoveAt(i);
|
|
|
|
|
i--;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-30 01:58:05 +00:00
|
|
|
_hitBuffer.SetData(_hits);
|
|
|
|
|
_field.SetBuffer("_Hits", _hitBuffer);
|
|
|
|
|
_field.SetInt("_HitCount", _hits.Count);
|
2022-08-08 22:43:55 +01:00
|
|
|
|
|
|
|
|
if (_meleeActive)
|
|
|
|
|
{
|
|
|
|
|
_meleeTime += Time.deltaTime / MeleeDuration;
|
2025-01-30 01:58:05 +00:00
|
|
|
if (_meleeTime < 1)
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_field.SetVector("_MeleeDirection", Quaternion.AngleAxis(sign(_meleeTime*2-1) * MeleeAngle * pow(abs(_meleeTime * 2 - 1), MeleeAngleExponent), Vector3.up) * -Vector3.forward);
|
|
|
|
|
_field.SetFloat("_MeleeDisplacement", pow(1 - 2 * abs(_meleeTime - .5f), MeleeRangeExponent) * MeleeRange);
|
2025-01-30 01:58:05 +00:00
|
|
|
_field.SetFloat("_MeleeShape", MeleeShaping);
|
|
|
|
|
_field.SetFloat("_MeleeFlattening", MeleeFlatness);
|
|
|
|
|
}
|
|
|
|
|
else
|
2022-08-08 22:43:55 +01:00
|
|
|
{
|
|
|
|
|
_meleeActive = false;
|
2025-01-30 01:58:05 +00:00
|
|
|
_field.SetFloat("_MeleeDisplacement", 0);
|
2022-08-08 22:43:55 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
|
2026-10-13 18:43:15 +02:00
|
|
|
if (_grabbing && _grabPhase != GrabPhase.Started)
|
2026-08-10 14:57:50 +01:00
|
|
|
{
|
2026-10-13 18:43:15 +02:00
|
|
|
_grabTime = min(1, _grabTime + Time.deltaTime / max(_phaseDuration, 1e-4f));
|
|
|
|
|
_tendrilBasePos = damp(_tendrilBasePos.ToCultMath(), _tendrilTargetPos.ToCultMath(), TendrilBaseDamping, Time.deltaTime).ToUnity();
|
|
|
|
|
switch (_grabPhase)
|
2026-08-10 14:57:50 +01:00
|
|
|
{
|
2026-10-13 18:43:15 +02:00
|
|
|
case GrabPhase.Extend:
|
|
|
|
|
_tendrilBendTarget = _grabStart;
|
|
|
|
|
_tendrilTargetPos = lerp(
|
|
|
|
|
lerp(_grabStartTendrilBase.ToCultMath(), _grabStart.ToCultMath(), _grabTime),
|
|
|
|
|
_grabStart.ToCultMath(),
|
|
|
|
|
pow(_grabTime, TendrilExtensionExponent)).ToUnity();
|
|
|
|
|
_field.SetFloat("_TendrilInfluence", pow(_grabTime, TendrilExtendBaseAnimationExponent));
|
|
|
|
|
_field.SetFloat("_TendrilSize", lerp(TendrilBaseRadius/2,TendrilBaseRadius,pow(_grabTime, TendrilExtendBaseAnimationExponent)));
|
|
|
|
|
_field.SetFloat("_TendrilRadius", TendrilTipScale * pow(_grabTime, TendrilTipRadiusAnimationExponent) * TendrilTipRadius);
|
|
|
|
|
break;
|
|
|
|
|
case GrabPhase.Envelop:
|
|
|
|
|
_tendrilBendTarget = _grabStart;
|
|
|
|
|
_tendrilTargetPos = _grabStart;
|
|
|
|
|
_field.SetFloat("_TendrilInfluence", 1);
|
|
|
|
|
_field.SetFloat("_TendrilSize", TendrilBaseRadius);
|
|
|
|
|
_field.SetFloat("_TendrilRadius", TendrilTipScale * TendrilTipRadius);
|
|
|
|
|
break;
|
|
|
|
|
case GrabPhase.Pull:
|
2026-10-13 22:25:49 +02:00
|
|
|
// The curve of Zone's held pose (Zone.HeldPull): the tip rides the item the simulation pulls in.
|
|
|
|
|
_tendrilTargetPos = lerp(_grabStart.ToCultMath(), transform.position.ToCultMath(), Zone.HeldPull(_grabTime)).ToUnity();
|
2026-10-13 18:43:15 +02:00
|
|
|
_tendrilBendTarget = _grabStart;
|
|
|
|
|
if (_fadePoint > .99 && transform.InverseTransformPoint(_tendrilTargetPos).sqrMagnitude < 1)
|
|
|
|
|
_fadePoint = _grabTime;
|
|
|
|
|
_field.SetFloat("_TendrilInfluence", pow(smoothstep(1, _fadePoint, _grabTime), TendrilFadeExponent));
|
|
|
|
|
_field.SetFloat("_TendrilSize", TendrilBaseRadius);
|
|
|
|
|
_field.SetFloat("_TendrilRadius", TendrilTipScale * TendrilTipRadius);
|
|
|
|
|
break;
|
2026-08-10 14:57:50 +01:00
|
|
|
}
|
2026-10-13 18:43:15 +02:00
|
|
|
var tendrilRadii = EnvelopeRadiiOrFallback();
|
|
|
|
|
_field.SetVector("_TendrilBase", Vector3.Scale(transform.InverseTransformPoint(_tendrilBasePos).normalized, tendrilRadii));
|
|
|
|
|
_field.SetVector("_TendrilBend", Vector3.Scale(transform.InverseTransformPoint(_tendrilBendTarget), tendrilRadii));
|
|
|
|
|
_field.SetVector("_TendrilTarget", Vector3.Scale(transform.InverseTransformPoint(_tendrilTargetPos), tendrilRadii));
|
2026-08-10 14:57:50 +01:00
|
|
|
}
|
2025-01-30 01:58:05 +00:00
|
|
|
}
|
2026-10-13 18:43:15 +02:00
|
|
|
|
2025-01-30 01:58:05 +00:00
|
|
|
private float almostIdentity( float x )
|
|
|
|
|
{
|
|
|
|
|
return x*x*(2.0f-x);
|
|
|
|
|
}
|
2022-08-08 22:43:55 +01:00
|
|
|
|
2025-01-30 01:58:05 +00:00
|
|
|
private float smooth(float t)
|
|
|
|
|
{
|
|
|
|
|
return t * t * (3.0f - 2.0f * t);
|
|
|
|
|
}
|
2022-08-08 22:43:55 +01:00
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
private const int GIZMO_STEPS = 16;
|
|
|
|
|
private void OnDrawGizmosSelected()
|
2025-01-30 01:58:05 +00:00
|
|
|
{
|
2026-10-13 18:43:15 +02:00
|
|
|
if (_grabbing)
|
2026-08-10 14:57:50 +01:00
|
|
|
{
|
|
|
|
|
Vector3 previous = _tendrilBasePos;
|
|
|
|
|
for (int i = 1; i <= GIZMO_STEPS; i++)
|
|
|
|
|
{
|
|
|
|
|
var l = (float)i / GIZMO_STEPS;
|
Delete AetheriaMath in favour of CultMath
AetheriaMath predates the CultMath swap and re-implemented helpers CultMath
now owns. Callers use CultMath directly: decay (Ship, AetherDrive, Entity),
damp (VelocityConversion, FieldTester, FieldDriver), first_order_intercept
(TurretController, Combat, GuidedProjectile), quadratic_bezier (FieldDriver)
and smootherstep (Ship). Parameter order matches in every case, and those
formulas have the same float evaluation order, so behaviour is unchanged.
Galaxy's commented-out segment check names distance_to_segment.
The other overloads had no callers and are dropped without a CultMath
counterpart being needed: the double Damp overloads, FindDistanceToSegment,
GetCatmullRomPosition, GetCubicSplinePosition, Smoothstep(x), and the
Deg2Rad/Rad2Deg constants.
AetherDb census, factions and hardpoint-fit are byte-identical to the
pre-cut captures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:02:41 +02:00
|
|
|
var next = quadratic_bezier(_tendrilBasePos.ToCultMath(), _tendrilBendTarget.ToCultMath(), _tendrilTargetPos.ToCultMath(), l).ToUnity();
|
2026-08-10 14:57:50 +01:00
|
|
|
Gizmos.DrawLine(previous, next);
|
|
|
|
|
previous = next;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-30 01:58:05 +00:00
|
|
|
}
|
2022-08-08 22:43:55 +01:00
|
|
|
|
2025-01-30 01:58:05 +00:00
|
|
|
private void OnDestroy()
|
|
|
|
|
{
|
|
|
|
|
_hitBuffer.Dispose();
|
2022-08-08 22:43:55 +01:00
|
|
|
}
|
|
|
|
|
}
|