- Frontend starts and waits for user to pick game mode/options.
- Frontend sends
setOptionsthenstart. - Worker starts engine and creates clients.
- Human client (
DataTransferClient) sends initialgameDatasnapshot once turn handling begins. - Frontend stores raw object map and runs
reconstituteDatato rebuild graph references. - As game changes occur, backend queues patches in
DataQueueand emitsgameDataPatch. - Frontend mutates local object map from patches, reconstitutes, and re-renders affected views.
- Player input sends
actionmessages; backend applies action and emits next patches.
ParentTransport(worker side)- Uses global
postMessage/addEventListener('message').
- Uses global
WorkerTransport(frontend side)- Uses
Worker.postMessageand worker event listeners. - Reconstitutes incoming hierarchy payloads before handing to caller.
- Uses
Both transport implementations normalize DataObject instances by converting to plain objects before send.
- Sent from
DataTransferClient.sendInitialData(). - Includes a
TransferObjectrooted at key entities (player, turn, year). - Frontend treats this as full state bootstrap.
DataQueue patch shape:
type:add | update | removeindex: optional object path string (foo.bar[3]style)value: object payload (or function on backend converted to payload)
Frontend patch handling in Renderer:
add/update- If
indexexists, set nested path inside target object. - Else replace root object entry.
- Merge any
value.objectsreferences into object map.
- If
remove- Remove nested path or root object.
patchdatareceivedis dispatched for each applied patch payload.dataupdatedis dispatched after state reconstitution.DataObserversubscribes by object IDs and triggers callbacks when observed IDs were touched.
When backend needs user choice (chooseFromList):
- Worker sends choice metadata (
choices,key, contextualdata). - Frontend opens
SelectionWindoworActionWindow(for some negotiation cases). - User selection sends chosen ID over
chooseFromListchannel. - Worker resolves pending
chooseFromList()promise and continues turn logic.
- Low-level textual notifications use
notificationchannel. - Rich game notifications use
gameNotificationchannel with typed data consumed byNotificationsUI.
Renderernotes expensive reconstitution in late game and TODOs around worker-thread offload.WorkerTransport.receive/receiveOncelisteners reconstitute hierarchy payloads only for their matching channel (fixed 2026-07-01; previously every listener reconstituted every hierarchy-shaped message).- Patch application relies on manual string-path mutation logic.
- The backend never emits
removepatches (the onlyDataQueue.removecall is commented out), so the frontend object map only shrinks viapruneObjectMap, which compacts unreachable entries every 5 turns — or on 1.5× growth since the last prune — once the map exceeds 5,000 objects. The older inline orphan-cleanup remains commented out for performance reasons.
See memory-growth-analysis-2026-07.md for a detailed analysis of these paths.