@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
102 lines (92 loc) • 4.41 kB
JavaScript
/**
* Packet-size constants for the fragmentation layer.
*
* The target transport MTU is 1200 bytes (Quake / Source / Glenn Fiedler's
* Networked Physics recommend ~1200 to stay under typical PMTU after IP/UDP
* headers; ethernet 1500 - 40 IPv6 - 8 UDP = 1452 in the best case, but
* intermediate hops with tunnelling, IPv6 extension headers, or DSLite
* routinely shave this down — 1200 is the safe number).
*
* Above this, the application's logical payload must be split into chunks
* via {@link send_fragmented} and reassembled on the receiver via
* {@link FragmentAssembler}.
*
* Header overhead breakdown for a fragment packet, from the transport's
* point of view:
* - {@link Channel} adds 8 bytes (seq + ack_latest + ack_bitfield).
* - The FRAGMENT packet type byte = 1 byte.
* - Fragment header (message_id uint16 + chunk_index uint8 + total_chunks
* uint8) = 4 bytes.
* Total overhead per fragment = 13 bytes; payload capacity per fragment =
* 1200 - 13 = 1187 bytes.
*
* For a logical message that fits in a single packet (no fragmentation
* needed), only the Channel header overhead applies — the payload starts
* with its own packet-type byte (e.g. ACTION_STREAM) and is sent directly.
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
/** Target transport MTU. */
export const MTU_BYTES = 1200;
/** Channel layer's per-packet header size. */
export const CHANNEL_HEADER_BYTES = 8;
/**
* Bytes the Channel reserves for the actual payload (no fragmentation):
* MTU minus the channel header.
*/
export const MAX_CHANNEL_PAYLOAD_BYTES = MTU_BYTES - CHANNEL_HEADER_BYTES;
/**
* Fragment header bytes within the channel payload:
* - 1 byte for the FRAGMENT packet-type marker
* - 2 bytes for message_id (uint16)
* - 1 byte for chunk_index (uint8)
* - 1 byte for total_chunks (uint8)
*/
export const FRAGMENT_HEADER_BYTES = 5;
/** Maximum number of payload bytes per fragment packet. */
export const MAX_FRAGMENT_CHUNK_BYTES = MAX_CHANNEL_PAYLOAD_BYTES - FRAGMENT_HEADER_BYTES;
/** Maximum number of chunks in a single logical message (limited by uint8 total_chunks). */
export const MAX_CHUNKS_PER_MESSAGE = 255;
/** Largest single logical payload supportable by this fragmentation scheme. */
export const MAX_LOGICAL_MESSAGE_BYTES = MAX_FRAGMENT_CHUNK_BYTES * MAX_CHUNKS_PER_MESSAGE;
/**
* NACK retransmit timing.
*
* FRAGMENT_NACK_INITIAL_DELAY_MS — how long the receiver waits after the
* first chunk of a message arrives before NACKing the missing chunks.
* Trades off latency-of-recovery against premature NACKs for chunks
* that were just slightly late.
* FRAGMENT_NACK_RESEND_INTERVAL_MS — period between NACK retries when a
* message remains incomplete.
* FRAGMENT_NACK_MAX_ROUNDS — after this many NACK rounds without
* completion, the receiver drops the message; the sender's retention
* ages out independently.
*
* 75 ms initial delay is sized for the consumer mobile RTT bands measured
* across US / EU / China in 2024–2025: 5G median 15–45 ms, 4G median
* 30–55 ms, per-path jitter <10 ms for the majority of long-lived
* connections. 75 ms sits comfortably above worst-case (4G median + jitter
* + wire time for a small burst) so legitimate stragglers land before the
* NACK fires, while keeping recovery latency on real loss to ~75 ms + 1
* RTT. On worst-case paths the receiver may occasionally NACK chunks
* still in transit; the receiver-side dedup absorbs the duplicate
* retransmits.
*/
export const FRAGMENT_NACK_INITIAL_DELAY_MS = 75;
export const FRAGMENT_NACK_RESEND_INTERVAL_MS = 75;
export const FRAGMENT_NACK_MAX_ROUNDS = 3;
/**
* Sender-side retention bounds. The sender keeps a copy of each fragmented
* message's bytes so it can satisfy NACK retransmits. Retention is
* FIFO-evicted at `FRAGMENT_RETENTION_MAX_MESSAGES` and age-evicted at
* `FRAGMENT_RETENTION_MAX_AGE_MS`.
*
* Worst-case memory per peer: `FRAGMENT_RETENTION_MAX_MESSAGES *
* max_message_size` bytes. With the FragmentAssembler default of 64 KiB,
* that's 1 MiB per peer at saturation; steady-state retention is empty
* because steady traffic fits in one packet and bypasses fragmentation.
*/
export const FRAGMENT_RETENTION_MAX_AGE_MS = 1500;
export const FRAGMENT_RETENTION_MAX_MESSAGES = 16;
export const FRAGMENT_RETENTION_MAX_RETRIES = 3;