7.5 KiB
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:
- Decide the duration in simulated days first. This is the primary value.
- Derive
duration_msby multiplication:duration_ms = duration_days × MS_PER_SIM_DAY - Never set
duration_msindependently and then divide to getduration_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:
- Verify that
duration_daysis a positive integer. - Compute
duration_ms = duration_days × MS_PER_SIM_DAY. - Record both values. Never record only one.
- If
MS_PER_SIM_DAYhas changed since the last route was added, recompute all existingduration_msvalues. 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.