Egor Urvanov

Architecture of “Chronicles of Kingdoms”: C4 and critical paths

C4 diagrams down to components and four critical paths as sequences: starting a match, a frame, an order and save/load.

“Chronicles of Kingdoms” is a real-time strategy in the spirit of Age of Empires II that runs as an ordinary website: no server and no build step, about 80 JavaScript modules and one canvas filling the page. It was ported line by line from the Python version, so the rules and numbers match the original. Below: C4 diagrams down to components and the four paths everything rests on.

C4: context and containers

Level 1. The system and its surroundings

Playermouse, keyboardChroniclesa browser gameStatic hostany, no codeBrowser storageIndexedDBAsset pipelinetools/, run oncePython versionreference for checksplaysserves filessavessprites, soundscross-check
The game needs only files and a browser. The pipeline and the Python version work outside the game: one prepares the pictures, the other is the reference for tests.

Level 2. Containers

Static hostfiles as they areLoaderindex.html, boot.jsAssetsassets/ + manifestGamemain.js + src/Runtimeruntime/Browser APIsCanvas, Audio, IDBthe pageasset groupsimportpygame callsreadsdraws, sounds
The loader fetches assets and shows a progress bar, then plugs the game in. The game talks to the browser only through the runtime, which mimics the pygame API.

C4 level 3: components

The main container, “Game”, splits into three parts: the simulation (the rules), the opponent (AI) and the interface. The simulation knows nothing about graphics, so tests run it without a screen. The runtime is described separately.

Simulation

Navynaval.jsTablesdata.js, content/Market, relicsmarket, relicsMatch rulesmatch.jsWorldworld.jsStatsstats, scoringMapsmapgen, terrainOrdersorders.jsDefensedefense.jsstatssettingsgenerationorder queuewalls, garrisonwater, shipstick hookevents
World holds the players, units, buildings, projectiles and fog and finds paths; one world step moves all of it.
  • Units, buildings, techs and the 14 civilizations sit in the content/* tables: a new unit is a line of data.
  • Match parameters (resources, ages, treaty, victory) are collected in match.js.
  • Maps: six recipes in mapgen.js plus relief with heights, cliffs and shallows.

Opponent (AI)

Economyeco_ai.jsWorldsame as the player’sNavynaval_ai.jsProfiles6 levels, per agePlannerai.jsDefenseai_defense.jsArmyai_army.jsWarai_war.jsnumberscmd_*surpluscompositionwavesbellwater maps
Each opponent thinks twice a second and issues the same commands as the player.
  • A profile says how many villagers to keep in each age, when to advance and when to attack; there are six levels, from easiest to extreme.
  • War runs in a loop: rally, march, engage, retreat; the decision comes from comparing strengths.
  • The navy is plugged in only on maps with water.

Interface

Menu and lobbymenu, lobby, screensSavessavegame, saves_uiAudiosound, music, synthWorldreads, sends ordersGame screenui.jsRenderinggfx, sprites3dPanelshud, hud_windowsControlscontrols.js, keymapLanguagesi18n, 7 localesupdate(dt)stateF7 / F8world eventsthe frameHUDinputtexts
The game screen is one big class with the panels, controls, menu, lobby and the screens around a match mixed into it.
  • The loop is simple: events, world update, drawing, sound; the menu, loading and results are states of the same loop.
  • Audio listens to the world’s events (a hit, a death, a building) and decides what to play.
  • The picture uses ready sprites; if they are missing, a fallback procedural graphic is drawn.

Runtime

Python idiomspy.jsGamesrc/numpynp.jsSerializerpickle.jspygamepygame.jsAssetsassets.jsFilesstorage.jsBrowser APIsCanvas, Audio, IDBnumbers, randomdraws, inputsound synthesissavesimages, soundCanvas, AudioIndexedDBfiles
The game code kept its Python style; the runtime pretends to be Python, pygame and numpy on top of the browser.
  • py.js reproduces numbers, sorting and random bit for bit, so a saved game and the tests give the same results as in Python.
  • storage.js behaves like plain files: it reads and writes at once and flushes to IndexedDB in the background.
  • assets.js loads by manifest in groups; unit sheets arrive one by one when needed.

Four critical paths

1. Starting a match

PlayerLobbyLoadingWorldAIAssetsStart Gamebegin_loadingnew World(...)map, resources, starting unitsworld readyone AI per opponentstarting unit sheetscamera, ground, panelssheets arrivedmatch is on
The world is built in one step and the picture waits for the starting units’ sprites (for a short limit at most).

2. One frame

LoopWorldUnitsBuildingsAIPicture, soundinput: mouse, keysupdate(dt)update of each unitpath, gather, fightqueues and buildingfog, hooks, victoryai.update(dt)cmd_* ordersworld updateddraw + world events
The frame time is cut into steps of at most 0.034 s; the AI decides every half second and idles the rest of the frames.

3. An order and its execution

PlayerGame screenOrdersUnitWorldright-click on the maptarget: ground, resource, enemy, buildingissue(unit, order)cmd_*: stateupdate every framefind_path (A*)pathwalks, gathers, fightson_idle: from queue
The player and the AI use the same unit commands; the Shift queue and patrols live in the orders module.

4. Save and load

PlayerGame screenSavespickle.jsstorage.jsIndexedDBF7 or a menu slotsave_worlddumps: world, cameratext, table addresses.tmp, then replacein backgroundF8 or a menu slotload_worldreads the fileheader + bodyloadsWorld, randomworld, camera, headerattach_world(w, ui)
The whole object graph goes into the file; static tables are written as addresses and taken from the current version of the game.

The rules everything rests on

  • The World is the only source of truth. The screen only reads it and sends orders.
  • The player and the AI are equals. Both command units through the same cmd_*.
  • Data is separate from code. Units, buildings, techs and civilizations live in tables.
  • Simulation without graphics. Tests run whole AI-vs-AI matches without a screen and compare them with CPython.
  • What is read synchronously is loaded in advance. The loader puts assets in memory before the game starts; unit sheets stream in one by one.

Weak spots

Where What is wrong
Big files world.js is 3.2 k lines, ui.js 2.2 k, hud.js 2 k: there are few boundaries inside them.
Mixed-in parts The game screen is assembled from six mixed-in parts that talk through shared fields.
Links by name Modules find each other through the modules.* registry at run time: the links are invisible in the code.
Step depends on the frame The step length comes from real time. A shared online match would need a fixed step.
Weight The first load is about 60 MB, all unit sheets another ~90 MB; in memory they inflate to gigabytes.
No server Saves live in one browser only, and there is no online play.

About the game

Game pageChronicles of KingdomsA real-time strategy in the spirit of Age of Empires II — right in your browser
© 2026 Egor Urvanov