A 2-dimensional multiplayer game with ray tracing. https://doppler.schmelczer.dev/
  • TypeScript 88%
  • SCSS 6.5%
  • JavaScript 4.2%
  • HTML 0.9%
  • Dockerfile 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Andras Schmelczer 3980d9948b
All checks were successful
Build & deploy / Build & publish server image (pull_request) Has been skipped
Build & deploy / Build & deploy website (pull_request) Successful in 27s
.
2026-09-14 22:19:49 +01:00
.forgejo/workflows . 2026-09-14 22:19:49 +01:00
.vscode No dumb comments 2026-09-14 22:13:15 +01:00
backend . 2026-09-14 22:19:49 +01:00
frontend . 2026-09-14 22:19:49 +01:00
media Add readme 2020-11-17 10:42:49 +01:00
shared . 2026-09-14 22:19:49 +01:00
test No dumb comments 2026-09-14 22:13:15 +01:00
.dockerignore No dumb comments 2026-09-14 22:13:15 +01:00
.gitattributes Improve multiplayer 2020-10-10 10:55:08 +02:00
.gitignore . 2026-09-14 22:19:49 +01:00
.node-version Fix ci 2026-06-15 21:46:03 +01:00
.nvmrc Fix ci 2026-06-15 21:46:03 +01:00
.prettierrc Setup formatting 2020-10-07 16:40:21 +02:00
Dockerfile . 2026-09-14 22:19:49 +01:00
eslint.config.js even more 2026-08-23 10:27:55 +01:00
package-lock.json . 2026-09-14 22:19:49 +01:00
package.json astra 2026-09-14 21:29:18 +01:00
README.md astra 2026-09-14 21:29:18 +01:00
tsconfig.json GLM 2026-08-22 18:35:53 +01:00
vitest.config.ts even more 2026-08-23 10:27:55 +01:00

doppler

A 2-dimensional multiplayer game utilising ray tracing.

Available at doppler.schmelczer.dev.

screenshot of in-game fight three screenshots taken at iPhone SE screen size

For optimised 2D ray tracing, SDF-2D is used.

Development

Run npm install && npm run init once, then npm run dev. Run npm test for regressions, npm run lint:check for formatting and lint, and npm run build for all production bundles. Tests rebuild the shared library before running.

Movement and networking

The server owns physics and combat. It simulates at 200 Hz, keeps each player's latest held movement direction, and sends snapshots at 25 Hz. Clients send input changes immediately on their next frame and send idle heartbeats at 30 Hz.

The local player replays input from the latest authoritative pose using the same physics as the server. The acknowledgement includes movement state, remaining leap cooldown, and the age of the acknowledged input, so the client can align replay without synchronizing wall clocks. Nearby planets are included even when offscreen because their gravity affects prediction. Replay stops after 400 ms without a fresh anchor; only reconciliation corrections are eased over 80 ms. The camera follows the displayed player directly.

Other objects interpolate snapshots on a server timeline delayed by 100 ms, with at most 60 ms of extrapolation. Creation and deletion use that same timeline. Remote effects use that timeline too; feedback for the local player is immediate. End-of-round slow motion comes from server snapshots and disables local prediction; outgoing velocity and projectile fade rates are expressed per wall-clock second. When a connection backs up, regenerable state can be skipped; lifecycle messages keep their timestamps. The frontend bundles one gl-matrix instance so prediction and server physics both use JavaScript's full numeric precision.

Deployment

CI/CD runs on Forgejo Actions (.forgejo/workflows/deploy.yml). On a push to main it:

  • builds the static frontend and rsyncs frontend/dist/ to the /pages/declared mount on the runner host (the mount keeps its pre-rebrand name), and
  • builds the server image from the root Dockerfile and pushes it to the Forgejo container registry as <registry>/<owner>/<repo>-server.

The registry job needs a FORGEJO_PACKAGE_TOKEN secret (with package write scope) and, optionally, a CONTAINER_REGISTRY_HOST variable to override the registry host.

The website's server list is hardcoded in frontend/src/scripts/configuration.ts — edit it to add or remove game-server origins.

Run the server image locally:

docker build -t doppler-server .
docker run -p 3000:3000 doppler-server --name "My server" --playerLimit 16