PhreeLabsStart a Project

Work / 002

Webs

In a fighting game, one late frame decides who wins. Webs resolves every hit, jump, and landing on a fixed 60 Hz tick, while the game’s rules stay editable mid-match.

Type
Game engine
Platform
macOS, Apple silicon
Built With
C++, Lua, Python, SDL
Status
In development
Role
Founder: product direction, design, and testing
Webs project artwork: a sharp metallic W connected by white web strands against black.
Webs identity artwork · Not a gameplay screenshot
Webs first playable match: a blue and an orange player on a floating platform above a floor, with health bars for P1 and P2 and on-screen controls.
First playable milestone · Engine capture · Development shapes, no third-party assets

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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

Local inputSDL keyboard and controller interfaces
C++ engineFixed 60 Hz tick: movement, collision, combat validation, health, defeat
Lua rulesSandboxed VM: decisions, attack timings, damage, hitboxes, stage values
RendererSDL, OpenGL on macOS
AudioSDL queued audio
Python toolsAsset generation, manifest checks, timing analysis
C++ owns time and state; Lua owns the rules. A rejected Lua reload never replaces the rules that are running.

06 Verified Results

MeasureResultContext
Input responseMedian 13.89 ms, p95 15.12 ms, max 15.65 ms120 taps, polling to presentation return on an Apple M3 MacBook Air. A software endpoint, not physical screen latency.
Frame stalls0 one-second stalls in 597 framesSame 120-input comparison, after the renderer change
Engine test suite22 of 22 passingCTest on the committed source the app is built from, 4 November 2026
Buffer, guard, throw, and motion107 of 107 checks passingRun through the shipped Lua rules, 4 November 2026
Live match checks18 of 18 mechanics, 59 of 59 combatInjected key events in the running app, checked against its own tick capture, 4 November 2026
Replay determinismSame state hash on all 3,000 ticksRandom inputs replayed twice on the development Mac
Self-contained app0 non-system library dependencies loaded from outside the appA moved copy launched from a temporary folder, 4 November 2026
Learning experiment: decision costp99 1.691 ms, max 2.007 msSoftware measurement in the final app
Learning experiment: outcome439 average damage vs 319 untrainedThree 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.

Need something
like this?

Tell me what the software should do. Similar problems are a good place to start. Engines and simulations where performance matters are part of Custom Software.

Next Project
Architect