252 lines
13 KiB
Markdown
252 lines
13 KiB
Markdown
# 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.
|
||
- "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 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 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-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.
|
||
```
|