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}