Refactor world simulation and API to enhance world state management; introduce StoredWorldDto for internal bookkeeping, improve synchronization between simulation and API data, and implement new simulation options for idle and catch-up behavior. Update documentation to reflect changes in data structures and API endpoints.

This commit is contained in:
Leonid Pershin
2026-08-16 23:28:29 +03:00
parent 2a8b7b49b3
commit cb5117edba
23 changed files with 639 additions and 121 deletions
+16
View File
@@ -113,6 +113,11 @@ them without reworking the data model.
| `DELETE /api/worlds/{id}` | Remove a world and its chunks |
| `GET /api/climates` | The climate catalogue for the create form, with the latitude band each preset is the default for |
`state.json` and the API do not share a type. `StoredWorldDto` holds what the simulation needs to resume — the
last tick stamp and the drifting pressure systems — and `WorldSummaryDto` holds what clients see. The only way
from one to the other is `ToSummary()`, so a new endpoint cannot publish the internals by forgetting to strip
them; the wire type has no field that could carry them.
Generation takes tens of seconds — mostly waiting on Overpass — so `POST` returns straight away and the client
polls for status. Only one generation runs at a time, to stay a good citizen on the shared Overpass mirrors.
The number of worlds that may exist at once is capped by `WorldStorage:MaxConcurrentWorlds` (today that means
@@ -136,6 +141,13 @@ The drifting systems are persisted in `state.json` so a restart resumes the sky
than a game day away and the model rolls a fresh sky for the season instead — stepping days of drift in one
jump is not a simulation, it is a teleport.
Time does not run without limit while nobody is here. A world banks at most `Simulation:MaxCatchUpGameHours`
of in-world time per step, so a host that was down for a week wakes its worlds a day older rather than years.
Worlds nobody is looking at also tick lazily, on `Simulation:IdleTickSeconds` instead of every pass. That
costs no accuracy — a step is driven by the wall time since *that* world last ticked, so one long step and
fifty short ones land on the same game time — and reading a world brings it current before answering, which
keeps the work proportional to how much anyone is actually watching.
Snow is the one part of the weather with memory. Everything else is a function of the current instant, but
you cannot tell how deep the snow lies without knowing what the sky did for the last few days, so it is
integrated as the world ticks and stored alongside the pressure systems. A world created in a Siberian
@@ -210,6 +222,10 @@ usual dissolve instead of a special case. The page chrome follows via a `data-th
- `WorldStorage:RootDirectory` — where generated worlds go (default `data/worlds`)
- `WorldStorage:MaxConcurrentWorlds` — how many worlds may exist at once (default `8`)
- `Simulation:MaxCatchUpGameHours` — in-world time a world may bank per step, so downtime does not cost years
(default `24`; `0` removes the limit)
- `Simulation:IdleAfterSeconds` — how long after the last request a world stops counting as watched (default `20`)
- `Simulation:IdleTickSeconds` — tick spacing for unwatched worlds (default `5`)
- `Osm:Endpoints` — Overpass mirrors, tried in order
- `Osm:CacheDirectory` — raw Overpass responses (default `data/osm-cache`)
- `Osm:QueryTimeoutSeconds` / `Osm:RequestTimeoutSeconds` — server-side and client-side budgets