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.
5.7 KiB
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(onlyc8andjsdom, used by the test suite — nothing the game runs needs them). - Run the test suite:
node tests/run_tests.js(aliasnpm test; prints a summary and exits non-zero on failure). - Coverage report:
npm run coverage(c8, writescoverage/). - Start the game server and client locally:
node server/server.js --port 27015 --bind 127.0.0.1. Then openhttp://127.0.0.1:27015/(redirects to/client/index.html). The browser connects tows(s)://<page-origin>/ws. - Install the versioned git hooks (runs the tests on every commit):
./scripts/install-git-hooks.sh; uninstall withgit config --unset core.hooksPath. - Build the deployable packages:
nix build .#serverandnix build .#web. - If
nodeis 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) andclient/js/(browser). No Node built-ins and no DOM APIs inshared/(server/websocket.jsis 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 (seeserver/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 theclient/+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
.jsfiles with 2 spaces..editorconfigonly 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 aGameState, validates orders and broadcasts snapshots over the transport inserver/server.js. The browser client is a view:client/js/map_view.jsrebuilds terrain from the shared seed, renders snapshots and sends orders. - Movement is continuous:
server/server.jsadvances movement every tick and strikes an in-game hour everySECONDS_PER_HOURreal seconds. The client extrapolates each unit along its snapshot path and predicts move orders locally (map_view.js), mirroringGameState.find_pathso 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 intoGameState.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 chunkedlayer-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,economyfigures ingame_state.js,game_clock.js,rng.js,noise.js,text_format.js,login_manager.js. Data-driven content lives inshared/data/(one small module per topic: civilisations, units, buildings, governments, technologies, terrain, ...).shared/data.jsis a barrel re-exporting the whole catalogue, so keep importing fromshared/data.jsand edit the file undershared/data/.shared/game_state.jsis likewise a thin class composed from the mixins inshared/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.jsfiles extending theTestCasebase intests/framework/;tests/run_tests.jsdiscovers and runs them. Add a newtests/<topic>_test.jsfor each new pure behaviour.
Deployment
flake.nixexposespackages.<system>.serverand.web, plusnixosModules.default. There is no compiler step:nix/package.nixcopies the app and wrapsnode server/server.js;webis the staticclient/andshared/tree.nix/module.nixruns the server as aservices.battle-for-tismosystemd unit and serves the static client through nginx, redirecting/to/client/index.htmland proxying/wsto the local Node server. The browser client connects to the page's own origin, so HTTPS gives itwss://.- Build locally with
nix build .#serverandnix build .#web. - Updating without a rebuild: set
services.battle-for-tismo.appDirectoryto a writable checkout (e.g./opt/battle-for-tismo); the unit then runsnode <dir>/server/server.jsinstead of the Nix-store copy. Serve nginxclient/andshared/from that same checkout (e.g.aliaslocations), thengit pullandsystemctl 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.