Files
otivm/docs/architecture/simulation-clock.md
2026-05-02 17:40:15 -04:00

221 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- CLAUDE CODE INSTRUCTIONS
Git protocol: git fetch origin main && git merge origin main BEFORE writing.
File to write: docs/architecture/simulation-clock.md
Action: CREATE new file at the path above with the content below this header block.
Commit message: "docs: add simulation clock canonical document"
Push: git push origin main
Rebuild required: NO
PM2 restart required: NO
Do not touch any other file.
-->
# Simulation Clock
## Integer Time Architecture for OTIVM and the Simulator
### TheRON — OTIVM / CIVICVS / TESSERA Stack
### Status: Canonical. Do not alter without project owner instruction.
### Date: 2026-05-02
---
## 0. Purpose
This document defines the governing time model for OTIVM and the Simulator. It
is a hard architectural constraint, not a preference. Every timed element in the
system — routes, scenarios, otium intervals, epoch calculations — must conform
to the integer clock rule defined here. A future assistant or developer who
bypasses this constraint will corrupt the behavioral record.
Read this document before touching any value that involves time, duration, or
simulated date.
---
## 1. The Governing Constant
```
MS_PER_SIM_DAY = 3,000
```
One simulated day equals 3,000 real milliseconds — 3 real seconds.
This is a game compression constant. It has no historical basis and is not
intended to represent any real ratio of Roman time. Its purpose is to make the
game playable without making the simulation clock meaningless. It is defined
once, in `src/constants.js`, and read from there everywhere it is used. It is
never hardcoded in any other file.
---
## 2. The Integer Constraint
**All `duration_ms` values must be exact integer multiples of `MS_PER_SIM_DAY`.**
This is not a preference. It is a hard requirement.
A `duration_ms` that is not a multiple of `MS_PER_SIM_DAY` produces a
fractional `duration_days` value in `venture_legs`. Fractional days corrupt the
behavioral record. The Simulator reads `duration_days` as an integer count. The
epoch arithmetic depends on integer day counts. A single fractional value
propagates error through every downstream calculation.
### The enforcement rule
When adding a route, scenario, or any timed game element:
1. Decide the duration in **simulated days** first. This is the primary value.
2. Derive `duration_ms` by multiplication: `duration_ms = duration_days × MS_PER_SIM_DAY`
3. Never set `duration_ms` independently and then divide to get `duration_days`.
The days are primary. The milliseconds are derived. Always in that order.
### The adjustment record
When `MS_PER_SIM_DAY` was set to 3,000, the Grain route (Brundisium →
Carthago) had a `duration_ms` of 13,000, which is not divisible by 3,000. It
was adjusted to 12,000ms (4 simulated days). The difference is 1 real second —
imperceptible in gameplay. This adjustment was made deliberately and is
recorded here so it is not treated as an error in future review.
---
## 3. Current Route Durations
| Route | From | To | mode | duration_ms | duration_days |
|---|---|---|---|---|---|
| olive | Ostia | Capua | road | 6,000 | 2 |
| wine | Capua | Brundisium | road | 9,000 | 3 |
| grain | Brundisium | Carthago | sea | 12,000 | 4 |
| linen | Carthago | Alexandria | sea | 18,000 | 6 |
All values are exact integer multiples of `MS_PER_SIM_DAY = 3,000`.
---
## 4. Otium Duration
```
OTIUM_DURATION_MS = 9,000 (3 simulated days)
```
Otium takes 3 simulated days. This value was chosen because:
- Otium is purposeful social activity, not passive rest. It requires time
proportional to its function.
- 3 days gives otium weight relative to the shortest route (2 days). A merchant
who completes the olive run and then takes otium has a 5-day cycle — a
working week with meaning.
- The otium duration is a social minimum, not a function of route distance. A
merchant returning from Alexandria takes the same minimum otium as one
returning from Capua. The social obligations otium represents do not scale
with distance travelled.
This produces the following complete cycles:
| Route | route days | otium days | cycle total |
|---|---|---|---|
| olive | 2 | 3 | 5 |
| wine | 3 | 3 | 6 |
| grain | 4 | 3 | 7 |
| linen | 6 | 3 | 9 |
All integers. Pairs of linen runs with one otium: 6 + 6 + 3 = 15 days. Integer
throughout.
---
## 5. The Simulation Epoch Window
The simulation runs from **1 January 100 BCE to 31 December 100 CE**.
| Boundary | Calendar date | Julian Day Number |
|---|---|---|
| Start | 1 January 100 BCE | 1684595 |
| End | 31 December 100 CE | 1757641 |
| Span | — | 73,047 simulated days |
The epoch anchor is `SIM_EPOCH_JDN_START = 1684595`. All simulated dates are
integer offsets from this anchor. The window closes when the simulated JDN
exceeds `SIM_EPOCH_JDN_END = 1757641`.
73,047 is well within the safe range of JavaScript integer arithmetic. No
floating point risk. No BigInt required.
---
## 6. What Is Never Stored
Simulated dates are **never stored in the database**.
`recorded_at` is always real wall-clock UTC in ISO 8601 format. This is the
only timestamp column in the schema. Simulated dates are always derived at
computation time from `recorded_at` and the session anchor.
The database stores facts. Derivations are computation. This separation must
never be violated.
---
## 7. Session Time Arithmetic
Each player session has a `started_at` timestamp recorded in `actor_profile`.
Simulated days elapsed in a session:
```
elapsed_sim_days = floor((now_ms - started_at_ms) / MS_PER_SIM_DAY)
```
This is integer arithmetic throughout. `floor()` ensures no fractional days
enter the calculation.
Current simulated JDN for a session:
```
current_jdn = SIM_EPOCH_JDN_START + elapsed_sim_days
```
Integer addition. No floating point at any step.
---
## 8. Future Route and Scenario Additions
Before adding any route or timed scenario element, the author must:
1. Verify that `duration_days` is a positive integer.
2. Compute `duration_ms = duration_days × MS_PER_SIM_DAY`.
3. Record both values. Never record only one.
4. If `MS_PER_SIM_DAY` has changed since the last route was added, recompute
all existing `duration_ms` values. The constant is the source of truth.
Changing `MS_PER_SIM_DAY` is a breaking change. It requires updating every
`duration_ms` in `constants.js` and every `duration_days` value in existing
player databases. It must not be changed without project owner instruction and
a documented migration plan.
---
## 9. Confidence and Maturity
The compression ratio (`MS_PER_SIM_DAY = 3,000`) is a design decision, not a
historical fact. It carries no `confidence_tag` because it is not a parameter
derived from the Roman world. It is infrastructure.
The epoch window (100 BCE to 100 CE) is historically grounded in the
project's design intent — the period when Rome is most fully itself, as
documented in `docs/DESCENSUS-genesis.md`. The JDN values are computed from
the proleptic Julian calendar and are verifiable.
Route durations in simulated days (`duration_days`) are currently tagged
`confidence_tag: 'estimated'` in the behavioral record. They reflect
compressed game time, not historical journey durations. When the corpus
develops grounded data on route travel times, new `duration_days` values will
enter the database as updated records. The compression ratio will adjust
`duration_ms` accordingly, following the enforcement rule in Section 2.
---
*Simulation Clock — 2026-05-02*
*TheRON — single contributor. AI assistants implement, document, flag — do not direct.*
*A constraint recorded here is a constraint enforced everywhere. No exceptions.*