code wiki / _hdl_build / nx_coach_gamify.nx

nx_coach_gamify.nx source

↩ module page · 76 lines · 4764 B

1// nx_coach_gamify.nx -- SKILL-COACH rung R4: GAMIFICATION (the operator's "gamified fun way"). Turns the coach 2// grade-signal (R1 cents / R3 chord-accuracy) into XP / LEVEL / STREAK / MASTERY. JOY-FIRST BY CONSTRUCTION 3// (feedback-games-as-test-avenues / joy-is-the-metric): points CLAMP >= 0 (a miss can NEVER subtract XP), a miss 4// resets the streak to 0 (never negative, never a punishment), mastery = a PROVEN run (best_streak), NOT grind -- 5// so loot-boxes / FOMO / loss-penalties are structurally impossible, not just "not added". Composes the coach 6// verdict (coach_score_*). Render is nx_game_raster (visual = the next rung). Self-gate: a practice session + 7// the JOY GUARD (a punitive negative-points attempt is refused). license_tier: ORIGINAL 8import "nx_syscalls.nx" 9import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 10import "nx_g_puts_lib.nx" 11 12const LEVEL_XP: i64 = 500 // XP per level (data-driven; R2 rubric-store could hold it) 13const MASTERY_STREAK: i64 = 5 // consecutive clean = mastered the current skill 14 15// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 16// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 17// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 18// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 19func g_putn(v: i64) -> i64 { nxi_out(v); return 0 } 20// coach verdict (sing) -> points. in_tune=100; sharp/flat scale by closeness (closer = more, but always >=0, 21// never a penalty); wrong-note still earns a little (effort, not punishment). 22func coach_score_sing(verdict: i64, cents: i64) -> i64 { 23 if verdict == 0 { return 100 } 24 if verdict == 1 { var a: i64 = cents; if a < 0 { a = 0 - a } var p: i64 = 100 - a; if p < 0 { p = 0 } return p } 25 if verdict == 2 { var a: i64 = cents; if a < 0 { a = 0 - a } var p: i64 = 100 - a; if p < 0 { p = 0 } return p } 26 return 10 27} 28// gam state: [0]=xp [1]=level [2]=streak [3]=best_streak [4]=attempts [5]=mastered 29func gam_new() -> *i64 { let s: *i64 = sys_mmap(8 * 8) as *i64; var i: i64 = 0; while i < 8 { s[i] = 0; i = i + 1 } return s } 30func gam_record(st: *i64, points: i64, is_clean: i64) -> i64 { 31 var p: i64 = points 32 if p < 0 { p = 0 } // JOY INVARIANT: XP can never go DOWN (no loss-penalty, by construction) 33 st[0] = st[0] + p 34 st[4] = st[4] + 1 35 if is_clean == 1 { 36 st[2] = st[2] + 1 37 if st[2] > st[3] { st[3] = st[2] } 38 } else { 39 st[2] = 0 // gentle reset -- NOT a penalty, NOT negative 40 } 41 st[1] = st[0] / LEVEL_XP 42 if st[3] >= MASTERY_STREAK { st[5] = 1 } 43 return 0 44} 45func main() -> i64 { 46 g_puts("SKILL-COACH (R4): gamify the grade-signal -> XP/level/streak/mastery, JOY-FIRST (no dark patterns)\n" as *u8) 47 let st: *i64 = gam_new() 48 var pass: i64 = 0 49 var ok_monotonic: i64 = 1 50 var prev: i64 = 0 51 // a practice session, scored from the COACH verdict (R1) 52 // a1 in-tune 53 gam_record(st, coach_score_sing(0, 0), 1) 54 if st[0] < prev { ok_monotonic = 0 } prev = st[0] 55 // a2 flat -30c (a MISS) -- still earns 70, streak resets, NO punishment 56 let xp_before_miss: i64 = st[0] 57 gam_record(st, coach_score_sing(2, 0 - 30), 0) 58 if st[0] < prev { ok_monotonic = 0 } prev = st[0] 59 if st[0] >= xp_before_miss { if st[2] == 0 { pass = pass + 1 } } // miss: XP did NOT drop + streak reset to 0 60 // a3..a7: five clean in a row -> mastery 61 var i: i64 = 0 62 while i < 5 { gam_record(st, coach_score_sing(0, 0), 1); if st[0] < prev { ok_monotonic = 0 } prev = st[0]; i = i + 1 } 63 if st[5] == 1 { if st[3] >= MASTERY_STREAK { pass = pass + 1 } } // mastered via a proven 5-streak 64 if st[1] >= 1 { pass = pass + 1 } // leveled up (xp >= 500) 65 if ok_monotonic == 1 { pass = pass + 1 } // JOY: XP never decreased across the session 66 // JOY GUARD (liar-kill): a punitive negative-points attempt MUST be refused (clamped) -- dark patterns impossible 67 let xp_g: i64 = st[0] 68 gam_record(st, 0 - 50, 0) 69 if st[0] == xp_g { pass = pass + 1 } // -50 clamped to 0 -> XP unchanged 70 g_puts(" xp="); g_putn(st[0]); g_puts(" level="); g_putn(st[1]) 71 g_puts(" best_streak="); g_putn(st[3]); g_puts(" mastered="); g_putn(st[5]) 72 g_puts(" checks="); g_putn(pass); g_puts("/5\n" as *u8) 73 if pass == 5 { g_puts("COACH-GAMIFY: GREEN -- XP/level/streak/mastery from the grade; JOY-FIRST by construction (no XP loss ever, miss resets gently, punitive points refused)\n" as *u8); return 0 } 74 g_puts("COACH-GAMIFY: RED\n" as *u8) 75 return 1 76}