Replaces the 3-line upstream stub with a manual that documents the platform
as it actually runs. Every claim is derived from the live code and registry
rather than from memory.
Challenge spec:
- 28-challenge tables (6 XVIII / 10 XVI / 12 XVII, 16 active) generated from
teams/challenge_registry.json, with per-challenge org_port, chall/ssh
offsets, and the real team-1 runtime ports read from state.json.
- Port formula corrected to the real one:
port = 30000 + idx*1000 + chall_offset. org_port is the native graveyard
port and is NOT used for runtime allocation, so two challenges sharing an
org_port (carbeat offset 1 vs anti-alchemy offset 30) never collide.
- Per-challenge ssh_user documented: only the 6 native XVIII images provision
ctfuser; all imported XVI/XVII images chpasswd root, so hardcoding ctfuser
breaks 10 of the 16 active challenges.
- Scoring: 100 per flag awarded to the ATTACKER (first solve only), +50 SLA
bonus at most once per 5-minute window, runtime threshold documented as
len(enabled_challenges()) rather than the hardcoded constant 6.
Setup and operations:
- Setup from clone: required /opt path, Docker, venv, panel credentials, both
systemd units verbatim, team creation, verification step.
- Full HTTP API split into public / admin / team, including why challenge
toggle and bulk team delete are async jobs.
- Troubleshooting and operational traps as declarative rules: the bare domain
is the receiver and not the panel, EOL base images, UFW default-deny
silently blackholing ports, the mandatory compose -p teamN project name,
and why docker image prune -af destroys services-* images that are in use.
- Image sizes measured from the host (189MB-903MB, ~8GB for 16 active)
instead of the incorrect "~3GB per challenge" figure.
- Topology section: PixiJS v8, on-demand rendering, and the parent-to-child
drag hierarchy derived from the edge list.
The flag example is redacted to a placeholder. No live credential, token, or
flag is committed. README.md is the only file touched.