13 KiB
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
- Build — missions are pre-created (
registration.php?build=N) or created on demand when a player registers and no open mission exists. - Fill — players self-register at
callsign.php(kiosk) or staff add them inregistration.php. Each mission holds up toMissionSizeplayers (= number of active pods). - Lock — staff can lock a mission so no new players join
(
locked = 1). - 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, withctimetimestamp), - deleted from
pqs_queue, - the mission row marked
Completed = 1.
- copied into
- Display —
ctimeof the last commit +timepergame + timetoresetdrives 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.sqlis the canonical schema;dbbuild.sqlis an older SQLyog dump with sample data.- "Number of pods" is duplicated in three places (
pqs_gameconfig.numpods, the 16 booleans inpqs_pods, and eachpqs_mission.MissionSize) and kept in sync manually byconsole.phpon 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 intopqs_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. Pollsincludes/getreg.phpevery 1s.console.php— game-ops console. Sets global game configuration (time limits, pods active, weather/radar/damage rules, etc. →pqs_gameconfigpqs_pods), locks and commits missions. Pollsgetconsole.php+mechpods.phpevery 2s.
history.php— paginated per-mission history viewer (prev/next), readspqs_pastmissionsjoined topqs_mission.search.php— look up a callsign across queued missions with ETDs.time.php— sets the server clock from the browser's JS time viasudo 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— emits 16 CSV lines (one per pod slot):active,callsign,rosterNum,team,unit— the pod loadout for the next mission.playFSTrainer.php— referenced bydocs/get.pybut missing from the repo; was polled by a Raspberry Pi to trigger the trainer video.
includes/ (AJAX fragments & shared code)
config.php— DB connect + loadpqs_gameconfig/pqs_podsinto$_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 infopanelUpdatedflag.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.exes,.mp4intro/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 +1sequence 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).numplayersis a manually-maintained counter that can drift from the actualCOUNT(*)inpqs_queue. - Pod-count is denormalized across three tables and reconciled by hand.
Security (mitigated only by the air gap)
- SQL injection is pervasive — nearly every query interpolates
$_GET/$_POSTdirectly (e.g.console.phpnummercs,api.phpplayerid, callsign fields). Safe only because the network is closed and trusted. time.phppasses a request value intoexec("sudo date -s …")— command execution by design; would be dangerous on any reachable network.api.phpand parts ofregistration.php/console.phpstill emitDEBUG: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 alongsidemysqli_*(would fatal on PHP 7+). The code targets an EOL PHP 5.x runtime. - 16 pods are hard-coded as
pod01..pod16everywhere (schema, console, FS output) — changing pod count is a code edit, not config. - Display refresh is fixed-interval full-fragment polling (no change detection except the one infopanel flag).
- Inline
<font>tags, table layouts, and absolute pixel sizes throughout.
Runtime assumptions
- Timezone hard-coded to
America/Chicagoin 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:
- Make mission-fill atomic (transaction +
SELECT … FOR UPDATE, or derivenumplayersfromCOUNT(*)) to end overfill races. - Parameterize all queries (mysqli prepared statements) and remove
DEBUG:output. - Centralize DB access into one include; replace remaining
mysql_*calls. - Make pod count and timezone configuration-driven, not hard-coded.
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, line count, delimiters) can only be inferred from the PHP.