nx_cdmap_lib.nx source
↩ module page · 859 lines · 39744 B
1// nx_cdmap_lib.nx -- THE ONE centrality/distinctiveness ruler for every /compare board (2026-09-15).
2//
3// WHY. Operator, 2026-09-15 (the /goal): "a multi variable four quadrant visual thats emitted to properly show
4// where we are and our competitors and where we are moving as time goes on daily". The field's instrument is the
5// Centrality-Distinctiveness map (Dawar and Bagga, Harvard Business Review, June 2015, "A Better Way to Map Brand
6// Strategy"): X = how representative of its category a brand is, Y = how far it stands apart, the bubble a third
7// variable and its colour a fourth; the category's own averages cut four quadrants -- Aspirational (central AND
8// distinctive), Mainstream (central, undistinctive), Peripheral (neither), Unconventional (distinctive, uncentral).
9// Operator, same day: "extensible as we get more and more variables ... a 3 dimensional x y z axis cube with a 2d
10// cut and a 3d view". So the AXES ARE A REGISTRY (cd_axis_*), every axis reads on one permil scale, a board picks
11// any two as its 2D cut and any three as its cube through DATA (knowledge/compare/<dom>.cdmap), and the isometric
12// projection lives HERE so the gate can pin it. Adding a variable is one axis id, one name, one value rule.
13//
14// HERE EVERY INPUT IS THE MATRIX THE BOARD ALREADY MEASURES. The map is a PROJECTION of the same cells the page
15// grades (Nishi's cell measured on disk, the rivals' cells documented presence), re-derived on every publish, so it
16// cannot say anything the cells do not; and the board's own history -- a per-domain day spine this lib writes once
17// per calendar day -- is the time axis, so "where we are moving" is a path on the page, never a claim.
18//
19// THE ARITHMETIC (integer permil; nofloat; every constant named).
20// players P = Nishi + the @cols rivals. pts(p,r) = cd_pts(code): Best 3, Yes 2, Part 1, No 0 -- the scale the
21// page's pros/cons strip already uses (pc_pts). have(p,r) = pts > 0. hc(r) = players holding row r.
22// CENTRALITY(p) = 1000 * sum_r have(p,r)*hc(r) / sum_r hc(r)
23// the share of the category's feature mass the player covers, each feature weighted by how many hold it:
24// a player holding everything the field holds reads 1000; one holding only what nobody else has reads low.
25// DISTINCTIVENESS(p) = 1000 * sum_r lead(p,r) / (3 * (P-1) * sum_r have(p,r)),
26// lead(p,r) = sum over rivals q of max(0, pts(p,r) - pts(q,r)):
27// the average lead over each rival on the rows the player holds -- a Best where rivals hold Yes counts, a
28// feature no rival holds counts most, a player whose every feature is matched reads 0.
29// A player holding nothing reads 0 on both axes and quadrant ABSENT (never a division by zero).
30// BREADTH(p) = 1000 * sum_r pts(p,r) / (3 * rows) -- depth-weighted share of the whole matrix (the bubble).
31// DEPTH(p) = 1000 * sum_r pts(p,r) / (3 * held) -- how deeply the rows it holds are held (Best everywhere = 1000).
32// MOMENTUM(p) = (C_today - C_prev) + (D_today - D_prev) over the spine -- signed; unknown until day two; plotted as
33// (m + 1000) / 2 so a still player sits mid-axis.
34// The quadrant dividers are the MEANS over the players -- the category's own centre, as the map's authors draw it.
35// Ties: central iff C >= mean; distinctive iff D > mean (a field of identical players is Mainstream, never Aspirational).
36//
37// THE SPINE. <dir><dom>.spine, one row per player per calendar day (UTC), appended by the FIRST publish of the day;
38// a re-publish the same day is a no-op by construction (the day column is the key):
39// cd|<epoch>|<day>|<player>|<centrality>|<distinctiveness>|<breadth-points>|<rows>|<players>|<held>
40// (held was appended 2026-09-15 for the depth axis; an older row without it reads held 0, never a crash.)
41// THE VIEWS. knowledge/compare/<dom>.cdmap rows `cut|<x>|<y>` and `cube|<x>|<y>|<z>` name axes; absent = the HBR
42// defaults (cut centrality|distinctiveness, cube centrality|distinctiveness|breadth); an unknown axis name REFUSES the
43// row by name and the renderer falls back to the default, announced.
44// license_tier: ORIGINAL. No hw writes (Rule 26).
45import "nx_syscalls.nx"
46
47const CD_PTS_BEST: i64 = 3
48const CD_PTS_YES: i64 = 2
49const CD_PTS_PART: i64 = 1
50const CD_PTS_NO: i64 = 0
51const CD_CODE_NO: i64 = 0
52const CD_CODE_YES: i64 = 1
53const CD_CODE_BEST: i64 = 2
54const CD_CODE_PART: i64 = 3
55const CD_NISHI_ABSENT: i64 = 0
56const CD_NISHI_PRESENT: i64 = 1
57const CD_NISHI_EXCEED: i64 = 2
58const CD_PERMIL: i64 = 1000
59const CD_W: i64 = 4 // result words per player
60const CD_R_C: i64 = 0
61const CD_R_D: i64 = 1
62const CD_R_BREADTH: i64 = 2 // raw depth-weighted points held
63const CD_R_HELD: i64 = 3
64const CD_Q_ABSENT: i64 = 0
65const CD_Q_ASPIRATIONAL: i64 = 1
66const CD_Q_MAINSTREAM: i64 = 2
67const CD_Q_PERIPHERAL: i64 = 3
68const CD_Q_UNCONVENTIONAL: i64 = 4
69const CD_SECS_PER_DAY: i64 = 86400
70const CD_I64_BYTES: i64 = 8
71const CD_SPINE_DIR: *u8 = "knowledge/status/cdmap/"
72const CD_SPINE_EXT: *u8 = ".spine"
73const CD_SPINE_TAG: *u8 = "cd|"
74const CD_SPINE_TAGLEN: i64 = 3
75const CD_SPINE_NF: i64 = 10
76const CD_F_TAG: i64 = 0
77const CD_F_EPOCH: i64 = 1
78const CD_F_DAY: i64 = 2
79const CD_F_PLAYER: i64 = 3
80const CD_F_C: i64 = 4
81const CD_F_D: i64 = 5
82const CD_F_BREADTH: i64 = 6
83const CD_F_ROWS: i64 = 7
84const CD_F_PLAYERS: i64 = 8
85const CD_F_HELD: i64 = 9
86const CD_PATH_CAP: i64 = 600
87const CD_ROW_CAP: i64 = 512
88const CD_NUM_SCRATCH: i64 = 32
89const CD_DIR_MODE: i64 = 493 // 0755
90const CD_FILE_MODE: i64 = 420 // 0644
91const CD_CH_PIPE: i64 = 124
92const CD_CH_LF: i64 = 10
93const CD_CH_CR: i64 = 13
94const CD_CH_HASH: i64 = 35
95const CD_CH_SPACE: i64 = 32
96const CD_CH_ZERO: i64 = 48
97const CD_CH_NINE: i64 = 57
98const CD_CH_MINUS: i64 = 45
99const CD_TEN: i64 = 10
100// per-player history off the spine: first-day and previous-day values of every spine column the axes derive from
101const CD_HIST_W: i64 = 14
102const CD_H_FC: i64 = 0
103const CD_H_FD: i64 = 1
104const CD_H_FDAY: i64 = 2
105const CD_H_PC: i64 = 3
106const CD_H_PD: i64 = 4
107const CD_H_PDAY: i64 = 5
108const CD_H_DAYS: i64 = 6
109const CD_H_LASTDAY: i64 = 7
110const CD_H_FB: i64 = 8 // first-day breadth points
111const CD_H_FH: i64 = 9 // first-day held
112const CD_H_FROWS: i64 = 10 // first-day matrix rows
113const CD_H_PB: i64 = 11 // previous-day breadth points
114const CD_H_PH: i64 = 12 // previous-day held
115const CD_H_PROWS: i64 = 13 // previous-day matrix rows
116// THE AXIS REGISTRY -- adding a variable is one id, one name (cd_axis_name), one value rule (cd_axis_value)
117const CD_AX_C: i64 = 0
118const CD_AX_D: i64 = 1
119const CD_AX_BREADTH: i64 = 2
120const CD_AX_DEPTH: i64 = 3
121const CD_AX_MOMENTUM: i64 = 4
122const CD_AX_N: i64 = 5
123const CD_AX_NONE: i64 = 0 - 1
124const CD_MOM_SPAN: i64 = 2000 // momentum ranges -1000..1000; plotted on 0..1000
125const CD_VIEW_CUT: i64 = 0
126const CD_VIEW_CUBE: i64 = 1
127const CD_CUT_AXES: i64 = 2
128const CD_CUBE_AXES: i64 = 3
129const CD_VIEW_DIR: *u8 = "knowledge/compare/"
130const CD_VIEW_EXT: *u8 = ".cdmap"
131const CD_VIEW_CUT_TAG: *u8 = "cut|"
132const CD_VIEW_CUBE_TAG: *u8 = "cube|"
133const CD_VIEW_ABSENT: i64 = 0 // no conf or no row for that view: the caller takes the default
134const CD_VIEW_REFUSED: i64 = 0 - 1 // a row named an axis the registry does not have
135// the isometric projection (30-degree axes): X' = ox + (x - y) cos30 u ; Y' = oy + (x + y) sin30 u - z u, u = px per 1000
136const CD_ISO_COS_PERMIL: i64 = 866
137const CD_ISO_SIN_PERMIL: i64 = 500
138// history slots by axis: which first/prev columns an axis reads (0 = C/D pair, 1 = breadth/held/rows triple)
139const CD_HIST_WHICH_FIRST: i64 = 0
140const CD_HIST_WHICH_PREV: i64 = 1
141
142func cd_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
143func cd_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o + i] = s[i]; i = i + 1 } return o + i }
144// copy a name into a spine row with the row's own separators neutralised (a rival named with a pipe or a newline
145// would otherwise shear every reader of the file)
146func cd_cat_safe(d: *u8, o: i64, s: *u8) -> i64 {
147 var i: i64 = 0
148 while s[i] != (0 as u8) {
149 var c: i64 = s[i] as i64
150 if c == CD_CH_PIPE { c = CD_CH_SPACE }
151 if c == CD_CH_LF { c = CD_CH_SPACE }
152 d[o + i] = c as u8
153 i = i + 1
154 }
155 return o + i
156}
157func cd_catn(d: *u8, o: i64, v: i64) -> i64 {
158 var p: i64 = o
159 var x: i64 = v
160 if x < 0 { d[p] = CD_CH_MINUS as u8; p = p + 1; x = 0 - x }
161 let t: *u8 = sys_mmap(CD_NUM_SCRATCH)
162 var k: i64 = 0
163 if x == 0 { t[0] = CD_CH_ZERO as u8; k = 1 } else { while x > 0 { t[k] = (CD_CH_ZERO + (x % CD_TEN)) as u8; x = x / CD_TEN; k = k + 1 } }
164 while k > 0 { k = k - 1; d[p] = t[k]; p = p + 1 }
165 return p
166}
167func cd_signed(v: i64, neg: i64) -> i64 { if neg == 1 { return 0 - v } return v }
168// leading decimal of a NUL-terminated string; a non-digit ends the number; empty reads 0 (a blank rival cell is No)
169func cd_atoi(s: *u8) -> i64 { return cd_atoi_n(s, cd_slen(s)) }
170// the same over a bounded byte run (a spine field is not NUL-terminated)
171func cd_atoi_n(s: *u8, n: i64) -> i64 {
172 var v: i64 = 0
173 var i: i64 = 0
174 var neg: i64 = 0
175 if n > 0 { if s[0] == (CD_CH_MINUS as u8) { neg = 1; i = 1 } }
176 while i < n {
177 let c: i64 = s[i] as i64
178 if c < CD_CH_ZERO { return cd_signed(v, neg) }
179 if c > CD_CH_NINE { return cd_signed(v, neg) }
180 v = v * CD_TEN + (c - CD_CH_ZERO)
181 i = i + 1
182 }
183 return cd_signed(v, neg)
184}
185func cd_isqrt(v: i64) -> i64 {
186 if v <= 0 { return 0 }
187 var x: i64 = v
188 var y: i64 = (x + 1) / 2
189 while y < x { x = y; y = (x + v / x) / 2 }
190 return x
191}
192func cd_clamp_permil(v: i64) -> i64 { if v < 0 { return 0 } if v > CD_PERMIL { return CD_PERMIL } return v }
193// a matrix cell code -> points (an unknown code is No, never a guess)
194func cd_pts(code: i64) -> i64 {
195 if code == CD_CODE_BEST { return CD_PTS_BEST }
196 if code == CD_CODE_YES { return CD_PTS_YES }
197 if code == CD_CODE_PART { return CD_PTS_PART }
198 return CD_PTS_NO
199}
200// the page's measured Nishi state (absent / present / exceed) -> points on the same scale
201func cd_nishi_pts(nishi: i64) -> i64 {
202 if nishi == CD_NISHI_EXCEED { return CD_PTS_BEST }
203 if nishi == CD_NISHI_PRESENT { return CD_PTS_YES }
204 return CD_PTS_NO
205}
206// pts: np x nr table, pts[p*nr + r]; out: np x CD_W. Returns the feature mass (sum of hc), 0 for an empty field.
207func cd_compute(pts: *i64, np: i64, nr: i64, out: *i64) -> i64 {
208 let hc: *i64 = sys_mmap((nr + 1) * CD_I64_BYTES) as *i64
209 var mass: i64 = 0
210 var r: i64 = 0
211 while r < nr {
212 var c: i64 = 0
213 var p: i64 = 0
214 while p < np { if pts[p * nr + r] > 0 { c = c + 1 } p = p + 1 }
215 hc[r] = c
216 mass = mass + c
217 r = r + 1
218 }
219 var q: i64 = 0
220 while q < np {
221 var cov: i64 = 0
222 var held: i64 = 0
223 var breadth: i64 = 0
224 var lead: i64 = 0
225 r = 0
226 while r < nr {
227 let v: i64 = pts[q * nr + r]
228 if v > 0 {
229 cov = cov + hc[r]
230 held = held + 1
231 breadth = breadth + v
232 var o: i64 = 0
233 while o < np {
234 if o != q { let dlt: i64 = v - pts[o * nr + r]; if dlt > 0 { lead = lead + dlt } }
235 o = o + 1
236 }
237 }
238 r = r + 1
239 }
240 var cval: i64 = 0
241 if mass > 0 { cval = CD_PERMIL * cov / mass }
242 var dval: i64 = 0
243 if held > 0 { if np > 1 { dval = CD_PERMIL * lead / (CD_PTS_BEST * (np - 1) * held) } }
244 out[q * CD_W + CD_R_C] = cval
245 out[q * CD_W + CD_R_D] = dval
246 out[q * CD_W + CD_R_BREADTH] = breadth
247 out[q * CD_W + CD_R_HELD] = held
248 q = q + 1
249 }
250 return mass
251}
252func cd_mean(out: *i64, np: i64, field: i64) -> i64 {
253 if np <= 0 { return 0 }
254 var s: i64 = 0
255 var p: i64 = 0
256 while p < np { s = s + out[p * CD_W + field]; p = p + 1 }
257 return s / np
258}
259func cd_quadrant(c: i64, d: i64, cmean: i64, dmean: i64, held: i64) -> i64 {
260 if held <= 0 { return CD_Q_ABSENT }
261 if c >= cmean { if d > dmean { return CD_Q_ASPIRATIONAL } return CD_Q_MAINSTREAM }
262 if d > dmean { return CD_Q_UNCONVENTIONAL }
263 return CD_Q_PERIPHERAL
264}
265func cd_quadrant_name(q: i64) -> *u8 {
266 if q == CD_Q_ASPIRATIONAL { return "Aspirational" as *u8 }
267 if q == CD_Q_MAINSTREAM { return "Mainstream" as *u8 }
268 if q == CD_Q_PERIPHERAL { return "Peripheral" as *u8 }
269 if q == CD_Q_UNCONVENTIONAL { return "Unconventional" as *u8 }
270 return "Absent" as *u8
271}
272// ---- THE AXIS REGISTRY ----
273func cd_axis_name(id: i64) -> *u8 {
274 if id == CD_AX_C { return "centrality" as *u8 }
275 if id == CD_AX_D { return "distinctiveness" as *u8 }
276 if id == CD_AX_BREADTH { return "breadth" as *u8 }
277 if id == CD_AX_DEPTH { return "depth" as *u8 }
278 if id == CD_AX_MOMENTUM { return "momentum" as *u8 }
279 return "unknown" as *u8
280}
281func cd_name_eq_n(name: *u8, s: *u8, n: i64) -> i64 {
282 if cd_slen(name) != n { return 0 }
283 var i: i64 = 0
284 while i < n { if name[i] != s[i] { return 0 } i = i + 1 }
285 return 1
286}
287// an axis id from its name over n bytes; CD_AX_NONE for a name the registry does not have
288func cd_axis_id(s: *u8, n: i64) -> i64 {
289 var id: i64 = 0
290 while id < CD_AX_N { if cd_name_eq_n(cd_axis_name(id), s, n) == 1 { return id } id = id + 1 }
291 return CD_AX_NONE
292}
293func cd_breadth_permil(points: i64, rows: i64) -> i64 { if rows <= 0 { return 0 } return CD_PERMIL * points / (CD_PTS_BEST * rows) }
294func cd_depth_permil(points: i64, held: i64) -> i64 { if held <= 0 { return 0 } return CD_PERMIL * points / (CD_PTS_BEST * held) }
295func cd_momentum_known(hist: *i64) -> i64 { if hist[CD_H_PDAY] > 0 { return 1 } return 0 }
296// today's momentum for player p: the C and D moves since the previous spine day (0 when unknown)
297func cd_momentum(out: *i64, p: i64, hist: *i64) -> i64 {
298 if cd_momentum_known(hist) == 0 { return 0 }
299 return (out[p * CD_W + CD_R_C] - hist[CD_H_PC]) + (out[p * CD_W + CD_R_D] - hist[CD_H_PD])
300}
301// a signed momentum on the 0..1000 plotting scale: still = 500
302func cd_mom_plot(m: i64) -> i64 { return cd_clamp_permil((m + CD_PERMIL) * CD_PERMIL / CD_MOM_SPAN) }
303// the axis's VALUE for player p today (momentum signed, the rest permil 0..1000)
304func cd_axis_value(out: *i64, p: i64, nr: i64, hist: *i64, id: i64) -> i64 {
305 if id == CD_AX_C { return out[p * CD_W + CD_R_C] }
306 if id == CD_AX_D { return out[p * CD_W + CD_R_D] }
307 if id == CD_AX_BREADTH { return cd_breadth_permil(out[p * CD_W + CD_R_BREADTH], nr) }
308 if id == CD_AX_DEPTH { return cd_depth_permil(out[p * CD_W + CD_R_BREADTH], out[p * CD_W + CD_R_HELD]) }
309 if id == CD_AX_MOMENTUM { return cd_momentum(out, p, hist) }
310 return 0
311}
312// the axis's PLOT coordinate for player p today, 0..1000 on every axis
313func cd_axis_plot(out: *i64, p: i64, nr: i64, hist: *i64, id: i64) -> i64 {
314 if id == CD_AX_MOMENTUM { return cd_mom_plot(cd_momentum(out, p, hist)) }
315 return cd_clamp_permil(cd_axis_value(out, p, nr, hist, id))
316}
317// the axis's plot coordinate on a PAST spine day (which = first or previous); a past momentum is unknown -> mid-axis
318func cd_hist_axis_plot(hist: *i64, which: i64, id: i64) -> i64 {
319 var c: i64 = hist[CD_H_FC]
320 var d: i64 = hist[CD_H_FD]
321 var b: i64 = hist[CD_H_FB]
322 var h: i64 = hist[CD_H_FH]
323 var rows: i64 = hist[CD_H_FROWS]
324 if which == CD_HIST_WHICH_PREV { c = hist[CD_H_PC]; d = hist[CD_H_PD]; b = hist[CD_H_PB]; h = hist[CD_H_PH]; rows = hist[CD_H_PROWS] }
325 if id == CD_AX_C { return cd_clamp_permil(c) }
326 if id == CD_AX_D { return cd_clamp_permil(d) }
327 if id == CD_AX_BREADTH { return cd_clamp_permil(cd_breadth_permil(b, rows)) }
328 if id == CD_AX_DEPTH { return cd_clamp_permil(cd_depth_permil(b, h)) }
329 if id == CD_AX_MOMENTUM { return cd_mom_plot(0) }
330 return 0
331}
332// the mean plot coordinate of an axis over the players (the divider for that axis)
333func cd_axis_mean_plot(out: *i64, np: i64, nr: i64, hist: *i64, id: i64) -> i64 {
334 if np <= 0 { return 0 }
335 var s: i64 = 0
336 var p: i64 = 0
337 while p < np { s = s + cd_axis_plot(out, p, nr, (hist as i64 + p * CD_HIST_W * CD_I64_BYTES) as *i64, id); p = p + 1 }
338 return s / np
339}
340// ---- THE VIEWS: which axes a board cuts and cubes on ----
341func cd_view_default(view: i64, ids: *i64) -> i64 {
342 ids[0] = CD_AX_C
343 ids[1] = CD_AX_D
344 if view == CD_VIEW_CUBE { ids[2] = CD_AX_BREADTH; return CD_CUBE_AXES }
345 return CD_CUT_AXES
346}
347func cd_view_axes(view: i64) -> i64 { if view == CD_VIEW_CUBE { return CD_CUBE_AXES } return CD_CUT_AXES }
348func cd_view_path(dir: *u8, dom: *u8, out: *u8) -> i64 {
349 var o: i64 = cd_cat(out, 0, dir)
350 o = cd_cat(out, o, dom)
351 o = cd_cat(out, o, CD_VIEW_EXT)
352 out[o] = 0 as u8
353 return o
354}
355// parse one `cut|a|b` / `cube|a|b|c` row into ids; returns the axis count, or CD_VIEW_REFUSED when a name is unknown
356func cd_view_row(line: *u8, n: i64, view: i64, ids: *i64) -> i64 {
357 let want: i64 = cd_view_axes(view)
358 let off: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
359 let len: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
360 var k: i64 = 0
361 while k < want {
362 if cd_field(line, n, k + 1, off, len) == 0 { return CD_VIEW_REFUSED }
363 var l: i64 = len[0]
364 if l > 0 { if line[off[0] + l - 1] == (CD_CH_CR as u8) { l = l - 1 } }
365 let id: i64 = cd_axis_id((line as i64 + off[0]) as *u8, l)
366 if id == CD_AX_NONE { return CD_VIEW_REFUSED }
367 ids[k] = id
368 k = k + 1
369 }
370 return want
371}
372func cd_row_tag_is(line: *u8, n: i64, tag: *u8) -> i64 {
373 let tn: i64 = cd_slen(tag)
374 if n < tn { return 0 }
375 var i: i64 = 0
376 while i < tn { if line[i] != tag[i] { return 0 } i = i + 1 }
377 return 1
378}
379// the view's axes from <dir><dom>.cdmap: the LAST matching row wins; absent file or row -> CD_VIEW_ABSENT (0);
380// a row naming an unknown axis -> CD_VIEW_REFUSED (-1), and ids are left as they were
381func cd_view_conf(dir: *u8, dom: *u8, view: i64, ids: *i64) -> i64 {
382 let path: *u8 = sys_mmap(CD_PATH_CAP)
383 cd_view_path(dir, dom, path)
384 let nb: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
385 nb[0] = 0
386 let buf: *u8 = sys_read_file(path, nb)
387 if (buf as i64) == 0 { return CD_VIEW_ABSENT }
388 let n: i64 = nb[0]
389 var tag: *u8 = CD_VIEW_CUT_TAG
390 if view == CD_VIEW_CUBE { tag = CD_VIEW_CUBE_TAG }
391 var res: i64 = CD_VIEW_ABSENT
392 let tmp: *i64 = sys_mmap(CD_CUBE_AXES * CD_I64_BYTES) as *i64
393 var p: i64 = 0
394 while p < n {
395 var e: i64 = p
396 while e < n { if buf[e] == (CD_CH_LF as u8) { break } e = e + 1 }
397 let line: *u8 = (buf as i64 + p) as *u8
398 let ln: i64 = e - p
399 p = e + 1
400 if cd_row_tag_is(line, ln, tag) == 1 {
401 let r: i64 = cd_view_row(line, ln, view, tmp)
402 if r < 0 { res = CD_VIEW_REFUSED } else { var k: i64 = 0; while k < r { ids[k] = tmp[k]; k = k + 1 } res = r }
403 }
404 }
405 return res
406}
407// ---- THE NOTES (datavis DV5, 2026-09-15): board-authored annotations pinned to a mark or a point ----
408// knowledge/compare/<dom>.cdnotes rows: note|<id>|<anchor>|<text>|<since YYYY-MM-DD>|<by>
409// anchor = a player name exactly as the board's column names it (Nishi, or a @cols rival), or at:<x>,<y> -- a point
410// in permil on the board's CURRENT cut axes. text carries no pipe; longer than CD_NOTE_TEXT_MAX is REFUSED by row.
411// A refused row (unknown player, malformed anchor, missing field, empty or over-long text, past the caller's cap)
412// is COUNTED in stats and named in the receipt, never rendered, never silently dropped. The emitter seats each note
413// with the SAME placer as the labels (nx_chartlay_lib cl_place) so a note can never collide with a mark or a label.
414const CD_NOTE_EXT: *u8 = ".cdnotes"
415const CD_NOTE_TAG: *u8 = "note|"
416const CD_NOTE_AT: *u8 = "at:"
417const CD_NOTE_AT_LEN: i64 = 3
418const CD_NOTE_TEXT_MAX: i64 = 120
419const CD_NOTE_W: i64 = 12
420const CD_N_KIND: i64 = 0
421const CD_N_PLAYER: i64 = 1
422const CD_N_X: i64 = 2
423const CD_N_Y: i64 = 3
424const CD_N_TOFF: i64 = 4
425const CD_N_TLEN: i64 = 5
426const CD_N_IDOFF: i64 = 6
427const CD_N_IDLEN: i64 = 7
428const CD_N_SOFF: i64 = 8
429const CD_N_SLEN: i64 = 9
430const CD_N_BOFF: i64 = 10
431const CD_N_BLEN: i64 = 11
432const CD_NK_PLAYER: i64 = 1
433const CD_NK_AT: i64 = 2
434const CD_NF_ID: i64 = 1
435const CD_NF_ANCHOR: i64 = 2
436const CD_NF_TEXT: i64 = 3
437const CD_NF_SINCE: i64 = 4
438const CD_NF_BY: i64 = 5
439const CD_NS_ROWS: i64 = 0 // stats: rows tagged note|
440const CD_NS_REFUSED: i64 = 1 // rows refused (counted, never rendered)
441const CD_NS_W: i64 = 2
442const CD_CH_COMMA: i64 = 44
443const CD_NOTE_REFUSED: i64 = 0 - 1
444func cd_notes_path(dir: *u8, dom: *u8, out: *u8) -> i64 {
445 var o: i64 = cd_cat(out, 0, dir)
446 o = cd_cat(out, o, dom)
447 o = cd_cat(out, o, CD_NOTE_EXT)
448 out[o] = 0 as u8
449 return o
450}
451func cd_note_rec(recs: *i64, k: i64) -> *i64 { return (recs as i64 + k * CD_NOTE_W * CD_I64_BYTES) as *i64 }
452// the player whose name is exactly the row's anchor bytes; CD_NOTE_REFUSED when no player is named that way
453func cd_notes_player(line: *u8, off: i64, len: i64, names: *i64, np: i64) -> i64 {
454 var p: i64 = 0
455 while p < np { if cd_name_eq_n(names[p] as *u8, (line as i64 + off) as *u8, len) == 1 { return p } p = p + 1 }
456 return CD_NOTE_REFUSED
457}
458// at:<x>,<y> -> xy[2] clamped to permil; 1 parsed, 0 when the field is not that shape
459func cd_note_at(line: *u8, off: i64, len: i64, xy: *i64) -> i64 {
460 if len <= CD_NOTE_AT_LEN { return 0 }
461 var i: i64 = 0
462 while i < CD_NOTE_AT_LEN { if line[off + i] != CD_NOTE_AT[i] { return 0 } i = i + 1 }
463 let c: i64 = off + CD_NOTE_AT_LEN
464 let e: i64 = off + len
465 var k: i64 = c
466 while k < e { if line[k] == (CD_CH_COMMA as u8) { break } k = k + 1 }
467 if k >= e { return 0 }
468 if k == c { return 0 }
469 if k + 1 >= e { return 0 }
470 xy[0] = cd_clamp_permil(cd_atoi_n((line as i64 + c) as *u8, k - c))
471 xy[1] = cd_clamp_permil(cd_atoi_n((line as i64 + k + 1) as *u8, e - k - 1))
472 return 1
473}
474// one note| row into rec (offsets are into the file buffer, base = the row's offset); 1 parsed, CD_NOTE_REFUSED otherwise
475func cd_note_row(line: *u8, n: i64, base: i64, names: *i64, np: i64, rec: *i64) -> i64 {
476 let off: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
477 let len: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
478 if cd_field(line, n, CD_NF_ID, off, len) == 0 { return CD_NOTE_REFUSED }
479 if len[0] <= 0 { return CD_NOTE_REFUSED }
480 rec[CD_N_IDOFF] = base + off[0]
481 rec[CD_N_IDLEN] = len[0]
482 if cd_field(line, n, CD_NF_ANCHOR, off, len) == 0 { return CD_NOTE_REFUSED }
483 let xy: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
484 if cd_note_at(line, off[0], len[0], xy) == 1 {
485 rec[CD_N_KIND] = CD_NK_AT
486 rec[CD_N_PLAYER] = CD_NOTE_REFUSED
487 rec[CD_N_X] = xy[0]
488 rec[CD_N_Y] = xy[1]
489 } else {
490 let pi: i64 = cd_notes_player(line, off[0], len[0], names, np)
491 if pi < 0 { return CD_NOTE_REFUSED }
492 rec[CD_N_KIND] = CD_NK_PLAYER
493 rec[CD_N_PLAYER] = pi
494 rec[CD_N_X] = 0
495 rec[CD_N_Y] = 0
496 }
497 if cd_field(line, n, CD_NF_TEXT, off, len) == 0 { return CD_NOTE_REFUSED }
498 if len[0] <= 0 { return CD_NOTE_REFUSED }
499 if len[0] > CD_NOTE_TEXT_MAX { return CD_NOTE_REFUSED }
500 rec[CD_N_TOFF] = base + off[0]
501 rec[CD_N_TLEN] = len[0]
502 rec[CD_N_SOFF] = base
503 rec[CD_N_SLEN] = 0
504 rec[CD_N_BOFF] = base
505 rec[CD_N_BLEN] = 0
506 if cd_field(line, n, CD_NF_SINCE, off, len) == 1 { rec[CD_N_SOFF] = base + off[0]; rec[CD_N_SLEN] = len[0] }
507 if cd_field(line, n, CD_NF_BY, off, len) == 1 {
508 var bl: i64 = len[0]
509 if bl > 0 { if line[off[0] + bl - 1] == (CD_CH_CR as u8) { bl = bl - 1 } }
510 rec[CD_N_BOFF] = base + off[0]
511 rec[CD_N_BLEN] = bl
512 }
513 return 1
514}
515// load <dir><dom>.cdnotes into recs[cap * CD_NOTE_W]; bufout[0] = the file buffer (the text lives there), bufout[1] = bytes;
516// stats[CD_NS_W] counts rows seen and rows refused. Returns the notes parsed; 0 for an absent file (a board without notes
517// is not an error) and stats say whether 0 means "no file" (rows 0) or "every row refused" (rows > 0).
518func cd_notes_load(dir: *u8, dom: *u8, names: *i64, np: i64, recs: *i64, cap: i64, bufout: *i64, stats: *i64) -> i64 {
519 stats[CD_NS_ROWS] = 0
520 stats[CD_NS_REFUSED] = 0
521 bufout[0] = 0
522 bufout[1] = 0
523 let path: *u8 = sys_mmap(CD_PATH_CAP)
524 cd_notes_path(dir, dom, path)
525 let nb: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
526 nb[0] = 0
527 let buf: *u8 = sys_read_file(path, nb)
528 if (buf as i64) == 0 { return 0 }
529 let n: i64 = nb[0]
530 bufout[0] = buf as i64
531 bufout[1] = n
532 var cnt: i64 = 0
533 var p: i64 = 0
534 while p < n {
535 var e: i64 = p
536 while e < n { if buf[e] == (CD_CH_LF as u8) { break } e = e + 1 }
537 let line: *u8 = (buf as i64 + p) as *u8
538 let ln: i64 = e - p
539 let base: i64 = p
540 p = e + 1
541 if cd_row_tag_is(line, ln, CD_NOTE_TAG) == 1 {
542 stats[CD_NS_ROWS] = stats[CD_NS_ROWS] + 1
543 if cnt >= cap { stats[CD_NS_REFUSED] = stats[CD_NS_REFUSED] + 1 } else {
544 if cd_note_row(line, ln, base, names, np, cd_note_rec(recs, cnt)) == 1 { cnt = cnt + 1 } else { stats[CD_NS_REFUSED] = stats[CD_NS_REFUSED] + 1 }
545 }
546 }
547 }
548 return cnt
549}
550// WORD-WRAP text[0..n) into at most maxlines lines of at most maxch bytes each, breaking only at spaces; loff/llen per line.
551// Returns the line count, or CD_NOTE_REFUSED when one word is longer than maxch or the text needs more lines than
552// maxlines -- it never truncates in silence. An empty text is 0 lines.
553func cd_notes_wrap(text: *u8, n: i64, maxch: i64, maxlines: i64, loff: *i64, llen: *i64) -> i64 {
554 var lines: i64 = 0
555 var i: i64 = 0
556 while i < n {
557 while i < n { if text[i] != (CD_CH_SPACE as u8) { break } i = i + 1 }
558 if i >= n { break }
559 if lines >= maxlines { return CD_NOTE_REFUSED }
560 var e: i64 = i
561 var j: i64 = i
562 while j < n {
563 var we: i64 = j
564 while we < n { if text[we] == (CD_CH_SPACE as u8) { break } we = we + 1 }
565 if we - i > maxch { break }
566 e = we
567 j = we
568 while j < n { if text[j] != (CD_CH_SPACE as u8) { break } j = j + 1 }
569 }
570 if e == i { return CD_NOTE_REFUSED }
571 loff[lines] = i
572 llen[lines] = e - i
573 lines = lines + 1
574 i = e
575 }
576 return lines
577}
578// ---- THE ISOMETRIC PROJECTION (permil coordinates -> pixels; unit = pixels per 1000 permil) ----
579func cd_iso_x(ox: i64, x: i64, y: i64, unit: i64) -> i64 { return ox + ((x - y) * CD_ISO_COS_PERMIL / CD_PERMIL) * unit / CD_PERMIL }
580func cd_iso_y(oy: i64, x: i64, y: i64, z: i64, unit: i64) -> i64 { return oy + ((x + y) * CD_ISO_SIN_PERMIL / CD_PERMIL) * unit / CD_PERMIL - z * unit / CD_PERMIL }
581// ---- THE SPINE ----
582func cd_day_of(epoch: i64) -> i64 { return epoch / CD_SECS_PER_DAY }
583func cd_spine_path(dir: *u8, dom: *u8, out: *u8) -> i64 {
584 var o: i64 = cd_cat(out, 0, dir)
585 o = cd_cat(out, o, dom)
586 o = cd_cat(out, o, CD_SPINE_EXT)
587 out[o] = 0 as u8
588 return o
589}
590// field k of a pipe row [line, line+n): its offset and length; 1 if the row has that field
591func cd_field(line: *u8, n: i64, k: i64, off: *i64, len: *i64) -> i64 {
592 var f: i64 = 0
593 var s: i64 = 0
594 var i: i64 = 0
595 while i <= n {
596 var hit: i64 = 0
597 if i == n { hit = 1 } else { if line[i] == (CD_CH_PIPE as u8) { hit = 1 } }
598 if hit == 1 {
599 if f == k { off[0] = s; len[0] = i - s; return 1 }
600 f = f + 1
601 s = i + 1
602 }
603 i = i + 1
604 }
605 return 0
606}
607func cd_field_int(line: *u8, n: i64, k: i64) -> i64 {
608 let off: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
609 let len: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
610 if cd_field(line, n, k, off, len) == 0 { return 0 }
611 return cd_atoi_n((line as i64 + off[0]) as *u8, len[0])
612}
613func cd_field_eq(line: *u8, n: i64, k: i64, s: *u8) -> i64 {
614 let off: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
615 let len: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
616 if cd_field(line, n, k, off, len) == 0 { return 0 }
617 let sl: i64 = cd_slen(s)
618 if len[0] != sl { return 0 }
619 var i: i64 = 0
620 while i < sl { if line[off[0] + i] != s[i] { return 0 } i = i + 1 }
621 return 1
622}
623func cd_row_is(line: *u8, n: i64) -> i64 {
624 if n < CD_SPINE_TAGLEN { return 0 }
625 var i: i64 = 0
626 while i < CD_SPINE_TAGLEN { if line[i] != CD_SPINE_TAG[i] { return 0 } i = i + 1 }
627 return 1
628}
629func cd_count_lines(buf: *u8, n: i64) -> i64 {
630 var c: i64 = 1
631 var i: i64 = 0
632 while i < n { if buf[i] == (CD_CH_LF as u8) { c = c + 1 } i = i + 1 }
633 return c
634}
635// one player's history off the spine bytes: hist[CD_HIST_W]; returns the distinct days seen for that player.
636// prev = the latest row whose day is BEFORE today, so a same-day re-publish still compares against yesterday.
637func cd_spine_history(buf: *u8, n: i64, player: *u8, today: i64, hist: *i64) -> i64 {
638 var i: i64 = 0
639 while i < CD_HIST_W { hist[i] = 0; i = i + 1 }
640 var days: i64 = 0
641 var lastday: i64 = 0 - 1
642 var first: i64 = 0
643 var p: i64 = 0
644 while p < n {
645 var e: i64 = p
646 while e < n { if buf[e] == (CD_CH_LF as u8) { break } e = e + 1 }
647 let line: *u8 = (buf as i64 + p) as *u8
648 let ln: i64 = e - p
649 p = e + 1
650 if cd_row_is(line, ln) == 1 { if cd_field_eq(line, ln, CD_F_PLAYER, player) == 1 {
651 let day: i64 = cd_field_int(line, ln, CD_F_DAY)
652 let c: i64 = cd_field_int(line, ln, CD_F_C)
653 let d: i64 = cd_field_int(line, ln, CD_F_D)
654 let b: i64 = cd_field_int(line, ln, CD_F_BREADTH)
655 let rows: i64 = cd_field_int(line, ln, CD_F_ROWS)
656 let h: i64 = cd_field_int(line, ln, CD_F_HELD)
657 if first == 0 { hist[CD_H_FC] = c; hist[CD_H_FD] = d; hist[CD_H_FDAY] = day; hist[CD_H_FB] = b; hist[CD_H_FH] = h; hist[CD_H_FROWS] = rows; first = 1 }
658 if day != lastday { days = days + 1; lastday = day }
659 if day < today { hist[CD_H_PC] = c; hist[CD_H_PD] = d; hist[CD_H_PDAY] = day; hist[CD_H_PB] = b; hist[CD_H_PH] = h; hist[CD_H_PROWS] = rows }
660 hist[CD_H_LASTDAY] = lastday
661 } }
662 }
663 hist[CD_H_DAYS] = days
664 return days
665}
666// THE DAILY TRAIL (2026-09-16). The map drew a player's path as three points -- first spine day, previous spine day,
667// today -- so from day four on every day in between vanished and "where are we moving, day by day" could not be read
668// off the picture. cd_spine_trail returns EVERY spine day before today for one player, oldest first, one record per
669// day, each a CD_HIST_W history record whose FIRST slots hold that day's values: cd_hist_axis_plot(rec, FIRST, axis)
670// then plots a past day with the one axis rule the rest of the map uses, for every registered axis. A day that
671// carries more than one row for the player keeps its last row. Returns the records written, never more than cap.
672func cd_trail_rec(recs: *i64, k: i64) -> *i64 { return (recs as i64 + k * CD_HIST_W * CD_I64_BYTES) as *i64 }
673func cd_spine_trail(buf: *u8, n: i64, player: *u8, today: i64, recs: *i64, cap: i64) -> i64 {
674 var nt: i64 = 0
675 var p: i64 = 0
676 while p < n {
677 var e: i64 = p
678 while e < n { if buf[e] == (CD_CH_LF as u8) { break } e = e + 1 }
679 let line: *u8 = (buf as i64 + p) as *u8
680 let ln: i64 = e - p
681 p = e + 1
682 if cd_row_is(line, ln) == 1 { if cd_field_eq(line, ln, CD_F_PLAYER, player) == 1 {
683 let day: i64 = cd_field_int(line, ln, CD_F_DAY)
684 if day < today {
685 var k: i64 = nt
686 if nt > 0 { let last: *i64 = cd_trail_rec(recs, nt - 1); if last[CD_H_FDAY] == day { k = nt - 1 } }
687 if k < cap {
688 let rec: *i64 = cd_trail_rec(recs, k)
689 var z: i64 = 0
690 while z < CD_HIST_W { rec[z] = 0; z = z + 1 }
691 rec[CD_H_FC] = cd_field_int(line, ln, CD_F_C)
692 rec[CD_H_FD] = cd_field_int(line, ln, CD_F_D)
693 rec[CD_H_FDAY] = day
694 rec[CD_H_FB] = cd_field_int(line, ln, CD_F_BREADTH)
695 rec[CD_H_FH] = cd_field_int(line, ln, CD_F_HELD)
696 rec[CD_H_FROWS] = cd_field_int(line, ln, CD_F_ROWS)
697 rec[CD_H_DAYS] = 1
698 if k == nt { nt = nt + 1 }
699 }
700 }
701 } }
702 }
703 return nt
704}
705func cd_spine_has_day(buf: *u8, n: i64, day: i64) -> i64 {
706 var p: i64 = 0
707 while p < n {
708 var e: i64 = p
709 while e < n { if buf[e] == (CD_CH_LF as u8) { break } e = e + 1 }
710 let line: *u8 = (buf as i64 + p) as *u8
711 let ln: i64 = e - p
712 p = e + 1
713 if cd_row_is(line, ln) == 1 { if cd_field_int(line, ln, CD_F_DAY) == day { return 1 } }
714 }
715 return 0
716}
717// the distinct days on the spine with their row counts (players recorded) and the matrix row count each day carried,
718// in file order; cap is caller-derived (cd_count_lines)
719func cd_spine_days(buf: *u8, n: i64, days: *i64, rows: *i64, mrows: *i64, cap: i64) -> i64 {
720 var nd: i64 = 0
721 var p: i64 = 0
722 while p < n {
723 var e: i64 = p
724 while e < n { if buf[e] == (CD_CH_LF as u8) { break } e = e + 1 }
725 let line: *u8 = (buf as i64 + p) as *u8
726 let ln: i64 = e - p
727 p = e + 1
728 if cd_row_is(line, ln) == 1 {
729 let day: i64 = cd_field_int(line, ln, CD_F_DAY)
730 var same: i64 = 0
731 if nd > 0 { if days[nd - 1] == day { same = 1 } }
732 if same == 1 { rows[nd - 1] = rows[nd - 1] + 1 } else { if nd < cap { days[nd] = day; rows[nd] = 1; mrows[nd] = cd_field_int(line, ln, CD_F_ROWS); nd = nd + 1 } }
733 }
734 }
735 return nd
736}
737// append today's rows (one per player) unless the day is already on the spine.
738// returns rows written, 0 when the day was present (no-op), -1 when the file cannot be opened.
739func cd_spine_append(dir: *u8, dom: *u8, epoch: i64, names: *i64, out: *i64, np: i64, nr: i64) -> i64 {
740 let path: *u8 = sys_mmap(CD_PATH_CAP)
741 cd_spine_path(dir, dom, path)
742 let day: i64 = cd_day_of(epoch)
743 let nb: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
744 nb[0] = 0
745 let old: *u8 = sys_read_file(path, nb)
746 if (old as i64) != 0 { if cd_spine_has_day(old, nb[0], day) == 1 { return 0 } }
747 sys_mkdir(dir, CD_DIR_MODE)
748 let fd: i64 = sys_openat_append(path, CD_FILE_MODE)
749 if fd < 0 { return 0 - 1 }
750 let row: *u8 = sys_mmap(CD_ROW_CAP)
751 var p: i64 = 0
752 while p < np {
753 var o: i64 = cd_cat(row, 0, CD_SPINE_TAG)
754 o = cd_catn(row, o, epoch); row[o] = CD_CH_PIPE as u8; o = o + 1
755 o = cd_catn(row, o, day); row[o] = CD_CH_PIPE as u8; o = o + 1
756 o = cd_cat_safe(row, o, names[p] as *u8); row[o] = CD_CH_PIPE as u8; o = o + 1
757 o = cd_catn(row, o, out[p * CD_W + CD_R_C]); row[o] = CD_CH_PIPE as u8; o = o + 1
758 o = cd_catn(row, o, out[p * CD_W + CD_R_D]); row[o] = CD_CH_PIPE as u8; o = o + 1
759 o = cd_catn(row, o, out[p * CD_W + CD_R_BREADTH]); row[o] = CD_CH_PIPE as u8; o = o + 1
760 o = cd_catn(row, o, nr); row[o] = CD_CH_PIPE as u8; o = o + 1
761 o = cd_catn(row, o, np); row[o] = CD_CH_PIPE as u8; o = o + 1
762 o = cd_catn(row, o, out[p * CD_W + CD_R_HELD])
763 row[o] = CD_CH_LF as u8; o = o + 1
764 sys_write(fd, row, o)
765 p = p + 1
766 }
767 sys_close(fd)
768 return np
769}
770
771// ---- SUBSTRATE ROWS (2026-09-17) ----
772// The map's authors plot what customers perceive. A matrix row whose plan rung carries the role substrate (a kernel, a
773// bound, an instrument) is the estate's own scaffolding: counting it as a feature no rival holds inflates Nishi's
774// distinctiveness, and its inherited rival cells inflate theirs (measured 2026-09-17 on the search board, where the
775// engine's kernel rows sat on the customer map). The role is DATA the board already declares (rungrole|<id>|substrate|...),
776// joined to the matrix row through the rung row's symbol field (rung|<id>|<title>|<symbol>|...). A symbol carried as
777// _ABSENT_:<symbol> joins by the bare symbol. A row with no rung, or a rung with no role row, is a feature row.
778const CD_ROLE_SUBSTRATE: *u8 = "substrate"
779const CD_ABSENT_PFX: *u8 = "_ABSENT_:"
780const CD_ABSENT_PFX_LEN: i64 = 9
781const CD_RUNG_TAG: *u8 = "rung|"
782const CD_RUNGROLE_TAG: *u8 = "rungrole|"
783const CD_RUNG_F_ID: i64 = 1
784const CD_RUNG_F_SYM: i64 = 3
785const CD_ROLE_F_ID: i64 = 1
786const CD_ROLE_F_ROLE: i64 = 2
787func cd_sym_bare(s: *u8) -> *u8 {
788 var i: i64 = 0
789 while i < CD_ABSENT_PFX_LEN { if s[i] != CD_ABSENT_PFX[i] { return s } i = i + 1 }
790 return ((s as i64) + CD_ABSENT_PFX_LEN) as *u8
791}
792// bytes [a, a+n) equal the NUL-terminated s exactly
793func cd_bytes_eq_str(a: *u8, n: i64, s: *u8) -> i64 {
794 if cd_slen(s) != n { return 0 }
795 var i: i64 = 0
796 while i < n { if a[i] != s[i] { return 0 } i = i + 1 }
797 return 1
798}
799// 1 when the plan buffer [0,pn) carries a rung row whose symbol field is sym (bare) AND a rungrole row for that rung's
800// id whose role field is substrate; 0 otherwise. Two passes over the lines, both bounded by pn.
801func cd_sym_is_substrate(plan: *u8, pn: i64, sym: *u8) -> i64 {
802 let bare: *u8 = cd_sym_bare(sym)
803 let bn: i64 = cd_slen(bare)
804 if bn == 0 { return 0 }
805 if pn <= 0 { return 0 }
806 let fo: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
807 let fl: *i64 = sys_mmap(CD_I64_BYTES * 2) as *i64
808 var id_at: i64 = 0 - 1
809 var id_len: i64 = 0
810 var p: i64 = 0
811 while p < pn {
812 var e: i64 = p
813 while e < pn { if plan[e] == (10 as u8) { break } e = e + 1 }
814 let line: *u8 = ((plan as i64) + p) as *u8
815 if id_at < 0 { if cd_row_tag_is(line, e - p, CD_RUNG_TAG) == 1 {
816 if cd_field(line, e - p, CD_RUNG_F_SYM, fo, fl) == 1 {
817 if fl[0] == bn { if cd_bytes_eq_str(((line as i64) + fo[0]) as *u8, fl[0], bare) == 1 {
818 if cd_field(line, e - p, CD_RUNG_F_ID, fo, fl) == 1 { id_at = p + fo[0]; id_len = fl[0] }
819 } }
820 }
821 } }
822 p = e + 1
823 }
824 if id_at < 0 { return 0 }
825 if id_len <= 0 { return 0 }
826 let idp: *u8 = ((plan as i64) + id_at) as *u8
827 p = 0
828 while p < pn {
829 var e2: i64 = p
830 while e2 < pn { if plan[e2] == (10 as u8) { break } e2 = e2 + 1 }
831 let line2: *u8 = ((plan as i64) + p) as *u8
832 if cd_row_tag_is(line2, e2 - p, CD_RUNGROLE_TAG) == 1 {
833 if cd_field(line2, e2 - p, CD_ROLE_F_ID, fo, fl) == 1 {
834 if fl[0] == id_len {
835 var same: i64 = 1
836 var k: i64 = 0
837 while k < id_len { if line2[fo[0] + k] != idp[k] { same = 0; k = id_len } else { k = k + 1 } }
838 if same == 1 {
839 if cd_field(line2, e2 - p, CD_ROLE_F_ROLE, fo, fl) == 1 {
840 if cd_bytes_eq_str(((line2 as i64) + fo[0]) as *u8, fl[0], CD_ROLE_SUBSTRATE) == 1 { return 1 }
841 }
842 }
843 }
844 }
845 }
846 p = e2 + 1
847 }
848 return 0
849}
850// mask[r] = 1 mapped, 0 excluded (substrate); returns the excluded count
851func cd_substrate_mask(plan: *u8, pn: i64, syms: *i64, rown: i64, mask: *i64) -> i64 {
852 var ex: i64 = 0
853 var r: i64 = 0
854 while r < rown {
855 if cd_sym_is_substrate(plan, pn, syms[r] as *u8) == 1 { mask[r] = 0; ex = ex + 1 } else { mask[r] = 1 }
856 r = r + 1
857 }
858 return ex
859}