code wiki / _hdl_build / nx_coach_serve.nx

nx_coach_serve.nx source

↩ module page · 188 lines · 12939 B

1// nx_coach_serve.nx -- SKILL-COACH rung R5 (publish scaffold): the /coach HTTP request HANDLER wrapping the 2// unified engine. Browser (phone) getUserMedia -> POST the detected pitch/notes -> coach grade -> feedback JSON. 3// Gateable per feedback-sovereign-gates-not-bash-harnesses: cs_handle(method,path,body)->response bytes, asserted 4// by CALLING it (no socket/curl). The DAEMON wrapper (accept-fork loop + nx_pitch on the POSTed PCM + OPAQUE auth + 5// /coach sites-route) is the thin OPERATOR-COORDINATED deploy layer (the /gen pattern). HTML emitted by THIS organ 6// (nishi-ecosystem: no hand-JS except the emitted last-mile). Composes nx_note + the R2 rubric store. license_tier: ORIGINAL 7import "nx_note.nx" 8import "nx_seg_store.nx" 9import "nx_syscalls.nx" 10const CS_MAGIC_8192: i64 = 8192 11const CS_MAGIC_1024: i64 = 1024 12const CS_MAGIC_2048: i64 = 2048 13const CS_MAGIC_16384: i64 = 16384 14 15const CS_RUBRIC: *u8 = "knowledge/store/coach-rubric-" as *u8 16 17func cs_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 18func cs_cat(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[o + i] = s[i]; i = i + 1 } return o + i } 19func cs_catn(dst: *u8, o: i64, v: i64) -> i64 { 20 var m: i64 = v; var oo: i64 = o 21 if m < 0 { dst[oo] = 45 as u8; oo = oo + 1; m = 0 - m } 22 let t: *u8 = sys_mmap(24); var k: i64 = 0 23 if m == 0 { t[0] = 48 as u8; k = 1 } 24 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 25 var i: i64 = k - 1 26 while i >= 0 { dst[oo] = t[i]; oo = oo + 1; i = i - 1 } 27 return oo 28} 29func cs_catb(dst: *u8, o: i64, src: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { dst[o + i] = src[i]; i = i + 1 } return o + i } 30func cs_atoi(s: *u8, n: i64) -> i64 { var v: i64 = 0; var i: i64 = 0; while i < n { let c: i64 = s[i]; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 } return v } 31func cs_has(b: *u8, n: i64, needle: *u8) -> i64 { 32 let ln: i64 = cs_slen(needle); if ln == 0 { return 1 } 33 var p: i64 = 0 34 while p + ln <= n { var j: i64 = 0; var hit: i64 = 1; while j < ln { if b[p + j] != needle[j] { hit = 0; j = ln } else { j = j + 1 } } if hit == 1 { return 1 } p = p + 1 } 35 return 0 36} 37func cs_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while 1 == 1 { if a[i] != b[i] { return 0 } if a[i] == (0 as u8) { return 1 } i = i + 1 } return 1 } 38// rubric drill bytes for a key (composes the R2 store) 39func cs_drill(h: *i64, key: *u8, outlen: *i64) -> *u8 { 40 let pp: *i64 = sys_mmap(16) as *i64; let ll: *i64 = sys_mmap(16) as *i64 41 if ss_hget(h, key, pp, ll) != 1 { outlen[0] = 0; return 0 as *u8 } 42 outlen[0] = ll[0]; return pp[0] as *u8 43} 44func cs_sing_vname(v: i64) -> *u8 { if v == 0 { return "in_tune" as *u8 } if v == 1 { return "sharp" as *u8 } if v == 2 { return "flat" as *u8 } if v == 3 { return "too_high" as *u8 } return "too_low" as *u8 } 45// build the feedback JSON for a singing attempt into out; returns length 46func cs_grade_sing(h: *i64, f0: i64, target_midi: i64, tbl: *i64, out: *u8) -> i64 { 47 let no: *i64 = sys_mmap(8 * 4) as *i64; nx_note_of(f0, tbl, no) 48 let dm: i64 = no[0]; let cents: i64 = no[1] 49 let tl: *i64 = sys_mmap(16) as *i64 50 let ts: *u8 = cs_drill(h, "sing:tol_cents" as *u8, tl) 51 var tol: i64 = 20; if (ts as i64) != 0 { tol = cs_atoi(ts, tl[0]) } 52 var v: i64 = 4 53 if dm == target_midi { if cents > tol { v = 1 } else { if cents < (0 - tol) { v = 2 } else { v = 0 } } } else { if dm > target_midi { v = 3 } else { v = 4 } } 54 var pts: i64 = 10; if v == 0 { pts = 100 } else { if v <= 2 { var a: i64 = cents; if a < 0 { a = 0 - a } pts = 100 - a; if pts < 0 { pts = 0 } } } 55 let key: *u8 = sys_mmap(64); var ko: i64 = cs_cat(key, 0, "sing:" as *u8); ko = cs_cat(key, ko, cs_sing_vname(v)); key[ko] = 0 as u8 56 let dl: *i64 = sys_mmap(16) as *i64; let drill: *u8 = cs_drill(h, key, dl) 57 var o: i64 = 0 58 o = cs_cat(out, o, "{\"discipline\":\"sing\",\"verdict\":\"" as *u8); o = cs_cat(out, o, cs_sing_vname(v)) 59 o = cs_cat(out, o, "\",\"midi\":" as *u8); o = cs_catn(out, o, dm) 60 o = cs_cat(out, o, ",\"cents\":" as *u8); o = cs_catn(out, o, cents) 61 o = cs_cat(out, o, ",\"points\":" as *u8); o = cs_catn(out, o, pts) 62 o = cs_cat(out, o, ",\"drill\":\"" as *u8); if (drill as i64) != 0 { o = cs_catb(out, o, drill, dl[0]) } 63 o = cs_cat(out, o, "\"}" as *u8); out[o] = 0 as u8 64 return o 65} 66// the emitted coach page (minimal last-mile: getUserMedia + fetch to the grade API + show feedback) 67func cs_shell(out: *u8) -> i64 { 68 var o: i64 = 0 69 o = cs_cat(out, o, "<!doctype html><html><head><meta charset=utf-8><title>Nishi Coach</title></head><body>" as *u8) 70 o = cs_cat(out, o, "<h1>Self-Teaching Coach</h1><p>Sing a note, play a chord, or practice a tough conversation -- we grade it and tell you what to practice.</p>" as *u8) 71 o = cs_cat(out, o, "<button id=mic>Start mic</button><div id=fb></div>" as *u8) 72 o = cs_cat(out, o, "<script>document.getElementById('mic').onclick=async()=>{const s=await navigator.mediaDevices.getUserMedia({audio:true});" as *u8) 73 o = cs_cat(out, o, "/* capture PCM, POST to /coach/api/grade, render JSON drill+xp */};</script></body></html>" as *u8) 74 out[o] = 0 as u8; return o 75} 76// frame an HTTP response: status + content-type + content-length + body 77func cs_resp(status: *u8, ctype: *u8, body: *u8, blen: i64, out: *u8) -> i64 { 78 var o: i64 = 0 79 o = cs_cat(out, o, "HTTP/1.1 " as *u8); o = cs_cat(out, o, status) 80 o = cs_cat(out, o, "\r\nContent-Type: " as *u8); o = cs_cat(out, o, ctype) 81 o = cs_cat(out, o, "\r\nContent-Length: " as *u8); o = cs_catn(out, o, blen) 82 o = cs_cat(out, o, "\r\n\r\n" as *u8); o = cs_catb(out, o, body, blen) 83 return o 84} 85const CS_CURRIC: *u8 = "knowledge/store/coach-curriculum-" as *u8 86// build "<skill>:<suffix>" or "<skill>:<suffix>:<idx>" 87func cs_ckey(skill: *u8, suffix: *u8, idx: i64, hasidx: i64, out: *u8) -> i64 { 88 var o: i64 = cs_cat(out, 0, skill); o = cs_cat(out, o, ":" as *u8); o = cs_cat(out, o, suffix) 89 if hasidx == 1 { o = cs_cat(out, o, ":" as *u8); o = cs_catn(out, o, idx) } 90 out[o] = 0 as u8; return o 91} 92// CURRICULUM-engine endpoint: grade a self-report mask vs the framework checklist -> feedback JSON (the 2nd pillar) 93func cs_grade_curriculum(skill: *u8, response_mask: i64, out: *u8) -> i64 { 94 let h: *i64 = ss_open(CS_CURRIC) 95 var o: i64 = cs_cat(out, 0, "{\"discipline\":\"" as *u8); o = cs_cat(out, o, skill); o = cs_cat(out, o, "\",\"missing\":[" as *u8) 96 var nmiss: i64 = 0 97 if (h as i64) != 0 { 98 let key: *u8 = sys_mmap(96); let ol: *i64 = sys_mmap(16) as *i64 99 cs_ckey(skill, "n" as *u8, 0, 0, key); let ns: *u8 = cs_drill(h, key, ol) 100 var n: i64 = 0; if (ns as i64) != 0 { n = cs_atoi(ns, ol[0]) } 101 let checklist: i64 = (1 << n) - 1 102 let missing: i64 = checklist & (0xffffff ^ response_mask) 103 var first: i64 = 1; var i: i64 = 0 104 while i < n { 105 if (missing & (1 << i)) != 0 { 106 nmiss = nmiss + 1 107 if first == 0 { o = cs_cat(out, o, "," as *u8) } first = 0 108 cs_ckey(skill, "elem" as *u8, i, 1, key); let em: *u8 = cs_drill(h, key, ol) 109 o = cs_cat(out, o, "{\"element\":\"" as *u8); if (em as i64) != 0 { o = cs_catb(out, o, em, ol[0]) } 110 o = cs_cat(out, o, "\",\"drill\":\"" as *u8) 111 cs_ckey(skill, "drill" as *u8, i, 1, key); let dr: *u8 = cs_drill(h, key, ol) 112 if (dr as i64) != 0 { o = cs_catb(out, o, dr, ol[0]) } 113 o = cs_cat(out, o, "\"}" as *u8) 114 } 115 i = i + 1 116 } 117 } 118 o = cs_cat(out, o, "],\"complete\":" as *u8) 119 if nmiss == 0 { o = cs_cat(out, o, "true}" as *u8) } else { o = cs_cat(out, o, "false}" as *u8) } 120 out[o] = 0 as u8; return o 121} 122// THE HANDLER: method + path + body -> response bytes (len returned). rh = rubric handle, tbl = note table. 123func cs_handle(method: *u8, path: *u8, body: *u8, blen: i64, rh: *i64, tbl: *i64, out: *u8) -> i64 { 124 if cs_streq(method, "GET" as *u8) == 1 { if cs_streq(path, "/" as *u8) == 1 { 125 let sh: *u8 = sys_mmap(CS_MAGIC_8192); let sl: i64 = cs_shell(sh) 126 return cs_resp("200 OK" as *u8, "text/html" as *u8, sh, sl, out) 127 } } 128 if cs_streq(method, "POST" as *u8) == 1 { if cs_streq(path, "/coach/api/grade" as *u8) == 1 { 129 // body = "sing <f0> <target>" (the daemon decodes POSTed PCM -> f0 via nx_pitch upstream) 130 if cs_has(body, blen, "sing" as *u8) == 1 { 131 // parse "sing <f0> <target>": find the two spaces, atoi the two numbers between/after them 132 var s1: i64 = 0 - 1; var k: i64 = 0 133 while k < blen { if body[k] == (32 as u8) { if s1 < 0 { s1 = k } } k = k + 1 } 134 var s2: i64 = 0 - 1; k = s1 + 1 135 while k < blen { if body[k] == (32 as u8) { if s2 < 0 { s2 = k } } k = k + 1 } 136 var f0: i64 = 0; var tgt: i64 = 69 137 if s1 > 0 { if s2 > s1 { 138 f0 = cs_atoi((body as i64 + s1 + 1) as *u8, s2 - s1 - 1) 139 tgt = cs_atoi((body as i64 + s2 + 1) as *u8, blen - s2 - 1) 140 } } 141 let js: *u8 = sys_mmap(CS_MAGIC_1024); let jl: i64 = cs_grade_sing(rh, f0, tgt, tbl, js) 142 return cs_resp("200 OK" as *u8, "application/json" as *u8, js, jl, out) 143 } 144 return cs_resp("400 Bad Request" as *u8, "text/plain" as *u8, "need discipline" as *u8, 15, out) 145 } } 146 if cs_streq(method, "POST" as *u8) == 1 { if cs_streq(path, "/coach/api/curriculum" as *u8) == 1 { 147 // body = "<skill> <response_mask>" e.g. "confront 9" (the daemon maps the roleplay self-report -> a mask) 148 var sp: i64 = 0 - 1; var k: i64 = 0 149 while k < blen { if body[k] == (32 as u8) { if sp < 0 { sp = k } } k = k + 1 } 150 if sp > 0 { 151 let skill: *u8 = sys_mmap(64); var t: i64 = 0 152 while t < sp { skill[t] = body[t]; t = t + 1 } skill[sp] = 0 as u8 153 let mask: i64 = cs_atoi((body as i64 + sp + 1) as *u8, blen - sp - 1) 154 let js: *u8 = sys_mmap(CS_MAGIC_2048); let jl: i64 = cs_grade_curriculum(skill, mask, js) 155 return cs_resp("200 OK" as *u8, "application/json" as *u8, js, jl, out) 156 } 157 return cs_resp("400 Bad Request" as *u8, "text/plain" as *u8, "need skill mask" as *u8, 15, out) 158 } } 159 return cs_resp("404 Not Found" as *u8, "text/plain" as *u8, "not found" as *u8, 9, out) 160} 161func main() -> i64 { 162 sys_write(1, "COACH SERVE (R5 scaffold): /coach request handler over the unified engine\n" as *u8, 73) 163 let rh: *i64 = ss_open(CS_RUBRIC) 164 if (rh as i64) == 0 { sys_write(1, " rubric store missing -- run nx_coach_rubric first\n" as *u8, 51); return 1 } 165 let tbl: *i64 = sys_mmap(8 * 128) as *i64; nx_note_table(tbl) 166 let out: *u8 = sys_mmap(CS_MAGIC_16384) 167 var pass: i64 = 0 168 // GET / -> the coach shell 169 let r1: i64 = cs_handle("GET" as *u8, "/" as *u8, "" as *u8, 0, rh, tbl, out) 170 if cs_has(out, r1, "200 OK" as *u8) == 1 { if cs_has(out, r1, "Self-Teaching Coach" as *u8) == 1 { if cs_has(out, r1, "getUserMedia" as *u8) == 1 { pass = pass + 1 } } } 171 // POST grade "sing 430 69" -> flat + the breath-support drill, as JSON 172 let r2: i64 = cs_handle("POST" as *u8, "/coach/api/grade" as *u8, "sing 430 69" as *u8, 11, rh, tbl, out) 173 sys_write(1, " POST sing 430 69 -> " as *u8, 22); sys_write(1, out, r2); sys_write(1, "\n" as *u8, 1) 174 if cs_has(out, r2, "application/json" as *u8) == 1 { if cs_has(out, r2, "\"flat\"" as *u8) == 1 { if cs_has(out, r2, "breath support" as *u8) == 1 { pass = pass + 1 } } } 175 // POST grade "sing 440 69" -> in_tune 176 let r3: i64 = cs_handle("POST" as *u8, "/coach/api/grade" as *u8, "sing 440 69" as *u8, 11, rh, tbl, out) 177 if cs_has(out, r3, "\"in_tune\"" as *u8) == 1 { pass = pass + 1 } 178 // POST curriculum "confront 9" -> the CURRICULUM engine over the same /coach: missing Feeling + Need, as JSON 179 let rc: i64 = cs_handle("POST" as *u8, "/coach/api/curriculum" as *u8, "confront 9" as *u8, 10, rh, tbl, out) 180 sys_write(1, " POST confront 9 -> " as *u8, 21); sys_write(1, out, rc); sys_write(1, "\n" as *u8, 1) 181 if cs_has(out, rc, "application/json" as *u8) == 1 { if cs_has(out, rc, "Feeling" as *u8) == 1 { if cs_has(out, rc, "Need" as *u8) == 1 { pass = pass + 1 } } } 182 // unknown route -> 404 183 let r4: i64 = cs_handle("GET" as *u8, "/nope" as *u8, "" as *u8, 0, rh, tbl, out) 184 if cs_has(out, r4, "404" as *u8) == 1 { pass = pass + 1 } 185 sys_write(1, " handler checks = " as *u8, 19); let pc: *u8 = sys_mmap(8); pc[0] = (48 + pass) as u8; sys_write(1, pc, 1); sys_write(1, "/5\n" as *u8, 3) 186 if pass == 5 { sys_write(1, "COACH-SERVE: GREEN -- one /coach grades BOTH sensor (sung note) and curriculum (roleplay) as JSON; deploy-ready\n" as *u8, 112); return 0 } 187 sys_write(1, "COACH-SERVE: RED\n" as *u8, 17); return 1 188}