01 Purpose
Webs is an original engine for 2D platform-fighting games. It runs PhreeLabs’ own fighter projects, and Architect builds games for it.
Intended user: a developer building a 2D platform fighter who needs exact timing and rules that can be tuned without recompiling.
02 Problem and Constraints
A platform fighter lives or dies on timing. Every hit, jump, and landing has to resolve the same way every tick, while designers keep changing the moves.
- A fixed 60 Hz simulation; drawing never advances game state.
- Game rules must change while a match runs, without recompiling C++.
- A bad rules edit must never crash a running match.
- No third-party character assets in the repository: the default match uses development shapes and an original synthesized sound.
03 What Was Built
- Two-player local matches: move, jump onto one-way platforms, punch and kick on the ground or in the air, guard, throw, lose health, reach defeat, and restart. The included prototype uses one arena and simple development shapes.
- An input buffer: an attack pressed during recovery or landing comes out on the first legal tick.
- Motion commands: down, down-forward, forward plus an attack performs the special, mirrored for either facing.
- Live-reloadable Lua rules for movement decisions, attack timings, damage, hitboxes, guard, throws, input windows, and stage values.
- A debug overlay and an input-timing capture that writes per-tick logs for analysis.
- Deterministic asset generation and validation in Python, checked against SHA-256 hashes.
- An experimental learning system that practises against an opponent using the same inputs as a human player.
- A self-contained Apple silicon Mac app that carries its non-system libraries. A copy moved away from the project launched without loading those libraries from elsewhere.
04 Design and Engineering Decisions
- 01
C++ Owns Time, Lua Owns Rules
C++ runs the 60 Hz tick, input, collision, combat validation, health, and audio. Lua decides movement and attacks and holds every tuning value, so rules change without touching the engine.
- 02
Validate Before Swapping
A reload builds a new Lua VM, validates the whole configuration, and probes its callbacks before replacing the running rules. A rejected reload shows its error while the last working rules keep running.
- 03
A Small, Sandboxed Contract
Lua receives read-only copies of the match state and returns bounded commands. The VM has an instruction limit and no file or operating-system access.
- 04
Fixed Steps, Capped Catch-Up
A fixed-step accumulator drives the simulation. Stalls are capped at 250 ms and eight catch-up ticks per frame, so one slow frame cannot snowball.
- 05
A Measured Renderer Choice
Profiling found repeated one-second waits for a Metal drawable through SDL on the development Mac. The macOS build now prefers SDL’s OpenGL renderer with batching and vsync, with a software fallback.
05 Architecture
06 Verified Results
| Measure | Result | Context |
|---|---|---|
| Input response | Median 13.89 ms, p95 15.12 ms, max 15.65 ms | 120 taps, polling to presentation return on an Apple M3 MacBook Air. A software endpoint, not physical screen latency. |
| Frame stalls | 0 one-second stalls in 597 frames | Same 120-input comparison, after the renderer change |
| Engine test suite | 22 of 22 passing | CTest on the committed source the app is built from, 4 November 2026 |
| Buffer, guard, throw, and motion | 107 of 107 checks passing | Run through the shipped Lua rules, 4 November 2026 |
| Live match checks | 18 of 18 mechanics, 59 of 59 combat | Injected key events in the running app, checked against its own tick capture, 4 November 2026 |
| Replay determinism | Same state hash on all 3,000 ticks | Random inputs replayed twice on the development Mac |
| Self-contained app | 0 non-system library dependencies loaded from outside the app | A moved copy launched from a temporary folder, 4 November 2026 |
| Learning experiment: decision cost | p99 1.691 ms, max 2.007 ms | Software measurement in the final app |
| Learning experiment: outcome | 439 average damage vs 319 untrained | Three trained seeds against a neutral-input opponent. It did not meet its 30-second defeat goal. |
07 Third-Party Components
SDL for the window, rendering, input, and audio, and Lua for game rules, both installed from their official sources, and FreeType for screen text in authored menus. Their license notices are kept in the repository and ship inside the packaged app. The default match contains no third-party art or audio.

