Intra-System Movement (Pose Authority)¶
Status: 🚧 Partial — Server-owned pose ✅ (
intrasystem_poseJSONB, helm burn/halt routes); layout parity ✅ (intrasystem_layout.py↔windshieldTableauLayout.ts); NPC leg broadcast ✅. v1 non-goals unchanged (§ Non-goals). (Re-verified 2026-08-21 vs Sectorwars2102 HEAD46bce720.)
Movement between sectors is covered by Movement, which also carries this system's implementation status. This page covers position inside a sector: where a ship is on the windshield band, how it burns between the star, planets, stations and free points, and how every client in the sector sees the same motion.
Authority model¶
The server owns every ship's in-system pose. The pose is persisted per player and per NPC (intrasystem_pose, JSONB), so a reload restores the exact position, heading and flight phase — the client never invents position. REST commits every movement; WebSocket events and sector-presence polling distribute it (no WS-only mutation, per ADR-0094).
Coordinate system — canonical %-space¶
Positions are percentages of the windshield band (x_pct, y_pct ∈ [0,100]; heading_deg CSS-convention). All pose math — server and client — is computed against the ratified fixed reference band (1440 × 334.7 px, 18.09 px em-root); clients render by scaling %-space to their local band and never measure their own geometry into pose math. Viewport-measured geometry drifts %-positions body-dependently; the fixed reference frame eliminates the class.
Celestial %-anchors come from a layout function that exists in two byte-parity implementations — TypeScript (rendering, services/player-client/src/components/tactical/windshieldTableauLayout.ts) and Python (server targeting, services/gameserver/src/services/intrasystem_layout.py:1-268). Any change to one must land in both, re-proving golden-vector parity; positions of unaffected bodies must not move.
Why parity matters. The server has no DOM and never renders a windshield, but it still has to pick a real destination %-coordinate whenever it targets an NPC leg at "the star" or "a planet" — the mission/patrol/commute legs described in NPC Scheduler § Colonist courier and science missions and ordinary station commutes alike. If the server's notion of where a planet sits in %-space diverged from the client's rendered position even slightly, every player watching that NPC in their own windshield would see it fly to empty space next to the planet, or dock/land proximity checks (§ Proximity gating above) would pass or fail against a position the player never actually sees the ship reach. Byte-for-byte parity is what makes "the server aims a leg at planet X" and "the client draws planet X here" the same claim.
The algorithm. intrasystem_layout.py is a direct, byte-for-byte port of windshieldTableauLayout.ts's star/orbit math, not an approximation:
- Star anchor. Each sector's star position is derived from a
SplitMix32PRNG stream (_MASK32/_GOLDEN_GAMMA_32constants,intrasystem_layout.py:52-84) seeded byfnv1a32("windshield-tableau:star:{sector_id}")(derive_child_seed/fnv1a32,intrasystem_layout.py:56-67). The seed produces exactly two sequential draws off one continuing stream — x then y, in that order (star_anchor(),intrasystem_layout.py:152-160) — matchingrng.ts'sSeededRng+deriveChildSeedandwindshieldTableauLayout.ts's ownstarAnchor(). Draw order is load-bearing: a prior server-side attempt derived x and y from two independent:x/:y-suffixed hashes instead of one continuing stream, producing a different number sequence than the client (superseded; see the module's own doc-comment,intrasystem_layout.py:1-38). - Safe orbit radii.
safe_orbit_radii()(intrasystem_layout.py:163-199) derives four independent per-direction radii (left/right/up/down) off the star's own off-center anchor plus a fixed reference band box, so a body's rendered footprint never lands outside[0,100]%on either axis. The server has no DOM to measure the band, so it uses the same fixed reference-band literal the client's own test suite asserts against —LAYOUT_BAND_WIDTH_PX = 1440.0,LAYOUT_BAND_HEIGHT_PX = 334.7,LAYOUT_BAND_REM_PX = 18.09(intrasystem_layout.py:119-125), the resolved.ssv-tableaugeometry at the 1440×900 reference cockpit resolution. - Orbital position.
orbital_position()(intrasystem_layout.py:202-239) fans bodies out primarily byorbit_auon X withphase_degas a secondary wiggle, and phase-dominant on Y — including theX_WIGGLE_TAPER_START_ORBIT_Ttaper (intrasystem_layout.py:100-117) that prevents the wiggle term from overshootingx_max_pctfor high orbits, a byte-equivalent port of the client's ownxWiggleTapersaturation fix. - Planet and station footprints get separately-sized safe-radii sets (
SectorLayout.planet_radii/.station_radii,intrasystem_layout.py:242-268) since a station's rendered footprint grows with its name length and needs a wider margin than a fixed-size planet disc — mirrors the client'ssafeRadiiPlanets/safeRadiiStationsuseMemopair.
Source-of-truth pair. services/gameserver/src/services/intrasystem_layout.py (server) and services/player-client/src/components/tactical/windshieldTableauLayout.ts (client) are jointly canonical — a change to the seeded-stream algorithm, the safe-radii formula, or the reference-band constants in one is a bug unless mirrored in the other, re-proven against shared golden vectors. Moon orbits, hazard arcs, warp-arrival obstacle avoidance, and cosmetic "other ship" flight poses are rendering-only concerns the client exports but the server has no leg-targeting analog for, and are intentionally not ported.
Pose and legs¶
A stationary ship is idle with its last settled pose. A burn commits a leg: origin, destination (planet | station | free point), start time, and a shared flight profile. Mid-leg position is a pure function of the plan and server time — clients interpolate; nothing streams per-frame.
Shared flight profile (ms): orient 1000 · accel 1800 · coast 1100 · flip 1300 · decel 2200 · settle 800 (nominal move 6400) · halt-flip 1800 · halt-brake 1600. Players and NPCs use the same profile.
Headings normalize to [0,360) at every persistence boundary, including the mid-course-redirect promotion path (a redirect during brake_turn re-plans from the derived current pose rather than freezing).
API¶
Under /api/v1/helm/intrasystem/:
| Endpoint | Effect |
|---|---|
GET /pose |
Own pose, derived at server_time, plus the flight profile |
POST /burn |
Commit a leg — {target_kind, target_id} or free-point {x_pct, y_pct}; burning while in flight = mid-course redirect |
POST /halt |
Abort mid-leg via the halt profile; stop point persists |
Pose is also enriched into the player-state payload and sector-presence entries.
Burn cost is free — burns spend wall-clock transit time, never turns; turns remain a sector-hop resource (ruled 2026-07-16, see DECISIONS.md). Empty-space Travel To is supported.
NPC traffic¶
The NPC scheduler ticks legs for NPCs with active activities (patrol, commute, station work); sleeping NPCs stay parked. Waypoints target real celestial %-anchors from the shared layout. Each NPC ticks inside its own savepoint with a bounded lock timeout, so one contended row cannot wipe a whole tick. Sector hops reset pose (ships emerge at their host anchor).
Multiplayer sync¶
Leg plans are broadcast (intrasystem.leg_started, sector room), and every client in the sector interpolates the same timeline; sector-presence polling (~5 s) is the guaranteed baseline path.
Proximity gating¶
Dock and land are validated server-side from pose: distance to the host must be within DOCK_LAND_PROXIMITY_RANGE_EM (5.0 em in reference space = 90.45 px — deliberately equal to the client's dock/land button range (DOCK_RANGE_EM), so the pair tunes together). The client's approach stand-off (~3.5 em) intentionally sits inside that range; the gap between stand-off and gate is the safety margin that makes server enforcement invisible to legitimate play. Fail-closed. Planet claiming, which lands the ship, honors the same proximity rule.
Non-goals (v1)¶
No AU/physics simulation or collision · no combat maneuvering from pose · no change to sector-hop turn costs · no WS mutation channel · no bit-identical pixels across band aspects (%-space is the contract; rendering scales).