A multiplayer, seasonal push-your-luck web game. Players extract strange artifacts from a mysterious mountain, destabilize them for greater value (risking catastrophic collapse), and sell to competing factions before the season ends.
Built with Mojolicious (Perl). See GAME_ARCHITECTURE.md for the full design
specification, AGENTS.md for codebase conventions, and docs/ for design
reference. All configurable fields for magic_mountain.yml are documented
in docs/TUNING.md.
Licensed under the MIT License.
This project was developed with AI-assisted tooling: specialized agents handle implementation scaffolding, boundary-rule review, and plan analysis, while human review gates every change through CI (tests, linting, structural checks). The result is a hybrid workflow — AI accelerates iteration, human judgment owns architecture and quality.
The game listens on http://localhost:9000 and must be placed behind a reverse
proxy (Apache, nginx, etc.) that terminates TLS and proxies to the backend.
Project name / public name: the codebase is Magic Mountain; the public facing deployment is ProspectBoy 3000 (pb3k). Do not use
pb3kas-is in production — replace it with a non-guessable path segment.
Apache — mount at something like https://your.domain/pb3k/:
ProxyPreserveHost On
ProxyPass /pb3k/ http://localhost:9000/
ProxyPassReverse /pb3k/ http://localhost:9000/
RequestHeader set X-Forwarded-Prefix "/pb3k"The X-Forwarded-Prefix header tells the app what path it's mounted under.
A before_dispatch hook in MagicMountain.pm splits the prefix from the
request path and moves it to the base URL, so url_for('route_name')
generates correct prefixed URLs.
The backend receives a clean path (no prefix). Direct access on
localhost:9000 also works with no prefix applied — the hook simply
returns early when the header is absent.
If you cannot inject the header, keep the prefix in the proxied path:
ProxyPreserveHost On
ProxyPass /pb3k/ http://localhost:9000/pb3k/
ProxyPassReverse /pb3k/ http://localhost:9000/pb3k/The before_dispatch hook detects the prefix in the incoming URL path
and shifts it to the base. A root-to-/pb3k/ redirect is recommended so
visitors landing on https://your.domain/ end up at the prefixed URL:
RedirectMatch ^/$ /pb3k/- All URLs must go through
url_for('named_route')— never hardcode a path string like'/game'. The only exception is the/_Gglobal set in the layout template, which usesurl_for()at render time. Hardcoded paths bypass the prefix and break behind the proxy. - Models never know about URLs. Controllers compute image paths via
url_for('/images')and inject them into model constructors. - Client-side navigation uses the
_Gglobal (defined viaurl_for()in<head>) for allfetch()andlocationassignments.
A plain /health endpoint returns { "ok": true } with no auth or
database requirement — useful for load-balancer probes:
GET https://your.domain/pb3k/health