Map: sim speed r2, the census of Unity-owned sim facts and their owners, body facts T11-T14, drawing ahead
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6d427b7aa9
commit
35ea0da431
1 changed files with 259 additions and 191 deletions
|
|
@ -4797,11 +4797,19 @@ its absence; the demo cast hulls (`demo-cast-hulls`) meet it first.
|
|||
| `parked/aether-drive` tag | The tag name; its message is the park note. | Fixed at the retirement's base; read by the rework, never merged back. | The operator's rework, after the thruster meta. |
|
||||
## Sim speed
|
||||
|
||||
Rulings `sim-speed-knob` and `sim-speed-presentation`, for the pace named in
|
||||
`pace-after-primitives`. Anchors are against `origin/master` `516af3e5`, read on
|
||||
2026-10-07 in the read-only worktree `C:\ar-retire-map`. `controls-helm` r2 is in flight
|
||||
on `ActionGameManager.cs`, `EntityInstance.cs` and `ShipInstance.cs`, so line numbers
|
||||
there move when it lands.
|
||||
Rulings `sim-speed-knob`, `sim-speed-presentation`, `sim-speed-surface`, `sim-speed-tenth`,
|
||||
`tick-rate-follows-design-speed` and `unity-presents-only-r3`, for the pace named in
|
||||
`pace-after-primitives`. The operator: "We don't want Unity having any authority over the sim
|
||||
whatsoever. The sim is authoritative and Unity should only be presenting." Anchors are against
|
||||
`origin/master` `6d427b7a`, read on 2026-10-07 in the read-only worktree `C:\ar-simspeed-map`.
|
||||
`controls-helm` r2 is in flight on `ActionGameManager.cs`, `EntityInstance.cs`, `ShipInstance.cs`
|
||||
and `Ship.cs`, so line numbers there move when it lands. None of `loot-1`..`loot-3`,
|
||||
`missile-records`, `missile-odds`, `missile-presenter` or `munition-shots` has landed at this base.
|
||||
|
||||
The cuts: `sim-speed` r2 (the fixed step and drawing ahead, at speed 1), the census cuts
|
||||
`ballistic-flight`, `sim-contact`, `retire-unity-mines`, `loot-2` r2, `missile-presenter` r3 and
|
||||
`munition-shots` r4, and `sim-speed-scale` r2 (the design speed, the tick rate and the player's
|
||||
factor), which waits on all of them.
|
||||
|
||||
### Body facts
|
||||
|
||||
|
|
@ -4809,214 +4817,274 @@ there move when it lands.
|
|||
in `Assets/Scripts` is `Zone.Update(Time.deltaTime)` at the end of
|
||||
`ActionGameManager.Update` (`Gameplay/ActionGameManager.cs:1311`), inside
|
||||
`if(!_paused)` (`:1244`). `Zone.Update` (`ServerShared/Zone.cs:202-222`) adds dt to
|
||||
`_time`, recomputes orbits and divides their displacement by dt (`:210`, so a zero dt
|
||||
gives an infinite velocity), steps agents, then entities, then `FireControl.Step`.
|
||||
Agents, cooldowns, shields, radiators, stat modifiers, consumables and thrusters all
|
||||
take that dt (`Agent.Update`, `Cooldown.Update`, `Shield.Update`, `Radiator.Update`,
|
||||
`StatModifier.Update`, `Entity.Update` `:1425-1453`). ServerShared reads no Unity
|
||||
clock: its only clock reads are `DateTime.Now` seeds (`ItemManager.cs:20`,
|
||||
`Galaxy.cs:111,167`, `GalaxyStage.cs:49`), which choose seeds and time nothing. This is
|
||||
M15 again: there is no fixed tick, and sim history depends on the frame rate today.
|
||||
- **T2. Nothing touches Unity's time scale in the game.** The one `Time.timeScale` write
|
||||
is the `Time Scale` field of `UI/FieldTester.cs:80`, the shield-field lab scene, where
|
||||
slowing presentation is the point. `ProjectSettings/TimeManager.asset` has Fixed
|
||||
Timestep 0.02 and Maximum Allowed Timestep 0.1; no script has a `FixedUpdate`.
|
||||
- **T3. Unity physics writes one sim fact.** `HullCollider.OnCollisionEnter`
|
||||
(`Gameplay/HullCollider.cs:14-17`) adds `impulse / Time.deltaTime` to
|
||||
`Entity.Velocity`. The shield rigidbody is kinematic (`Content/Prefabs/Shield.prefab`,
|
||||
`m_IsKinematic: 1`) and ships are placed by transform (`EntityInstance.cs:403`), so the
|
||||
impulses that arrive come from non-kinematic loot and mine bodies
|
||||
(`Prefabs/RPG/Pickups/*.prefab`, `Mine Launcher.prefab`). It divides by the frame's dt,
|
||||
not by any step.
|
||||
`_time` (a `double`, `:35`), recomputes orbits and divides their displacement by dt
|
||||
(`:210`, so a zero dt gives an infinite velocity), steps agents, then entities, then
|
||||
`FireControl.Step`. ServerShared reads no Unity clock: its only clock reads are
|
||||
`DateTime.Now` seeds (`ItemManager.cs:20`, `Galaxy.cs:111,167`, `GalaxyStage.cs:49`). There is
|
||||
no fixed tick (M15), and sim history depends on the frame rate today.
|
||||
- **T2. Nothing touches Unity's time scale in the game.** The one `Time.timeScale` write is
|
||||
the `Time Scale` field of `UI/FieldTester.cs:80`, the shield-field lab scene.
|
||||
`ProjectSettings/TimeManager.asset` has Fixed Timestep 0.02 and Maximum Allowed Timestep
|
||||
0.1; no script has a `FixedUpdate`.
|
||||
- **T3. Unity physics writes sim velocity in two places.** `HullCollider.OnCollisionEnter`
|
||||
(`Gameplay/HullCollider.cs:14-17`) adds `impulse / Time.deltaTime` to `Entity.Velocity`;
|
||||
the impulses come from non-kinematic loot and mine bodies. `ShieldManager.OnCollisionEnter`
|
||||
(`Gameplay/ShieldManager.cs:32-81`) decides three facts: an item pickup (`TryStore` on
|
||||
collision, `:42-56`), a mine detonation (`mine.Explode()`, `:57-60`), and the ship-on-ship
|
||||
bounce, an elastic exchange of normal velocity written to `Entity.Velocity` (`:70-79`, with
|
||||
`PostCollisionVelocity` at `:83-86`).
|
||||
- **T4. Unity-owned world objects keep their own clocks.** Loot drift and attraction
|
||||
(`GridObject.cs:37-49`), the tractor pull (`TractorBeam.cs:26`), the player's tractor
|
||||
ramp (`ActionGameManager.cs:1308-1309`, written onto `Entity.TractorPower`, read only
|
||||
by `ShipInstance.cs:115`), and mine arming, lifetime and blast delay (`Mine.cs:46-82`,
|
||||
`Time.time`, ending in `Explode`, which calls `FireControl.Detonate`) are world
|
||||
behaviour that Unity integrates on real time. `loot-1`..`loot-3` move loot into the
|
||||
simulation; follow-up `bodies-entity-and-mines` covers mines. Neither has landed.
|
||||
- **T5. Presenters that integrate or time a sim quantity.** `Projectile.Update`
|
||||
(`Weapons/Projectile.cs:43-50`) flies a round's drawn position by `Time.deltaTime`
|
||||
until Range or the shot's resolution. `ProjectileManager.cs:25` spreads a spawn along
|
||||
the velocity by `Random.value * Time.deltaTime`. `GuidedProjectile` integrates thrust
|
||||
and position by `Time.deltaTime` (`:143,205,210`) and ends its binding when
|
||||
`Time.time - _spawnTime` passes `BindingLifetime` (`:232`), which
|
||||
`GuidedProjectileManager.cs:24` sets from `shot.ArrivalTime - zone.Time`, a sim
|
||||
interval compared against real time. Entities are drawn at their last stepped pose:
|
||||
`EntityInstance.cs:403` sets the position from `Entity.Position`, and
|
||||
`ShipInstance.cs:141` sets the rotation from `Ship.Rotation`.
|
||||
- **T6. Presenters that read sim state and animate in real time.** Charge effects read
|
||||
`Weapon.Charge` (`ParticleChargeEffect.cs:56`). Beams and hitscan flashes
|
||||
(`ConstantLaser.cs`, `ConstantLightning.cs`, `Laser.cs`, `HitscanEffect.cs`) start and
|
||||
stop on sim events and time only their own fades. Shield panels
|
||||
(`GridObject.cs:37-49`), the tractor pull (`TractorBeam.cs:21-27`, a
|
||||
`Physics.SphereCastAll` that pushes `GridObject.Velocity`), the player's tractor ramp
|
||||
(`ActionGameManager.cs:1307-1309`, `Time.deltaTime * 2` onto `Entity.TractorPower`), the
|
||||
loot roll on death (`EntityInstance.cs:293-308`, `UnityEngine.Random`), and the mine
|
||||
(`Weapons/Mine.cs:46-100`: real-time arming, `Physics.OverlapSphere` proximity, range from the
|
||||
source transform, lifetime, blast delay, then `FireControl.Detonate` called from Unity).
|
||||
- **T5. The drawn rounds fly themselves.** `Projectile.Update`
|
||||
(`Weapons/Projectile.cs:43-50`) integrates gravity, drag and position by `Time.deltaTime`
|
||||
and dies at a Unity distance from its start; `ProjectileManager.Fire` (`:16-28`) picks the
|
||||
drawn direction from `barrel.forward` with `UnityEngine.Random` spread, offsets the spawn by
|
||||
`Random.value * Time.deltaTime` and adds the shooter's velocity. The simulation's round
|
||||
(`PendingShot`, `FireControl.cs:563-590`, `:1748-1798`) flies straight from `FireOrigin`
|
||||
along `TravelDirection` at the weapon's velocity, inheriting nothing, and arrives at
|
||||
`ArrivalTime`. FireControl decides every hit (fire control Cuts 3-4); the two flights
|
||||
disagree, and Unity ends its own at a distance it chose.
|
||||
- **T6. The guided round flies itself and times its binding in real time.**
|
||||
`GuidedProjectile` integrates thrust and position by `Time.deltaTime` (`:143,205,210`), dodges
|
||||
with `UnityEngine.Random` (`:118,173`), and ends its binding when
|
||||
`Time.time - _spawnTime` passes `BindingLifetime` (`:116,232`), which
|
||||
`GuidedProjectileManager.cs:24` sets from `shot.ArrivalTime - zone.Time + 1`: a sim interval
|
||||
compared against real time.
|
||||
- **T7. Presenters that read sim state and animate in real time.** Charge effects, beams
|
||||
and hitscan flashes (`ConstantLaser.cs`, `ConstantLightning.cs`, `Laser.cs`,
|
||||
`HitscanEffect.cs`: start and stop on sim events, no physics query), shield panels
|
||||
(`ShieldInterceptor.cs:63-146`), fades (`EntityInstance.cs:363,381`), mount slew
|
||||
(`ArticulationPoint.cs:36-47`), field ripples (`FieldDriver.cs`), the sun, volumes and
|
||||
wave scroll (`Zone Display/*`), HUD blinks, the hit marker, the intro and the death
|
||||
transition (`ActionGameManager.cs:857,1173-1185,1246-1289`) and every menu tween are
|
||||
real-time animation. The gas giant's wave phase reads `Zone.Time`
|
||||
(`ZoneRenderer.cs:503`), a sim quantity read, not integrated. No audio presenter is
|
||||
live: the Wwise path is dead and `audio-1` has not landed.
|
||||
- **T7. Input.** Each frame `ActionGameManager.Update` writes the view, `LookDirection`
|
||||
(`Aim` after `controls-helm`), `MovementDirection` and, after `controls-helm`,
|
||||
(`ArticulationPoint.cs:36-47`), field ripples (`FieldDriver.cs:231-264`), sun, volumes, wave
|
||||
scroll, HUD blinks, the hit marker, the death transition and every menu tween. The gas
|
||||
giant's wave phase reads `Zone.Time` (`ZoneRenderer.cs:503`). `FieldDriver.cs:307-323` moves
|
||||
a grabbed transform itself (loot-3 removes it).
|
||||
- **T8. Input and commands.** Each frame `ActionGameManager.Update` writes the view, `Aim`
|
||||
(`LookDirection` before `controls-helm`), `MovementDirection` and, after `controls-helm`,
|
||||
`Ship.Turn`. Weapon slots call `Activate` and `Deactivate` from input callbacks
|
||||
(`:424-425`); `Weapon.Activate` sets a held flag (`Behaviors/Weapon.cs:149-157`) and
|
||||
`InstantWeapon.Activate` fires at once if it can (`InstantWeapon.cs:285-290`). Commands
|
||||
are state the next step reads; no input integrates anything.
|
||||
- **T8. Pause.** `_paused` (`ActionGameManager.cs:187`) is set by `ToggleFullscreenMenu`
|
||||
(`:728,740`) and gates the whole of `Update`, the step included.
|
||||
- **T9. Saving.** `Zone.Pack` saves `Time` (`Zone.cs:184`), and a zone loads it
|
||||
(`:86`). `PlayerSettings` lives in `player.cc` (`ActionGameManager.cs:68,90`); the
|
||||
Gameplay Settings menu edits `PlayerGameplaySettings` and saves on Back
|
||||
(`UI/MainMenu.cs:262-283`). Designer tuning lives in `GameplaySettings`
|
||||
(`ServerShared/Settings.cs:159`).
|
||||
(`:424-425`). `UI/ClickRaycaster.cs:26` picks drawn objects with `Physics.Raycast`: input
|
||||
choosing a target, not physics deciding a fact. The console's give-item command
|
||||
(`ActionGameManager.cs:531`) is an operator tool.
|
||||
- **T9. Pause and saving.** `_paused` (`ActionGameManager.cs:187`) gates the whole of
|
||||
`Update`, the step included. `Zone.Pack` saves `Time` (`Zone.cs:184`).
|
||||
`PlayerGameplaySettings` (`ServerShared/PlayerSettings.cs:56`) is saved in `player.cc`;
|
||||
designer tuning lives in `GameplaySettings` (`ServerShared/Settings.cs:159`, values in
|
||||
`Assets/Resources/Settings.asset`).
|
||||
- **T10. Headless tests step zones directly** with chosen dts, `0f` included
|
||||
(`FireAuthorityTests.cs:192`, `FireControlCut11Tests.cs:192,225,411`), and
|
||||
`ScenarioTests.Launch` (`:64-72`) builds a staged arena from any scenario, `Duel`
|
||||
included.
|
||||
(`FireAuthorityTests.cs:192`, `FireControlCut11Tests.cs:192,225,411`); three fixtures
|
||||
hard-code 1/60 (`MiningCut1Tests.cs`, `RestoredHullsTests.cs`) and choose their own dt.
|
||||
- **T11. Kinematic contact pairs are on.** `ProjectSettings/DynamicsManager.asset`
|
||||
`m_ContactPairsMode: 1` enables kinematic-kinematic pairs, so the kinematic shield colliders
|
||||
(`Content/Prefabs/Shield.prefab`, `m_IsKinematic: 1`) do raise `OnCollisionEnter`: T3's
|
||||
ship-on-ship bounce is live.
|
||||
- **T12. Which effect prefabs the catalog fires.** A byte search of `GameData/Aetheria.cc`
|
||||
for each effect prefab's GUID (as hex text and as raw bytes) finds ProjectileManager
|
||||
prefabs AC5, 6k Shooter, DeathCluster, prettypretty, ClearPath, ChargeBlast SG,
|
||||
ChargeBlast+- and FastBlast+-, and GuidedProjectileManager prefabs Micromissile, Regular
|
||||
Missile, SRM and scorchedvoid.
|
||||
- **T13. Nothing fires the mine.** The same search finds the Mine Launcher prefab
|
||||
(GUID `24eddacc819f20542962f2395930f261`, the only prefab carrying `MineManager`) zero
|
||||
times. `IntroCutscene` (`ActionGameManager.cs:839-866`), which writes a ship's position,
|
||||
direction and velocity from a real-time coroutine, is never started (its call at `:791` is
|
||||
commented out).
|
||||
- **T14. What depends on step count rather than sim seconds.** Measured by source read for
|
||||
the design-speed step. `Zone._time` is a `double`, so time does not drift with more steps.
|
||||
`Entity.Position` is a `float3` and zones reach `ZoneRadius` 10000 (`Settings.asset`), where
|
||||
a float's spacing is about 0.001 units: a per-step displacement of v * Step rounds to it,
|
||||
so slow movement far from the origin loses precision as the step shrinks (at Step 1/300,
|
||||
5 u/s is about 17 spacings per step, a few percent of error; at 1/60 it was 85). The
|
||||
manoeuvre track (`ManoeuvreTrack.Observe`, evasion-term branch) measures acceleration as
|
||||
the per-step velocity change over dt and calls it changed above `ChangeThreshold` 0.01;
|
||||
float rounding of that difference grows as 1/dt (about 0.005 at 100 u/s and dt 1/600). Its
|
||||
exponentials and its quiet timer use dt and are exact. Cooldowns charge the firing tick
|
||||
(`InstantWeapon.cs:254`) and drop the remainder, so a gun's real period rounds up to whole
|
||||
steps: a smaller step brings it nearer the authored cadence. Agents decide every step
|
||||
(`Agent.Update`), and `Combat` draws one jittered range sample per step (`Combat.cs:145`);
|
||||
a smaller step means more decisions per sim second, the same per real second. No ruling
|
||||
fixes a per-step count.
|
||||
|
||||
### Does the pace lever hold?
|
||||
### Census
|
||||
|
||||
The claim: a sim speed setting leaves every sim-time rule (evasion window, tracking rates,
|
||||
cooldowns) numerically unchanged and only slows what the player experiences.
|
||||
Every Unity-side place that integrates, times or decides a sim fact at `6d427b7a`, and the cut
|
||||
that moves it. Presenters that only read sim state (T7) and input (T8) are not entries.
|
||||
|
||||
- **Not today.** By T1, the simulation's history is a function of the frame times it was
|
||||
handed. A scaled dt would change the history again: every integration, threshold
|
||||
crossing and per-step statistic sees different steps. The claim holds only once the
|
||||
step is fixed and the scale changes how many steps run per real second.
|
||||
- **With the fixed step, it holds exactly for the simulation.** Every rule in ServerShared
|
||||
reads only the step's dt and `Zone.Time`, so a run at scale k is the run at scale 1, step
|
||||
for step, given the same commands at the same steps. TTK, evasion windows, tracking and
|
||||
cooldowns are unchanged in sim seconds. The `combat-pace-pass` measurements stay valid
|
||||
at any scale.
|
||||
- **What it changes besides the clock.** Everything the player does in real time gets
|
||||
relatively faster: the view, the reticle, target selection and trigger timing all keep
|
||||
real-time rates while the world slows. At half speed the player has twice the real time
|
||||
per sim second to read, aim and choose; AI agents get no such gift, because they think
|
||||
in sim time. So sim speed is a difficulty lever as well as a pace lever, and it does not
|
||||
change any sim ratio: whether the first manoeuvre decides an engagement
|
||||
(`aether-drive-rework-rotor`) is a sim fact, and the knob cannot fix it. The helm slows
|
||||
with the sim (ruling `sim-speed-presentation`), so a turn takes 1/k real seconds as
|
||||
long; that is intended.
|
||||
- **Holes it must close.** T3's physics write and T4's real-time world objects would run
|
||||
at real speed in a slowed world. This cut gives them sim time; it does not move them
|
||||
into the simulation, which the loot cuts and `bodies-entity-and-mines` own.
|
||||
| Row | Site | Fact Unity decides | Owner |
|
||||
|---|---|---|---|
|
||||
| C1 | `ActionGameManager.cs:1311` | The step's length (frame dt) | `sim-speed` r2 |
|
||||
| C2 | `ActionGameManager.cs:839-866` (dead) | A ship's position, direction, velocity | `sim-speed` r2 deletes it |
|
||||
| C3 | `HullCollider.cs:14-17` | Velocity from physics impulses | `sim-contact` |
|
||||
| C4 | `ShieldManager.cs:70-86` | Ship-on-ship bounce | `sim-contact` (new rule in Zone) |
|
||||
| C5 | `GuidedProjectile.cs`, `GuidedProjectileManager.cs:24-83` | A missile's flight; when its binding ends | `missile-records`, `missile-odds`, `missile-presenter` r3 |
|
||||
| C6 | `ActionGameManager.cs:1307-1309` | Tractor spin-up | `loot-2` r2 |
|
||||
| C7 | `Projectile.cs:43-50`, `ProjectileManager.cs:16-28` | A round's drawn flight and end | `ballistic-flight` (new) |
|
||||
| C8 | `Mine.cs`, `MineManager.cs`, `ShieldManager.cs:57-60` | Mine flight, arming, lifetime, detonation | `retire-unity-mines` (new; unreachable per T13); the design returns under `bodies-entity-and-mines` |
|
||||
| C9 | `GridObject.cs`, `EntityInstance.cs:293-308`, `ZoneRenderer` loot, `ShieldManager.cs:42-56` | Loot drop, drift, expiry, pickup | `loot-1` |
|
||||
| C10 | `TractorBeam.cs:21-27`, `FieldDriver.cs:307-323`, binder resolvers | Tractor pull, grabbed pose | `loot-3` |
|
||||
| C11 | `ActionGameManager.cs:705-711` | Executes a zone transfer the sim decided | follow-up `zone-transfer-owner` |
|
||||
| C12 | `EntityInstance.cs:403`, `ShipInstance.cs:141` | None (draws the latest pose, so a fixed step hops) | `sim-speed` r2 draws ahead |
|
||||
|
||||
`sim-speed-scale` runs the census patterns as negative checks before any code, so the setting
|
||||
cannot ship while a row is open.
|
||||
|
||||
### The design
|
||||
|
||||
One clock, `SimClock` (ServerShared, engine-free), owns the step and the scale.
|
||||
One clock, `SimClock` (ServerShared, engine-free), owns the step and its pacing.
|
||||
|
||||
- `Step` is a constant, 1/60 s. It never changes with the scale or the frame rate.
|
||||
- `Scale` is clamped to the setting's range and written only by the settings surface.
|
||||
- `Advance(realSeconds, step)` adds `min(realSeconds, MaxFrameSeconds) * Scale` sim
|
||||
seconds to an accumulator, then calls `step(Step)` while the accumulator holds a whole
|
||||
step. `MaxFrameSeconds` is 0.1 s, as Unity's own Maximum Allowed Timestep, so a hitch
|
||||
slows the world instead of spiralling.
|
||||
- `SimDelta` is the sim seconds that elapsed this frame (the clamped real seconds times
|
||||
the scale), for presenters that integrate a sim quantity.
|
||||
- `Lead` is the sim seconds since the last step (the accumulator, below one step), and
|
||||
`Steps` counts steps, for presenters that draw a pose between steps.
|
||||
- `Step` is fixed for the clock's life. `sim-speed` builds it at 1/60 (speed 1);
|
||||
`sim-speed-scale` builds it at `SimSpeed / TickRate` from game data.
|
||||
- `StepsPerRealSecond` is `TickRate` times the player's factor (`sim-speed-scale`; 60 before
|
||||
it). The factor never reaches `Step`, so one set of game data gives one history at any
|
||||
player setting.
|
||||
- `Advance(realSeconds, step)` adds the clamped real seconds (at most 0.1) to an accumulator
|
||||
and calls `step(Step)` each time it holds one tick. A hitch slows the world instead of
|
||||
spiralling.
|
||||
- `Lead` is the sim seconds since the last step (below one `Step`); `Steps` counts steps.
|
||||
There is no `SimDelta`: nothing in Unity integrates on the clock.
|
||||
|
||||
`ActionGameManager` holds the one clock and calls `Clock.Advance(_paused ? 0 :
|
||||
Time.deltaTime, Zone.Update)` every frame, outside the pause gate, so a paused frame
|
||||
reports zero `SimDelta`. Pause stays the menu's state; it feeds the clock zero real time
|
||||
and owns nothing else.
|
||||
`ActionGameManager` calls `Clock.Advance(_paused ? 0 : Time.deltaTime, Zone.Update)` every frame,
|
||||
outside the pause gate. Pause stays the menu's.
|
||||
|
||||
Presenters follow ruling `sim-speed-presentation`, one rule: presentation reads current
|
||||
sim state and animates in real time; only presentation that integrates or times a sim
|
||||
quantity takes sim time.
|
||||
Presenters draw the latest sim state ahead by `Lead`, through `DrawAhead` (ServerShared, pure,
|
||||
never called by a rule): an entity's position plus velocity times `Lead`, a ship's rotation
|
||||
turned by `Entity.TurnRate` times `Lead` (the sim writes `TurnRate` from the actuators each step;
|
||||
staging and wormhole writes leave it zero), and `Zone.Time + Lead` for presenters that evaluate a
|
||||
sim function of time. A ballistic round is drawn at `FireControl.RoundAt(shot, Zone.Time + Lead)`,
|
||||
the line the simulation flies it; a missile at its record's position plus velocity times `Lead`.
|
||||
The presenter API carries the latest state and `Lead`; nothing a presenter receives can feed back.
|
||||
|
||||
| Presenter | Integrates or times a sim quantity? | Clock |
|
||||
| Presenter | Reads | Clock |
|
||||
|---|---|---|
|
||||
| Entity pose (`EntityInstance` position, `ShipInstance` rotation) | Draws a pose between steps | `Lead`: position + velocity x `Lead`; rotation carried on by the last step's turn, scaled by `Lead / Step` |
|
||||
| `Projectile` flight, `ProjectileManager` spawn spread | Integrates a round's drawn position | `SimDelta`; the trail's fade after `Kill` stays real |
|
||||
| `GuidedProjectile` thrust, position and binding life | Integrates position; binding ends with a sim event | `SimDelta`; binding ends when `Zone.Time` passes the shot's arrival plus one sim second; the noise wobble stays real |
|
||||
| Loot drift and attraction (`GridObject`), tractor pull, tractor ramp | World motion Unity owns until the loot cuts | `SimDelta`; the spin stays real |
|
||||
| Mine arming, lifetime, blast delay | Times a sim event (`Detonate`) | `Zone.Time`; emission and pulse stay real |
|
||||
| Charge effects, beams, flashes, shields, fades, mount slew, ripples, sun, volumes, HUD, menus, intro, death transition | No | Real time, unchanged |
|
||||
| Thruster flames, particles, audio pitch | No (ruling `sim-speed-presentation`) | Real time, unchanged |
|
||||
| Coast path and lead markers (`controls-hud`, not landed) | No: they draw sim predictions from current state | Horizons stay in sim seconds |
|
||||
|
||||
`HullCollider`'s write is deleted, not re-timed: Unity physics may not decide a sim fact
|
||||
(invariant `sim-owns-facts`), and an impulse divided by a frame dt is not a sim rule at any
|
||||
scale. Loot and mines stop nudging ships; collision as a sim rule, if wanted, belongs to the
|
||||
entity work that brings bodies into the simulation.
|
||||
| Entity pose (`EntityInstance`, `ShipInstance`) | `DrawAhead` | `Lead` |
|
||||
| Ballistic round (`Projectile`) | its `PendingShot` copy, `RoundAt`, `ShotResolved` | `Zone.Time + Lead`; trail fade real |
|
||||
| Guided round (`GuidedProjectile`) | its munition record, `ShotResolved` | `Lead`; lift curve on sim seconds; fades real |
|
||||
| Floating item (`ItemPickup`, loot-1) | its body | latest state (ahead by `Lead` when loot-1 lands after `sim-speed`) |
|
||||
| Charge effects, beams, flashes, shields, fades, slew, ripples, sun, volumes, HUD, menus | sim events and state | real time (ruling `sim-speed-presentation`) |
|
||||
| Thruster flames, particles, audio pitch | current state | real time |
|
||||
|
||||
### Authority map
|
||||
|
||||
- **Owner.** `SimClock` owns sim time's relation to real time: the step, the scale and the
|
||||
accumulator. `Zone` still owns sim time itself (`_time`) and what a step does.
|
||||
- **Inputs.** Real frame seconds and the pause state from `ActionGameManager`; the scale
|
||||
from the settings surface.
|
||||
- **Outputs.** `Zone.Update(Step)` calls; `SimDelta`, `Lead` and `Steps` for presenters.
|
||||
- **Derived state.** `SimDelta`, `Lead` and `Steps` are presentation-only and never read
|
||||
by ServerShared rules. A ship's previous-step rotation in `ShipInstance` is a
|
||||
presentation cache, written there only, never read by the simulation.
|
||||
- **Forbidden writers.** Any `Zone.Update` call in `Assets/Scripts` other than through
|
||||
`SimClock.Advance`; any write of `Time.timeScale` outside `FieldTester`; Unity physics
|
||||
writing `Entity.Velocity` (`HullCollider`); presenters in T5 and T4 reading
|
||||
`Time.deltaTime` or `Time.time` for the quantities the table names.
|
||||
- **Shared paths.** The game, every scenario and the headless test of the clock reach the
|
||||
simulation through `SimClock.Advance`; tests that step a zone directly keep doing so,
|
||||
because they choose their own dt on purpose.
|
||||
- **Deletion line.** Delete `Zone.Update(Time.deltaTime)` and `HullCollider`'s
|
||||
`OnCollisionEnter` before adding `SimClock`.
|
||||
- **Owner.** `SimClock` owns sim time's relation to real time. `Zone` owns sim time and the
|
||||
step. Game data owns `SimSpeed` and `TickRate`; the player owns the factor.
|
||||
- **Inputs.** Real frame seconds and pause; `SimSpeed`, `TickRate`, `SimSpeedFactor`.
|
||||
- **Outputs.** `Zone.Update(Step)` calls; `Lead` and `Steps` for presenters.
|
||||
- **Derived state.** `Lead`, `Steps` and every drawn pose are presentation-only.
|
||||
`Entity.TurnRate` is sim-written and read only by `DrawAhead`. Sim seconds per real second
|
||||
(`SimSpeed * SimSpeedFactor`) is derived.
|
||||
- **Forbidden writers.** Any other `Zone.Update` caller; `Time.timeScale` outside
|
||||
`FieldTester`; any Unity code integrating, timing or deciding a census fact; a presenter
|
||||
given a sim-time delta; the player's factor reaching `Step`.
|
||||
- **Shared paths.** The game and every scenario reach the simulation through the one
|
||||
`Advance`; every presenter draws with the same `Lead`.
|
||||
- **Deletion line.** Each census cut deletes its Unity owner before adding the sim owner;
|
||||
`sim-speed-scale` adds no code until every census pattern is empty.
|
||||
|
||||
### Model page rows
|
||||
|
||||
| Kind | Named by | Over time | Decided by |
|
||||
|---|---|---|---|
|
||||
| Sim speed setting | its field (surface per question `sim-speed-surface`) | Set by the player or operator, saved with its settings | the settings surface; read by `SimClock` |
|
||||
| Design speed, tick rate | `GameplaySettings.SimSpeed`, `TickRate` | Set by designers in game data | game data; read by `SimClock` at zone entry |
|
||||
| Player slow-down | `PlayerGameplaySettings.SimSpeedFactor` | Set by the player, saved in `player.cc` | the settings menu |
|
||||
| Zone time | `Zone.Time`, saved in `ZonePack.Time` | Advances by `Step` per step | `Zone.Update`, called only by `SimClock` |
|
||||
| Clock accumulator | none | Under one step, dropped on load and zone change | `SimClock` |
|
||||
| Clock accumulator | none | Under one tick, dropped on load and zone change | `SimClock` |
|
||||
| Turn rate | `Entity.TurnRate` | Rewritten every step | the rotation actuators |
|
||||
| Ship contact | `Zone.Contacted` events | Published at each closing contact | `Contacts.Step` |
|
||||
|
||||
### Prior art
|
||||
|
||||
From documentation and talks already read, not re-fetched for this pass.
|
||||
|
||||
- **Gaffer On Games, "Fix Your Timestep!" (Glenn Fiedler, 2004).** A fixed dt fed from
|
||||
an accumulator of real time, a cap on frame time against the spiral of death, and
|
||||
drawing between steps with `alpha = accumulator / dt`. The design is this article,
|
||||
with the scale applied where real time enters the accumulator.
|
||||
- **Factorio.** The simulation runs fixed ticks at 60 UPS; `game.speed` changes how many
|
||||
ticks run per real second, never the tick, because the multiplayer lockstep and replays
|
||||
need identical histories.
|
||||
- **Paradox grand strategy (EU4, CK3, Stellaris).** Speeds 1-5 change the real time
|
||||
between fixed daily ticks; the day's rules are untouched, which is what keeps
|
||||
multiplayer in sync across speeds.
|
||||
- **RTS lockstep and replays (StarCraft II, Age of Empires II).** StarCraft II runs 16
|
||||
game loops per game second; the Faster game speed and replay playback change loops per
|
||||
real second, and a replay re-runs the same loops from recorded commands. This is the
|
||||
test below: same commands at the same steps, same history.
|
||||
- **Unity `Time.timeScale`.** Scales `deltaTime` and the physics step for everything
|
||||
that does not ask for unscaled time, particles included, which is why ruling
|
||||
`sim-speed-knob` keeps it out.
|
||||
- **Celeste Assist Mode.** Game speed from 50 to 100 percent in 10 percent steps, a
|
||||
saved player option framed as accessibility. It is the precedent for the player-option
|
||||
answer to `sim-speed-surface`.
|
||||
- **Gaffer On Games, "Fix Your Timestep!" (Glenn Fiedler, 2004).** A fixed dt fed from an
|
||||
accumulator of real time, a cap on frame time against the spiral of death, and drawing
|
||||
between steps with `alpha = accumulator / dt`. The design is this article, with the factor
|
||||
applied where real time enters the accumulator.
|
||||
- **Unity `Rigidbody.interpolation`.** Interpolate draws one physics step behind;
|
||||
Extrapolate draws ahead from velocity and can show a body briefly past a collider before it
|
||||
is corrected. Unity recommends Interpolate for the camera's subject.
|
||||
- **Client-side prediction (Quake 3, Source).** Remote entities interpolate a little in the
|
||||
past; the player's own avatar is drawn ahead because input lag on it is what players feel.
|
||||
- **Factorio, Paradox, StarCraft II replays.** Speed changes the tick rate, never the tick,
|
||||
because lockstep and replays need identical histories. Supreme Commander simulates at
|
||||
10 Hz and draws in between; Source servers tick at 66 Hz, Overwatch at about 63.
|
||||
- **Celeste Assist Mode.** Game speed from 50 to 100 percent, a saved accessibility option;
|
||||
the precedent for the factor and for its 50 percent floor.
|
||||
|
||||
### Rationale
|
||||
|
||||
**Why more steps per second and not a bigger dt.** A scaled dt changes every integration
|
||||
and threshold, so the run at half speed would be a different game; the operator's premise
|
||||
(rules numerically unchanged) needs the step held fixed. Every precedent above that cares
|
||||
about history (Factorio, Paradox, RTS replays) scales the step rate, not the step.
|
||||
**Why the step follows the design speed.** Ruling `tick-rate-follows-design-speed`. At a design
|
||||
speed of 0.1 and a 1/60 step the world ticks at 6 Hz; drawing ahead hides the motion, but not
|
||||
the input: a trigger or a turn waits up to a tick (167 ms) to enter the simulation. With
|
||||
`Step = SimSpeed / TickRate` the real tick rate is a chosen constant, and the player's factor
|
||||
alone changes ticks per real second, so `APlayerFactorIsTheSameHistory` holds for one set of
|
||||
game data. A design-speed change is a balance change: it changes the step, so the pace pass
|
||||
measures at the shipped values. Per T14 no ruled behaviour depends on a step count; what moves
|
||||
is closer to the continuous model (cooldowns nearer their authored cadence, agents deciding more
|
||||
often per sim second). The two costs are float precision and the manoeuvre track's threshold,
|
||||
both pinned as measurements in `sim-speed-scale`.
|
||||
|
||||
**Why 1/60 s.** It is close to today's typical frame dt, so the fixed step changes today's
|
||||
feel least; Factorio uses it; and the headless tests already step at 0.016 and coarser.
|
||||
The cost is that the game's sim history no longer follows the frame rate, which is a fix:
|
||||
M15 recorded that nothing was replayable.
|
||||
**The tick rate: 30 Hz at design speed, with the factor floored at 0.5.** Recommendation,
|
||||
moderate confidence; `TickRate` is game data, so the play check can move it without code.
|
||||
|
||||
**Why draw ahead of the step instead of between steps.** At scale 0.25 there are 15 steps
|
||||
per real second, so poses must be drawn between steps or ships visibly hop. Drawing ahead
|
||||
by `Lead` (velocity times the time since the step) adds no latency and needs no cache for
|
||||
position; interpolation would put every drawn ship a step behind the simulation the HUD
|
||||
reads. Rotation has no angular velocity in the sim, so the ship's presenter keeps the last
|
||||
step's rotation and carries its turn forward; the error is corrected at the next step.
|
||||
| Tick rate at design speed | 20 Hz | 30 Hz | 60 Hz |
|
||||
|---|---|---|---|
|
||||
| Step at design 0.1 | 1/200 sim s | 1/300 sim s | 1/600 sim s |
|
||||
| Input latency, factor 1 (worst) | 50 ms | 33 ms | 17 ms |
|
||||
| Input latency, factor 0.5 (worst) | 100 ms | 67 ms | 33 ms |
|
||||
| Steps per real second vs today | about a third | about half | today's at 60 fps |
|
||||
| Float spacings per step at 5 u/s, 9000 units out | 25 | 17 | 8 |
|
||||
|
||||
**Why the clock does not own pause.** Pause is the menu's; making the clock answer it would
|
||||
give the clock a second reason to stop. The menu feeds it zero real time instead.
|
||||
Today's game steps once per frame, so 30 Hz costs about half of today's simulation per real
|
||||
second at 60 fps and less at higher frame rates. 60 Hz buys latency nobody needs in a game
|
||||
slowed tenfold and halves the float headroom (T14). 20 Hz puts the slowed factor at 10 Hz, near
|
||||
the rate the operator called noticeable. A factor floor of 0.25 at 30 Hz would tick at 7.5 Hz,
|
||||
so the floor is 0.5 (Celeste's), keeping the slowest tick at 15 Hz.
|
||||
|
||||
**Why re-time T4 now when the loot cuts delete it.** A slowed world with real-speed loot
|
||||
and mines is a visible desync, and the change is a few lines on each. The loot cuts and
|
||||
`bodies-entity-and-mines` remove these lines when they move the objects into the
|
||||
simulation, where `SimClock` steps them anyway.
|
||||
**Drawing between steps: extrapolate everything from the latest state.** Interpolation shows only
|
||||
states the simulation produced, but draws one step behind; extrapolation has no lag but can
|
||||
overshoot a turn or a contact and snap at the next step. At 30 Hz and design speed 0.1:
|
||||
|
||||
| | Factor 1 (30 Hz) | Factor 0.5 (15 Hz) |
|
||||
|---|---|---|
|
||||
| Interpolation lag | 33 ms real (one step) | 67 ms real |
|
||||
| Extrapolation snap, a turn stopping at 3 rad/s | 0.57 degrees | 0.57 degrees, half as often |
|
||||
| Extrapolation snap, a contact reversing 100 u/s | 0.33 units | 0.33 units |
|
||||
| Extrapolation error, steady acceleration of 100 u/s2 | under 0.001 units | under 0.001 units |
|
||||
|
||||
The player's ship is extrapolated (the operator's acceptance, and where lag is felt). The rest
|
||||
are extrapolated too, for three reasons: one draw time keeps the geometry the player aims at
|
||||
consistent with their own ship (interpolating the others would set them a step behind it, up to
|
||||
0.67 units at 200 u/s closing); the analytic presenters (`RoundAt`, a missile's record) are
|
||||
exact under it; and it needs no previous-state buffer, so no presentation store has to be owned
|
||||
or kept out of the simulation's reach. If the play check shows visible snaps on contact, the
|
||||
fallback is interpolating non-player entities through a presentation-only buffer written by the
|
||||
step wrapper, not a change to the simulation.
|
||||
|
||||
**Why rounds are drawn on the sim line.** FireControl already owns every hit. A drawn round that
|
||||
inherits velocity, jitters, drops under gravity and dies at its own distance is a second flight
|
||||
that disagrees with the first; at a slowed design speed the disagreement is visible for
|
||||
seconds. `RoundAt` states the simulation's line; the presenter blends from the barrel into it
|
||||
in the first tenth of a sim second and ends where the outcome says.
|
||||
|
||||
**Why contact moves into the simulation instead of being deleted.** The bounce is live (T11) and
|
||||
the game has it today; deleting it would be a product change no ruling asks for. The rule moves
|
||||
verbatim (the same elastic exchange, once per contact, no push-out) onto a disc that bounds the
|
||||
hull, so ships may come a little closer than the shield ellipsoid let them.
|
||||
|
||||
**Why the Unity mine is deleted, not moved.** No catalog item fires it (T13), it owns its whole
|
||||
life in Unity, and the design (arming, proximity, lifetime, blast) belongs to the simulation
|
||||
bodies of `bodies-entity-and-mines` and `drones-munitions-substrate`. Moving it now would build a
|
||||
munition entity no demo item uses.
|
||||
|
||||
**Why the split.** `sim-speed` lands the fixed step at speed 1 now: at speed 1 the integrators
|
||||
still on `Time.deltaTime` run at the sim's average rate, so nothing desyncs, and replays become
|
||||
possible (M15). The setting waits for the census, because any open row runs at real speed in a
|
||||
slowed world. Ordering: `sim-speed` (after `controls-helm`); then `ballistic-flight` and
|
||||
`sim-contact` (independent, small); `missile-presenter` r3 after `missile-odds`; the loot branch
|
||||
(`loot-1`, `loot-2` r2, `loot-3`); `retire-unity-mines` after `loot-3`; `sim-speed-scale` last,
|
||||
before the `combat-pace-pass`.
|
||||
|
||||
**Why the clock does not own pause.** Pause is the menu's; the menu feeds the clock zero real
|
||||
time.
|
||||
|
|
|
|||
Loading…
Reference in a new issue