This document is a starting map for future LLMs working on the JavaScript port in
scripts/ and web/. It describes the shape of the current implementation, not
an ideal end state.
web/index.htmldefines the applet-like DOM shell, modal windows, multiplayer lobby, shop, inventory, round setup, stats, about, and license UI.web/styles.cssrecreates the Java applet/window look and sizes the field from CSS variables set by the game.web/scorch.jscontains almost all browser-side logic: rendering, terrain, weapons, AI, inventory/shop, single-player flow, multiplayer client protocol, and DOM event wiring.scripts/multiplayer-server.mjsis a dependency-free Node server. It serves files fromweb/, accepts WebSocket upgrades at/ws, coordinates multiplayer rooms, validates deterministic turn reports, relays chat and state, and writes a generatedstats.html.web/COPYINGis displayed in the license modal.web/bugs.txtis a small note file, not part of the runtime.
There is no bundler, package manifest, or module graph. The browser app is loaded
directly by index.html with <script src="./scorch.js"></script>.
For local multiplayer or served single-player:
node scripts/multiplayer-server.mjsThe server listens on PORT or 4174 by default and serves
http://localhost:4174/. It writes stats to web/stats.html unless
SCORCH_STATS_HTML or STATS_HTML is set.
For browser-only single-player, opening web/index.html directly mostly works,
but the startup code still attempts to connect to /ws and falls back to a
single-player path if the socket fails.
web/scorch.js is organized as one large script with these major layers:
- Constants and data tables:
WEAPONS,ITEMS,tankData, player colors, AI tuning, physics constants, and initial cash. - Utility classes:
Randomis a seedable LCG used for deterministic terrain and effects.Bitmapowns the pixel buffer, terrain/background operations, and primitive drawing helpers used by the game simulation.Playerstores tank identity, position, aim, health/power limit, cash, weapons, items, shield, tracer, and AI flags.
ScorchGameowns the live game simulation and canvas rendering. Important responsibilities include:- sizing the canvas and CSS field dimensions with
resize(); - creating rounds with
newRound(),randomBackground(),generateTerrain(),placeTanks(), anddrop(); - rendering terrain, tanks, shields, wind, chat, tracer trails, and tooltips;
- firing and animating weapons through
fire(),fireMirv(),rollAndExplode(),napalm(),sandExplosion(),diggerExplosion(),funkyExplosion(),explode(), andexplodeMany(); - applying damage, cash, kills, inventory effects, tank settling, and round transitions;
- running AI targeting with
scheduleAiTurn(),takeAiTurn(),findAiShot(),searchAiCandidates(), andsimulateAiMissile(); - producing
checksum()values for multiplayer desync detection.
- sizing the canvas and CSS field dimensions with
- Utility functions convert colors, write pixels/lines, play simple audio, escape HTML, and update shared UI.
MultiplayerSessionwraps the browser WebSocket client. It owns connection state, lobby state, identity, settings, roster, game list, chat log, active turn id, and message handling.- Bottom-of-file DOM wiring connects buttons, forms, tank pickers, keyboard shortcuts, canvas hover, shop/inventory actions, modals, and startup behavior.
The current code is intentionally close to the original applet model: global state plus a central game object. Avoid splitting files casually unless the refactor includes a clear loading strategy, because there is no build step today.
ScorchGame is the source of truth for local simulation state:
bitmap.pixelsholds the terrain/background pixels.playersis the ordered turn roster. Player ids are array indexes.activeis the active player index.wind,maxWind,weapon,animating, androundOvercontrol turn flow.statsSnapshotpreserves displayed stat values across render/update timing.randmust be seeded consistently for multiplayer rounds.
Player.powerLimit functions as health. Damage lowers it; a tank dies when it
falls below MIN_POWER. Player cash and stats are updated during damage and kill
recording, then synchronized in multiplayer turn reports.
Terrain edits happen through Bitmap methods. After destructive or constructive
terrain changes, code normally calls drop() to settle terrain and
settleTanks() to move unsupported tanks and apply fall damage.
Multiplayer is deterministic client simulation with server coordination:
- The host creates a room with settings: resolution, wind, initial cash, rounds, privacy, title, and tank type.
- The server assigns a room code and tracks
clientsplusparticipants. - On start, the server chooses a seed, sends each client a
startpayload, and the browser builds the sameScorchGamestate from that seed and roster. - Aim updates are relayed with
aim. - The active player sends
fire; the server relays it to all clients. - Every client simulates the shot locally, computes
game.checksum(), and sendsturn-completewith alive players, stats, and inventory. - The server waits for all client reports, compares checksums, merges stats and
inventory, and either advances with
turn, ends the round withround-over, or stops the game withdesync.
The server does not simulate physics or weapons. It is authoritative for room membership, turn ordering, active turn ids, round count, shop/ready gates, stats aggregation, and desync handling.
AI in multiplayer is host-driven. Non-host clients receive the same aim and
fire messages, then simulate locally like any other turn.
Client-to-server message types handled in handleMessage():
createjoinadd-aistartaimfiremass-killturn-completechatround-readyshop-updateuse-itemlist-games
Server-to-client message types handled in MultiplayerSession.handle():
helloerrorgame-listlobbystartround-startround-waitinground-ready-completechatgalslashop-updateuse-itemaimfiremass-killturnround-overdesync
When adding protocol fields, update both scripts/multiplayer-server.mjs and
MultiplayerSession.handle()/send helpers in web/scorch.js. Prefer additive
fields with defaults because clients may briefly be out of step during manual
testing.
scripts/multiplayer-server.mjs keeps all state in memory:
gamesmaps room codes to room objects.clientstracks connected sockets.statsByNameaccumulates global human player stats for generated HTML stats.serverMetricsrecords simple lifetime metrics.
Room objects contain room settings, host id, current seed, participant roster, turn ids, active player, ready sets, pending shop clients, desync timer state, and round/game lifecycle flags.
The server includes a tiny WebSocket implementation rather than using a package.
send() writes unmasked JSON text frames to clients. consumeFrames() decodes
masked client text frames and dispatches JSON payloads. Keep this in mind before
using browser WebSocket features that require binary frames, compression, pings,
or large payloads.
Static file serving is deliberately small: paths resolve under web/, / maps
to /index.html, and MIME types are listed in mime.
Multiplayer depends on all clients producing the same post-turn state. Be careful with:
- calls to
Math.random()inside deterministic gameplay paths; - reading wall-clock time for simulation decisions;
- iteration order changes over unordered data;
- DOM, canvas, audio, or browser-specific APIs feeding back into game state;
- floating point changes in projectile, napalm, AI, or damage math;
- async timing differences that can alter game state before checksum capture.
ScorchGame.checksum() samples bitmap pixels plus player alive/position/health
state. It does not cover every field. If a new feature affects future simulation,
consider whether the checksum should include it or whether the server should
merge/relay it explicitly.
One known exception is startGalslaMode(), which uses Math.random() for a
visual gag after a chat trigger. Treat it as cosmetic unless future work makes it
part of authoritative gameplay.
Most UI is static HTML in index.html; scorch.js mutates content and classes.
Hidden modals use the hidden class. Controls are disabled through
setControlsDisabled(), which also accounts for remote turns and AI turns.
The field size is coordinated in two places:
- canvas width/height attributes in
ScorchGame.resize(); - CSS variables
--field-widthand--field-height, also set byresize().
The old applet aesthetic is intentional. styles.css relies heavily on beveled
border colors, fixed pixel sizing, and image-rendering: pixelated.
- Add or tune weapons: edit
WEAPONS, add handling inScorchGame.fire()and terrain/damage helpers as needed, then check shop/inventory display. - Add items: edit
ITEMS,inventoryAction(),applyItemUse(), shop rows, and multiplayeruse-itembehavior if it affects active turns. - Change round setup: update
index.htmlcontrols, DOM event wiring nearstartRound, and multiplayer create payload validation on the server. - Change multiplayer room settings: update create UI,
MultiplayerSession.create(), server validation,lobbyState(),startPayload(), and clientapplySettings(). - Change stats: update browser stat capture/reporting, server
recordStatsDelta()orrecordGameFinished(), andrenderStatsHtml(). - Change turn rules: update both local
ScorchGame.nextTurn()behavior and serveradvanceTurn()behavior.
There is no automated test harness in this repository today. For risky changes, manually verify:
- served startup:
node scripts/multiplayer-server.mjs, then openhttp://localhost:4174/; - single-player round setup, firing, AI turns, shop, inventory, and mass kill;
- multiplayer room create/join in two browser tabs;
- host-driven AI in multiplayer;
- round end, between-round shopping, next-round readiness, and final game end;
- stats generation at
/stats.html; - desync-sensitive changes by confirming both clients advance turns instead of
receiving
desync.