From e85954aaaee765155526469733f7fc667b25fb85 Mon Sep 17 00:00:00 2001 From: TheRON Date: Sat, 2 May 2026 17:40:15 -0400 Subject: [PATCH] Establishes time conversions --- docs/architecture/simulation-clock.md | 220 ++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 docs/architecture/simulation-clock.md diff --git a/docs/architecture/simulation-clock.md b/docs/architecture/simulation-clock.md new file mode 100644 index 0000000..3142e91 --- /dev/null +++ b/docs/architecture/simulation-clock.md @@ -0,0 +1,220 @@ + + +# 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.*