FTEQW Q2Jump browser prototype engine

License: GNU General Public License version 2 or later, as in the upstream
FTEQW source. See COPYING and the source archive for component notices.

The source.tar.gz download contains the exact upstream source archive,
the pinned Emscripten SDK bootstrap source, and the browser adapter source
and build script. No Quake II game assets are included.

Build on x86-64 Linux (including WSL) with Python 3.12+, GNU make, a native
C compiler, native zlib development headers/library, and an internet
connection for the pinned compiler toolchain:

    tar -xf source.tar.gz
    cd q2jump-web
    python3 engine/build-engine.py

Outputs appear in public/engine. The script downloads Emscripten 3.1.64
inside .cache, activates it only there, verifies the upstream source and
SDK bootstrap archive SHA-256 checksums, and builds FTEQW's gl-rel web
target. Its Emscripten zlib port is pinned by the SDK. The default initial
Wasm memory is 128 MiB, with memory growth enabled by upstream.

Local changes: an additive read-only C sign-on-state getter; a JavaScript
console sink preserving complete-line buffering and forwarding fatal
errors to Module.onAbort; suppression of upstream's unvalidated window
message command launcher; JavaScript methods fteReady, fteState,
fteCommand, fteLoadMap, fteLoadAsset, and fteResumeAudio on Module. The command bridge preserves the
website chat input's focus. Optional Speex, Opus, and
botlib static dependencies are omitted. Quake II support remains enabled.
The linker explicitly exports malloc and free for FTE's JavaScript command
and file adapters, alongside main and the retained sign-on-state getter.
The Q2 handshake uses the negotiated protocol to select qport width (two
bytes for protocol 34, one or zero for 35/36); nc=0 selects the requested
old netchan and does not change qport width. Unsupported netchan selections
are rejected. FTE_WEB_UDP_RELAY keeps end-to-end acknowledgments and retry
for WebSocket connections because their final relay-to-server hop is UDP.
These exact source edits are recorded in source-patches.json. The build
first compiles and runs extracted-source protocol fixtures with native cc.
For this relay build, Q2PRO negotiates zlib packet compression and minor
version 1020. This permits compressed layouts while excluding version
1021's streaming download format, which FTE's download parser does not
implement. An extracted-source fixture checks the connect fields and
decodes a compressed packet larger than the server's default message cap.
Large in-game HUD layouts can still be truncated by FTE's layout buffer.

The relay build's download queue calls Module.fteLocalFile(path, missing) before
starting a network download. The second argument reports whether FTE already has
the file; selected local maps may still override it. Return 0 for no pending
browser asset, 1 while reading a local file or fetching the fixed mirror, or 2
once after mounting it. The queue waits on 1; on 2 it refreshes filesystem hashes,
model state and dependencies, and failed sounds before checking the file again.
Explicit-overwrite and non-game package requests bypass this callback.
Module.fteLoadMap accepts a literal maps/*.bsp or maps/*.lit path and an ArrayBuffer
up to 128 MiB. Module.fteLoadAsset accepts loose maps, textures, sky images, player
files, models, sounds and pictures up to 64 MiB, excluding packages and executable
content. Both preserve filename case and punctuation and share their bytes under
jump and baseq2. The shell bounds retained bytes and authenticates parent messages.
The build runs an extracted C queue fixture for pending, ready, missing, existing,
local-override, overwrite/package bypass and late sound paths without a server.
The web Q2 sky adapter queues the six canonical env/<sky><face>.tga files when
missing, retaining existing TGA/PCX/PNG/JPG/JPEG faces. Sky downloads start only
after sign-on; required maps and ordinary resources keep their normal priority.
Sky setup waits before probing image caches, then retries once after downloads
finish or fail. Missing optional skies cannot hold sign-on open or loop retries.
An extracted-source fixture covers local formats, canonical names, queue priority,
success, all-failed completion, map changes, and exclusion of other protocols.

Q2 entity animation interpolation starts from the last received pose, rather
than the potentially older network delta base. Held poses do not restart the
animation timer. This retains the normal 100 ms animation interval and leaves
position interpolation unchanged. The build checks these cases offline using
the extracted entity-update code and animation interpolation block.

The web client queues each player's requested model and skin before choosing a
temporary fallback, including on team changes after sign-on. Completed player
skin or model downloads refresh only players whose current userinfo still needs
that file. Mirror and normal server downloads share the skin-refresh callback;
player sounds and weapon files do not trigger it. Offline fixtures cover late
skins/models, team switches during downloads, existing files and failed assets.

The public browser shell and network relay are separate from this engine.
Module.files supplies local asset ArrayBuffers, or administrator-provided
asset URLs; Module.arguments selects Quake II. The engine does not bundle
game assets or a server address.

Live Quake II / Q2Jump sign-on and chat echo have been tested. This does
not establish rendering, chase-camera, or mobile compatibility. See the
prototype's README for the current tested boundaries and launch instructions.
