Documentation
¶
Overview ¶
Package wire defines the client/server protocol the websocket transport carries. Encoding is JSON: it keeps kbot free of codegen tooling and is the path of least resistance for the browser client. The shapes are deliberately small — in steady state only orders and command frames flow; snapshots and hashes are the periodic authoritative backstop.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Ack ¶
type Ack struct {
ConfirmedTick uint64 `json:"confirmedTick"`
}
Ack reports the last tick a client has confirmed, for RTT and flow control.
type ClientMsg ¶
type ClientMsg struct {
Type MsgType `json:"type"`
Join *JoinReq `json:"join,omitempty"`
Order *order.Order `json:"order,omitempty"`
Ack *Ack `json:"ack,omitempty"`
Control *Control `json:"control,omitempty"`
Ping *Ping `json:"ping,omitempty"`
}
ClientMsg is anything a client sends to the server.
func DecodeClient ¶
DecodeClient unmarshals client message bytes.
type CommandFrame ¶
CommandFrame is the authoritative set of orders for a future tick. The client applies them at exactly that tick to stay in step with the server.
type Control ¶
type Control struct {
Action string `json:"action,omitempty"`
Paused bool `json:"paused"`
Rate float64 `json:"rate,omitempty"`
Tick uint64 `json:"tick,omitempty"`
}
Control is a sandbox runtime command and its echoed result. A client sends one with Action set ("pause", "resume", "step", "rate"); the authority applies it and broadcasts a Control whose Paused / Rate / Tick describe the resulting shared clock state so every client converges on it. Rate is a wall-clock pacing multiplier (1 = real time, 0.5 = half speed, 2 = double); it changes only how often the fixed-size tick advances, never the per-tick simulation content, so determinism is unaffected.
type HashMsg ¶
HashMsg is a cheap per-interval state digest for desync detection. The hash spans the full uint64 range, which overflows a JavaScript number's 53-bit integer precision, so it crosses the wire as a decimal string and the browser compares it as text against its own string-encoded hash.
type JoinAccept ¶
type JoinAccept struct {
PlayerSlot int `json:"playerSlot"`
TickRate int `json:"tickRate"`
InputDelay int `json:"inputDelay"`
Seed uint32 `json:"seed"`
Tick uint64 `json:"tick"`
}
JoinAccept seeds the client's local engine so it can run in lockstep.
type MsgType ¶
type MsgType string
MsgType tags a message so the receiver knows which payload field is set.
const ( MsgJoin MsgType = "join" MsgJoinAccept MsgType = "join_accept" MsgOrder MsgType = "order" MsgCommand MsgType = "command" MsgSnapshot MsgType = "snapshot" MsgHash MsgType = "hash" MsgAck MsgType = "ack" // MsgLeave lets a client relinquish its slot without dropping the socket, // so the authority can free the seat and reap an emptied match promptly // rather than waiting for the transport to notice the disconnect. MsgLeave MsgType = "leave" // MsgControl carries sandbox runtime commands (pause / resume / single-step / // tick-rate change) both ways: a client requests one, the authority applies // it to the shared simulation clock and echoes the resulting state to every // client so all windows pause, step and slow together. It is a developer / // sandbox affordance, not a competitive-play control. MsgControl MsgType = "control" // MsgPing / MsgPong are a lightweight round-trip latency probe. The client // sends a Ping carrying an opaque sequence number; the authority answers // immediately with a Pong echoing that sequence plus its own wall clock, so // the client can both measure RTT and estimate the server's clock offset // without disturbing the simulation. Pings are answered off the read path so // they never queue behind a simulation tick. MsgPing MsgType = "ping" MsgPong MsgType = "pong" // MsgResync asks the authority to push a fresh full snapshot to the // requesting client, used by the sandbox "Force Sync" affordance to discard // the client's locally diverged state and re-seed from authority. MsgResync MsgType = "resync" // MsgDiagnose asks the authority for a full snapshot the client uses for // read-only drift inspection. Unlike MsgResync the client does NOT restore // from it — the returned Snapshot carries Diagnostic=true so the client routes // it to the diff UI instead of re-seeding its local engine. MsgDiagnose MsgType = "diagnose" )
type Ping ¶
type Ping struct {
Seq uint64 `json:"seq"`
}
Ping is a client latency probe. Seq is an opaque counter the client uses to match the answering Pong to its send time; the server never interprets it.
type Pong ¶
Pong answers a Ping. It echoes the client's Seq so the client can compute RTT against the send time it stored, and carries the authority's wall clock in Unix milliseconds so the client can estimate the server-clock offset.
type ProjectileSnap ¶
type ProjectileSnap struct {
ID uint32 `json:"id"`
OwnerID uint32 `json:"ownerId"`
TargetID uint32 `json:"targetId,omitempty"`
Slot int `json:"slot"`
Mode uint8 `json:"mode"`
Phase uint8 `json:"phase"`
Model string `json:"model"`
Weapon string `json:"weapon"`
X fixed.Fixed `json:"x"`
Y fixed.Fixed `json:"y"`
Z fixed.Fixed `json:"z"`
VX fixed.Fixed `json:"vx"`
VY fixed.Fixed `json:"vy"`
VZ fixed.Fixed `json:"vz"`
OX fixed.Fixed `json:"ox"`
OY fixed.Fixed `json:"oy"`
OZ fixed.Fixed `json:"oz"`
TX fixed.Fixed `json:"tx"`
TY fixed.Fixed `json:"ty"`
TZ fixed.Fixed `json:"tz"`
LaunchY fixed.Fixed `json:"launchY"`
Speed fixed.Fixed `json:"speed"`
VMax fixed.Fixed `json:"vmax"`
Accel fixed.Fixed `json:"accel"`
TurnAng int32 `json:"turnAng"`
HomingR fixed.Fixed `json:"homingR"`
Gravity fixed.Fixed `json:"gravity"`
AoE fixed.Fixed `json:"aoe"`
Damage fixed.Fixed `json:"damage"`
AgeSec fixed.Fixed `json:"ageSec"`
LifeSec fixed.Fixed `json:"lifeSec"`
LastDist fixed.Fixed `json:"lastDist"`
Closing bool `json:"closing,omitempty"`
Heading int32 `json:"heading"`
Pitch int32 `json:"pitch"`
// FromPiece is the emitter piece index the weapon's query-script returned at
// launch. The sim spawns from the unit origin (it has no geometry), so the
// renderer uses this to offset the model to the actual muzzle.
FromPiece int `json:"fromPiece,omitempty"`
}
ProjectileSnap is one in-flight model weapon (missile/rocket/bomb) carried in a join snapshot so the joiner resumes the authority's live shots verbatim rather than starting with an empty sky. Every field stepProjectile reads is included so flight (and the damage a shot applies on arrival) continues identically.
type QueuedSnap ¶
type QueuedSnap struct {
Kind uint8 `json:"kind"`
TX fixed.Fixed `json:"tx,omitempty"`
TZ fixed.Fixed `json:"tz,omitempty"`
TargetUnit uint32 `json:"targetUnit,omitempty"`
Name string `json:"name,omitempty"`
}
QueuedSnap is one deferred order on a unit's shift-queue in a join snapshot. Kind mirrors order.Kind numerically (1 = move, 2 = attack).
type ServerMsg ¶
type ServerMsg struct {
Type MsgType `json:"type"`
JoinAccept *JoinAccept `json:"joinAccept,omitempty"`
Command *CommandFrame `json:"command,omitempty"`
Snapshot *Snapshot `json:"snapshot,omitempty"`
Hash *HashMsg `json:"hash,omitempty"`
Control *Control `json:"control,omitempty"`
Pong *Pong `json:"pong,omitempty"`
}
ServerMsg is anything the server sends to a client.
func DecodeServer ¶
DecodeServer unmarshals server message bytes.
type Snapshot ¶
type Snapshot struct {
Tick uint64 `json:"tick"`
Hash uint64 `json:"hash,string"`
Units []UnitSnap `json:"units"`
Projectiles []ProjectileSnap `json:"projectiles,omitempty"`
// RuntimeRng is the script RNG's draw position at the snapshot tick. OP_RAND
// consumes this stream, so a joiner that adopts it keeps script-driven
// randomness (and the animation it drives) in lockstep with the authority.
// Only join snapshots carry it.
RuntimeRng uint32 `json:"runtimeRng,omitempty"`
// Diagnostic marks a snapshot the client requested for read-only drift
// inspection (MsgDiagnose). The client must NOT restore its engine from it;
// it is routed to the network-panel diff UI instead.
Diagnostic bool `json:"diagnostic,omitempty"`
}
Snapshot is a full authoritative state for join/reconnect/resync. The hash is string-encoded for the same JS-precision reason as HashMsg.
type UnitSnap ¶
type UnitSnap struct {
ID uint32 `json:"id"`
Name string `json:"name"`
Side int `json:"side"`
X fixed.Fixed `json:"x"`
Y fixed.Fixed `json:"y"`
Z fixed.Fixed `json:"z"`
Heading fixed.Fixed `json:"heading"` // raw fractional TA-angle
Speed fixed.Fixed `json:"speed"`
HasMove bool `json:"hasMove"`
TX fixed.Fixed `json:"tx"` // move target X
TZ fixed.Fixed `json:"tz"` // move target Z
Health fixed.Fixed `json:"health"`
Dead bool `json:"dead"`
// Combat state so a late joiner re-engages weapons/attacks that were live on
// the authority, replaying the firing animation instead of snapping to rest.
// Always serialized (no omitempty): the Diagnose diff compares these against
// the client's own export field-by-field, and dropping false/0 here would
// surface a phantom mismatch (server undefined vs client false/0).
HasAttack bool `json:"hasAttack"`
AttackTarget uint32 `json:"attackTarget"`
Weapons [3]WeaponSnap `json:"weapons,omitempty"`
// Queue is the unit's shift-queued follow-up orders, so a joiner advances
// through the same waypoint chain. omitempty on both ends (the client-side
// export also skips an empty queue) keeps the Diagnose diff symmetric.
Queue []QueuedSnap `json:"queue,omitempty"`
// Construction state: a buildee's progress (below 100 it stays inert on
// the joiner) and a builder's live job. The Build* job fields are
// omitempty on both ends like Queue.
BuildPercent fixed.Fixed `json:"buildPercent"`
BuildState uint8 `json:"buildState,omitempty"`
BuildName string `json:"buildName,omitempty"`
BuildSiteX fixed.Fixed `json:"buildSiteX,omitempty"`
BuildSiteZ fixed.Fixed `json:"buildSiteZ,omitempty"`
BuildTargetID uint32 `json:"buildTargetId,omitempty"`
BuildGateMs int64 `json:"buildGateMs,omitempty"`
ProdQueue []string `json:"prodQueue,omitempty"`
// Standing orders + working state (posts, patrol/auto-engage flags).
MoveMode uint8 `json:"moveMode"`
FireMode uint8 `json:"fireMode"`
HomeX fixed.Fixed `json:"homeX"`
HomeZ fixed.Fixed `json:"homeZ"`
AutoEngaged bool `json:"autoEngaged,omitempty"`
CurIsPatrol bool `json:"curIsPatrol,omitempty"`
SelfDAtMs int64 `json:"selfDAtMs,omitempty"`
// Transport links: the carrier this unit rides, its own passengers, and
// any in-flight pickup / drop job. All omitempty on both ends.
CarriedBy uint32 `json:"carriedBy,omitempty"`
Carrying []uint32 `json:"carrying,omitempty"`
LoadTarget uint32 `json:"loadTarget,omitempty"`
StallTicks uint16 `json:"stallTicks,omitempty"`
AvoidFlip bool `json:"avoidFlip,omitempty"`
ProgressX fixed.Fixed `json:"progressX,omitempty"`
ProgressZ fixed.Fixed `json:"progressZ,omitempty"`
HasUnload bool `json:"hasUnload,omitempty"`
UnloadX fixed.Fixed `json:"unloadX,omitempty"`
UnloadZ fixed.Fixed `json:"unloadZ,omitempty"`
// Cob carries the unit's full live script VM state so the joiner resumes the
// authority's exact piece poses (turret aim, mid-recoil) rather than
// re-deriving them from a Create/StartMoving replay. Only join snapshots carry
// it; periodic backstop snapshots omit it to avoid a bandwidth spike.
Cob *frame.CobSnapshot `json:"cob,omitempty"`
}
UnitSnap is the authoritative per-unit state used to (re)initialize or resync a client. It carries the raw fixed-point locostate plus the move target so a unit caught mid-move keeps driving identically on the resyncing client. Piece animation is re-derived locally, so it is not sent.
type WeaponSnap ¶
type WeaponSnap struct {
HasTarget bool `json:"hasTarget,omitempty"`
TargetUnit uint32 `json:"targetUnit,omitempty"`
PX fixed.Fixed `json:"px,omitempty"`
PY fixed.Fixed `json:"py,omitempty"`
PZ fixed.Fixed `json:"pz,omitempty"`
Source string `json:"source,omitempty"`
LastFireMs int64 `json:"lastFireMs,omitempty"`
}
WeaponSnap is one weapon slot's standing aim/fire order in a join snapshot. LastFireMs carries the slot's last shot time so the joiner inherits the authority's reload cadence instead of starting a fresh one.