code wiki / _hdl_build / nx_worldsim.nx
nx_worldsim.nx source
↩ module page · 163 lines · 6402 B
1// nx_worldsim.nx -- the SOVEREIGN FACTION / ECONOMY WORLD-SIM PART. nx_gamebench gap-queue rank 1 and a hard
2// GAP (nothing existed): blocks 4 benchmarked titles (Cataclysm-DDA factions, Freeciv civs+diplomacy,
3// Endless Sky governments) AND is the keystone of the operator's north-star output Strive: Conquest --
4// a persistent faction/economy world sim wrapped in a VN shell.
5//
6// THE INVARIANTS THIS PART EXISTS TO HOLD (each a gate tooth):
7// 1. ★CONSERVATION. A trade moves wealth, it does not mint or burn it: total after == total before, always.
8// Resource leaks and duplication are the defining bug of every simulated economy -- here the transfer is
9// a single guarded move, so conservation is structural rather than hoped-for.
10// 2. ★SYMMETRIC RELATIONS BY CONSTRUCTION. attitude(A,B) and attitude(B,A) are the SAME cell, so the two
11// can never disagree. In stores that keep both halves, one-sided updates silently desync diplomacy --
12// A is at war with B while B thinks they are allies.
13// 3. ACCOUNTED PRODUCTION. Wealth entering the world each tick equals exactly the sum of what territories
14// produced -- growth is auditable, never arbitrary.
15// 4. DETERMINISTIC. Same world + same tick count -> identical state. Integer-only, seeded xorshift, so a
16// campaign replays exactly (the determinism exceed carried into simulation).
17// 5. BOUNDED. Relations clamp to [-1000,1000]; resources never go negative; a faction cannot trade with
18// itself or trade what it does not have.
19// State is a flat i64 arena so a whole world drops into gs_save/gs_load (nx_gamesave).
20// LIB ONLY -- no main() by ecosystem convention.
21// license_tier: ORIGINAL expect_exit: 0
22import "nx_syscalls.nx"
23
24const WS_HDR: i64 = 4 // nfac, tick, rngstate, reserved
25const WS_RELMAX: i64 = 1000
26const WS_RELMIN: i64 = 0-1000
27
28func ws_nfac(a: *i64) -> i64 { return a[0] }
29func ws_tickno(a: *i64) -> i64 { return a[1] }
30
31func ws_o_res(a: *i64) -> i64 { return WS_HDR }
32func ws_o_pop(a: *i64) -> i64 { return WS_HDR + ws_nfac(a) }
33func ws_o_terr(a: *i64) -> i64 { return WS_HDR + 2*ws_nfac(a) }
34func ws_o_rel(a: *i64) -> i64 { return WS_HDR + 3*ws_nfac(a) }
35
36func ws_words(nfac: i64) -> i64 { return WS_HDR + 3*nfac + nfac*nfac + 8 }
37func ws_bytes(nfac: i64) -> i64 { return ws_words(nfac) * 8 }
38
39func ws_clamp(v: i64, lo: i64, hi: i64) -> i64 {
40 if v < lo { return lo }
41 if v > hi { return hi }
42 return v
43}
44
45func ws_init(a: *i64, nfac: i64, seed: i64) -> i64 {
46 a[0] = nfac
47 a[1] = 0
48 a[2] = seed
49 if a[2] == 0 { a[2] = 1 }
50 a[3] = 0
51 var i: i64 = 0
52 while i < nfac {
53 a[ws_o_res(a) + i] = 0
54 a[ws_o_pop(a) + i] = 0
55 a[ws_o_terr(a) + i] = 0
56 i = i + 1
57 }
58 var r: i64 = 0
59 while r < nfac*nfac { a[ws_o_rel(a) + r] = 0; r = r + 1 }
60 return 0
61}
62
63// ---- accessors ----
64func ws_res(a: *i64, f: i64) -> i64 { return a[ws_o_res(a) + f] }
65func ws_pop(a: *i64, f: i64) -> i64 { return a[ws_o_pop(a) + f] }
66func ws_terr(a: *i64, f: i64) -> i64 { return a[ws_o_terr(a) + f] }
67func ws_set_res(a: *i64, f: i64, v: i64) -> i64 {
68 if v < 0 { a[ws_o_res(a) + f] = 0; return 0 }
69 a[ws_o_res(a) + f] = v
70 return 1
71}
72func ws_set_pop(a: *i64, f: i64, v: i64) -> i64 { a[ws_o_pop(a) + f] = ws_clamp(v,0,0x3FFFFFFF); return 1 }
73func ws_set_terr(a: *i64, f: i64, v: i64) -> i64 { a[ws_o_terr(a) + f] = ws_clamp(v,0,0x3FFFFFFF); return 1 }
74
75// ---- SYMMETRIC relations: one canonical cell for the unordered pair ----
76// the canonical cell is (min,max); both lookups resolve to it, so the halves cannot disagree.
77func ws_rel_idx(a: *i64, i: i64, j: i64) -> i64 {
78 var lo: i64 = i
79 var hi: i64 = j
80 if i > j { lo = j; hi = i }
81 return ws_o_rel(a) + lo*ws_nfac(a) + hi
82}
83func ws_rel(a: *i64, i: i64, j: i64) -> i64 {
84 if i == j { return WS_RELMAX } // a faction is always at peace with itself
85 if i < 0 { return 0 }
86 if j < 0 { return 0 }
87 if i >= ws_nfac(a) { return 0 }
88 if j >= ws_nfac(a) { return 0 }
89 return a[ws_rel_idx(a, i, j)]
90}
91func ws_adjust_rel(a: *i64, i: i64, j: i64, d: i64) -> i64 {
92 if i == j { return 0 } // no self-relation edits
93 if i < 0 { return 0 }
94 if j < 0 { return 0 }
95 if i >= ws_nfac(a) { return 0 }
96 if j >= ws_nfac(a) { return 0 }
97 let k: i64 = ws_rel_idx(a, i, j)
98 a[k] = ws_clamp(a[k] + d, WS_RELMIN, WS_RELMAX)
99 return 1
100}
101
102// ---- CONSERVING trade: a guarded single move, cannot mint or burn ----
103// returns the amount actually moved (0 if refused).
104func ws_trade(a: *i64, from: i64, to: i64, amount: i64) -> i64 {
105 if from == to { return 0 }
106 if amount <= 0 { return 0 }
107 if from < 0 { return 0 }
108 if to < 0 { return 0 }
109 if from >= ws_nfac(a) { return 0 }
110 if to >= ws_nfac(a) { return 0 }
111 let have: i64 = a[ws_o_res(a) + from]
112 var amt: i64 = amount
113 if amt > have { amt = have } // never overdraw -> never negative
114 if amt <= 0 { return 0 }
115 a[ws_o_res(a) + from] = have - amt
116 a[ws_o_res(a) + to] = a[ws_o_res(a) + to] + amt
117 ws_adjust_rel(a, from, to, 5) // trade warms relations
118 return amt
119}
120
121func ws_total_res(a: *i64) -> i64 {
122 var t: i64 = 0
123 var i: i64 = 0
124 while i < ws_nfac(a) { t = t + a[ws_o_res(a) + i]; i = i + 1 }
125 return t
126}
127// what one tick of production WILL add -- lets a caller audit growth exactly
128func ws_production(a: *i64, rate: i64) -> i64 {
129 var t: i64 = 0
130 var i: i64 = 0
131 while i < ws_nfac(a) { t = t + a[ws_o_terr(a) + i] * rate; i = i + 1 }
132 return t
133}
134
135func ws_rng(a: *i64) -> i64 {
136 var x: i64 = a[2]
137 x = x ^ (x << 13)
138 x = x ^ (x >> 7)
139 x = x ^ (x << 17)
140 a[2] = x
141 if x < 0 { return 0 - x }
142 return x
143}
144
145// ---- one deterministic world tick: accounted production, then relation drift ----
146func ws_tick(a: *i64, rate: i64) -> i64 {
147 let n: i64 = ws_nfac(a)
148 var i: i64 = 0
149 while i < n {
150 a[ws_o_res(a) + i] = a[ws_o_res(a) + i] + a[ws_o_terr(a) + i] * rate
151 i = i + 1
152 }
153 // relation drift: one deterministic pair nudged per tick
154 if n >= 2 {
155 let p: i64 = ws_rng(a) % n
156 var q: i64 = ws_rng(a) % n
157 if q == p { q = (q + 1) % n }
158 var d: i64 = (ws_rng(a) % 21) - 10
159 ws_adjust_rel(a, p, q, d)
160 }
161 a[1] = a[1] + 1
162 return a[1]
163}