Files
Battle-for-Tismo/AGENTS.md
T
adrien 3acd2487df Added roads, regional taxation and a reworked budget view
Roads are pre-generated between cities with a greedy geometric spanner and flatten unit movement cost. City improvements now only affect the tiles their region controls, with per-region tax collection and a Cities tab. The budget is a grouped, scrollable table with 25%/year tax and a 10%-of-GDP opening treasury. Also merged the tile info into the city panel, kept conflict and budget expansion state across snapshots, added join notifications, filled border corners, drew battle sides as stacks and made tab overflow scroll.
2026-09-18 07:11:59 +02:00

5.7 KiB

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. Roads (shared/roads.js, pre-generated into GameState.roads) flatten a tile's movement cost on both sides through _tileTravelHours / _stepHours; the road set is shipped in the snapshot and drawn in the chunked layer-roads.
  • 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.