95 lines
5.5 KiB
Markdown
95 lines
5.5 KiB
Markdown
# AGENTS.md
|
|
|
|
Battle for 'Tismo — HTML5 port of the strategy game. Pure JavaScript: shared
|
|
game logic runs both in Node (authoritative server) and in the browser (client).
|
|
There is no build step and no runtime dependency; the client vendors jQuery and
|
|
everything else (transport included) is hand-written.
|
|
|
|
## Commands
|
|
|
|
- Install dev dependencies once: `npm install` (only `c8` and `jsdom`, used by
|
|
the test suite — nothing the game runs needs them).
|
|
- Run the test suite: `node tests/run_tests.js` (alias `npm test`; prints a
|
|
summary and exits non-zero on failure).
|
|
- Coverage report: `npm run coverage` (c8, writes `coverage/`).
|
|
- Start the game server and client locally:
|
|
`node server/server.js --port 27015 --bind 127.0.0.1`. Then open
|
|
`http://127.0.0.1:27015/` (redirects to `/client/index.html`). The browser
|
|
connects to `ws(s)://<page-origin>/ws`.
|
|
- Install the versioned git hooks (runs the tests on every commit):
|
|
`./scripts/install-git-hooks.sh`; uninstall with
|
|
`git config --unset core.hooksPath`.
|
|
- Build the deployable packages: `nix build .#server` and `nix build .#web`.
|
|
- If `node` is not on PATH, use the pinned one: `nix-shell -p nodejs --run "node tests/run_tests.js"`.
|
|
|
|
There is no linter, formatter, or CI. Do not invent commands beyond these.
|
|
|
|
## Gotchas
|
|
|
|
- Keep the shared modules framework-free: they are imported unchanged by both
|
|
`server/` (Node, ESM) and `client/js/` (browser). No Node built-ins and no DOM
|
|
APIs in `shared/` (`server/websocket.js` is the only place Node built-ins are
|
|
expected).
|
|
- Do not add JavaScript libraries. The only third-party code allowed is the
|
|
vendored jQuery under `client/vendor/`; implement everything else by hand
|
|
(see `server/websocket.js`, a dependency-free RFC 6455 server).
|
|
- The game server also serves the app as static files from the repository root,
|
|
so modules resolve as `/client/...` and `/shared/...`. Preserve the
|
|
`client/` + `shared/` layout when packaging (`nix/package.nix`).
|
|
- WebSocket state snapshots can be hundreds of KiB; the custom server handles
|
|
fragmented and large frames (`server/websocket.js`). Do not lower the buffer
|
|
limits without checking the snapshot path.
|
|
- Indent `.js` files with 2 spaces. `.editorconfig` only enforces UTF-8.
|
|
|
|
## Architecture
|
|
|
|
- Game logic is server-authoritative. `shared/game_state.js` (`GameState`) is a
|
|
framework-free model that owns the world, units, cities, territory, economy,
|
|
training and visibility. `server/game_server.js` (`GameServer`) owns a
|
|
`GameState`, validates orders and broadcasts snapshots over the transport in
|
|
`server/server.js`. The browser client is a view: `client/js/map_view.js`
|
|
rebuilds terrain from the shared seed, renders snapshots and sends orders.
|
|
- Movement is continuous: `server/server.js` advances movement every tick and
|
|
strikes an in-game hour every `SECONDS_PER_HOUR` real seconds. The client
|
|
extrapolates each unit along its snapshot path and predicts move orders
|
|
locally (`map_view.js`), mirroring `GameState.find_path` so a click feels
|
|
instant. **Any pathfinding or movement-cost change must be mirrored on both
|
|
sides** or prediction will fight the server.
|
|
- `shared/` holds the reusable algorithms so they stay testable without a
|
|
server: `hex.js` (topology/geometry), `map_generator.js`, `hex_pathfinder.js`,
|
|
`terrain_stats.js`, `rules.js`, `economy` figures in `game_state.js`,
|
|
`game_clock.js`, `rng.js`, `noise.js`, `text_format.js`, `login_manager.js`.
|
|
Data-driven content lives in `shared/data/` (one small module per topic:
|
|
civilisations, units, buildings, governments, technologies, terrain, ...).
|
|
`shared/data.js` is a barrel re-exporting the whole catalogue, so keep
|
|
importing from `shared/data.js` and edit the file under `shared/data/`.
|
|
`shared/game_state.js` is likewise a thin class composed from the mixins in
|
|
`shared/game_state/` (world, territory, economy, movement, combat, status,
|
|
diplomacy, orders, visibility, serialization).
|
|
- `client/js/` splits the UI: `app.js` (screens and flow), `net.js` (transport
|
|
wrapper), `game_screen.js` + `game_screen/` (HUD and panels), `map_view.js` +
|
|
`map_view/` (DOM map), `modals.js` + `modals/` (training, buildings,
|
|
nation/technology, confirm, news).
|
|
- Tests live in `tests/` as `*_test.js` files extending the `TestCase` base in
|
|
`tests/framework/`; `tests/run_tests.js` discovers and runs them. Add a new
|
|
`tests/<topic>_test.js` for each new pure behaviour.
|
|
|
|
## Deployment
|
|
|
|
- `flake.nix` exposes `packages.<system>.server` and `.web`, plus
|
|
`nixosModules.default`. There is no compiler step: `nix/package.nix` copies
|
|
the app and wraps `node server/server.js`; `web` is the static `client/` and
|
|
`shared/` tree.
|
|
- `nix/module.nix` runs the server as a `services.battle-for-tismo` systemd unit
|
|
and serves the static client through nginx, redirecting `/` to
|
|
`/client/index.html` and proxying `/ws` to the local Node server. The browser
|
|
client connects to the page's own origin, so HTTPS gives it `wss://`.
|
|
- Build locally with `nix build .#server` and `nix build .#web`.
|
|
- Updating without a rebuild: set `services.battle-for-tismo.appDirectory` to a
|
|
writable checkout (e.g. `/opt/battle-for-tismo`); the unit then runs
|
|
`node <dir>/server/server.js` instead of the Nix-store copy. Serve nginx
|
|
`client/` and `shared/` from that same checkout (e.g. `alias` locations), then
|
|
`git pull` and `systemctl restart battle-for-tismo`. Keep the client and
|
|
server trees on the same commit: the client mirrors the server's movement
|
|
model, so a mismatched pair makes prediction fight the server.
|