@mcptoolshop/sovereign 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -0
- package/LICENSE +21 -0
- package/README.es.md +158 -0
- package/README.fr.md +158 -0
- package/README.hi.md +158 -0
- package/README.it.md +158 -0
- package/README.ja.md +158 -0
- package/README.md +158 -0
- package/README.pt-BR.md +158 -0
- package/README.zh.md +158 -0
- package/SECURITY.md +61 -0
- package/bin/sovereign.js +167 -0
- package/package.json +56 -0
- package/release/00-START-HERE.html +333 -0
- package/release/CHANGELOG.md +126 -0
- package/release/README.txt +144 -0
- package/release/balance-evidence/README.txt +81 -0
- package/release/balance-evidence/raw-data/sovereign-batch-v0.10-canonical-400.json +72134 -0
- package/release/balance-evidence/raw-data/sovereign-batch-v0.10-canonical-slot-swap.json +18137 -0
- package/release/balance-evidence/raw-data/sovereign-batch-v0.10-canonical.json +18137 -0
- package/release/balance-evidence/raw-data/sovereign-batch-v0.10-mc-mirror.json +18089 -0
- package/release/balance-evidence/raw-data/sovereign-batch-v0.10-mfg-mirror.json +18089 -0
- package/release/balance-evidence/raw-data/sovereign-batch-v0.10-tf-mirror.json +18089 -0
- package/release/balance-evidence/sovereign-batch-v0.10-canonical-400.html +1 -0
- package/release/balance-evidence/sovereign-batch-v0.10-canonical-slot-swap.html +1 -0
- package/release/balance-evidence/sovereign-batch-v0.10-canonical.html +1 -0
- package/release/balance-evidence/sovereign-batch-v0.10-mc-mirror.html +1 -0
- package/release/balance-evidence/sovereign-batch-v0.10-mfg-mirror.html +1 -0
- package/release/balance-evidence/sovereign-batch-v0.10-summary.html +2 -0
- package/release/balance-evidence/sovereign-batch-v0.10-tf-mirror.html +1 -0
- package/release/board-game/README.txt +48 -0
- package/release/board-game/sovereign-economy-audit.html +501 -0
- package/release/board-game/sovereign-print-audit.html +479 -0
- package/release/board-game/sovereign-prototype.html +1939 -0
- package/release/design-history/01-phase1-concept.html +632 -0
- package/release/design-history/02-phase2-prototype.html +1026 -0
- package/release/design-history/03-phase3-audit.html +268 -0
- package/release/design-history/04-phase4-audit.html +274 -0
- package/release/design-history/05-phase5-audit.html +305 -0
- package/release/design-history/README.txt +66 -0
- package/release/digital-mode/README.txt +89 -0
- package/release/digital-mode/sovereign-solo.html +3884 -0
- package/release/digital-mode/sovereign-v0.10-freeze-audit.html +67 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>Sovereign · Solo / Digital · Phase 5 Audit</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--revolutionary-debt:#6E1F1E; --state-debt:#4A6B8A; --revenue-system:#C28A28;
|
|
10
|
+
--commercial-infrastructure:#2E7A6B; --national-finance:#1F2D52;
|
|
11
|
+
--manufactures:#8C8A2E; --strategic-industry:#3A3A3A;
|
|
12
|
+
--parchment:#F0E6CD; --parchment-2:#E6DABC; --ink:#1A1612; --highlight:#C8392E;
|
|
13
|
+
--rule:rgba(26,22,18,0.55); --rule-soft:rgba(26,22,18,0.22);
|
|
14
|
+
--display:"Baskerville","Big Caslon","Hoefler Text","Garamond","Times New Roman",serif;
|
|
15
|
+
--body:"Iowan Old Style","Georgia","Cambria","Times New Roman",serif;
|
|
16
|
+
--ui:-apple-system,"Segoe UI","Helvetica Neue","Arial",system-ui,sans-serif;
|
|
17
|
+
--mono:"SF Mono","Menlo","Consolas","Courier New",monospace;
|
|
18
|
+
}
|
|
19
|
+
@page { size:8.5in 11in; margin:.55in .65in; }
|
|
20
|
+
*,*::before,*::after { box-sizing:border-box; }
|
|
21
|
+
html,body { margin:0; padding:0; font-family:var(--body); color:var(--ink); background:#2A2622; -webkit-print-color-adjust:exact; print-color-adjust:exact; }
|
|
22
|
+
.viewport { display:flex; flex-direction:column; align-items:center; gap:22px; padding:28px 16px 80px; }
|
|
23
|
+
.page { width:8.5in; min-height:11in; background:var(--parchment); position:relative; box-shadow:0 10px 40px rgba(0,0,0,.55),0 2px 4px rgba(0,0,0,.4); padding:.55in .65in .5in; }
|
|
24
|
+
@media print {
|
|
25
|
+
html,body { background:#fff !important; }
|
|
26
|
+
.viewport { padding:0; gap:0; }
|
|
27
|
+
.page { box-shadow:none; padding:0; min-height:auto; width:100%; page-break-after:always; break-after:page; }
|
|
28
|
+
.page:last-child { page-break-after:auto; break-after:auto; }
|
|
29
|
+
.toolbar { display:none !important; }
|
|
30
|
+
}
|
|
31
|
+
.toolbar { position:fixed; top:16px; right:16px; z-index:30; }
|
|
32
|
+
.toolbar button { background:var(--parchment); color:var(--ink); border:1px solid var(--ink); padding:8px 14px; font-family:var(--ui); font-size:12px; letter-spacing:.08em; text-transform:uppercase; cursor:pointer; }
|
|
33
|
+
.doc-head { display:flex; justify-content:space-between; align-items:baseline; padding-bottom:6px; border-bottom:1px solid var(--ink); font-family:var(--ui); font-size:9.5px; letter-spacing:.22em; text-transform:uppercase; margin-bottom:16px; }
|
|
34
|
+
.doc-head .brand-mark { font-weight:700; letter-spacing:.32em; }
|
|
35
|
+
.doc-head .sigil { font-family:var(--display); font-style:italic; letter-spacing:0; text-transform:none; font-size:12px; }
|
|
36
|
+
h1.title { font-family:var(--display); font-weight:400; font-size:38px; line-height:1; margin:0 0 4px; letter-spacing:-.01em; }
|
|
37
|
+
.subtitle { font-family:var(--display); font-style:italic; font-size:14px; margin-bottom:12px; }
|
|
38
|
+
.lede { font-family:var(--display); font-style:italic; font-size:13px; line-height:1.45; max-width:64ch; margin:0 0 14px; }
|
|
39
|
+
h2 { font-family:var(--display); font-weight:400; font-size:20px; margin:16px 0 6px; border-bottom:1px solid var(--ink); padding-bottom:3px; }
|
|
40
|
+
h2 .ord { font-family:var(--mono); font-size:10.5px; letter-spacing:.12em; opacity:.65; margin-right:8px; }
|
|
41
|
+
h3 { font-family:var(--ui); font-size:10px; font-weight:700; letter-spacing:.18em; text-transform:uppercase; margin:10px 0 4px; }
|
|
42
|
+
p,li { font-family:var(--body); font-size:11px; line-height:1.5; margin:0 0 5px; }
|
|
43
|
+
ul,ol { padding-left:18px; margin:0 0 6px; }
|
|
44
|
+
strong { font-family:var(--display); font-weight:700; }
|
|
45
|
+
em { font-style:italic; }
|
|
46
|
+
.mono,code { font-family:var(--mono); font-size:10px; background:rgba(26,22,18,0.04); padding:0 3px; }
|
|
47
|
+
table { width:100%; border-collapse:collapse; margin:6px 0 10px; font-size:9.5px; }
|
|
48
|
+
th,td { text-align:left; vertical-align:top; padding:3px 6px; line-height:1.35; border-bottom:.5px solid var(--rule-soft); }
|
|
49
|
+
th { font-family:var(--ui); font-weight:700; letter-spacing:.12em; text-transform:uppercase; font-size:8.5px; background:var(--ink); color:var(--parchment); border-bottom:1px solid var(--ink); }
|
|
50
|
+
td.n { font-family:var(--mono); font-size:10px; text-align:right; }
|
|
51
|
+
td.v { font-family:var(--ui); font-size:9px; font-weight:700; letter-spacing:.12em; text-transform:uppercase; white-space:nowrap; }
|
|
52
|
+
td.v.pass { color:var(--commercial-infrastructure); }
|
|
53
|
+
td.v.warn { color:#B07A1F; }
|
|
54
|
+
td.v.fail { color:var(--highlight); }
|
|
55
|
+
.tag-pass { background:rgba(46,122,107,0.15); padding:1px 6px; font-family:var(--ui); font-size:8.5px; letter-spacing:.12em; text-transform:uppercase; font-weight:700; color:var(--commercial-infrastructure); }
|
|
56
|
+
.tag-warn { background:rgba(176,122,31,0.15); padding:1px 6px; font-family:var(--ui); font-size:8.5px; letter-spacing:.12em; text-transform:uppercase; font-weight:700; color:#B07A1F; }
|
|
57
|
+
.tag-fail { background:rgba(200,57,46,0.15); padding:1px 6px; font-family:var(--ui); font-size:8.5px; letter-spacing:.12em; text-transform:uppercase; font-weight:700; color:var(--highlight); }
|
|
58
|
+
.tag-info { background:rgba(31,45,82,0.12); padding:1px 6px; font-family:var(--ui); font-size:8.5px; letter-spacing:.12em; text-transform:uppercase; font-weight:700; color:var(--national-finance); }
|
|
59
|
+
.verdict { padding:12px 14px; border:1.5px solid var(--ink); background:var(--parchment-2); margin:10px 0; position:relative; }
|
|
60
|
+
.verdict::before { content:""; position:absolute; inset:5px; border:.5px solid var(--rule-soft); pointer-events:none; }
|
|
61
|
+
.verdict .stamp { display:inline-block; font-family:var(--display); font-size:14px; letter-spacing:.12em; text-transform:uppercase; border:1.5px solid var(--commercial-infrastructure); color:var(--commercial-infrastructure); padding:4px 10px; margin-bottom:6px; background:var(--parchment); font-weight:700; }
|
|
62
|
+
.callout { padding:8px 12px; border-left:2px solid var(--ink); background:var(--parchment-2); margin:6px 0; }
|
|
63
|
+
.callout p { margin:0 0 4px; }
|
|
64
|
+
.callout p:last-child { margin-bottom:0; }
|
|
65
|
+
.severity-l { display:inline-block; font-family:var(--ui); font-size:8px; letter-spacing:.16em; text-transform:uppercase; font-weight:700; padding:1px 5px; border:0.5px solid var(--rule); margin-right:4px; }
|
|
66
|
+
.severity-l.low { background:rgba(46,122,107,0.15); color:var(--commercial-infrastructure); border-color:var(--commercial-infrastructure); }
|
|
67
|
+
.severity-l.med { background:rgba(176,122,31,0.15); color:#B07A1F; border-color:#B07A1F; }
|
|
68
|
+
.severity-l.high { background:rgba(200,57,46,0.15); color:var(--highlight); border-color:var(--highlight); }
|
|
69
|
+
.severity-l.crit { background:var(--highlight); color:#fff; border-color:var(--ink); }
|
|
70
|
+
.summary-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:8px; margin:8px 0 12px; }
|
|
71
|
+
.summary-grid .cell { padding:6px 10px; background:var(--parchment-2); border:1px solid var(--ink); }
|
|
72
|
+
.summary-grid .cell .label { font-family:var(--ui); font-size:8.5px; letter-spacing:.18em; text-transform:uppercase; opacity:.75; }
|
|
73
|
+
.summary-grid .cell .val { font-family:var(--display); font-size:18px; line-height:1; margin-top:2px; }
|
|
74
|
+
.summary-grid .cell .val.pass { color:var(--commercial-infrastructure); }
|
|
75
|
+
.summary-grid .cell .val.warn { color:#B07A1F; }
|
|
76
|
+
.summary-grid .cell .val.fail { color:var(--highlight); }
|
|
77
|
+
.fileref { font-family:var(--mono); font-size:9px; background:var(--ink); color:var(--parchment); padding:1px 5px; letter-spacing:.1em; }
|
|
78
|
+
</style>
|
|
79
|
+
</head>
|
|
80
|
+
<body>
|
|
81
|
+
|
|
82
|
+
<div class="toolbar"><button onclick="window.print()">Print / save PDF</button></div>
|
|
83
|
+
|
|
84
|
+
<div class="viewport">
|
|
85
|
+
|
|
86
|
+
<!-- ============================================================ PAGE 1 ============================================================ -->
|
|
87
|
+
<section class="page">
|
|
88
|
+
<header class="doc-head"><span class="brand-mark">SOVEREIGN</span><span class="sigil">Phase 5 freeze audit</span><span>1 / 4</span></header>
|
|
89
|
+
|
|
90
|
+
<h1 class="title">Phase 5 Audit</h1>
|
|
91
|
+
<div class="subtitle">Narration · Replay · Save / Load — additive-layer review.</div>
|
|
92
|
+
|
|
93
|
+
<p class="lede">Phase 5 was scoped as a purely additive layer on top of the frozen Phase 4 scripted-opponents baseline: narration library, replay UI, save / load, and an autosave. This audit verifies that the layer mutates no rule, no reducer, no balance value, and no opponent policy; that save and load round-trip with hash integrity; that the replay surface is read-only and deterministic; and that the nine acceptance criteria from the Phase 5 brief are met. Source under audit: <span class="fileref">sovereign-solo-phase5-narration-replay-save.html</span>. Baseline: <span class="fileref">sovereign-solo-phase4-scripted-opponents.html</span>.</p>
|
|
94
|
+
|
|
95
|
+
<div class="summary-grid">
|
|
96
|
+
<div class="cell"><div class="label">Phase boundary</div><div class="val pass">Pass</div></div>
|
|
97
|
+
<div class="cell"><div class="label">Narration layer</div><div class="val pass">Pass</div></div>
|
|
98
|
+
<div class="cell"><div class="label">Save format</div><div class="val pass">Pass</div></div>
|
|
99
|
+
<div class="cell"><div class="label">Load & integrity</div><div class="val pass">Pass</div></div>
|
|
100
|
+
<div class="cell"><div class="label">Decision log</div><div class="val warn">Pass · note</div></div>
|
|
101
|
+
<div class="cell"><div class="label">Replay UI</div><div class="val pass">Pass</div></div>
|
|
102
|
+
<div class="cell"><div class="label">Determinism</div><div class="val pass">Pass</div></div>
|
|
103
|
+
<div class="cell"><div class="label">Regression</div><div class="val pass">Pass</div></div>
|
|
104
|
+
<div class="cell"><div class="label">Polish discipline</div><div class="val pass">Pass</div></div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<h2><span class="ord">§ 01</span>Method</h2>
|
|
108
|
+
|
|
109
|
+
<p>The audit ran static byte-diffing first, runtime probing second, and a manual playthrough only where the prior two were insufficient. The Phase 4 baseline and Phase 5 candidate were compared at the level of the named code blocks the brief calls out as load-bearing: the reducer, the seventeen action shapes, the opponent profile dispatch, the act table, the asset table, the route ladder, the space table, both card decks, scoring, rent computation, and <code>initialState()</code>. Where the Phase 5 file overrode or wrapped a Phase 4 function, the wrapper was inspected line-for-line for any path that could mutate game state.</p>
|
|
110
|
+
|
|
111
|
+
<p>Runtime probing exercised the four scenarios where save / load behavior is most likely to drift: a clean save → load round-trip on a partial game, four malformed payloads (bad version, missing seed, malformed log, null payload), a tampered <code>finalState.hash</code>, and a same-seed determinism check on two independently restarted games. The Phase 4 voting orchestration debt — already in the baseline — was reproduced and classified, not patched.</p>
|
|
112
|
+
|
|
113
|
+
<h2><span class="ord">§ 02</span>Phase Boundary · Byte-Identity of the Load-Bearing Surface</h2>
|
|
114
|
+
|
|
115
|
+
<p>Every Phase-4 surface the brief names as canonical was byte-diffed against the Phase 5 file. All eleven match exactly. Phase 5 introduces no new reducer actions, no new opponent decisions, no new rent / scoring code paths, and no changes to v0.2 balance.</p>
|
|
116
|
+
|
|
117
|
+
<table>
|
|
118
|
+
<thead><tr><th style="width:34%">Code block</th><th style="width:14%">Match</th><th>Notes</th></tr></thead>
|
|
119
|
+
<tbody>
|
|
120
|
+
<tr><td>Reducer body <code>function reduce(s, action)</code></td><td class="v pass">Byte-identical</td><td>15 339 chars in both files. All 17 action cases unchanged.</td></tr>
|
|
121
|
+
<tr><td><code>const ACTS</code> (7 federal acts)</td><td class="v pass">Byte-identical</td><td>Funding +50 %, Assumption ×2, Charter dice multiplier, Tariff +50 %, Coinage 50 TN tribute, Mfg upgrade half, Whiskey ×2.</td></tr>
|
|
122
|
+
<tr><td><code>const ASSETS</code> (40 spaces)</td><td class="v pass">Byte-identical</td><td>v0.2 balance preserved: Continental base 4, Soldier Pay 6, Bank cost 150, Naval Yard 400.</td></tr>
|
|
123
|
+
<tr><td><code>const ROUTE_LADDER</code></td><td class="v pass">Byte-identical</td><td><code>[0, 25, 50, 100, 150]</code> as v0.2 canon.</td></tr>
|
|
124
|
+
<tr><td><code>const SPACES</code></td><td class="v pass">Byte-identical</td><td>40-space layout including corner, tax, card, route, institution, and system spaces.</td></tr>
|
|
125
|
+
<tr><td><code>const PROFILES</code></td><td class="v pass">Byte-identical</td><td>Both Treasury / Finance and Merchant / Infrastructure decision functions unchanged.</td></tr>
|
|
126
|
+
<tr><td><code>const MARKET_SHOCK_CARDS</code></td><td class="v pass">Byte-identical</td><td>All 12 cards including Yellow Fever <code>pendingResolveLanding</code> hand-off and Speculation Fever auction trigger.</td></tr>
|
|
127
|
+
<tr><td><code>const REPUBLIC_DEBATE_CARDS</code></td><td class="v pass">Byte-identical</td><td>All 12 cards including Strict Construction choice array.</td></tr>
|
|
128
|
+
<tr><td><code>function scorePlayer</code></td><td class="v pass">Byte-identical</td><td>Capacity ≥ 8 bonus capped at +4 (2 Mfg + 2 Strategic).</td></tr>
|
|
129
|
+
<tr><td><code>function computeRent</code></td><td class="v pass">Byte-identical</td><td>Act multipliers, Capacity bonus, shipping disruption gate all unchanged.</td></tr>
|
|
130
|
+
<tr><td><code>function initialState</code></td><td class="v pass">Byte-identical</td><td>Single RNG source (<code>mulberry32(seed)</code>); deck shuffles seeded from <code>state.rng</code>.</td></tr>
|
|
131
|
+
</tbody>
|
|
132
|
+
</table>
|
|
133
|
+
|
|
134
|
+
<p>Phase 5 adds a thin <strong>additive wrapper</strong> on <code>dispatch()</code>: before each <code>reduce()</code> call the wrapper appends an entry to <code>DECISION_LOG</code>; after the call it runs <code>detectNarration()</code> and, on <code>END_TURN</code> / <code>END_GAME</code>, snapshots state and writes the autosave. None of these branches read or mutate any field that <code>reduce()</code> later reads; they are observers only. <code>runOpponent()</code> is byte-identical to Phase 4.</p>
|
|
135
|
+
|
|
136
|
+
</section>
|
|
137
|
+
|
|
138
|
+
<!-- ============================================================ PAGE 2 ============================================================ -->
|
|
139
|
+
<section class="page">
|
|
140
|
+
<header class="doc-head"><span class="brand-mark">SOVEREIGN</span><span class="sigil">Narration · save · load</span><span>2 / 4</span></header>
|
|
141
|
+
|
|
142
|
+
<h2><span class="ord">§ 03</span>Narration Layer</h2>
|
|
143
|
+
|
|
144
|
+
<p>The narration library is a frozen data object keyed by trigger. Each entry has three fields: a heading, a default snippet, and an expand snippet (the endgame republic-summary is the exception — it carries empty default / expand strings and is rendered through <code>buildRepublicSummary(state)</code> at game over). Twenty-five trigger keys are defined: eight first-purchase, seven act-passed, six track-threshold, three endgame-winner, and one endgame-summary.</p>
|
|
145
|
+
|
|
146
|
+
<table>
|
|
147
|
+
<thead><tr><th style="width:34%">Check</th><th style="width:14%">Verdict</th><th>Evidence</th></tr></thead>
|
|
148
|
+
<tbody>
|
|
149
|
+
<tr><td>Derived from ledger / state events</td><td class="v pass">Pass</td><td><code>detectNarration()</code> reads only <code>STATE.players[*].ownedAssets</code>, <code>STATE.acts.passed</code>, and <code>STATE.tracks.*.value</code>. It calls no reducer action.</td></tr>
|
|
150
|
+
<tr><td>Never mutates game state</td><td class="v pass">Pass</td><td>Writes touch only the module-scope sets <code>NARRATION_FIRED</code>, <code>SYS_FIRST_OWNED</code>, and <code>LAST_TRACK</code> (UI memo) and the array <code>NARRATION_LOG</code>. No <code>STATE.*</code> field is written.</td></tr>
|
|
151
|
+
<tr><td>Keyed and non-duplicating</td><td class="v pass">Pass</td><td><code>fireNarration(key)</code> short-circuits if the key is in <code>NARRATION_FIRED</code>. Each trigger fires at most once per game.</td></tr>
|
|
152
|
+
<tr><td>Default 40–60 words</td><td class="v pass">Pass</td><td>All 24 narrative entries measured in range (min 40, max 60). Endgame republic-summary intentionally empty default — rendered live.</td></tr>
|
|
153
|
+
<tr><td>Expand 150–200 words</td><td class="v pass">Pass</td><td>All 21 expand entries measured in range (min 150, max 200). The three endgame-winner entries have no expand text by design and are stamped <em>no expand</em> in the drawer.</td></tr>
|
|
154
|
+
<tr><td>Endgame summary scoped to actual state</td><td class="v pass">Pass</td><td><code>buildRepublicSummary(state)</code> reads <code>state.acts.passed</code>, <code>state.tracks</code>, <code>p.ownedAssets</code>, completed sets, route count, both institutions, and bankrupt-lap count. Five paragraphs, all branched on real state.</td></tr>
|
|
155
|
+
<tr><td>Toggle without altering state</td><td class="v pass">Pass</td><td><code>NARRATION_ENABLED</code> gates only the render path. The narration log keeps accumulating; toggling re-renders.</td></tr>
|
|
156
|
+
<tr><td>No narration for non-events</td><td class="v pass">Pass</td><td>Track-threshold detector compares prev and current via <code>LAST_TRACK</code>, so a re-render at the same value will not re-fire. Act-passed detector consumes <code>STATE.acts.passed.length</code> as a high-water mark.</td></tr>
|
|
157
|
+
</tbody>
|
|
158
|
+
</table>
|
|
159
|
+
|
|
160
|
+
<p>Tone discipline is observed throughout: no invented quotes from historical figures, specific dates and names where they ground the moment, and a consistent narrator voice that speaks to the republic rather than to the player.</p>
|
|
161
|
+
|
|
162
|
+
<h2><span class="ord">§ 04</span>Save Format</h2>
|
|
163
|
+
|
|
164
|
+
<p>The save payload is the four-field object the brief specifies. No DOM state, no narration log, no derived ledger, no opponent profile object reference. The reconstruction path is the only legitimate way to derive the runtime structures.</p>
|
|
165
|
+
|
|
166
|
+
<table>
|
|
167
|
+
<thead><tr><th style="width:34%">Check</th><th style="width:14%">Verdict</th><th>Evidence</th></tr></thead>
|
|
168
|
+
<tbody>
|
|
169
|
+
<tr><td>Contains <code>version</code>, <code>seed</code>, <code>decisionLog</code>, optional <code>finalState</code></td><td class="v pass">Pass</td><td><code>buildSavePayload()</code> returns exactly those four keys; <code>finalState</code> is <code>null</code> while the game is active.</td></tr>
|
|
170
|
+
<tr><td>No transient UI / DOM state</td><td class="v pass">Pass</td><td>Payload carries no <code>NARRATION_LOG</code>, no <code>TURN_SNAPSHOTS</code>, no <code>REPLAY</code>, no DOM references.</td></tr>
|
|
171
|
+
<tr><td>Autosave only on safe boundaries</td><td class="v pass">Pass</td><td><code>writeAutosave()</code> is called by the dispatch wrapper only when <code>action.type === 'END_TURN' || 'END_GAME'</code>. Sub-turn states are not persisted.</td></tr>
|
|
172
|
+
<tr><td>Manual save downloads valid JSON</td><td class="v pass">Pass</td><td><code>downloadSave()</code> emits a 2-space-indented JSON blob with a timestamped filename. Verified at runtime: <code>JSON.parse</code> round-trips the export.</td></tr>
|
|
173
|
+
<tr><td>Version validated on load</td><td class="v pass">Pass</td><td>First check inside <code>loadFromPayload()</code>; mismatch produces “Load failed · version mismatch”.</td></tr>
|
|
174
|
+
<tr><td>Bad JSON fails safely</td><td class="v pass">Pass</td><td>The file-picker handler wraps <code>JSON.parse</code> in a <code>try / catch</code>; runtime verified by feeding garbage input — error pill displayed, state untouched.</td></tr>
|
|
175
|
+
<tr><td>Malformed shape fails safely</td><td class="v pass">Pass</td><td>Verified at runtime against four hostile payloads (bad version, missing seed, log-not-array, null). All returned <code>false</code> and posted a visible <span class="tag-fail">load failed</span> pill. No state mutation.</td></tr>
|
|
176
|
+
</tbody>
|
|
177
|
+
</table>
|
|
178
|
+
|
|
179
|
+
</section>
|
|
180
|
+
|
|
181
|
+
<!-- ============================================================ PAGE 3 ============================================================ -->
|
|
182
|
+
<section class="page">
|
|
183
|
+
<header class="doc-head"><span class="brand-mark">SOVEREIGN</span><span class="sigil">Load · replay · determinism</span><span>3 / 4</span></header>
|
|
184
|
+
|
|
185
|
+
<h2><span class="ord">§ 05</span>Load & Replay Integrity</h2>
|
|
186
|
+
|
|
187
|
+
<table>
|
|
188
|
+
<thead><tr><th style="width:34%">Check</th><th style="width:14%">Verdict</th><th>Evidence</th></tr></thead>
|
|
189
|
+
<tbody>
|
|
190
|
+
<tr><td>Reconstructs from <code>initialState(seed) + decisionLog</code></td><td class="v pass">Pass</td><td><code>loadFromPayload()</code> calls <code>initialState(payload.seed)</code>, then loops the decision log, dispatching each action through <code>reduce()</code>. No shortcut from <code>finalState</code>.</td></tr>
|
|
191
|
+
<tr><td>Does not blindly trust <code>finalState</code></td><td class="v pass">Pass</td><td><code>finalState</code> is only used as a verifier, never to set values.</td></tr>
|
|
192
|
+
<tr><td>Final hash compared</td><td class="v pass">Pass</td><td>After replay, <code>snapshotHash(STATE)</code> is compared against <code>payload.finalState.hash</code>. Mismatch refuses the load.</td></tr>
|
|
193
|
+
<tr><td>Integrity mismatch visible</td><td class="v pass">Pass</td><td>Tampered hash (<code>hash: 'CORRUPTED'</code>) reproduced at runtime → “Load failed · integrity divergence at end of replay” <span class="tag-fail">err</span> pill, state untouched.</td></tr>
|
|
194
|
+
<tr><td>Successful load message</td><td class="v pass">Pass</td><td>Clean save→load run posts “Loaded · integrity verified” <span class="tag-pass">ok</span> pill.</td></tr>
|
|
195
|
+
<tr><td>Same save loaded twice → identical state</td><td class="v pass">Pass</td><td>Round-trip verified: ledger length, decision-log length, narration-log length, and full state hash all identical before and after load.</td></tr>
|
|
196
|
+
</tbody>
|
|
197
|
+
</table>
|
|
198
|
+
|
|
199
|
+
<h2><span class="ord">§ 06</span>Decision Log</h2>
|
|
200
|
+
|
|
201
|
+
<p>The log captures every dispatched action with playerIdx, action type, params, turn, and lap. Each <code>dispatch()</code> call writes one entry before the reducer is invoked, so the log is a faithful input transcript.</p>
|
|
202
|
+
|
|
203
|
+
<table>
|
|
204
|
+
<thead><tr><th style="width:34%">Check</th><th style="width:14%">Verdict</th><th>Evidence</th></tr></thead>
|
|
205
|
+
<tbody>
|
|
206
|
+
<tr><td>Captures buys / declines</td><td class="v pass">Pass</td><td><code>BUY_ASSET</code> and <code>DECLINE_ASSET</code> recorded on every dispatch path.</td></tr>
|
|
207
|
+
<tr><td>Captures auction bids / passes</td><td class="v pass">Pass</td><td><code>AUCTION_BID</code> with <code>{playerIndex, amount, reason}</code>.</td></tr>
|
|
208
|
+
<tr><td>Captures votes</td><td class="v pass">Pass</td><td><code>CAST_VOTE</code> with <code>{playerIndex, vote, reason}</code>.</td></tr>
|
|
209
|
+
<tr><td>Captures card resolution & choices</td><td class="v pass">Pass</td><td><code>RESOLVE_CARD</code>, <code>RESOLVE_CARD_CHOICE</code> with <code>{choiceIndex}</code>.</td></tr>
|
|
210
|
+
<tr><td>Captures Crisis choices</td><td class="v pass">Pass</td><td><code>ROLL_DICE</code> with <code>{crisisChoice}</code> param preserved.</td></tr>
|
|
211
|
+
<tr><td>Captures upgrades</td><td class="v pass">Pass</td><td><code>UPGRADE_ASSET</code> with <code>{playerIndex, spaceNum, reason}</code>.</td></tr>
|
|
212
|
+
<tr><td>Dice / shuffle RNG reconstructed from seed</td><td class="v pass">Pass</td><td>All randomness flows through <code>state.rng = mulberry32(seed)</code>. Replay shares the same seed and the same action sequence; the RNG draws identically.</td></tr>
|
|
213
|
+
<tr><td>Does not include ledger as input</td><td class="v pass">Pass</td><td>Ledger is derived, never persisted as authoritative input.</td></tr>
|
|
214
|
+
<tr><td>Opponent decisions not redundantly stored</td><td class="v warn">Note · non-blocking</td><td>Opponent actions <em>are</em> captured because they flow through the same <code>dispatch()</code> wrapper as human ones. This is a deliberate, defensive choice: it survives any future change to opponent timing or profile logic without invalidating existing saves, and replay integrity does not depend on re-running the profile decision tree. See <span class="tag-info">deviation 1</span> below.</td></tr>
|
|
215
|
+
</tbody>
|
|
216
|
+
</table>
|
|
217
|
+
|
|
218
|
+
<h2><span class="ord">§ 07</span>Replay UI</h2>
|
|
219
|
+
|
|
220
|
+
<table>
|
|
221
|
+
<thead><tr><th style="width:34%">Check</th><th style="width:14%">Verdict</th><th>Evidence</th></tr></thead>
|
|
222
|
+
<tbody>
|
|
223
|
+
<tr><td>Read-only</td><td class="v pass">Pass</td><td><code>openReplay()</code> builds frame snapshots through <code>reduce()</code> on a local <code>s</code> variable; live <code>STATE</code> is never reassigned. Closing the overlay drops the <code>REPLAY</code> object; nothing persists.</td></tr>
|
|
224
|
+
<tr><td>Scrubber reconstructs from <code>initialState(seed) + decisionLog</code></td><td class="v pass">Pass</td><td>Frame array seeded with <code>initialState(REPLAY.seed)</code>, then each log entry dispatched through <code>reduce()</code>, with a frame pushed on every <code>turnIndex</code> increment.</td></tr>
|
|
225
|
+
<tr><td>Lap markers align L1 – L7</td><td class="v pass">Pass</td><td>Scrubber tick is stamped <code>L{lap}</code> at the first frame of each new lap; verified visually at lap 1 / lap 2 boundary.</td></tr>
|
|
226
|
+
<tr><td>Step ← / → works</td><td class="v pass">Pass</td><td><code>replayStep(±1)</code> clamps to <code>[0, frames.length-1]</code>.</td></tr>
|
|
227
|
+
<tr><td>Play / pause at stable cadence</td><td class="v pass">Pass</td><td><code>setInterval(..., 1500)</code> matches the brief; auto-pauses on reaching the final frame.</td></tr>
|
|
228
|
+
<tr><td>Mini-ledger matches reconstructed frame</td><td class="v pass">Pass</td><td>Each cloned frame carries <code>s.ledger.slice()</code> — the ledger up to that turn. Mini-ledger renders that slice.</td></tr>
|
|
229
|
+
<tr><td>Board shows tokens, ownership, tracks, Acts, active player</td><td class="v pass">Pass</td><td>Verified visually: replay screenshot shows player tokens on correct spaces, owned tiles dotted with the owner color, Credit / Resistance / Industry pills, Acts roman numerals, and active-player border.</td></tr>
|
|
230
|
+
<tr><td>Integrity indicator turns red on mismatch</td><td class="v pass">Pass</td><td><code>REPLAY.integrityOk = false</code> triggers <code>.integrity.err</code> CSS class; surface reads “⚠ Replay integrity error”.</td></tr>
|
|
231
|
+
</tbody>
|
|
232
|
+
</table>
|
|
233
|
+
|
|
234
|
+
</section>
|
|
235
|
+
|
|
236
|
+
<!-- ============================================================ PAGE 4 ============================================================ -->
|
|
237
|
+
<section class="page">
|
|
238
|
+
<header class="doc-head"><span class="brand-mark">SOVEREIGN</span><span class="sigil">Determinism · regression · verdict</span><span>4 / 4</span></header>
|
|
239
|
+
|
|
240
|
+
<h2><span class="ord">§ 08</span>Determinism</h2>
|
|
241
|
+
|
|
242
|
+
<p>Two clean restarts on seed 2026 with one human <code>CAST_VOTE</code> dispatched produced identical state hashes and identical ledger lengths. The save → load round-trip on a 17-action partial game produced an identical hash to the live state. <code>mulberry32(state.rngSeed)</code> remains the only RNG source; no <code>Math.random</code> or <code>Date.now</code> call influences game truth. The dispatch wrapper introduces <code>setTimeout</code> only for UI pacing of opponent moves — the underlying state transition is synchronous through <code>reduce()</code>, and replay omits the timer entirely.</p>
|
|
243
|
+
|
|
244
|
+
<h2><span class="ord">§ 09</span>Regression Spot Check</h2>
|
|
245
|
+
|
|
246
|
+
<table>
|
|
247
|
+
<thead><tr><th style="width:50%">Item</th><th style="width:12%">Verdict</th><th>Notes</th></tr></thead>
|
|
248
|
+
<tbody>
|
|
249
|
+
<tr><td>Phase 4 · Yellow Fever resolves landing before turn end</td><td class="v pass">Pass</td><td><code>pendingResolveLanding = true</code> intact; <code>finishLanding()</code> re-resolves.</td></tr>
|
|
250
|
+
<tr><td>Phase 4 · Speculation Fever auction survives card resolution</td><td class="v pass">Pass</td><td>Deterministic lowest-spaceNum pick; <code>pendingAuction</code> set, <code>phase = 'auction'</code>.</td></tr>
|
|
251
|
+
<tr><td>Phase 4 · Failed Act ledger text honest</td><td class="v pass">Pass</td><td>“skipped for this game in Phase 4; re-queue behavior deferred” preserved.</td></tr>
|
|
252
|
+
<tr><td>Phase 4 · Opponent BUY / DECLINE / UPGRADE reasons in ledger</td><td class="v pass">Pass</td><td>Decision functions return <code>{ buy, reason }</code> / <code>{ spaceNum, cost, reason }</code>; reason concatenated to ledger detail string.</td></tr>
|
|
253
|
+
<tr><td>Phase 4 · Human Crisis choice — Pay / Doubles / Skip</td><td class="v pass">Pass</td><td><code>phase = 'crisis-choice'</code> branch with three buttons unchanged.</td></tr>
|
|
254
|
+
<tr><td>Phase 4 · Strict Construction profile-driven</td><td class="v pass">Pass</td><td>Opponent path routes through <code>profile.decideCardChoice(s, pIdx, card)</code>; reasoned ledger entry posted.</td></tr>
|
|
255
|
+
<tr><td>Phase 3 · Lap-7 endgame renders</td><td class="v pass">Pass</td><td><code>END_TURN</code> case still checks <code>s.lap > 7</code> and dispatches <code>END_GAME</code>.</td></tr>
|
|
256
|
+
<tr><td>Phase 3 · bankruptLaps counts once per lap</td><td class="v pass">Pass</td><td><code>bankruptThisLap</code> reset only inside <code>BEGIN_LAP</code> case.</td></tr>
|
|
257
|
+
<tr><td>Phase 3 · Capacity ≥ 8 bonus caps at +4</td><td class="v pass">Pass</td><td>Score code: <code>let bonus = 0; if (hasMfg) bonus += 2; if (hasStrat) bonus += 2;</code> — ceiling is 4.</td></tr>
|
|
258
|
+
<tr><td>v0.2 · Route ladder 25 / 50 / 100 / 150</td><td class="v pass">Pass</td><td><code>const ROUTE_LADDER = [0, 25, 50, 100, 150]</code> byte-identical.</td></tr>
|
|
259
|
+
<tr><td>v0.2 · Coinage 50 TN / Credit +1 / Capacity +1</td><td class="v pass">Pass</td><td>Act 5 apply function unchanged.</td></tr>
|
|
260
|
+
<tr><td>v0.2 · Capacity ≥ 6 payment bonus, ≥ 8 endgame bonus</td><td class="v pass">Pass</td><td>Both thresholds intact in <code>computeRent</code> and <code>scorePlayer</code>.</td></tr>
|
|
261
|
+
<tr><td>v0.2 · Continental base 4 · Soldier Pay base 6</td><td class="v pass">Pass</td><td><code>1:{cost:60,base:4,…}</code> / <code>3:{cost:60,base:6,…}</code>.</td></tr>
|
|
262
|
+
</tbody>
|
|
263
|
+
</table>
|
|
264
|
+
|
|
265
|
+
<h2><span class="ord">§ 10</span>Polish Discipline</h2>
|
|
266
|
+
|
|
267
|
+
<ul>
|
|
268
|
+
<li>No runtime placeholder pills or demo banners in the live UI. The only <code>placeholder</code> occurrence is a legitimate HTML attribute on the auction bid input (<code>placeholder="${minBid}"</code>).</li>
|
|
269
|
+
<li>No developer-aside comments. Grep for “TODO”, “FIXME”, “XXX”, “hmm”, “let me” returned zero hits in narrative source.</li>
|
|
270
|
+
<li>No external assets, fonts, or network dependencies. All typography is system-stack; no <code>cdn</code>, <code>googleapis</code>, or absolute URL anywhere in the file.</li>
|
|
271
|
+
</ul>
|
|
272
|
+
|
|
273
|
+
<h2><span class="ord">§ 11</span>Phase 4 Voting Caveat — Classification</h2>
|
|
274
|
+
|
|
275
|
+
<p>The Phase 5 builder flagged a pre-existing Phase 4 orchestration issue: <em>opponent Act votes are only dispatched when an opponent becomes the active player after a roll.</em> Static inspection confirms this: <code>dispatch()</code> kicks <code>runOpponent()</code> only when <code>STATE.players[STATE.activePlayerIndex].profile !== 'human'</code> and the phase is one of <code>awaiting-roll</code> or <code>act-vote</code>. At lap start the active player is the human, so the trigger never fires from voting alone; in interactive play the human votes, the reducer does not finalize because the opponent slots remain <code>null</code>, and the loop stalls.</p>
|
|
276
|
+
|
|
277
|
+
<p><strong>Classification:</strong> <span class="tag-warn">Phase 4 orchestration debt</span> — inherited, pre-Phase-5, must not be patched at this freeze.</p>
|
|
278
|
+
|
|
279
|
+
<div class="callout">
|
|
280
|
+
<p><strong>Risk to Phase 5 deliverables:</strong> none. Replay reconstructs from <code>initialState(seed) + decisionLog</code>, and the decision log is a faithful transcript of whatever <em>did</em> dispatch. If the caveat stalls a live game at an act vote, no <code>END_TURN</code> fires, no autosave is written, and the partial log replays cleanly to the stall point.</p>
|
|
281
|
+
<p><strong>Risk to determinism:</strong> none. The caveat is in the event-loop scheduler, not in the reducer or the RNG.</p>
|
|
282
|
+
<p><strong>Risk to integrity hashing:</strong> none. <code>snapshotHash()</code> reads the state the reducer produced; whatever state was produced will replay byte-identically.</p>
|
|
283
|
+
<p>The fix is a Phase 4 orchestration patch — schedule the opponent vote sweep on entry to <code>act-vote</code> rather than on activePlayerIndex rotation — and explicitly belongs to a future Phase 4 maintenance pass, not to Phase 5.</p>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
<h2><span class="ord">§ 12</span>Non-Blocking Deviations</h2>
|
|
287
|
+
|
|
288
|
+
<p><strong>Deviation 1 — Opponent decisions stored in <code>decisionLog</code>.</strong> The brief prefers opponent decisions be omitted from the log on the grounds that they are deterministic from state. Phase 5 stores them. The trade is intentional: storing makes the log self-contained against any future change to opponent timing, profile data, or decision-function refactor, and the replay path can ignore the profile decision tree entirely. Save-file size grows linearly but remains small (a finished game well under 200 KB). Severity: <span class="severity-l low">Low</span> — non-blocking; preserves replay integrity even if Phase 6 changes opponent behavior.</p>
|
|
289
|
+
|
|
290
|
+
<p><strong>Deviation 2 — Decision-log metadata recomputed on load.</strong> <code>loadFromPayload()</code> reconstructs <code>DECISION_LOG</code> from the payload but overwrites <code>playerIdx</code>, <code>turn</code>, and <code>lap</code> fields with the values derived from the current replay state rather than trusting the saved metadata. The action sequence is what matters for state reconstruction, so this is harmless. Severity: <span class="severity-l low">Low</span>.</p>
|
|
291
|
+
|
|
292
|
+
<h2><span class="ord">§ 13</span>Freeze Verdict</h2>
|
|
293
|
+
|
|
294
|
+
<div class="verdict">
|
|
295
|
+
<div class="stamp">Pass</div>
|
|
296
|
+
<p>Phase 5 is freeze-ready. The reducer, action set, opponent profiles, scoring, rent computation, act table, asset table, route ladder, card decks, and <code>initialState</code> are all byte-identical to the Phase 4 baseline. Narration is pure data; the detector reads state, never writes it. Save / load round-trips with verified hash integrity, rejects four malformed payload shapes and one tampered <code>finalState</code> cleanly, and writes an autosave only at safe boundaries. The replay UI reconstructs frames from <code>initialState(seed) + decisionLog</code> through a pure reducer and surfaces an integrity indicator. Determinism through <code>mulberry32(state.rngSeed)</code> is preserved.</p>
|
|
297
|
+
<p>The two non-blocking deviations and the inherited Phase 4 voting caveat are documented above; none compromises save, load, replay, or integrity in the present file. <strong>No required fixes before freeze.</strong></p>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
</section>
|
|
301
|
+
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
</body>
|
|
305
|
+
</html>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Design History
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
Archival snapshots of the design and audit artifacts produced during the
|
|
5
|
+
Sovereign Solo / Digital Mode build. These are NOT the running game - the
|
|
6
|
+
running game is in ../digital-mode/sovereign-solo.html.
|
|
7
|
+
|
|
8
|
+
These exist so that the design path is inspectable: how the digital
|
|
9
|
+
adaptation was specified before any code was written, and how each layer
|
|
10
|
+
was audited before being frozen.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Files
|
|
14
|
+
-----
|
|
15
|
+
01-phase1-concept.html
|
|
16
|
+
Sovereign Solo / Digital Mode Concept v0.1 (post wording-fix).
|
|
17
|
+
The design contract for the digital adaptation. Defines the eight
|
|
18
|
+
surfaces, three solo modes, four opponent profiles, state model, the
|
|
19
|
+
five-hypothesis bottleneck matrix, and the acceptance criteria.
|
|
20
|
+
|
|
21
|
+
02-phase2-prototype.html
|
|
22
|
+
Phase 2 static clickable prototype.
|
|
23
|
+
8 surfaces rendered with placeholder state, 11-step turn walkthrough,
|
|
24
|
+
accessibility floor (keyboard / focus / readable track values).
|
|
25
|
+
Proved the digital loop and the surface set before any real state
|
|
26
|
+
machine code was written.
|
|
27
|
+
|
|
28
|
+
03-phase3-audit.html
|
|
29
|
+
Phase 3 local state machine freeze audit.
|
|
30
|
+
Verified the reducer / dispatch architecture, 17 actions, mulberry32
|
|
31
|
+
determinism, full 7-lap game loop, all 40 spaces, all 24 cards, all
|
|
32
|
+
7 Acts, scoring-from-rules. Three bug fixes patched then re-audited.
|
|
33
|
+
|
|
34
|
+
04-phase4-audit.html
|
|
35
|
+
Phase 4 scripted opponents freeze audit.
|
|
36
|
+
Verified the players[] migration, deterministic decision functions
|
|
37
|
+
for Treasury / Finance and Merchant / Infrastructure, auction
|
|
38
|
+
mechanic, multiplayer rent, vote tallying. Six bug fixes patched.
|
|
39
|
+
|
|
40
|
+
05-phase5-audit.html
|
|
41
|
+
Phase 5 narration / replay / save freeze audit.
|
|
42
|
+
Verified the narration library reads from ledger and does not mutate
|
|
43
|
+
state, save / load with hash integrity, replay reconstructs from
|
|
44
|
+
initialState(seed) + decisionLog. Verdict PASS, no fixes required.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
Phases 6, 6.1, and the balance evolution
|
|
48
|
+
----------------------------------------
|
|
49
|
+
Phase 6 (local balance telemetry + batch + Manufacturer profile) is in
|
|
50
|
+
production: open ../digital-mode/sovereign-solo.html. It is the live
|
|
51
|
+
substrate of the v0.10 baseline.
|
|
52
|
+
|
|
53
|
+
Phase 6.1 (telemetry hygiene) is also in production.
|
|
54
|
+
|
|
55
|
+
The v0.3 -> v0.10 balance arc was driven by deterministic 400-game evidence
|
|
56
|
+
passes. See ../CHANGELOG.md for what each version changed and what the
|
|
57
|
+
evidence showed.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
Why these artifacts are archival rather than removed
|
|
61
|
+
----------------------------------------------------
|
|
62
|
+
Audit trails are the freeze guarantee. Every layer of Sovereign Solo /
|
|
63
|
+
Digital Mode was frozen against a written audit. Keeping the audits in
|
|
64
|
+
the bundle means future maintainers can verify that a given fix (e.g.
|
|
65
|
+
BUG-01 Yellow Fever landing resolution, BUG-04 opponent reasons) was
|
|
66
|
+
intentional, not an artifact of a regression.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Digital Mode (v0.10 balance baseline · FROZEN)
|
|
2
|
+
==============================================
|
|
3
|
+
|
|
4
|
+
The single self-contained HTML file that runs the full game locally in a browser.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Files
|
|
8
|
+
-----
|
|
9
|
+
sovereign-solo.html The game. Open in any modern browser.
|
|
10
|
+
sovereign-v0.10-freeze-audit.html 38-check freeze audit (all PASS).
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
How to run
|
|
14
|
+
----------
|
|
15
|
+
Double-click sovereign-solo.html or drag it into a browser. No installer, no
|
|
16
|
+
dependencies, no internet required. The header shows "v0.10 balance candidate" -
|
|
17
|
+
that label is the frozen baseline.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
What's inside
|
|
21
|
+
-------------
|
|
22
|
+
Game loop
|
|
23
|
+
- Full 7-lap solo game (1 human + 2 scripted opponents).
|
|
24
|
+
- 17-action reducer / dispatch pattern.
|
|
25
|
+
- All 40 board spaces, 22 properties, 4 routes, 2 institutions wired.
|
|
26
|
+
- All 24 cards (12 Market Shock + 12 Republic Debate) resolve deterministically.
|
|
27
|
+
- All 7 Acts of Congress fire in historical order with majority voting.
|
|
28
|
+
|
|
29
|
+
Scripted opponents
|
|
30
|
+
- Hamilton: Treasury / Finance profile.
|
|
31
|
+
- Morris: Merchant / Infrastructure profile.
|
|
32
|
+
- Plus Manufacturer / Industry profile available for batch play.
|
|
33
|
+
- Every decision is a pure function of visible state and logs its reason.
|
|
34
|
+
- All four MVP profile decision functions: decideBuy, decideAuctionBid,
|
|
35
|
+
decideUpgrade, decideVote, decideEarlyVoteFee, decideCardChoice.
|
|
36
|
+
|
|
37
|
+
Narration
|
|
38
|
+
- 25-entry library covering first-purchases, Act passages, track thresholds,
|
|
39
|
+
Default, Rebellion, endgame republic summary.
|
|
40
|
+
- 40-60 word defaults, 150-200 word expansions, ~300-500 word endgame.
|
|
41
|
+
- Triggered by reading the ledger; never mutates state.
|
|
42
|
+
|
|
43
|
+
Save / load
|
|
44
|
+
- Autosave to localStorage on every END_TURN.
|
|
45
|
+
- Manual JSON export / import.
|
|
46
|
+
- Hash integrity check on load.
|
|
47
|
+
- Version-gated (v0.10).
|
|
48
|
+
|
|
49
|
+
Replay
|
|
50
|
+
- Full scrubber over any completed game.
|
|
51
|
+
- Reconstructs from initialState(seed) + decisionLog.
|
|
52
|
+
- Read-only; never mutates live state.
|
|
53
|
+
- Green integrity pill confirms byte-identical reconstruction.
|
|
54
|
+
|
|
55
|
+
Batch simulation
|
|
56
|
+
- 10, 50, or 100 deterministic games per run.
|
|
57
|
+
- Choose profile per slot.
|
|
58
|
+
- Charter Enabled / Disabled toggle for diagnostic control.
|
|
59
|
+
- Aggregate output: win rates, average Influence, route dominance, debt and
|
|
60
|
+
industry contribution, failure event frequency, mirror slot edge.
|
|
61
|
+
- JSON + HTML exports per batch.
|
|
62
|
+
- Cross-config summary.
|
|
63
|
+
|
|
64
|
+
Telemetry
|
|
65
|
+
- Acquisition funnel (landings, buy outcomes, cash-at-opportunity, auction
|
|
66
|
+
access, turn-order geometry, five-hypothesis classifier).
|
|
67
|
+
- Scoring decomposition (per category, with counterfactual analysis for
|
|
68
|
+
Treasury-specific dominance analysis).
|
|
69
|
+
- All reporting-side; never mutates game state.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Determinism guarantee
|
|
73
|
+
---------------------
|
|
74
|
+
Same seed + same human decisions = byte-identical ledger across runs, browsers,
|
|
75
|
+
and time. Verified across 1,000+ deterministic games during the v0.2 -> v0.10
|
|
76
|
+
balance arc. Single RNG: mulberry32(state.rngSeed).
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
Freeze audit
|
|
80
|
+
------------
|
|
81
|
+
sovereign-v0.10-freeze-audit.html shows the 38 static checks that PASS:
|
|
82
|
+
|
|
83
|
+
v0.10 scoring rule consistency 6 / 6 PASS
|
|
84
|
+
Preserved balance state (v0.2 -> v0.8) 14 / 14 PASS
|
|
85
|
+
Digital infrastructure regressions 14 / 14 PASS
|
|
86
|
+
Determinism (CANONICAL x 100 byte-identical) PASS
|
|
87
|
+
Balance targets (6 of 6) PASS
|
|
88
|
+
|
|
89
|
+
No source edits were required at the freeze gate.
|