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 ('&' -> '&')
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}