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}