A local-first Left 4 Dead 2 demo review workbench for match statistics, tactical reconstruction, and explainable review signals.
L4DStats turns SourceTV and player-recorded POV .dem files into a browsable
account of a Versus match. It reconstructs rounds and players, calculates descriptive statistics,
plots tick-addressed events, and surfaces unusual moments for human review.
It is designed to preserve uncertainty. Missing telemetry stays missing, provenance travels with derived results, and every review signal includes its explanation, limitations, and strongest available counterevidence.
Important
L4DStats does not determine whether a player cheated. Its output is descriptive and must not be used for automated enforcement or as the sole basis for punitive action.
- Upload one to ten demos and follow each analysis job live.
- Group official and custom-campaign map demos into complete games using embedded server, roster-overlap, campaign, chapter, and generation continuity—not filenames alone.
- Explore Survivor and Infected scoreboards, weapons, SI classes, pins, incaps, revives, deaths, checkpoints, Tank and Witch outcomes, and campaign scoring.
- Scrub a filterable event story using ticks as the canonical coordinate.
- Inspect movement and view-angle coverage, parser quality, reconstruction availability, and provenance alongside the statistics.
- For player-POV recordings, inspect recorder-only command intent separately from server-observed state and outcomes; attack input is never labelled a shot or accuracy.
- Review prerequisite-gated detector windows with tick deep links, explanations, limitations, counterevidence, and quality metadata.
- Visualize player paths against provenance-stamped analytical geometry for all 57 official campaign maps.
- Reanalyze retained artifacts with the current engine without pretending old and new parser versions are equivalent.
The complete local stack requires Docker Desktop (or Docker Engine with Compose):
git clone https://github.com/banja-au/l4dstats.git
cd l4dstats
docker compose up --buildOpen http://localhost:5173 and drop in up to ten
.dem files. The API, worker, SQLite database, upload storage, native parser,
and web app run in Compose; application data and dependencies remain in named
volumes.
Stop the stack with Ctrl+C, then remove its containers with:
pnpm dev:docker:downTo expose the workbench beyond localhost, set credentials and replace the development API secret first:
export L4DSTATS_WEB_USERNAME=l4dstats
export L4DSTATS_WEB_PASSWORD='use-a-long-random-password'
export L4DSTATS_API_TOKEN="$(openssl rand -hex 32)"
pnpm dev:dockerUse that access gate only behind TLS or a trusted private network. See the local operations guide for role-based credentials, backup, restore, retention, and production Compose guidance.
Native development requires Node.js 24+, Corepack, and the Rust toolchain
pinned by rust-toolchain.toml:
./init.sh
pnpm build
pnpm devpnpm dev starts the web surface. Use pnpm dev:docker for the complete
upload-to-analysis pipeline. The repository-built Node-API addon is mandatory:
if it is missing, stale, or incompatible, analysis fails explicitly rather
than falling back to a different parser.
browser uploads
│ streamed, bounded, SHA-256 hashed
▼
API ──► durable SQLite queue ──► worker
│
clean-room Rust decode + L4D2 projection
│
statistics + explainable review signals
│
browser ◄── versioned analysis artifact
The decoder is clean-room Rust exposed through one coarse, bytes-only Node-API boundary. TypeScript owns strict contract adaptation, statistics, detectors, storage, and presentation. Parser execution is isolated from network access; uploaded archives pass through bounded, fail-closed expansion checks before decoding.
The monorepo is organized around narrow interfaces:
apps/web/ React/Vite review experience
apps/api/ streaming uploads and job state
apps/worker/ isolated, retryable analysis jobs
apps/cli/ deterministic demo and corpus inspection
apps/edge/ hosted Cloudflare boundary
packages/contracts/ versioned observation and evidence contracts
packages/detectors/ explainable review-signal detectors
packages/l4d2-rating/ shared rating methodology implementation
packages/storage/ SQLite/Turso metadata and artifact indexes
packages/map-source1/ bounded analytical map-geometry extraction
crates/demo-source1-native/ clean-room Source 1 decoder and L4D2 projection
crates/demo-source1-node/ bytes-only Node-API binding
Read the architecture guide and
threat model for the full trust boundaries.
The hosted production boundary uses Terraform-owned capacity policy and a
Wrangler-published Worker/Container image; production currently permits ten
concurrent standard-3 parser Containers with matching Queue concurrency.
Each claimed job now exposes coarse stage progress, deterministic 4xx parser
failures stop immediately instead of consuming three attempts, and privacy-safe
PostHog events distinguish real attempt latency from total job age.
The browser accepts raw .dem files. Hosted ingestion also supports a single
demo in .zip, .dem.gz, .dem.xz, .dem.bz2, or .dem.zst, subject to
suffix/magic agreement, member and path checks, compressed and expanded byte
caps, ratio limits, timeouts, hashing, and idempotent job handling.
Demo telemetry is not ground truth. SourceTV/player-POV perspective, packet loss, parser coverage, pauses, skips, map assets, and game context all limit what can be concluded. L4DStats therefore:
- uses ticks as the primary timeline and stores derived demo time separately;
- identifies a player by stable Steam identity plus a demo-local connection epoch, never a slot or user ID alone;
- records parser, detector, model, configuration, map-asset, and derivation lineage;
- renders unavailable values as unavailable, never as zero; and
- reports review priority or insufficient data—not a verdict about conduct.
The exact extracted, derived, and unavailable fields are documented in DEMO-DATA.md. See RESEARCH.md for the scientific limits and DETECTORS.md for signal semantics.
Install dependencies with ./init.sh, then run the repository gates:
pnpm format:check
pnpm check
pnpm test
pnpm buildAdditional boundaries have focused gates:
pnpm test:e2e # browser tests; real-corpus cases require ignored fixtures
pnpm test:production # production Compose model, probes, stack, and sandbox
pnpm test:recovery # backup/restore and archive-safety recovery checks
pnpm test:sandbox # parser process isolation
pnpm security:check # JS and Rust dependency/license policyReal demos, archives, player identifiers, databases, clips, and source game assets are intentionally excluded from the repository. Tests that need a real corpus validate ignored local fixtures by hash.
- L4D2 and Versus domain model
- Demo data and availability contract
- Rating methodology
- Architecture
- Detector behavior
- Threat model
- Third-party and licensing notes
- Architecture decision records
Contributions are welcome. Start with CONTRIBUTING.md, keep changes focused, add tests at the cheapest useful layer, and preserve the project's evidence and privacy boundaries.
L4DStats processes untrusted binary files and potentially sensitive behavioral telemetry. Please read SECURITY.md before reporting a vulnerability, and never include private demos or real player data in an issue.
