code wiki / (root) / nx_circadian.nx

nx_circadian.nx source

↩ module page · 132 lines · 5119 B

1// nx_circadian.nx -- distributed clock sync + scheduling (NTP/cron class). 2// 3// Biology: circadian rhythm is the ~24-hour cycle in metabolic + behavior 4// regulated by cellular oscillators (suprachiasmatic nucleus in mammals, 5// per/cry/clock genes). Sync drift: ~15-30 min/day without external cue. 6// Reset by light, temperature, food timing. 7// 8// Substrate: distributed-clock-class primitive for substrate-internal 9// rhythms (heartbeat, scheduled work, peer-clock-sync alignment). 10// 11// Per [[feedback-predictive-planning-allowed-exploitation-refused]]: 12// substrate supports rhythm-detection for operator's own planning; 13// REFUSES rhythm-exploitation (engagement loops, addiction architecture). 14// 15// lineage_id: substrate_circadian_v1 16 17import "nx_syscalls.nx" 18import "nx_tier.nx" 19const NX_MAGIC_30000: i64 = 30000 20const NX_MAGIC_60000: i64 = 60000 21const NX_MAGIC_3600000: i64 = 3600000 22const NX_MAGIC_86400000: i64 = 86400000 23const NX_MAGIC_604800000: i64 = 604800000 24const NX_MAGIC_7884000000: i64 = 7884000000 25 26const NX_CD_RHYTHM_SUBSTRATE_HEARTBEAT: nx_int = 0 // ~30s heartbeat 27const NX_CD_RHYTHM_SHORT_INTERVAL: nx_int = 1 // 1-15 min 28const NX_CD_RHYTHM_HOURLY: nx_int = 2 29const NX_CD_RHYTHM_DAILY: nx_int = 3 // canonical circadian 30const NX_CD_RHYTHM_WEEKLY: nx_int = 4 31const NX_CD_RHYTHM_SEASONAL: nx_int = 5 32const NX_CD_RHYTHM_N: nx_int = 6 33 34const NX_CD_DRIFT_OK: nx_int = 0 35const NX_CD_DRIFT_MINOR: nx_int = 1 // re-sync soon 36const NX_CD_DRIFT_MAJOR: nx_int = 2 // re-sync now 37const NX_CD_DRIFT_INVALID: nx_int = 99 38const NX_CD_DRIFT_N: nx_int = 3 39 40const NX_CD_V_OK: nx_int = 0 41const NX_CD_V_ERR_NULL_INPUT: nx_int = 1 42const NX_CD_V_ERR_INVALID_RHYTHM: nx_int = 2 43const NX_CD_V_N: nx_int = 3 44 45struct NxCircadianRhythm { 46 rhythm: nx_int, 47 period_ms: nx_size, 48 last_tick_at_us: nx_size, 49 tick_count: nx_size, 50 accumulated_drift_us: nx_size, 51} 52 53const NX_CD_BYTES: nx_int = 48 54 55func nx_cd_rhythm_is_valid(r: nx_int) -> nx_int { 56 if r < 0 { return 0 } 57 if r >= NX_CD_RHYTHM_N { return 0 } 58 return 1 59} 60 61func nx_cd_drift_is_valid(d: nx_int) -> nx_int { 62 if d < 0 { return 0 } 63 if d >= NX_CD_DRIFT_N { return 0 } 64 return 1 65} 66 67func nx_cd_v_is_valid(v: nx_int) -> nx_int { 68 if v < 0 { return 0 } 69 if v >= NX_CD_V_N { return 0 } 70 return 1 71} 72 73func nx_cd_rhythm_canonical_period_ms(r: nx_int) -> nx_size { 74 if r == NX_CD_RHYTHM_SUBSTRATE_HEARTBEAT { return NX_MAGIC_30000 } // 30 sec 75 if r == NX_CD_RHYTHM_SHORT_INTERVAL { return NX_MAGIC_60000 } // 1 min 76 if r == NX_CD_RHYTHM_HOURLY { return NX_MAGIC_3600000 } // 1 hour 77 if r == NX_CD_RHYTHM_DAILY { return NX_MAGIC_86400000 } // 24 hr 78 if r == NX_CD_RHYTHM_WEEKLY { return NX_MAGIC_604800000 } // 7 day 79 if r == NX_CD_RHYTHM_SEASONAL { return NX_MAGIC_7884000000 } // ~91 days 80 return 0 81} 82 83func nx_cd_rhythm_new(rhythm: nx_int, now_us: nx_size) -> *NxCircadianRhythm { 84 if nx_cd_rhythm_is_valid(rhythm) == 0 { return 0 as *NxCircadianRhythm } 85 let raw: *u8 = sys_mmap(NX_CD_BYTES) 86 let c: *NxCircadianRhythm = raw as *NxCircadianRhythm 87 c.rhythm = rhythm 88 c.period_ms = nx_cd_rhythm_canonical_period_ms(rhythm) 89 c.last_tick_at_us = now_us 90 c.tick_count = 0 91 c.accumulated_drift_us = 0 92 return c 93} 94 95func nx_cd_tick(c: *NxCircadianRhythm, now_us: nx_size) -> nx_int { 96 if (c as i64) == 0 { return NX_CD_V_ERR_NULL_INPUT } 97 let elapsed_us: nx_size = now_us - c.last_tick_at_us 98 let expected_us: nx_size = c.period_ms * 1000 99 var drift: nx_size = 0 100 if elapsed_us > expected_us { drift = elapsed_us - expected_us } 101 if elapsed_us < expected_us { drift = expected_us - elapsed_us } 102 c.accumulated_drift_us = c.accumulated_drift_us + drift 103 c.last_tick_at_us = now_us 104 c.tick_count = c.tick_count + 1 105 return NX_CD_V_OK 106} 107 108func nx_cd_drift_classify(c: *NxCircadianRhythm) -> nx_int { 109 if (c as i64) == 0 { return NX_CD_DRIFT_INVALID } 110 // Drift > 10% of period = MAJOR 111 let period_us: nx_size = c.period_ms * 1000 112 let major_threshold: nx_size = period_us / 10 113 let minor_threshold: nx_size = period_us / 100 114 if c.accumulated_drift_us >= major_threshold { return NX_CD_DRIFT_MAJOR } 115 if c.accumulated_drift_us >= minor_threshold { return NX_CD_DRIFT_MINOR } 116 return NX_CD_DRIFT_OK 117} 118 119func nx_cd_resync(c: *NxCircadianRhythm, now_us: nx_size) -> nx_int { 120 if (c as i64) == 0 { return NX_CD_V_ERR_NULL_INPUT } 121 c.last_tick_at_us = now_us 122 c.accumulated_drift_us = 0 123 return NX_CD_V_OK 124} 125 126func nx_cd_due_for_tick(c: *NxCircadianRhythm, now_us: nx_size) -> nx_int { 127 if (c as i64) == 0 { return 0 } 128 let elapsed_us: nx_size = now_us - c.last_tick_at_us 129 let expected_us: nx_size = c.period_ms * 1000 130 if elapsed_us >= expected_us { return 1 } 131 return 0 132}