# PQS — Player Queueing System Documentation generated from a full read of the codebase (2026-07-01). ## 1. What this is PQS is a **player queueing and mission-management system for a pod-based mech-combat simulator** (Virtual World / BattleTech-style networked cockpits). It is deployed at large venues to register, queue, and cycle several thousand players through a fixed number of physical pods across a day. The system runs entirely on an **air-gapped LAN**. There is no external internet dependency and DB credentials (`pqs`/`pqs` on `localhost`) are a formality of that closed network. Branding strings in the code reference "FallOut Shelter" and "Cougar CON" (the operator / an event); the sim itself is referred to as **FS** (the game pods that consume `getFS*.php`). ## 2. Technology stack | Layer | Technology | |-------|-----------| | Server | PHP (written for PHP 5.4/5.5, `mysqli` procedural API) | | Database | MySQL 5.1–5.5, MyISAM/InnoDB mix (`pqs` schema) | | Frontend | Static HTML + jQuery 1.x, AJAX polling via `setInterval` | | Pod / kiosk hardware | Raspberry Pi running `omxplayer` for intro/trainer videos, GPIO buttons (`docs/omx_button.py`, `docs/get.py`) | | Time sync | `time.php` shells out to `sudo date -s` to set box clock from a browser | There is **no framework, no router, no build step, no dependency manager**. Each `.php` file is a standalone page reached directly by URL. Shared logic lives in `includes/`. ## 3. Physical / deployment topology ``` ┌─────────────────────────────────────────┐ │ Air-gapped venue LAN (10.0.0.x) │ │ │ Players ─────▶│ Registration kiosk → callsign.php │ │ │ Staff ─────▶│ Reg console → registration.php │ Staff ─────▶│ Game/ops console → console.php │ │ │ Big TVs ─────▶│ Queue displays → combinedqueue.php │ │ smallcombinedqueue │ │ genconqueue.php │ │ │ Console ─────▶│ AutoHotkey scripts (NOT YET RECOVERED) │ operator PC │ read getFSgame.php (mission settings) │ │ read getFSplayers.php (roster/pods CSV)│ │ → keyboard-inject into the game console│ │ which then configures the FS pods │ │ │ Pi video ────▶│ Poll playFSTrainer.php (missing file) │ │ → triggers omxplayer intro video │ └─────────────────────────────────────────┘ All → single PHP + MySQL host ``` ## 4. Core domain model The whole system revolves around **missions** (one pod launch) that fill up with **players**, then get **committed** (launched) one at a time. ### Lifecycle of a mission 1. **Build** — missions are pre-created (`registration.php?build=N`) or created on demand when a player registers and no open mission exists. 2. **Fill** — players self-register at `callsign.php` (kiosk) or staff add them in `registration.php`. Each mission holds up to `MissionSize` players (= number of active pods). 3. **Lock** — staff can lock a mission so no new players join (`locked = 1`). 4. **Commit** — at `console.php` ("Commit Mission"), the lowest-numbered queued mission's players are: - copied into `pqs_currentmission` (who is in the pods right now), - archived into `pqs_pastmissions` (history, with `ctime` timestamp), - deleted from `pqs_queue`, - the mission row marked `Completed = 1`. 5. **Display** — `ctime` of the last commit + `timepergame + timetoreset` drives every "ETD" (estimated departure) and countdown on the display walls. ### How the mission reaches the pods PQS does **not** talk to the game/pods directly. The console operator runs a set of **AutoHotkey scripts** (currently **not recovered** — a gap to close) on the game-console PC. Those scripts read the plain-text output of `getFSgame.php` (mission settings) and `getFSplayers.php` (per-pod roster) and **keyboard-inject those values into the game console UI**, which in turn configures the physical pods. So the `getFS*.php` files are a *data source scraped by AHK*, not an endpoint the pods poll. Their output layout is dictated by whatever those AHK scripts expect — recovering the scripts is required to know the exact contract. ### Group registration (squads) `callsign.php` supports **Group Leader / Group Member** flows: a leader reserves `N` slots in one mission, creating placeholder rows `LEADER-2`, `LEADER-3`…; members later "claim" a placeholder by matching `CallSign LIKE 'who-%'`. Gated by the `GroupAvalible` config flag. ## 5. Database schema (`docs/pqs.sql`) | Table | Purpose | |-------|---------| | `pqs_queue` | Players currently waiting, keyed to a `MissionID`. Core working set. | | `pqs_mission` | One row per mission: size, #players, completed/locked flags, gametype, map, time. | | `pqs_currentmission` | Snapshot of who is in the pods for the mission that just launched. | | `pqs_pastmissions` | Append-only history (callsign, mech, mission, `ctime`). Drives all ETD math. | | `pqs_gameconfig` | Single-row global settings: pod count, time-per-game, weather, radar, friendly fire, etc. | | `pqs_pods` | Single-row 16 booleans: which physical pods (`pod01`..`pod16`) are active. | | `pqs_mechinfo` | Static catalog of ~63 mechs (class, tons, stats, roster #, image). | | `pqs_mapinfo` | Static catalog of maps and which game types each supports. | | `pqs_gametype` | Static catalog of the 10 game modes. | | `pqs_infopanel` | Single `Updated` flag used to trigger display-wall ad rotation refresh. | Notes: - `pqs.sql` is the canonical schema; `dbbuild.sql` is an older SQLyog dump with sample data. - The **16 pod booleans** (`pod01`..`pod16`) are a fixed structure, not a limitation to normalize away: 16 is the max cockpits one game console can control, and each pod is toggled individually because the console addresses every position regardless of whether it is online/operational (see `getFSplayers.php`). `MissionSize` is the *count* of active pods; the booleans say *which* ones. `pqs_gameconfig.numpods` caches that count. `console.php` keeps these in sync on every config save. ## 6. File-by-file reference ### Player-facing (kiosk) - **`callsign.php`** — the player registration kiosk. 1024×768 themed screen: enter callsign, pick class → mech, optional group role, submit. Inserts into `pqs_queue`, auto-creating a mission if none is open. Shows the assigned mission # and ETD, then returns to itself. ### Staff-facing - **`registration.php`** — staff registration console. Add / edit / delete players, add one player to multiple future missions, build missions in bulk, edit a mission's game type & map, lock missions, search. Polls `includes/getreg.php` every 1s. - **`console.php`** — game-ops console. Sets global game configuration (time limits, pods active, weather/radar/damage rules, etc. → `pqs_gameconfig` + `pqs_pods`), locks and **commits** missions. Polls `getconsole.php` + `mechpods.php` every 2s. - **`history.php`** — paginated per-mission history viewer (prev/next), reads `pqs_pastmissions` joined to `pqs_mission`. - **`search.php`** — look up a callsign across queued missions with ETDs. - **`time.php`** — sets the server clock from the browser's JS time via `sudo date -s` (keeps the air-gapped box's clock correct without NTP). ### Display walls (read-only, auto-refresh) - **`combinedqueue.php`** — main wall: next mission + groups 2–5 + current pods + clock + rotating info panel. 10s refresh. - **`smallcombinedqueue.php`** — compact version of the main wall (missions 1–5 + current pods + info panel), 5s refresh. - **`genconqueue.php`** — "extended" wall showing missions 6–15 (further-out queue). 30s refresh. - **`combinedqueue2.php`** — mid-range wall showing missions 6–10 + current pods + clock + info panel, 10s refresh. ### Game-console integration (plain-text output, no HTML) Consumed by the operator's **AutoHotkey scripts** (not the pods directly — see §4 "How the mission reaches the pods"). The AHK scripts are **not yet recovered**. - **`getFSgame.php`** — emits the next mission's settings as newline-delimited values (gametype, map, visibility, weather, … cameraship) for AHK to read. - **`getFSplayers.php`** — **always emits exactly 16 CSV lines** (one per pod slot): `active,callsign,rosterNum,team,unit` — the pod loadout for the next mission. The line count is fixed at 16 by design: the game console addresses all 16 cockpit positions **positionally** whether or not each is online, so an inactive pod is sent as a zeroed line (`0,0,0,0,0`) rather than omitted. 16 is the maximum number of cockpits one game console can currently control. - **`playFSTrainer.php`** — *referenced by `docs/get.py` but missing from the repo*; was polled by a Raspberry Pi to trigger the trainer video. ### `includes/` (AJAX fragments & shared code) - `config.php` — DB connect + load `pqs_gameconfig`/`pqs_pods` into `$_SESSION`. - `getreg.php` / `getqueue.php` / `getconsole.php` / `getcurrent.php` / `getgroup.php` — HTML fragments rendered into the consoles/walls via AJAX. - `now.php` — clock + "estimated time remaining" for current mission. - `mechpods.php` — echoes active pod count from session. - `api.php` — tiny AJAX endpoint: toggle a player's "merc" flag, poll the infopanel `Updated` flag. - `infopanel.php` / `smallinfopanel.php` — rotating advertisement/announcement slides (jQuery Cycle). - `graph.php` — GD-generated progress-bar PNG (appears unused by current pages). ### `docs/` (assets & operator scratch) - `pqs.sql`, `dbbuild.sql` — schema / seed dumps. - `omx_button.py`, `get.py` — Raspberry Pi video-kiosk helper scripts. - `Clear Code.txt` — the SQL to reset for a new event (truncate the 4 live tables). - `readme screen adjust.txt`, `SQL_TCPip_Fixes.reg`, installer `.exe`s, `.mp4` intro/roleplay videos, spare image sets. ## 7. Known issues, risks & technical debt These matter most for the "improve vs. rewrite" decision. **Correctness / concurrency** - **No transactions or locking.** Filling a mission is a read-`numplayers` → compare → `UPDATE +1` sequence with no atomicity. With thousands of players and a kiosk plus staff console writing concurrently, **race conditions can overfill a pod bay** ("Containment Bay OVERLOADED" is literally a handled UI state). `numplayers` is a manually-maintained counter that can drift from the actual `COUNT(*)` in `pqs_queue`. - Pod state is spread across `pqs_pods` (which 16 are active), `MissionSize` (the count), and `pqs_gameconfig.numpods` (a cached count), reconciled by `console.php`. Note the fixed count of **16 is intentional** — the hardware max per console — so this is a sync concern, not a "make pod count dynamic" concern. **Security (mitigated only by the air gap)** - **SQL injection is pervasive** — nearly every query interpolates `$_GET`/ `$_POST` directly (e.g. `console.php` `nummercs`, `api.php` `playerid`, callsign fields). Safe *only* because the network is closed and trusted. - `time.php` passes a request value into `exec("sudo date -s …")` — command execution by design; would be dangerous on any reachable network. - `api.php` and parts of `registration.php`/`console.php` still emit `DEBUG:` output to the page. **Maintainability** - Presentation, business logic, and SQL are fully interleaved in every file; connection boilerplate is copy-pasted dozens of times. - Deprecated `mysql_error()` calls remain alongside `mysqli_*` (would fatal on PHP 7+). The code targets an EOL PHP 5.x runtime. - 16 pods are hard-coded as `pod01..pod16` everywhere (schema, console, FS output). This is **intentional** — 16 is the hardware max per game console and the console requires all 16 positions in a fixed order — but it does make the code verbose (16 near-identical branches in `console.php` and `getFSplayers.php`). A loop over a pod count of 16 would be cleaner without changing the contract. - Display refresh is fixed-interval full-fragment polling (no change detection except the one infopanel flag). - Inline `` tags, table layouts, and absolute pixel sizes throughout. **Runtime assumptions** - Timezone hard-coded to `America/Chicago` in most files. - URLs hard-coded to `/pqs/…` and to server IP via `$_SERVER['SERVER_ADDR']`. ## 8. Strengths worth preserving - The **domain model is sound and battle-tested**: mission → fill → lock → commit → history, with ETD projected from the last commit time. Any rewrite should keep this exact lifecycle. - It is **operationally simple**: drop PHP files on one box, point kiosks and TVs at URLs. No build, no services to orchestrate — valuable at a venue. - Feature set is complete for real events: group/squad reservations, per-mission game/map overrides, merc tracking, bulk mission build, history, live walls, and direct pod integration. ## 9. Improve vs. rewrite — starting point If **improving in place**, the highest-value, lowest-risk fixes first: 1. Make mission-fill atomic (transaction + `SELECT … FOR UPDATE`, or derive `numplayers` from `COUNT(*)`) to end overfill races. 2. Parameterize all queries (mysqli prepared statements) and remove `DEBUG:` output. 3. Centralize DB access into one include; replace remaining `mysql_*` calls. 4. Make timezone configuration-driven; collapse the 16 repeated pod branches into a loop (keep the fixed 16-slot output contract — see below). If **rewriting from scratch**, preserve section 4's lifecycle and section 5's tables as the spec. Treat `getFSgame.php` / `getFSplayers.php` output formats as a **hard contract**, but note the real contract is defined by the **unrecovered AutoHotkey scripts** that scrape them and keyboard-inject into the game console. **Recovering those AHK scripts is a prerequisite** to safely changing anything about how missions reach the pods — without them the exact expected output layout (field order, delimiters) can only be inferred from the PHP. One part of the contract is already known and must be kept: `getFSplayers.php` **always returns exactly 16 lines** in fixed pod order, inactive pods zeroed rather than omitted, because the game console addresses all 16 positions positionally. ```