# Native OpenQ4 baseline

The native build is a compatibility oracle and the authoritative multiplayer
server for the browser port. It is built from pinned OpenQ4/OpenQ4-game sources
with tracked local patches, while retail Quake 4 data remains ignored and
outside every public path.

## Build

Ubuntu 20.04 needs GCC 13 and the Linux development dependencies listed in
OpenQ4's `BUILDING.md`. Build and stage the native runtime with:

```bash
tools/build-native.sh --clean --jobs 4
```

The Ubuntu 20.04 baseline defaults to OpenQ4's supported native X11/GLX
backend. The default SDL3 fallback currently requires `libpipewire-0.3`, while
Ubuntu 20.04 provides only PipeWire 0.2. A host with PipeWire 0.3 development
files can request that backend with `OPENQ4_PLATFORM_BACKEND=sdl3`.

Generated source copies live under `build/native-src`, Meson output under
`build/native-release`, and the runnable package under `runtime/native`.
`runtime/native/q4base` is a local symlink to `assets-src/gog/q4base`.
After every clean native build, regenerate the private 22-map retail navigation pack:

```bash
node tools/stage-retail-botnav.mjs
node tools/stage-retail-botnav.mjs --verify
```

The primary generator is the pinned local compiler with
`retail-playerclip-aas32-v1`; its temporary `playerClipSolid=1` declaration treats
player-clip brushes as AAS solids. A temporary team-spawn seed and the playerclip
override are compiler inputs only and never enter the output PK4.

## Dedicated smoke

```bash
port/native/smoke-dedicated.sh
```

The smoke starts the shipped bot-ready `mp/q4web_arena` as a 12-slot DM
server on UDP 27666, maintains eight bots, prints the engine's local `serverInfo`,
reloads the map, and requires a finalized full-state demo on both sides of the
rotation. Retail maps use the separately generated private navigation catalog
described below. Evidence is stored in
`runtime/native-smoke`. See [SERVER_DEMOS.md](SERVER_DEMOS.md) for the replay
format, controls, playback, and retention contract.

The bot population reset regression has a focused, dedicated-only gate:

```bash
port/native/smoke-bot-minplayers-reset.sh
```

It starts the bot-ready arena with eight bots, performs five further map rotations
with eight fully spawned bots after each load, then changes `bot_minplayers` to zero.
The gate requires all bot slots and entities to disappear and verifies that the empty
population remains stable. Evidence is stored in `runtime/native-bot-reset-smoke`.

## Retail navigation smoke

```bash
node tools/smoke-retail-botnav.mjs
```

The private navigation manifest and PK4 cover all 22 retail multiplayer maps, but
navigation presence does not imply supported bot AI. Bots are enabled only for DM on
the 17 retail maps that offer that mode; Team DM, CTF, Arena CTF, and Tourney remain
human-only. The smoke loads and validates AAS on all 22 maps. For each DM map it also
requires a live bot to issue movement input (`|forward| + |right| >= 16`) and travel
at least 64 XY units in a valid AAS area while `spawnAt` and `minRespawnTime` remain
unchanged. Death, spectating, respawn, teleport state, or a movement discontinuity
above 512 units resets the sample. Patch `0034` keeps real bot clients out of the
spectator-only path while leaving fake-client replay behavior unchanged, so
spectator-camera movement cannot satisfy this gate.

Local playerclip compilation is always attempted first. A map-specific Q4Max or SABot
source may be used only after an exact allowlisted compiler-failure signature and only
when the outer archive, inner PK4, AAS entry, map CRC, size, and SHA-256 match the
pinned profile. The verified entry is copied without graph mutation; every unknown
failure or identity mismatch aborts staging. The Q4Max/SABot archives in
`build/native-aas/`, compilation logs, generated navigation PK4, and manifest remain
private operator state and are never public browser or source-offer files.
`mp/q4xdm13` selects its pinned SABot entry directly because the local compiler fails
nondeterministically both before and after a complete write.

## Source pins

- OpenQ4: `975e296b80b66f63b817d16ea78b53e0685c5c55`
- OpenQ4-game: `eb26e5f3a52358c58ee00bee5602575e5b057164`
- Native engine patches: `0001`, `0002`, `0004`, `0005`, `0007`, `0008`,
  `0011`, `0013`, `0015`, `0016`, `0018`, `0020`, `0023`, `0024`, `0025`,
  `0028`, `0032`, and `0035` under `port/native/patches`, in that order. The
  WebAssembly and source-offer paths additionally apply web-only `0010`,
  `0012`, `0014`, `0026`, `0027`, and `0030` in their declared positions.
- Game-module patches: `0003`, `0006`, `0009`, `0017`, `0019`, `0021`, `0022`,
  `0029`, `0031`, `0033`, and `0034` under `port/native/patches`, in that order.

The authoritative patch arrays live in `tools/build-wasm.sh`,
`tools/build-native.sh`, and `tools/stage-source-offer.sh`; the
WebAssembly and source-offer arrays are identical. Patch `0020` adds bounded
per-viewer replay reliable backpressure; patch `0021` keeps ordinary replay
viewers on the full-state playback path. Patch `0023` keeps browser WebSocket
callbacks free of engine output and reports their bounded notices from normal
network polling. Patch `0032` makes playerclip participation an opt-in AAS compiler
policy; patch `0033` preserves the existing `botStatus` summary and appends read-only
per-bot navigation telemetry used by the retail movement gate. Patch `0034` routes
real bot clients through the normal server respawn lifecycle instead of forcing them
to remain spectators, without changing the separate fake-client replay path. Patch
`0035` refreshes the collision manager's cached trace-model material at each map load
so full engine reloads cannot reuse a declaration from the previous decl-manager epoch.
