Build log · Software & apps

Arena — LAN Multiplayer Game

Software & appsdone5 versionsWebSocketPythonCanvasLANpkgbuildReal-time

reflection not written yet · every fact below is generated from this project’s own spec and files

What it is

A top-down arena shooter that runs on your own Wi-Fi: one Mac hosts it and everyone else joins from a browser. Double-click Arena Server and it starts the match, prints the join address and shows a QR code; open Arena Join on any other Mac and it sweeps the local network, finds the host on its own and opens the game — no IP address to type, no internet, no account. Real-time WebSocket combat with a health bar, live scoreboard and kill feed, on a canvas arena that keeps running. Twin-stick: WASD and the mouse on a Mac, two separate thumb sticks on a phone so you can run one way and shoot the other. Both apps install from self-contained .pkg packages — the Python runtime, the server and the client are inside them, so there is nothing else to install. The host also gets a console: a roster of everyone connected with their HP and score, and a Kill button on every row that drops that player instantly — served only to the host machine, so friends on the Wi-Fi are refused. The last pass was about not breaking mid-match: hostile or malformed frames from a client can no longer freeze or kill the simulation, and a bad tick is counted and skipped rather than ending the match.

Version history

Straight from the project’s own card data — what each version added.

V1

Single-room WebSocket arena: real-time movement, aiming, shooting, health and a scoreboard.

V2

Phone support — a separate move stick and aim stick, so you can run one way and shoot another, plus a fire button.

V3

LAN discovery in the join app: it sweeps the network for the host instead of being told an address, and the host page shows the join link plus a QR code.

V3.1

Shipped as two double-click .pkg installers (host + join) and verified by running the packaged bytes rather than the build tree: 39 protocol, 21 hostile-frame and 20 headless-browser checks. Hostile frames can no longer freeze the sim, a bad tick is skipped instead of ending the match, and player names and chat are scrubbed before they reach anyone else's screen.

V4

Host console: the host opens ⚔ Host from the game or the join page and gets a live roster — HP, kills, deaths — with a Kill button per player that drops them instantly, bypassing iframes and crediting nobody. The routes answer loopback only (a LAN request gets 403), the button is hidden from friends, and phone aiming was fixed so firing follows the stick you are actually holding. Verified with 9 host-console checks plus the existing 39 protocol, 21 hostile-frame and QR suites; the shipped payload was unpacked and grepped to prove the new routes are in the .pkg, not just the build tree.

By the numbers

Measured from the real working folder ~/Documents/projects/apps/multiplayer-game-server — not a claim, a count.

31
files
4,319
lines of code
4
file types used
2026-10-11
oldest file
2026-10-24
last touched

Mostly: Python ×24 · Shell ×4 · Markdown ×1 · HTML ×1

What broke, and what I’d change

Not written yet. This is the part only I can write — what actually went wrong, and what I’d do differently. It lives in builds/reflections/multiplayer-game-server.md (see _TEMPLATE.md); as soon as that file has words in it, this section fills in on the next build.

Everything else on this page — the spec, the version history, the file counts — is generated from the project’s own build notes and files, so nothing here is guesswork.

Build spec

The project’s own build notes, rendered. Open the raw .md ↗

Show the spec

Arena — LAN Multiplayer Game

Category: Software & Apps · Status: Done

A top-down arena shooter that runs on your own Wi-Fi: one Mac hosts it and everyone else joins from a browser. Double-click Arena Server and it starts the match, prints the join address and shows a QR code; open Arena Join on any other Mac and it sweeps the local network, finds the host on its own and opens the game — no IP address to type, no internet, no account. Real-time WebSocket combat with a health bar, live scoreboard and kill feed, on a canvas arena that keeps running. Twin-stick: WASD and the mouse on a Mac, two separate thumb sticks on a phone so you can run one way and shoot the other. The host also gets a ⚔ Host console — a live roster of everyone connected with HP and score, and a Kill button on each row that drops that player instantly; the routes answer loopback only, so friends on the Wi-Fi are refused.

Stack / Tools: WebSocket, Python, Canvas, LAN, pkgbuild, Real-time

Build path:

  • V1 — Single-room WebSocket arena: real-time movement, aiming, shooting, health and a scoreboard.
  • V2 — Phone support — a separate move stick and aim stick, so you can run one way and shoot another, plus a fire button.
  • V3 — LAN discovery in the join app: it sweeps the network for the host instead of being told an address, and the host page shows the join link plus a QR code.
  • V3.1 — Shipped as two double-click .pkg installers (host + join), verified by running the packaged bytes rather than the build tree: 39 protocol, 21 hostile-frame and 20 headless-browser checks. Hostile frames can no longer freeze the sim, a bad tick is skipped instead of ending the match, and player names and chat are scrubbed before they reach anyone else's screen.

Source folder: ~/Documents/projects/apps/multiplayer-game-server The user's own build spec for this project is at the end of this file.

Where it lives

  • Source folder: ~/Documents/projects/apps/multiplayer-game-server
  • Artefacts on the site: 28 files · 3,815 lines of text · 135 KB

Stack

  • Python ×21 · Shell ×4 · .txt ×1 · Markdown ×1 · requirements.txt
  • external tools invoked: python3, uvicorn

What's in the code

  • client/index.html — 741 lines (HTML)
  • server/game/game_manager.py — 547 lines (Python)
  • tests/test_multiplayer.py — 374 lines (Python)
  • server/main.py — 484 lines (Python)
  • tests/browser_test.py — 229 lines (Python)
  • scripts/build_join_app.sh — 208 lines (Shell)
  • tests/test_hostile_frames.py — 192 lines (Python)
  • tests/test_admin.py — 92 lines (Python)
  • README.md — 201 lines (Markdown)
  • scripts/build_host_app.sh — 181 lines (Shell)
  • scripts/make_icon.py — 163 lines (Python)
  • scripts/build_pkgs.sh — 155 lines (Shell)
  • server/game/ecs/components.py — 94 lines (Python)

Verified in the Python source

  • routes: get /, get /health, get /status, get /info, get /qr.svg, get /join, get /admin, get /admin/players, post /admin/kill/{entity_id}
  • classes: BulletTag, Client, Collider, ConnectionManager, Entity, GameManager, Health, Input, ItemTag, Name, PlayerTag, Raw, Renderable, Score
  • functions: check, circles_overlap, clamp, clean_text, decode, diamond_alpha, disc_alpha, dist_sq, downsample, encode, error_msg, event_msg, get, info, join, join_url

Notes found in the scripts

  • Verify the shipped bytes inside the rebuilt .pkg payloads.
  • Build the two distributable installers into dist/:
  • "Arena Server.pkg" — install this on the Mac that hosts the game
  • "Arena Join.pkg" — send this to a friend; it installs the little

_Spec generated from the project's own files on 2026-10-13, host-console pass 2026-10-22 (gen-project-specs.py). Everything above was read from the listed files — file counts, line counts, names, routes, headings and script comments. No description was invented._