Merge controls-helm: Ship.Turn is the only facing command; the helm split and face-the-aim
Soul gate closed at verdict cut-controls-helm.s3 (head b644fceb). Merged
under operator ruling aetheria-release:ruling:completed-cuts-merge.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
3fcdc20c00
34 changed files with 950 additions and 129 deletions
|
|
@ -46,6 +46,9 @@ public class Agent
|
|||
|
||||
public void Update(float delta)
|
||||
{
|
||||
// The pilot tick writes Turn every tick, in every state: a state that pilots overwrites this zero with its
|
||||
// demand, and one that does not leaves the hull holding course instead of inheriting the last state's rate.
|
||||
Ship.Turn = 0;
|
||||
_currentState.Update(delta);
|
||||
foreach (var transition in _currentState.Transitions)
|
||||
if (transition.Condition())
|
||||
|
|
@ -63,10 +66,10 @@ public class Agent
|
|||
// If Delta V is above the threshold, direct the ship towards the delta and use only main thrusters
|
||||
if (!noTurn && deltaVMag > FORWARD_DELTA_THRESHOLD)
|
||||
{
|
||||
Ship.LookDirection = float3(deltaVDirection.x, 0, deltaVDirection.y);
|
||||
Ship.Turn = Steering.Toward(Ship, deltaVDirection);
|
||||
Ship.MovementDirection = float2(0, pow(dot(Ship.Direction, deltaVDirection), 2));
|
||||
}
|
||||
// If Delta V is low, direct the ship towards the target and use all thrusters
|
||||
// If Delta V is low, use all thrusters and leave Turn to the caller (MoveTo already pointed it at the target)
|
||||
else if(deltaVMag > THRUST_DELTA_THRESHOLD)
|
||||
{
|
||||
var right = Ship.Direction.Rotate(ItemRotation.Clockwise);
|
||||
|
|
|
|||
|
|
@ -124,7 +124,8 @@ public class CombatState : BaseState
|
|||
weapon.Deactivate();
|
||||
}
|
||||
}
|
||||
_agent.Ship.LookDirection = toTarget;
|
||||
_agent.Ship.Aim = toTarget;
|
||||
_agent.Ship.Turn = Steering.Toward(_agent.Ship, toTarget.xz);
|
||||
_agent.Accelerate(movementDirection * _agent.TopSpeed, true);//selectedGroup >= 0);
|
||||
|
||||
// Fire charged guns!
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ public abstract class MoveToState : BaseState
|
|||
|
||||
// We want to go top speed in the direction of our target
|
||||
var desiredVelocity = dir * _agent.TopSpeed;
|
||||
_agent.Ship.LookDirection = float3(dir.x, 0, dir.y);
|
||||
_agent.Ship.Aim = float3(dir.x, 0, dir.y);
|
||||
_agent.Ship.Turn = Steering.Toward(_agent.Ship, dir);
|
||||
_agent.Accelerate(desiredVelocity);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ public class LockWeapon : InstantWeapon
|
|||
DirectionImpact = Evaluate(_data.DirectionImpact);
|
||||
Decay = Evaluate(_data.Decay);
|
||||
|
||||
var degrees = acos(dot(normalize(target.Position - Entity.Position), normalize(Entity.LookDirection))) * 57.2958f;
|
||||
var degrees = acos(dot(normalize(target.Position - Entity.Position), normalize(Entity.Aim))) * 57.2958f;
|
||||
if (degrees < LockAngle)
|
||||
{
|
||||
var lerp = 1 - unlerp(0, 90, degrees);
|
||||
|
|
|
|||
|
|
@ -71,10 +71,10 @@ public class TurretController : Behavior, IInitializableBehavior
|
|||
target.Position, targetVelocity
|
||||
);
|
||||
predictedPosition.y = Entity.Zone.GetHeight(predictedPosition.xz) + targetHullData.GridOffset;
|
||||
Entity.LookDirection = normalize(predictedPosition - Entity.Position);
|
||||
Entity.Aim = normalize(predictedPosition - Entity.Position);
|
||||
}
|
||||
else
|
||||
Entity.LookDirection = normalize(diff);
|
||||
Entity.Aim = normalize(diff);
|
||||
|
||||
foreach (var x in _weapons)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ public abstract class Entity
|
|||
// notice and clear it.
|
||||
public ReactiveProperty<EquippedItem> TargetItem = new ReactiveProperty<EquippedItem>((EquippedItem)null);
|
||||
|
||||
public float3 LookDirection;
|
||||
// Where the pilot points: reticle, lock cone, tractor, guided rounds, fire direction. Steers nothing; the
|
||||
// hull turns only by Ship.Turn.
|
||||
public float3 Aim;
|
||||
|
||||
public string Name;
|
||||
|
||||
|
|
@ -406,7 +408,7 @@ public abstract class Entity
|
|||
// The candidate nearest the look direction by planar angle; picking the held target again clears it.
|
||||
public bool TargetUnderReticle()
|
||||
{
|
||||
var look = float2(LookDirection.x, LookDirection.z);
|
||||
var look = float2(Aim.x, Aim.z);
|
||||
if (!PickTarget(new AngleKey(this, Position.xz, look), out var picked)) return false;
|
||||
return SetTarget(Target.Value.Equals(picked) ? TargetRef.None : picked);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,16 +107,16 @@ public static class FireControl
|
|||
}
|
||||
|
||||
// Operator ruling 2026-09-30: a shot with nothing to bear on flies along where its shooter is aiming
|
||||
// (Entity.LookDirection -- the player's mouse or a turret's tracking), restricted by the mount's arc. The
|
||||
// (Entity.Aim -- the player's mouse or a turret's tracking), restricted by the mount's arc. The
|
||||
// arc is InArc's own cone: an aim inside it is used as is, one outside it is turned to the nearest edge of
|
||||
// the same cone. There is no second bearing test here; the edge is where InArc stops passing. A shooter
|
||||
// with no aim set (LookDirection zero) aims down the mount, the same fallback InArc takes for a zero bearing.
|
||||
// with no aim set (Aim zero) aims down the mount, the same fallback InArc takes for a zero bearing.
|
||||
// The simulation had no aim clamp before this: the Unity barrel's ArticulationPoint clamps the picture,
|
||||
// and a target outside the arc merely priced the shot at zero.
|
||||
public static float2 AimDirection(EquippedItem item, Entity shooter)
|
||||
{
|
||||
var mount = MountDirection(item).xz;
|
||||
var look = shooter.LookDirection.xz;
|
||||
var look = shooter.Aim.xz;
|
||||
if (lengthsq(look) < 1e-6f) return mount;
|
||||
look = normalize(look);
|
||||
if (InArc(item, float3(look.x, 0, look.y))) return look;
|
||||
|
|
|
|||
18
Assets/Scripts/ServerShared/HelmInput.cs
Normal file
18
Assets/Scripts/ServerShared/HelmInput.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using CultMath;
|
||||
using static CultMath.math;
|
||||
|
||||
// The player's face-the-aim state, engine-free. Held follows the Face Aim hold input each frame; Latched is
|
||||
// flipped by the Caps Lock press. Either one faces the aim; neither leaves the hull to the turn axis.
|
||||
// Runtime only, never saved: Latched starts false each session.
|
||||
public class HelmInput
|
||||
{
|
||||
public bool Held;
|
||||
public bool Latched;
|
||||
|
||||
public bool FaceAim => Held || Latched;
|
||||
|
||||
public void Toggle() => Latched = !Latched;
|
||||
|
||||
public float Demand(Ship ship, float2 aim, float turnAxis) =>
|
||||
FaceAim ? Steering.Toward(ship, aim) : clamp(turnAxis, -1f, 1f);
|
||||
}
|
||||
2
Assets/Scripts/ServerShared/HelmInput.cs.meta
Normal file
2
Assets/Scripts/ServerShared/HelmInput.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f863bb50e53ae30408f5ef8be1462e12
|
||||
|
|
@ -25,6 +25,9 @@ public class Ship : Entity
|
|||
// [IgnoreMember] public Targetable Target;
|
||||
public Entity HomeEntity;
|
||||
public float2 MovementDirection;
|
||||
// The one facing command, -1..1, positive clockwise. Written each tick by the pilot (player input or the
|
||||
// ship's agent); a heading becomes a demand only through Steering.Toward. Runtime only, never packed.
|
||||
public float Turn;
|
||||
public bool IsPlayerShip;
|
||||
|
||||
private HashSet<EquippedItem> _thrusterItems;
|
||||
|
|
@ -275,20 +278,11 @@ public class Ship : Entity
|
|||
foreach (var thruster in _forwardThrusters) thruster.Axis += MovementDirection.y;
|
||||
foreach (var thruster in _reverseThrusters) thruster.Axis += -MovementDirection.y;
|
||||
|
||||
var look = normalize(LookDirection.xz);
|
||||
var deltaRot = dot(look, normalize(Direction).Rotate(ItemRotation.Clockwise));
|
||||
if (abs(deltaRot) < .01f)
|
||||
{
|
||||
deltaRot = 0;
|
||||
Direction = lerp(Direction, look, min(delta, 1));
|
||||
}
|
||||
deltaRot = pow(abs(deltaRot), .5f) * sign(deltaRot);
|
||||
|
||||
foreach (var thruster in _clockwiseThrusters) thruster.Axis += deltaRot;
|
||||
foreach (var thruster in _counterClockwiseThrusters) thruster.Axis += -deltaRot;
|
||||
foreach (var thruster in _clockwiseThrusters) thruster.Axis += Turn;
|
||||
foreach (var thruster in _counterClockwiseThrusters) thruster.Axis += -Turn;
|
||||
|
||||
foreach (var drive in _aetherDrives)
|
||||
drive.Axis = float3(MovementDirection.y, MovementDirection.x, deltaRot);
|
||||
drive.Axis = float3(MovementDirection.y, MovementDirection.x, Turn);
|
||||
}
|
||||
|
||||
var velocityMagnitude = length(Velocity);
|
||||
|
|
|
|||
24
Assets/Scripts/ServerShared/Steering.cs
Normal file
24
Assets/Scripts/ServerShared/Steering.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using CultMath;
|
||||
using static CultMath.math;
|
||||
|
||||
// The one law that turns a wanted heading into a facing command. Ship.Update reads only Ship.Turn; whoever
|
||||
// wants the hull to face somewhere (the player's face-the-aim state, an agent state) asks here and writes Turn.
|
||||
public static class Steering
|
||||
{
|
||||
const float Deadband = .01f;
|
||||
|
||||
// Positive is clockwise. Lateral component of the heading with sqrt shaping and a deadband; a heading
|
||||
// behind the hull turns at full demand (its lateral component alone would vanish at exactly astern).
|
||||
// A zero or non-finite heading holds course.
|
||||
public static float Toward(Ship ship, float2 heading)
|
||||
{
|
||||
if (!float.IsFinite(heading.x) || !float.IsFinite(heading.y) || lengthsq(heading) < 1e-12f) return 0f;
|
||||
var dir = normalize(ship.Direction);
|
||||
var wanted = normalize(heading);
|
||||
var d = dot(wanted, dir.Rotate(ItemRotation.Clockwise));
|
||||
if (dot(wanted, dir) < 0f) return d < 0f ? -1f : 1f;
|
||||
if (abs(d) < Deadband) return 0f;
|
||||
return sqrt(abs(d)) * sign(d);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/ServerShared/Steering.cs.meta
Normal file
2
Assets/Scripts/ServerShared/Steering.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8b84ac5db42877d45b7f6286cc2e2b93
|
||||
Loading…
Reference in a new issue