221 lines
7.5 KiB
Markdown
221 lines
7.5 KiB
Markdown
<!-- 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.*
|