Map: Controls section, with prior art, body facts and the authority map for the facing-separate-from-aim rebuild

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-06 19:41:16 +02:00
commit e1296f1fac

View file

@ -3187,3 +3187,229 @@ This is a default, not a ruling. The operator can ask for calibres at any time.
is full (Q6a A), because pickups were Unity-only (mining-cut.md:286-287). After
`loot-1`, overflow could float through `Zone.Release` like any other loot. Follow-up
`mining-cuts-4-5-7` carries the question to that map.
## Controls
The control rebuild of ruling `facing-separate-from-aim`, which ruling
`demo-controls-rebuild` puts before the demo. The pilot steers the hull's facing with
its own input, the cursor aims, fixed mounts fire when the aim is inside their arc and
turrets track. Articulated mounts (follow-up `articulated-mounts`, ruling
`arc-and-traverse-on-the-link`) are full game; this section keeps every arc read behind
the seam they will plug into. Anchors are against `origin/master` `228f241e`, read in a
detached scratch worktree on 2026-10-06.
### Prior art
Sources are marked: **primary** (the game's own source, manual or developer post, read
in full), **wiki** (a community wiki page read in full), **snippet** (a search-result
summary only, not read at source; treat as unverified).
- **Starsector** (wiki: the fandom `Piloting` page, read through the MediaWiki API; the
Fractal Softworks manual PDF could not be fetched). WASD is ship-relative: A and D
turn, Ctrl-A and Ctrl-D strafe. Holding Shift makes the ship "automatically point
towards the mouse"; a setting makes that the default. The mouse aims the selected
weapon group, which tracks the cursor and fires on the left button. Shift+1-5 puts a
group on autofire: those weapons pick and attack their own targets, and the HUD marks
the group with a filled square. R designates the ship under the mouse, and weapons
"preferentially fire at the target". The wiki's own advice is to put everything except
missiles on autofire so that the mouse governs only shields. That advice is the
community's answer to the core failure of a cursor-aimed, arc-limited model: holding
the cursor inside a narrow arc while manoeuvring is hard (snippet, forum). Vanilla
draws weapon arcs, and a popular mod redraws them (snippet).
- **Endless Sky** (primary: `source/Preferences.cpp` and `PreferencesPanel.cpp` on
`master`). Fixed guns fire along the hull; turrets track. Player options: "Automatic
aiming" off / always on / when firing (default when firing), "Automatic firing" off /
on / guns only / turrets only (default off), "Aim turrets with mouse", "Turrets focus
fire" (default on), "Control ship with mouse", and "Turret overlays" off / always on /
blindspots only (default blindspots only). So the arc display defaults to showing only
where the guns cannot reach.
- **Escape Velocity Nova** (primary: the Nova Bible, weapon `Guidance` field). Keyboard
turning; fixed guns fire ahead. Guidance 7 is a "front-quadrant turret" that can fire
plus or minus 45 degrees off the nose and "fires straight ahead if no target";
guidance 8 is the rear quadrant. A designated target pulls a limited-arc turret's
rounds onto it, and without one the mount fires down its axis. This is the closest
precedent for Aetheria's designation-based fire control.
- **Cosmoteer** (primary: developer blog, "Cosmoteer 0.14.11 - Arcade Style Ship
Controls"). Direct Control: W and S thrust, A and D rotate, Q and E strafe; the mouse
aims and fires. Turreted weapons fire on the left button and fixed weapons on the right
by default, rebindable per weapon. Rotating to face the cursor is an opt-in setting.
- **Nebulous: Fleet Command** (snippet). Weapons engage designated targets on their own
within their arcs; ships auto-orient to bring mounts to bear. Its known failures are
arc failures: narrow-arc guns idle while the ship searches for an angle that brings
every turret to bear, and turrets report "target masked by hull".
- **Battlestar Galactica Deadlock** (snippet). Broadside, bow and stern batteries have
about 90-degree arcs; dorsal and ventral guns cover 360 degrees with limited elevation.
Arcs show when a turret is clicked in the radial menu, and a key toggles them on the
selected ship. Players asked for more visible arcs (a Steam thread title).
- **SubSpace and Continuum** (snippet). Arrow keys rotate, up thrusts, guns fire along
the nose: the pure nose-aim model this ruling retires.
- **World of Warships** (snippet), added because Aetheria's camera is a chase camera, not
top-down (body fact C7). The mouse orbits the camera and aims; the keyboard is the helm.
Guns traverse toward the aim at a turret traverse speed. The classic complaint is "the
guns won't fire though the view looks on target": the turret has not finished
traversing or cannot bear, and the reticle's gun markers carry that state.
**Failure modes to design against.**
1. "My guns don't fire": the aim or target is outside a mount's arc, or a turret has not
traversed yet, and nothing on screen says so (Starsector forum, Nebulous, World of
Warships). Answer: the HUD shows each group's state where the eye already is (the
group crosshair and the arc fan), and per-group autofire exists for players who would
rather manoeuvre.
2. Holding the cursor inside a narrow arc while also flying the ship is hard (Starsector).
Answer: a hold-to-face modifier for nose guns, and autofire for side guns.
3. Over-eager auto-orientation fights the player (Nebulous ships hunting for an angle).
Answer: the player's hull never turns itself; only an AI hull turns to bring a group to
bear, and it chooses one group.
4. Arcs drawn everywhere become clutter. Answer: draw only arcs below 360 degrees, the
Endless Sky "blindspots only" default.
### Body facts
- **C1. The input layout.** `Assets/Resources/Aetheria.inputactions`, map Player: `Move`
(Vector2: W/S as y, A/D as x, gamepad left stick), `Look` (pointer delta, right stick),
and `Turn` (1D axis, Q negative, E positive), which no script reads. Fire is the action
bar: `ActionGameManager.cs:101-103` binds the left, right and middle mouse buttons to
action-bar slots, and `ActionBarWeaponGroupBinding.Activate` (`ActionBarSlot.cs:189-195`)
activates every weapon in its group. No Shift binding exists in the Player or Global maps.
- **C2. The player's steering is the aim.** `ActionGameManager.cs:1277-1281`: the pointer
delta (cursor locked, `:648`) integrates `_entityYawPitch`, which becomes
`_viewDirection`, which is written to `CurrentEntity.LookDirection`. `:1290` writes
`Move` to `ship.MovementDirection` (x strafe, y thrust).
- **C3. `LookDirection` is the facing command.** `Ship.Update` (`Ship.cs:278-291`) sets
`deltaRot = dot(look, right)`, shapes it `sqrt(|d|) * sign(d)`, feeds it to the rotation
thrusters and the aether drives' `Axis.z`, and below `|d| < .01` writes
`Direction = lerp(Direction, look, ...)` directly. So aim and facing are one field.
- **C4. Turning is kinematic.** No angular velocity exists. A thruster rotates
`Entity.Direction` by `input * Torque * Thrust * TorqueMultiplier / Mass * dt`
(`Thruster.cs:106-107`); an aether drive by `force.z * axis.z * AetherTorqueMultiplier /
Mass` (`AetherDrive.cs:154-155`). `Settings.asset` (LFS): `TorqueFloor 0.5`,
`TorqueMultiplier 0.1`, `AetherTorqueMultiplier 0.1`. `Ship.TurnTime` (`Ship.cs:61-66`)
estimates a turn. Turn rate is therefore torque over mass times one global multiplier.
- **C5. FireControl's arc.** `ArcFor` (`FireControl.cs:36-41`) is
`HardpointData.FiringArc` (`ItemData.cs:569`, key 6) when above zero, else
`GameplaySettings.FiringArc` (`Settings.cs:214`, initializer 120; the field is absent
from `Settings.asset`, so 120 holds). `InArc` (`:50-58`) compares a planar bearing with
`MountDirection` (hull `Direction` rotated by the item's rotation); 360 or more passes,
and a bearing under 1e-6 passes. `AimDirection` (`:116-126`) clamps `LookDirection` to
the arc's nearer edge. The Turret hull's hardpoints author 360 (`AetherDb
firing-arc-migrate`, `Program.cs:1037`).
- **C6. The trigger gate reads the target, not the aim.** `ArcPermitsFire` (`:76-88`):
with no target it passes; with a target it tests the target's bearing, and a fused
weapon passes anyway (operator ruling 2026-09-30, `fire-control-cut.md:2628-2640`).
`PFire` (`:256-270`) prices a shot at zero out of arc. `Solve` (`:440-473`) flies a
round to the target's intercept when it is engaged (`TravelDirection`, `:396-400`), and
along `AimDirection` otherwise. So the cursor today decides only facing, reticle
targeting (`Entity.TargetUnderReticle`, `Entity.cs:407-412`), no-lock fused flight,
the lock cone (`LockWeapon.cs:100`), the tractor (`ShipInstance.cs:116`), guided
rounds' aim point (`GuidedProjectileManager.cs:79`) and loot's view direction
(`ZoneRenderer.cs:440`).
- **C7. The camera is a chase camera that looks at the aim.** `FollowCamera` follows the
ship and looks at `EntityInstance.LookAtPoint`, set each frame to ship position plus
`LookDirection` times the target range, or 10,000 with no target
(`EntityInstance.cs:401-402`, `ActionGameManager.cs:1039-1040`). The scene's two
framing transposers (`ARPG.unity:4963`, `:21276`) position the camera and do not rotate
it, so the camera's heading comes from the aim and survives the split. Which of the two
belongs to `FollowCamera` was not resolved.
- **C8. What the HUD draws.** `UpdateTargetIndicators` (`ActionGameManager.cs:1390-1422`):
`ViewDot` at `LookAtPoint`; one crosshair per articulation group at the mean of its
barrels' forward rays (`_articulationGroups`, `:1041-1057`, grouped by the barrel's
`ArticulationPoint.Group`); the target indicator; lock indicators. `Update` fills the
target panel's bars and `UpdateFireControlDebug` (`:1319-1380`) prints the fire-control
gates as text, including `arc {d.InArc}`. No arc, no lead marker and no predicted path
is drawn.
- **C9. The barrel picture has its own arc.** `ArticulationPoint` (Unity) slews a barrel
toward `Target` (every point's target is `LookAtPoint`, `EntityInstance.cs:278-283`)
at `Speed` and clamps yaw to `YawMin..YawMax`, authored by `ShipPrefabAuthoring.cs:141-148`
and unrelated to `FiringArc`. Package hulls have none (`ShipModShips.cs:164`): their
weapons are points, so every package-hull weapon lands in group -1 and one crosshair.
- **C10. AI writers treat `LookDirection` as facing.** `Agent.Accelerate`
(`Agent.cs:59-79`) writes it to turn toward the velocity error. `Combat` (`:98-128`)
calls `Accelerate(noTurn: true)` and then writes `LookDirection = toTarget` (the
intercept), which turns the nose to the target. `MoveTo.cs:26` writes it.
`TurretController.cs:74-77` writes it; a turret hull has no rotation thrusters, so it
only aims. Faction-play r1's new `FollowState` "looks at the anchor" and `FleeState`
flies through `Accelerate`, so they inherit the same assumption; their r2 (follow-up
`faction-play-reanchor-demo`) must use the helm port below.
- **C11. The Arcs scenario assumes nose aim.** `Scenarios/Arcs.cs`: forward mounts, a bare
hull off the bow and one off the stern, a hostile 360 turret. Its check "nothing fires at
the stern one through the ship" could only be reached by turning, because turning the
view turned the hull. Checklist item 11 (`merge-to-master-checklist.md:49`) also wants
side mounts firing abeam.
- **C12. Weapon groups persist.** `Entity.WeaponGroups` (`Entity.cs:69`) is saved as
`EntityPack` key 16 (`EntitySerializer.cs:189`) and `Loadout` key 3 (`Loadout.cs:19`).
### Authority map
- **Owner.** `Ship` owns facing: `Ship.Update` turns the hull from one demand,
`Ship.Turn` (-1..1, positive clockwise), and from nothing else. `Entity.Aim`
(`LookDirection` renamed) owns where the pilot points. `FireControl` owns every mount
question: its arc (`ArcFor`), its axis (`MountDirection`), whether its trigger is free
(`ArcPermitsFire`), and the one direction its next round flies (`MountAim`, new).
- **Inputs.** The player's `Turn` axis and hold-to-face button, the pointer delta, the AI
states' chosen headings, the hardpoint's `FiringArc` and item rotation, the designated
target.
- **Outputs.** Hull rotation through the thrusters and drives; the trigger gate; each
round's flight direction; the facts the HUD draws.
- **Derived state.** `Steering.Toward(ship, heading)` derives a turn demand from a
heading (the C3 law, moved). The barrel picture, the group crosshairs, the arc fans,
the lead marker and the predicted path are display-only derivations of FireControl and
`Ship.Coast`.
- **Demotions.** `LookDirection` is no longer an owner of facing; it is renamed `Aim` and
steers nothing. `ArticulationPoint` is no longer an owner of arcs; its yaw derives from
`MountAim`. The barrel transforms are no longer the source of the group crosshair.
- **Forbidden writers.** Any write of `Entity.Direction` outside the thrusters, the aether
drives, wormhole exit, load and staging (the `lerp` snap in `Ship.Update` goes). Any
read of `Aim` in `Ship.Update`. Any read of `FiringArc` outside `FireControl.ArcFor`,
hardpoint validation and AetherDb. Any arc, bearing or intercept arithmetic in
`Assets/Scripts/Gameplay` or `UI`.
- **Shared paths.** The player and every AI state reach the hull only through `Ship.Turn`;
player and AI rounds leave through the same `MountAim`; the HUD, the barrel picture and
`Solve` read the same `MountAim`.
- **Deletion line.** Before any new behaviour: the facing block of `Ship.Update`
(`Ship.cs:278-291`) is replaced, the `LookDirection` field is renamed so that every old
writer fails to compile until it chooses aim or helm, and `ArticulationPoint`'s yaw
clamp is deleted.
### Model page rows
| Kind | Identity | Lifecycle | Authority |
|---|---|---|---|
| Turn demand (`Ship.Turn`) | Per ship, runtime only. | Written every frame by the player input or the ship's agent; never saved. | The pilot (input or agent), through `Steering.Toward` for headings. |
| Aim (`Entity.Aim`) | Per entity, runtime only. | Written every frame by the player's view or the agent; never saved (as `LookDirection` was not). | The pilot. |
| Mount aim | Derived per weapon from aim, target, arc and mount. | Computed on read. | `FireControl.MountAim`. |
| Group autofire flag (if question `controls-autofire` rules yes) | Per weapon group of an entity. | Set by the player; saved beside `WeaponGroups` in `EntityPack`. | The player. |
| Arc-display preference | `PlayerSettings` player store. | Edited by a toggle; persists. | The player. |
### Rationale
**Why rename `LookDirection`.** A rename makes every old writer stop compiling until
someone decides whether it meant aim or helm. Keeping the name would leave AI states that
mean "turn there" compiling as "aim there", silently, which is exactly the split-brain the
rebuild removes. The Gameplay and test call sites are mechanical.
**Why a turn demand rather than a desired heading.** The player's keys command a rate;
the AI and the hold-to-face button command a heading. A demand is the narrower port: a
heading converts to it through one function (`Steering.Toward`), and a rate needs no
conversion. A heading port would force the keys to invent a far-off heading.
**Why the cursor gates and the target lands.** The ruling's words make the aim the
trigger gate: a fixed mount fires when the aim is inside its arc. FireControl's lock
model, its hit pricing and its tests are built on a designated target. Question
`controls-target-pulls-rounds` asks whether a designated target inside the arc pulls the
rounds and turrets onto it (Escape Velocity's front-quadrant turret), or whether rounds
always fly along the clamped aim, which would reopen FireControl's pricing.
**Why traverse is not simulated in the demo.** Traverse speed is a stat of the moving
part (ruling `arc-and-traverse-on-the-link`), and that model is full game. `MountAim` is
stateless now; articulated mounts make it stateful behind the same signature, and every
caller (Solve, the trigger, the HUD, the barrel) already reads it, so nothing else moves.
**Why the predicted path is a coast.** It answers "where does this hull go if I let go",
which is the question gravity wells and slower turns make hard. It reuses the hull's own
drag and gravity step (extracted to `Ship.Coast`), so the line cannot disagree with the
simulation. Thrust is not projected: it changes every frame.
**Cut order.** `controls-helm` first: the split itself, and the rename forces every writer
to be ported in the same cut. `controls-mount-aim` second: the trigger and the one round
direction. Then `controls-hud`, `controls-autofire` and `controls-ai-bearing`, which are
independent of each other.