Files
PQS/docs/ARCHITECTURE.md
T

13 KiB
Raw Blame History

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.15.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. Displayctime 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.
  • "Number of pods" is duplicated in three places (pqs_gameconfig.numpods, the 16 booleans in pqs_pods, and each pqs_mission.MissionSize) and kept in sync manually by console.php 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 25 + current pods
    • clock + rotating info panel. 10s refresh.
  • smallcombinedqueue.php — compact version of the main wall (missions 15 + current pods + info panel), 5s refresh.
  • genconqueue.php — "extended" wall showing missions 615 (further-out queue). 30s refresh.
  • combinedqueue2.php — mid-range wall showing missions 610 + 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.phpreferenced 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 .exes, .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-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/ $_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) — 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/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 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.