code wiki / (root) / nx_compare_feed_lib.nx

nx_compare_feed_lib.nx source

↩ module page · 547 lines · 28611 B

1// nx_compare_feed_lib.nx -- THE COMPARE FEED (ecosystem EC47, 2026-09-16): every /compare board's daily place on the 2// four-quadrant map and the board rows journaled that day, emitted as ONE RSS 2.0 feed the regenerator publishes at 3// /compare/feed.xml on the same beat that republishes the pages. Operator (goal, 2026-09-15): "where we are and our 4// competitors and where we are moving as time goes on daily like the rss feed with more and more expansive reviews". 5// INPUTS -- all data the estate already writes, nothing typed here: 6// knowledge/compare/regen.list the boards 7// knowledge/status/cdmap/<dom>.spine cd|<epoch>|<day>|<player>|<C>|<D>|<breadth-points>|<rows>|<players>|<held> 8// one row per player per calendar day, written by the FIRST publish of the day 9// knowledge/compare/<dom>.plan log|<epoch>|<rung>|<kind>|<text> rows, the board's own worked ledger 10// OUTPUT: one <item> per board per spine day inside the window (CF_WINDOW_DAYS), board-major and newest day first inside a 11// board; every item carries its own pubDate (the spine row's epoch) so a reader orders them by time. The title carries 12// Nishi's centrality, distinctiveness and quadrant and the move since the previous spine day; the body lists every 13// player's position, quadrant and move, then the board rows journaled on that day -- so the review grows with the board. 14// THE QUADRANT IS THE MAP'S OWN RULE: cd_quadrant over the day's player means (nx_cdmap_lib), never a second cut. 15// Composes nx_rss_lib (the estate's RSS 2.0 builder), nx_timefmt (civil dates) and nx_cdmap_lib (the quadrant rule). 16// Pure over the root it is pointed at (root prefix + the relative paths above), so nx_compare_feed_gate drives it on a 17// fixture root. Every count it decides on is published in st[] (partition: items = distinct in-window (board, day) pairs). 18// A spine or plan larger than its index is announced as OVERFLOW and read as a prefix, never silently. A body that would 19// not fit its scratch is announced as SHORT and the board's item is not emitted (a truncated feed is not a feed). 20// license_tier: ORIGINAL. No hw writes (Rule 26). 21import "nx_syscalls.nx" 22import "nx_rss_lib.nx" 23import "nx_cdmap_lib.nx" 24import "nx_timefmt.nx" 25 26const CF_MAX_ROWS: i64 = 4096 // spine rows indexed per board (16 players x 256 days); beyond = OVERFLOW, prefix read 27const CF_MAX_LOG: i64 = 8192 // plan log rows indexed per board; beyond = OVERFLOW, prefix read 28const CF_MAX_DAYS: i64 = 512 // distinct spine days a board may carry in the index 29const CF_WINDOW_DAYS: i64 = 14 // days of history the feed covers, newest first 30const CF_TEXT_MAX: i64 = 480 // bytes of one board row quoted in an item (cut at a UTF-8 boundary, announced with ...) 31const CF_DAY_S: i64 = 86400 32const CF_PATH_MAX: i64 = 1024 33const CF_LINE_MAX: i64 = 4096 // scratch for a title, a link, a guid or one list line 34const CF_BODY_BASE: i64 = 65536 // body scratch base; grown by the rows a board can quote 35const CF_ROW_QUOTE: i64 = 640 // scratch per quoted row: CF_TEXT_MAX escaped (worst case x6 for '&') is bounded by this x6 below 36const CF_ESC_X: i64 = 6 // worst-case expansion of html escaping ('&' -> '&amp;') 37const CF_OUT_START: i64 = 262144 // the growable output's first capacity; doubled as needed, never a cap 38const CF_OUT_HEAD: i64 = 8192 // headroom kept for the channel open and close 39const CF_ST_SLOTS: i64 = 10 40const CF_ST_DOMAINS: i64 = 0 41const CF_ST_SPINES: i64 = 1 42const CF_ST_ITEMS: i64 = 2 43const CF_ST_ROWS: i64 = 3 44const CF_ST_MALFORMED: i64 = 4 45const CF_ST_OVERFLOW: i64 = 5 46const CF_ST_BYTES: i64 = 6 47const CF_ST_SHORT: i64 = 7 48const CF_ST_NEWEST: i64 = 8 49const CF_ST_WINDOW: i64 = 9 50const CF_ROW_W: i64 = 8 51const CF_R_EPOCH: i64 = 0 52const CF_R_DAY: i64 = 1 53const CF_R_NOFF: i64 = 2 54const CF_R_NLEN: i64 = 3 55const CF_R_C: i64 = 4 56const CF_R_D: i64 = 5 57const CF_R_HELD: i64 = 6 58const CF_LOG_W: i64 = 4 59const CF_L_EPOCH: i64 = 0 60const CF_L_DAY: i64 = 1 61const CF_L_OFF: i64 = 2 62const CF_L_LEN: i64 = 3 63const CF_PIPE: i64 = 124 64const CF_LF: i64 = 10 65const CF_HASHB: i64 = 35 66const CF_DOT: i64 = 46 67const CF_ZERO: i64 = 48 68const CF_NINE: i64 = 57 69const CF_MINUS: i64 = 45 70const CF_UTF8_CONT_LO: i64 = 128 // a UTF-8 continuation byte is 10xxxxxx: 128..191 (tested arithmetically, no bitwise op) 71const CF_UTF8_CONT_HI: i64 = 192 72const CF_FEED_TITLE: *u8 = "Nishi /compare -- daily positions and reviews" as *u8 73const CF_FEED_LINK: *u8 = "https://nishifamily.com/compare/" as *u8 74const CF_FEED_DESC: *u8 = "Every /compare board's place on the four-quadrant centrality-distinctiveness map, its move since the previous day, and the board rows journaled that day. Emitted by nx_compare_regen on the compare beat; positions are measured from the boards, never typed." as *u8 75const CF_GUID_SEP: *u8 = "cd-" as *u8 // written after a CF_HASHB byte: the lexer keeps that byte out of literals 76const CF_SPINE_DIR: *u8 = "knowledge/status/cdmap/" as *u8 77const CF_SPINE_EXT: *u8 = ".spine" as *u8 78const CF_PLAN_DIR: *u8 = "knowledge/compare/" as *u8 79const CF_PLAN_EXT: *u8 = ".plan" as *u8 80const CF_LIST_PATH: *u8 = "knowledge/compare/regen.list" as *u8 81const CF_NISHI: *u8 = "Nishi" as *u8 82const CF_ROW_TAG: *u8 = "cd|" as *u8 83const CF_LOG_TAG: *u8 = "log|" as *u8 84const CF_SPINE_F_EPOCH: i64 = 1 85const CF_SPINE_F_DAY: i64 = 2 86const CF_SPINE_F_NAME: i64 = 3 87const CF_SPINE_F_C: i64 = 4 88const CF_SPINE_F_D: i64 = 5 89const CF_SPINE_F_HELD: i64 = 9 90const CF_LOG_F_EPOCH: i64 = 1 91const CF_LOG_F_RUNG: i64 = 2 92const CF_LOG_F_KIND: i64 = 3 93const CF_LOG_F_TEXT: i64 = 4 94 95func cf_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 96func cf_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; var p: i64 = o; while s[i] != (0 as u8) { d[p] = s[i]; p = p + 1; i = i + 1 } return p } 97func cf_catb(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { d[o + i] = s[i]; i = i + 1 } return o + n } 98func cf_catn(d: *u8, o: i64, v: i64) -> i64 { 99 var m: i64 = v 100 var p: i64 = o 101 if m < 0 { d[p] = CF_MINUS as u8; p = p + 1; m = 0 - m } 102 let t: *u8 = sys_mmap(32) 103 var k: i64 = 0 104 if m == 0 { t[0] = CF_ZERO as u8; k = 1 } 105 while m > 0 { t[k] = (CF_ZERO + (m % 10)) as u8; m = m / 10; k = k + 1 } 106 var i: i64 = 0 107 while i < k { d[p] = t[k - 1 - i]; p = p + 1; i = i + 1 } 108 sys_munmap(t, 32) 109 return p 110} 111// a signed move: an explicit + for a non-negative value, so "+0" reads as a measured stillness 112func cf_catsigned(d: *u8, o: i64, v: i64) -> i64 { 113 var p: i64 = o 114 if v >= 0 { d[p] = 43 as u8; p = p + 1 } 115 return cf_catn(d, p, v) 116} 117// YYYY-MM-DD for a day count since the epoch (Hinnant civil date, nx_timefmt) 118func cf_date(d: *u8, o: i64, day: i64) -> i64 { 119 let y: *i64 = sys_mmap(8) as *i64 120 let m: *i64 = sys_mmap(8) as *i64 121 let dd: *i64 = sys_mmap(8) as *i64 122 civil_from_days(day, y, m, dd) 123 var p: i64 = o 124 p = p + emit_zpad(d, p, y[0], 4) 125 d[p] = CF_MINUS as u8; p = p + 1 126 p = p + emit_zpad(d, p, m[0], 2) 127 d[p] = CF_MINUS as u8; p = p + 1 128 p = p + emit_zpad(d, p, dd[0], 2) 129 sys_munmap(y as *u8, 8); sys_munmap(m as *u8, 8); sys_munmap(dd as *u8, 8) 130 return p 131} 132// digits in buf[off..end) -> value; -1 when there is no digit (a malformed field is refused by name, never read as 0) 133func cf_atoi(buf: *u8, off: i64, end: i64) -> i64 { 134 var i: i64 = off 135 var neg: i64 = 0 136 if i < end { if (buf[i] as i64) == CF_MINUS { neg = 1; i = i + 1 } } 137 var v: i64 = 0 138 var nd: i64 = 0 139 var go: i64 = 1 140 while go == 1 { 141 if i >= end { go = 0 } else { 142 let c: i64 = buf[i] as i64 143 if c >= CF_ZERO { if c <= CF_NINE { v = v * 10 + (c - CF_ZERO); nd = nd + 1; i = i + 1 } else { go = 0 } } else { go = 0 } 144 } 145 } 146 if nd == 0 { return 0 - 1 } 147 if neg == 1 { return 0 - v } 148 return v 149} 150// field k (0-based, '|'-separated) of the line buf[start..end): out2[0]=off out2[1]=len; 1 found / 0 absent 151func cf_field(buf: *u8, start: i64, end: i64, k: i64, out2: *i64) -> i64 { 152 var i: i64 = start 153 var f: i64 = 0 154 var fs: i64 = start 155 while i <= end { 156 var sep: i64 = 0 157 if i == end { sep = 1 } else { if (buf[i] as i64) == CF_PIPE { sep = 1 } } 158 if sep == 1 { 159 if f == k { out2[0] = fs; out2[1] = i - fs; return 1 } 160 f = f + 1 161 fs = i + 1 162 } 163 i = i + 1 164 } 165 return 0 166} 167func cf_starts(buf: *u8, off: i64, end: i64, tag: *u8) -> i64 { 168 var i: i64 = 0 169 while tag[i] != (0 as u8) { if off + i >= end { return 0 } if buf[off + i] != tag[i] { return 0 } i = i + 1 } 170 return 1 171} 172func cf_name_eq(buf: *u8, off: i64, n: i64, s: *u8) -> i64 { 173 var i: i64 = 0 174 while i < n { if s[i] == (0 as u8) { return 0 } if buf[off + i] != s[i] { return 0 } i = i + 1 } 175 if s[n] != (0 as u8) { return 0 } 176 return 1 177} 178func cf_names_eq(a: *u8, ao: i64, an: i64, b: *u8, bo: i64, bn: i64) -> i64 { 179 if an != bn { return 0 } 180 var i: i64 = 0 181 while i < an { if a[ao + i] != b[bo + i] { return 0 } i = i + 1 } 182 return 1 183} 184// the end of the line starting at i: the LF index or n (a flag, never a sentinel written into the cursor) 185func cf_eol(buf: *u8, n: i64, i: i64) -> i64 { 186 var e: i64 = i 187 var hit: i64 = 0 188 while hit == 0 { if e >= n { hit = 1 } else { if (buf[e] as i64) == CF_LF { hit = 1 } else { e = e + 1 } } } 189 return e 190} 191func cf_path(dst: *u8, root: *u8, dir: *u8, dom: *u8, domlen: i64, ext: *u8) -> i64 { 192 var o: i64 = cf_cat(dst, 0, root) 193 o = cf_cat(dst, o, dir) 194 o = cf_catb(dst, o, dom, domlen) 195 o = cf_cat(dst, o, ext) 196 dst[o] = 0 as u8 197 return o 198} 199// index the spine: rows[r*CF_ROW_W + ...]; malformed rows counted and skipped; beyond cap announced as overflow 200func cf_load_spine(buf: *u8, n: i64, rows: *i64, cap: i64, st: *i64) -> i64 { 201 let f2: *i64 = sys_mmap(16) as *i64 202 var nr: i64 = 0 203 var i: i64 = 0 204 while i < n { 205 let e: i64 = cf_eol(buf, n, i) 206 if cf_starts(buf, i, e, CF_ROW_TAG) == 1 { 207 if nr >= cap { st[CF_ST_OVERFLOW] = st[CF_ST_OVERFLOW] + 1; i = n } else { 208 var ok: i64 = 1 209 var epoch: i64 = 0 - 1 210 var day: i64 = 0 - 1 211 var c: i64 = 0 - 1 212 var d: i64 = 0 - 1 213 var held: i64 = 0 214 var noff: i64 = 0 215 var nlen: i64 = 0 216 if cf_field(buf, i, e, CF_SPINE_F_EPOCH, f2) == 1 { epoch = cf_atoi(buf, f2[0], f2[0] + f2[1]) } else { ok = 0 } 217 if cf_field(buf, i, e, CF_SPINE_F_DAY, f2) == 1 { day = cf_atoi(buf, f2[0], f2[0] + f2[1]) } else { ok = 0 } 218 if cf_field(buf, i, e, CF_SPINE_F_NAME, f2) == 1 { noff = f2[0]; nlen = f2[1] } else { ok = 0 } 219 if cf_field(buf, i, e, CF_SPINE_F_C, f2) == 1 { c = cf_atoi(buf, f2[0], f2[0] + f2[1]) } else { ok = 0 } 220 if cf_field(buf, i, e, CF_SPINE_F_D, f2) == 1 { d = cf_atoi(buf, f2[0], f2[0] + f2[1]) } else { ok = 0 } 221 if cf_field(buf, i, e, CF_SPINE_F_HELD, f2) == 1 { held = cf_atoi(buf, f2[0], f2[0] + f2[1]); if held < 0 { held = 0 } } 222 if epoch < 0 { ok = 0 } 223 if day < 0 { ok = 0 } 224 if c < 0 { ok = 0 } 225 if d < 0 { ok = 0 } 226 if nlen <= 0 { ok = 0 } 227 if ok == 1 { 228 let b: i64 = nr * CF_ROW_W 229 rows[b + CF_R_EPOCH] = epoch; rows[b + CF_R_DAY] = day; rows[b + CF_R_NOFF] = noff; rows[b + CF_R_NLEN] = nlen 230 rows[b + CF_R_C] = c; rows[b + CF_R_D] = d; rows[b + CF_R_HELD] = held; rows[b + 7] = 0 231 nr = nr + 1 232 } else { st[CF_ST_MALFORMED] = st[CF_ST_MALFORMED] + 1 } 233 } 234 } 235 if i < n { i = e + 1 } 236 } 237 sys_munmap(f2 as *u8, 16) 238 return nr 239} 240// index the plan's log rows: idx[k*CF_LOG_W + ...] = epoch, day, line offset, line length 241func cf_load_log(buf: *u8, n: i64, idx: *i64, cap: i64, st: *i64) -> i64 { 242 let f2: *i64 = sys_mmap(16) as *i64 243 var nl: i64 = 0 244 var i: i64 = 0 245 while i < n { 246 let e: i64 = cf_eol(buf, n, i) 247 if cf_starts(buf, i, e, CF_LOG_TAG) == 1 { 248 if nl >= cap { st[CF_ST_OVERFLOW] = st[CF_ST_OVERFLOW] + 1; i = n } else { 249 var epoch: i64 = 0 - 1 250 if cf_field(buf, i, e, CF_LOG_F_EPOCH, f2) == 1 { epoch = cf_atoi(buf, f2[0], f2[0] + f2[1]) } 251 if epoch >= 0 { 252 let b: i64 = nl * CF_LOG_W 253 idx[b + CF_L_EPOCH] = epoch; idx[b + CF_L_DAY] = epoch / CF_DAY_S; idx[b + CF_L_OFF] = i; idx[b + CF_L_LEN] = e - i 254 nl = nl + 1 255 } 256 } 257 } 258 if i < n { i = e + 1 } 259 } 260 sys_munmap(f2 as *u8, 16) 261 return nl 262} 263// the row of player (noff,nlen of `buf`) on `day`, or -1 264func cf_row_of(buf: *u8, rows: *i64, nr: i64, day: i64, noff: i64, nlen: i64) -> i64 { 265 var r: i64 = 0 266 while r < nr { 267 let b: i64 = r * CF_ROW_W 268 if rows[b + CF_R_DAY] == day { if cf_names_eq(buf, noff, nlen, buf, rows[b + CF_R_NOFF], rows[b + CF_R_NLEN]) == 1 { return r } } 269 r = r + 1 270 } 271 return 0 - 1 272} 273// escape-quote a board row's text into the body (cut at CF_TEXT_MAX on a UTF-8 boundary, announced with ...) 274func cf_quote_text(body: *u8, bcap: i64, bo: i64, buf: *u8, off: i64, len: i64) -> i64 { 275 var k: i64 = len 276 var cut: i64 = 0 277 if k > CF_TEXT_MAX { k = CF_TEXT_MAX; cut = 1 } 278 if cut == 1 { 279 var back: i64 = 1 280 while back == 1 { 281 if k <= 0 { back = 0 } else { 282 let b: i64 = buf[off + k] as i64 283 if b >= CF_UTF8_CONT_LO { if b < CF_UTF8_CONT_HI { k = k - 1 } else { back = 0 } } else { back = 0 } 284 } 285 } 286 } 287 var o: i64 = rss_put_escaped(body, bcap, bo, (buf as i64 + off) as *u8, k) 288 if o < 0 { return o } 289 if cut == 1 { o = cf_cat(body, o, "..." as *u8) } 290 return o 291} 292// one board -> its items appended to the growable output; returns the new output length, or -1 on a short scratch 293func cf_board(root: *u8, dom: *u8, domlen: i64, today: i64, outp: *i64, ocap: *i64, olen: i64, st: *i64) -> i64 { 294 let path: *u8 = sys_mmap(CF_PATH_MAX) 295 let szp: *i64 = sys_mmap(16) as *i64 296 cf_path(path, root, CF_SPINE_DIR, dom, domlen, CF_SPINE_EXT) 297 szp[0] = 0 298 let sbuf: *u8 = sys_read_file(path, szp) 299 let sn: i64 = szp[0] 300 if (sbuf as i64) == 0 { sys_munmap(path, CF_PATH_MAX); sys_munmap(szp as *u8, 16); return olen } 301 if sn <= 0 { sys_munmap(path, CF_PATH_MAX); sys_munmap(szp as *u8, 16); return olen } 302 st[CF_ST_SPINES] = st[CF_ST_SPINES] + 1 303 let rows: *i64 = sys_mmap(CF_MAX_ROWS * CF_ROW_W * 8) as *i64 304 let nr: i64 = cf_load_spine(sbuf, sn, rows, CF_MAX_ROWS, st) 305 cf_path(path, root, CF_PLAN_DIR, dom, domlen, CF_PLAN_EXT) 306 szp[0] = 0 307 let pbuf: *u8 = sys_read_file(path, szp) 308 var pn: i64 = szp[0] 309 if (pbuf as i64) == 0 { pn = 0 } 310 let idx: *i64 = sys_mmap(CF_MAX_LOG * CF_LOG_W * 8) as *i64 311 var nl: i64 = 0 312 if pn > 0 { nl = cf_load_log(pbuf, pn, idx, CF_MAX_LOG, st) } 313 // the board's distinct spine days in the order written (chronological by construction of the writer) 314 let days: *i64 = sys_mmap(CF_MAX_DAYS * 8) as *i64 315 var nd: i64 = 0 316 var r: i64 = 0 317 while r < nr { 318 let dy: i64 = rows[r * CF_ROW_W + CF_R_DAY] 319 var seen: i64 = 0 320 var q: i64 = 0 321 while q < nd { if days[q] == dy { seen = 1 } q = q + 1 } 322 if seen == 0 { if nd < CF_MAX_DAYS { days[nd] = dy; nd = nd + 1 } else { st[CF_ST_OVERFLOW] = st[CF_ST_OVERFLOW] + 1 } } 323 r = r + 1 324 } 325 // scratch sized from the inputs: a body can quote every log row of the board once, escaped 326 let bcap: i64 = CF_BODY_BASE + nl * CF_ROW_QUOTE * CF_ESC_X + nr * 256 327 let body: *u8 = sys_mmap(bcap) 328 let title: *u8 = sys_mmap(CF_LINE_MAX) 329 let link: *u8 = sys_mmap(CF_LINE_MAX) 330 let guid: *u8 = sys_mmap(CF_LINE_MAX) 331 var ll: i64 = cf_cat(link, 0, CF_FEED_LINK) 332 ll = cf_catb(link, ll, dom, domlen) 333 link[ll] = 47 as u8; ll = ll + 1 334 var cur: i64 = olen 335 var di: i64 = nd - 1 336 var short: i64 = 0 337 while di >= 0 { 338 let dy: i64 = days[di] 339 if dy > today - CF_WINDOW_DAYS { if dy <= today { 340 var prev: i64 = 0 - 1 341 if di > 0 { prev = days[di - 1] } 342 // the day's players: means for the quadrant cut, Nishi first 343 var np: i64 = 0 344 var csum: i64 = 0 345 var dsum: i64 = 0 346 var epoch: i64 = 0 347 var nishi: i64 = 0 - 1 348 r = 0 349 while r < nr { 350 let b: i64 = r * CF_ROW_W 351 if rows[b + CF_R_DAY] == dy { 352 np = np + 1; csum = csum + rows[b + CF_R_C]; dsum = dsum + rows[b + CF_R_D] 353 if epoch == 0 { epoch = rows[b + CF_R_EPOCH] } 354 if nishi < 0 { if cf_name_eq(sbuf, rows[b + CF_R_NOFF], rows[b + CF_R_NLEN], CF_NISHI) == 1 { nishi = r } } 355 } 356 r = r + 1 357 } 358 var cmean: i64 = 0 359 var dmean: i64 = 0 360 if np > 0 { cmean = csum / np; dmean = dsum / np } 361 // TITLE 362 var to: i64 = cf_catb(title, 0, dom, domlen) 363 to = cf_cat(title, to, " " as *u8) 364 to = cf_date(title, to, dy) 365 to = cf_cat(title, to, ": " as *u8) 366 if nishi < 0 { to = cf_cat(title, to, "Nishi absent from the map" as *u8) } else { 367 let nb: i64 = nishi * CF_ROW_W 368 to = cf_cat(title, to, "Nishi C" as *u8); to = cf_catn(title, to, rows[nb + CF_R_C]) 369 to = cf_cat(title, to, " D" as *u8); to = cf_catn(title, to, rows[nb + CF_R_D]) 370 to = cf_cat(title, to, " " as *u8) 371 to = cf_cat(title, to, cd_quadrant_name(cd_quadrant(rows[nb + CF_R_C], rows[nb + CF_R_D], cmean, dmean, rows[nb + CF_R_HELD]))) 372 if prev < 0 { to = cf_cat(title, to, ", first day on the map" as *u8) } else { 373 let pr: i64 = cf_row_of(sbuf, rows, nr, prev, rows[nb + CF_R_NOFF], rows[nb + CF_R_NLEN]) 374 if pr < 0 { to = cf_cat(title, to, ", new on the map" as *u8) } else { 375 let pb: i64 = pr * CF_ROW_W 376 to = cf_cat(title, to, ", moved " as *u8) 377 to = cf_catsigned(title, to, rows[nb + CF_R_C] - rows[pb + CF_R_C]) 378 to = cf_cat(title, to, "/" as *u8) 379 to = cf_catsigned(title, to, rows[nb + CF_R_D] - rows[pb + CF_R_D]) 380 to = cf_cat(title, to, " since " as *u8) 381 to = cf_date(title, to, prev) 382 } 383 } 384 } 385 // BODY (HTML inside CDATA; every quoted byte escaped by the one escaper the builder uses) 386 var bo: i64 = cf_cat(body, 0, "<p>Four-quadrant map (centrality vs distinctiveness, permil; Dawar and Bagga 2015): quadrants cut at the day's player means, C " as *u8) 387 bo = cf_catn(body, bo, cmean); bo = cf_cat(body, bo, " D " as *u8); bo = cf_catn(body, bo, dmean) 388 bo = cf_cat(body, bo, ". Players on the map: " as *u8); bo = cf_catn(body, bo, np); bo = cf_cat(body, bo, ".</p>\n<ul>\n" as *u8) 389 var pass: i64 = 0 390 while pass < 2 { 391 r = 0 392 while r < nr { 393 let b: i64 = r * CF_ROW_W 394 if rows[b + CF_R_DAY] == dy { 395 var isn: i64 = 0 396 if r == nishi { isn = 1 } 397 var take: i64 = 0 398 if pass == 0 { if isn == 1 { take = 1 } } else { if isn == 0 { take = 1 } } 399 if take == 1 { if bo >= 0 { 400 bo = cf_cat(body, bo, "<li>" as *u8) 401 if isn == 1 { bo = cf_cat(body, bo, "<b>" as *u8) } 402 bo = rss_put_escaped(body, bcap, bo, (sbuf as i64 + rows[b + CF_R_NOFF]) as *u8, rows[b + CF_R_NLEN]) 403 if bo >= 0 { 404 if isn == 1 { bo = cf_cat(body, bo, "</b>" as *u8) } 405 bo = cf_cat(body, bo, ": C " as *u8); bo = cf_catn(body, bo, rows[b + CF_R_C]) 406 bo = cf_cat(body, bo, ", D " as *u8); bo = cf_catn(body, bo, rows[b + CF_R_D]) 407 bo = cf_cat(body, bo, ", " as *u8) 408 bo = cf_cat(body, bo, cd_quadrant_name(cd_quadrant(rows[b + CF_R_C], rows[b + CF_R_D], cmean, dmean, rows[b + CF_R_HELD]))) 409 if prev >= 0 { 410 let pr2: i64 = cf_row_of(sbuf, rows, nr, prev, rows[b + CF_R_NOFF], rows[b + CF_R_NLEN]) 411 if pr2 < 0 { bo = cf_cat(body, bo, ", new on the map" as *u8) } else { 412 let pb2: i64 = pr2 * CF_ROW_W 413 bo = cf_cat(body, bo, ", moved " as *u8) 414 bo = cf_catsigned(body, bo, rows[b + CF_R_C] - rows[pb2 + CF_R_C]) 415 bo = cf_cat(body, bo, "/" as *u8) 416 bo = cf_catsigned(body, bo, rows[b + CF_R_D] - rows[pb2 + CF_R_D]) 417 } 418 } 419 bo = cf_cat(body, bo, "</li>\n" as *u8) 420 } 421 } } 422 } 423 r = r + 1 424 } 425 pass = pass + 1 426 } 427 if bo >= 0 { 428 bo = cf_cat(body, bo, "</ul>\n" as *u8) 429 // the board rows journaled that day, in ledger order 430 var nrows_day: i64 = 0 431 var k: i64 = 0 432 while k < nl { if idx[k * CF_LOG_W + CF_L_DAY] == dy { nrows_day = nrows_day + 1 } k = k + 1 } 433 bo = cf_cat(body, bo, "<p>Board rows journaled on " as *u8); bo = cf_date(body, bo, dy) 434 bo = cf_cat(body, bo, ": " as *u8); bo = cf_catn(body, bo, nrows_day); bo = cf_cat(body, bo, ".</p>\n" as *u8) 435 if nrows_day > 0 { 436 bo = cf_cat(body, bo, "<ul>\n" as *u8) 437 k = 0 438 let f2: *i64 = sys_mmap(16) as *i64 439 while k < nl { 440 let lb: i64 = k * CF_LOG_W 441 if idx[lb + CF_L_DAY] == dy { if bo >= 0 { 442 let lo: i64 = idx[lb + CF_L_OFF] 443 let le: i64 = lo + idx[lb + CF_L_LEN] 444 bo = cf_cat(body, bo, "<li>[" as *u8) 445 if cf_field(pbuf, lo, le, CF_LOG_F_KIND, f2) == 1 { bo = rss_put_escaped(body, bcap, bo, (pbuf as i64 + f2[0]) as *u8, f2[1]) } 446 if bo >= 0 { bo = cf_cat(body, bo, "] " as *u8) } 447 if bo >= 0 { if cf_field(pbuf, lo, le, CF_LOG_F_RUNG, f2) == 1 { bo = rss_put_escaped(body, bcap, bo, (pbuf as i64 + f2[0]) as *u8, f2[1]) } } 448 if bo >= 0 { bo = cf_cat(body, bo, ": " as *u8) } 449 if bo >= 0 { if cf_field(pbuf, lo, le, CF_LOG_F_TEXT, f2) == 1 { bo = cf_quote_text(body, bcap, bo, pbuf, f2[0], le - f2[0]) } } 450 if bo >= 0 { bo = cf_cat(body, bo, "</li>\n" as *u8); st[CF_ST_ROWS] = st[CF_ST_ROWS] + 1 } 451 } } 452 k = k + 1 453 } 454 sys_munmap(f2 as *u8, 16) 455 if bo >= 0 { bo = cf_cat(body, bo, "</ul>\n" as *u8) } 456 } 457 } 458 if bo >= 0 { 459 bo = cf_cat(body, bo, "<p>Board: <a href=\"" as *u8); bo = cf_catb(body, bo, link, ll) 460 bo = cf_cat(body, bo, "\">" as *u8); bo = cf_catb(body, bo, link, ll) 461 bo = cf_cat(body, bo, "</a> (api.json, cdmap.svg and cdmap.png beside it).</p>\n" as *u8) 462 } 463 if bo < 0 { short = 1 } else { 464 var go: i64 = cf_catb(guid, 0, link, ll) 465 guid[go] = CF_HASHB as u8; go = go + 1; go = cf_cat(guid, go, CF_GUID_SEP); go = cf_catn(guid, go, dy) 466 // grow the output so the item cannot be short: the item is bounded by its parts plus the builder's tags 467 let need: i64 = cur + to + ll + bo + go + 512 468 if need > ocap[0] { 469 var ncap: i64 = ocap[0] * 2 470 if ncap < need + CF_OUT_HEAD { ncap = need + CF_OUT_HEAD } 471 let nbuf: *u8 = sys_mmap(ncap) 472 let old: *u8 = outp[0] as *u8 473 cf_catb(nbuf, 0, old, cur) 474 sys_munmap(old, ocap[0]) 475 outp[0] = nbuf as i64 476 ocap[0] = ncap 477 } 478 let out: *u8 = outp[0] as *u8 479 let ncur: i64 = rss_item(out, ocap[0], cur, title, to, link, ll, body, bo, guid, go, epoch) 480 if ncur < 0 { short = 1 } else { 481 cur = ncur 482 st[CF_ST_ITEMS] = st[CF_ST_ITEMS] + 1 483 if epoch > st[CF_ST_NEWEST] { st[CF_ST_NEWEST] = epoch } 484 } 485 } 486 } } 487 di = di - 1 488 } 489 if short == 1 { st[CF_ST_SHORT] = st[CF_ST_SHORT] + 1 } 490 sys_munmap(body, bcap); sys_munmap(title, CF_LINE_MAX); sys_munmap(link, CF_LINE_MAX); sys_munmap(guid, CF_LINE_MAX) 491 sys_munmap(days as *u8, CF_MAX_DAYS * 8); sys_munmap(idx as *u8, CF_MAX_LOG * CF_LOG_W * 8); sys_munmap(rows as *u8, CF_MAX_ROWS * CF_ROW_W * 8) 492 if pn > 0 { sys_munmap(pbuf, pn) } 493 sys_munmap(sbuf, sn) 494 sys_munmap(path, CF_PATH_MAX); sys_munmap(szp as *u8, 16) 495 return cur 496} 497// THE FEED: root prefix ("" from buildroot), the regen list, today's day count -> outp[0] = the feed bytes (mmap'd, 498// caller-owned), returns its length; 0 with st[CF_ST_SHORT] set means nothing publishable. Every input is read once. 499func cf_build(root: *u8, listpath: *u8, today: i64, st: *i64, outp: *i64) -> i64 { 500 var s: i64 = 0 501 while s < CF_ST_SLOTS { st[s] = 0; s = s + 1 } 502 st[CF_ST_WINDOW] = CF_WINDOW_DAYS 503 let ocap: *i64 = sys_mmap(8) as *i64 504 ocap[0] = CF_OUT_START 505 outp[0] = sys_mmap(CF_OUT_START) as i64 506 let path: *u8 = sys_mmap(CF_PATH_MAX) 507 var po: i64 = cf_cat(path, 0, root); po = cf_cat(path, po, listpath); path[po] = 0 as u8 508 let szp: *i64 = sys_mmap(16) as *i64 509 szp[0] = 0 510 let lbuf: *u8 = sys_read_file(path, szp) 511 var ln: i64 = szp[0] 512 if (lbuf as i64) == 0 { ln = 0 } 513 // the channel: its pubDate is the newest item's, which is not known until the boards are read -- so the boards 514 // are appended first into a body-only buffer and the channel is assembled around them at the end 515 var cur: i64 = 0 516 var i: i64 = 0 517 while i < ln { 518 let e: i64 = cf_eol(lbuf, ln, i) 519 var dl: i64 = e - i 520 // trim a trailing CR (a list edited on Windows must not name a board with a carriage return in it) 521 if dl > 0 { if (lbuf[i + dl - 1] as i64) == 13 { dl = dl - 1 } } 522 if dl > 0 { if (lbuf[i] as i64) != CF_HASHB { 523 st[CF_ST_DOMAINS] = st[CF_ST_DOMAINS] + 1 524 cur = cf_board(root, (lbuf as i64 + i) as *u8, dl, today, outp, ocap, cur, st) 525 } } 526 i = e + 1 527 } 528 // assemble: channel open + items + close 529 let head: *u8 = sys_mmap(CF_OUT_HEAD) 530 var pub: i64 = st[CF_ST_NEWEST] 531 if pub <= 0 { pub = today * CF_DAY_S } 532 let ho: i64 = rss_begin(head, CF_OUT_HEAD, 0, CF_FEED_TITLE, cf_slen(CF_FEED_TITLE), CF_FEED_LINK, cf_slen(CF_FEED_LINK), CF_FEED_DESC, cf_slen(CF_FEED_DESC), pub) 533 if ho < 0 { st[CF_ST_SHORT] = st[CF_ST_SHORT] + 1; return 0 } 534 let total: i64 = ho + cur + 64 535 let fin: *u8 = sys_mmap(total) 536 var fo: i64 = cf_catb(fin, 0, head, ho) 537 fo = cf_catb(fin, fo, outp[0] as *u8, cur) 538 fo = rss_end(fin, total, fo) 539 if fo < 0 { st[CF_ST_SHORT] = st[CF_ST_SHORT] + 1; return 0 } 540 sys_munmap(outp[0] as *u8, ocap[0]) 541 outp[0] = fin as i64 542 st[CF_ST_BYTES] = fo 543 sys_munmap(head, CF_OUT_HEAD) 544 if ln > 0 { sys_munmap(lbuf, ln) } 545 sys_munmap(path, CF_PATH_MAX); sys_munmap(szp as *u8, 16); sys_munmap(ocap as *u8, 8) 546 return fo 547}