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}