code wiki / _hdl_build / nx_gamehud.nx

nx_gamehud.nx source

↩ module page · 224 lines · 8493 B

1// nx_gamehud.nx -- certified composable game part: UI/MENUS/HUD (gamebench capability 18). 2// A HUD is BOUND VALUES rendered fresh every frame (never cached text), and a menu is a MODAL, 3// DETERMINISTIC state machine whose selection CAUSES the game action (not decoration painted after 4// the fact). This part owns both, game-agnostically: 5// - HUD slots: caller binds integer values (hp/depth/kills/...) each turn; labels are passed at 6// RENDER time only, so serialized state is PURE i64 values (pointers never enter a save file). 7// - Menu: open(n items) -> move (wraps BOTH directions) -> sel returns the cursor item's id and 8// closes (modal select-close). Cursor/items live in state; a replay reproduces every selection. 9// - Render: composites over a caller RGB24 framebuffer (w*h*3, like nx_png_write_rgb) with the 10// sovereign nx_font8x8 glyphs; per-pixel clipped so off-screen text can NEVER write out of bounds. 11// - Serialization: gh state words [0..GH_NSV) are plain i64s -> ride nx_gamesave like every other 12// certified part (rpgstats T8 / entity T9 pattern). 13// LIB, no main (ecosystem convention, cf. nx_swgpu.nx). license_tier: ORIGINAL 14import "nx_syscalls.nx" 15import "nx_font8x8.nx" 16const GH_MAGIC_1469598103: i64 = 1469598103 17 18// ---- state layout (i64 words; ALL serializable words first, GH_NSV of them) ---- 19const GH_F_NSLOT: i64 = 0 20const GH_F_MOPEN: i64 = 1 21const GH_F_MN: i64 = 2 22const GH_F_MCUR: i64 = 3 23const GH_F_NSEL: i64 = 4 // selections made (lifetime) 24const GH_F_LAST: i64 = 5 // last selected id (-1 until first select) 25const GH_O_SLOT: i64 = 8 // slot values [8..24) 26const GH_O_MID: i64 = 24 // menu item ids [24..40) 27const GH_O_MVAL: i64 = 40 // menu item values [40..56) 28const GH_NSV: i64 = 56 // serializable span 29const GH_WORDS: i64 = 64 // arena size in words 30const GH_MAXSLOT: i64 = 16 31const GH_MAXITEM: i64 = 16 32// render geometry (data, not magic: one place to retune) 33const GH_BAR_X: i64 = 2 34const GH_BAR_Y: i64 = 2 35const GH_GAP: i64 = 10 // px between HUD slots 36const GH_MX: i64 = 8 // menu panel origin 37const GH_MY: i64 = 14 38const GH_MW: i64 = 110 // menu panel width 39const GH_ROWH: i64 = 10 40 41func gh_init(g: *i64, nslots: i64) -> i64 { 42 var i: i64 = 0 43 while i < GH_WORDS { g[i] = 0; i = i + 1 } 44 var n: i64 = nslots 45 if n < 0 { n = 0 } 46 if n > GH_MAXSLOT { n = GH_MAXSLOT } 47 g[GH_F_NSLOT] = n 48 g[GH_F_LAST] = 0 - 1 49 return 0 50} 51func gh_slot_set(g: *i64, i: i64, v: i64) -> i64 { 52 if i < 0 { return 0 - 1 } 53 if i >= g[GH_F_NSLOT] { return 0 - 1 } 54 g[GH_O_SLOT + i] = v 55 return 0 56} 57func gh_menu_open(g: *i64, n: i64) -> i64 { 58 var m: i64 = n 59 if m < 1 { return 0 - 1 } 60 if m > GH_MAXITEM { m = GH_MAXITEM } 61 g[GH_F_MOPEN] = 1 62 g[GH_F_MN] = m 63 g[GH_F_MCUR] = 0 64 return 0 65} 66func gh_menu_item(g: *i64, i: i64, id: i64, val: i64) -> i64 { 67 if i < 0 { return 0 - 1 } 68 if i >= g[GH_F_MN] { return 0 - 1 } 69 g[GH_O_MID + i] = id 70 g[GH_O_MVAL + i] = val 71 return 0 72} 73// move the cursor d steps; WRAPS in both directions (the classic clamp bug is the mutation target) 74func gh_menu_move(g: *i64, d: i64) -> i64 { 75 if g[GH_F_MOPEN] == 0 { return 0 - 1 } 76 let n: i64 = g[GH_F_MN] 77 var c: i64 = g[GH_F_MCUR] + d 78 while c < 0 { c = c + n } 79 while c >= n { c = c - n } 80 g[GH_F_MCUR] = c 81 return c 82} 83// modal select-close: returns the id UNDER THE CURSOR and closes the menu 84func gh_menu_sel(g: *i64) -> i64 { 85 if g[GH_F_MOPEN] == 0 { return 0 - 1 } 86 let id: i64 = g[GH_O_MID + g[GH_F_MCUR]] 87 g[GH_F_NSEL] = g[GH_F_NSEL] + 1 88 g[GH_F_LAST] = id 89 g[GH_F_MOPEN] = 0 90 return id 91} 92func gh_menu_close(g: *i64) -> i64 { g[GH_F_MOPEN] = 0; return 0 } 93 94// order-sensitive rolling checksum of the serializable state (nh_ckv shape) 95func gh_ckv(ck: i64, v0: i64) -> i64 { 96 var v: i64 = v0 97 if v < 0 { v = (0 - v) * 2 + 1 } else { v = v * 2 } 98 return (ck * 131 + v) & 0x7FFFFFFFFFFFFFFF 99} 100func gh_ck(g: *i64) -> i64 { 101 var ck: i64 = GH_MAGIC_1469598103 102 var i: i64 = 0 103 while i < GH_NSV { ck = gh_ckv(ck, g[i]); i = i + 1 } 104 return ck 105} 106 107// ---- render: every pixel write goes through gh_px (clip BY CONSTRUCTION, no caller can escape it) ---- 108func gh_px(fb: *u8, w: i64, h: i64, x: i64, y: i64) -> i64 { 109 if x < 0 { return 0 } 110 if x >= w { return 0 } 111 if y < 0 { return 0 } 112 if y >= h { return 0 } 113 let o: i64 = (y * w + x) * 3 114 fb[o] = 255 as u8 115 fb[o + 1] = 255 as u8 116 fb[o + 2] = 255 as u8 117 return 1 118} 119func gh_glyph(fb: *u8, w: i64, h: i64, font: *u8, ch: i64, x: i64, y: i64) -> i64 { 120 if ch <= 0x20 { return 0 } 121 if ch > 0x7E { return 0 } 122 let go: i64 = (ch - 0x20) * 8 123 var row: i64 = 0 124 while row < 8 { 125 let bits: i64 = font[go + row] & 0xff 126 var col: i64 = 0 127 while col < 8 { 128 if ((bits >> col) & 1) == 1 { gh_px(fb, w, h, x + col, y + row) } 129 col = col + 1 130 } 131 row = row + 1 132 } 133 return 0 134} 135// draw nul-terminated text; returns the x AFTER the last advance (proportional metrics from the font lib) 136func gh_text(fb: *u8, w: i64, h: i64, font: *u8, x0: i64, y: i64, s: *u8) -> i64 { 137 var x: i64 = x0 138 var i: i64 = 0 139 while s[i] != (0 as u8) { 140 let ch: i64 = s[i] & 0xff 141 gh_glyph(fb, w, h, font, ch, x, y) 142 x = x + font8x8_adv(font, ch) 143 i = i + 1 144 } 145 return x 146} 147func gh_numdraw(fb: *u8, w: i64, h: i64, font: *u8, x0: i64, y: i64, v0: i64) -> i64 { 148 var x: i64 = x0 149 var v: i64 = v0 150 if v < 0 { gh_glyph(fb, w, h, font, 45, x, y); x = x + font8x8_adv(font, 45); v = 0 - v } 151 let t: *u8 = sys_mmap(24) 152 var k: i64 = 0 153 if v == 0 { t[0] = 48 as u8; k = 1 } 154 while v > 0 { t[k] = (48 + (v % 10)) as u8; v = v / 10; k = k + 1 } 155 var q: i64 = k - 1 156 while q >= 0 { 157 let ch: i64 = t[q] & 0xff 158 gh_glyph(fb, w, h, font, ch, x, y) 159 x = x + font8x8_adv(font, ch) 160 q = q - 1 161 } 162 return x 163} 164func gh_hline(fb: *u8, w: i64, h: i64, x0: i64, x1: i64, y: i64) -> i64 { 165 var x: i64 = x0 166 while x <= x1 { gh_px(fb, w, h, x, y); x = x + 1 } 167 return 0 168} 169func gh_vline(fb: *u8, w: i64, h: i64, x: i64, y0: i64, y1: i64) -> i64 { 170 var y: i64 = y0 171 while y <= y1 { gh_px(fb, w, h, x, y); y = y + 1 } 172 return 0 173} 174// composite the HUD (and menu, if open) over the caller's frame. labels[i]/mlabels[i] are *u8 string 175// ptrs passed AT RENDER TIME (never stored, never serialized). Pure function of (state, labels, font): 176// same inputs -> byte-identical pixels; that purity is a gate tooth, not a hope. 177func gh_render(g: *i64, fb: *u8, w: i64, h: i64, font: *u8, labels: *i64, mlabels: *i64) -> i64 { 178 var x: i64 = GH_BAR_X 179 var i: i64 = 0 180 while i < g[GH_F_NSLOT] { 181 let lp: i64 = labels[i] 182 let s: *u8 = lp as *u8 183 x = gh_text(fb, w, h, font, x, GH_BAR_Y, s) 184 x = gh_text(fb, w, h, font, x, GH_BAR_Y, ":" as *u8) 185 x = gh_numdraw(fb, w, h, font, x, GH_BAR_Y, g[GH_O_SLOT + i]) 186 x = x + GH_GAP 187 i = i + 1 188 } 189 if g[GH_F_MOPEN] == 1 { 190 let mn: i64 = g[GH_F_MN] 191 let py1: i64 = GH_MY + 4 + mn * GH_ROWH + 2 192 gh_hline(fb, w, h, GH_MX, GH_MX + GH_MW, GH_MY) 193 gh_hline(fb, w, h, GH_MX, GH_MX + GH_MW, py1) 194 gh_vline(fb, w, h, GH_MX, GH_MY, py1) 195 gh_vline(fb, w, h, GH_MX + GH_MW, GH_MY, py1) 196 var r: i64 = 0 197 while r < mn { 198 let ry: i64 = GH_MY + 4 + r * GH_ROWH 199 if r == g[GH_F_MCUR] { gh_text(fb, w, h, font, GH_MX + 3, ry, ">" as *u8) } 200 let mp: i64 = mlabels[r] 201 let ms: *u8 = mp as *u8 202 var mx: i64 = gh_text(fb, w, h, font, GH_MX + 12, ry, ms) 203 mx = gh_text(fb, w, h, font, mx, ry, " " as *u8) 204 gh_numdraw(fb, w, h, font, mx, ry, g[GH_O_MVAL + r]) 205 r = r + 1 206 } 207 } 208 return 0 209} 210// frame checksum + ink census (gate instruments; order-sensitive like gh_ck) 211func gh_frame_ck(fb: *u8, w: i64, h: i64) -> i64 { 212 var ck: i64 = GH_MAGIC_1469598103 213 var i: i64 = 0 214 let n: i64 = w * h * 3 215 while i < n { ck = (ck * 131 + (fb[i] & 0xff)) & 0x7FFFFFFFFFFFFFFF; i = i + 1 } 216 return ck 217} 218func gh_ink(fb: *u8, w: i64, h: i64) -> i64 { 219 var c: i64 = 0 220 var i: i64 = 0 221 let n: i64 = w * h * 3 222 while i < n { if (fb[i] & 0xff) != 0 { c = c + 1 } i = i + 3 } 223 return c 224}