code wiki / (root) / nx_cdmap_lib.nx

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}