nx_triple_companion_compose_test.nx source
↩ module page · 194 lines · 6967 B
1// nx_triple_companion_compose_test.nx -- heterogeneous 3-modality smoke.
2//
3// Goes beyond [[nx_dual_companion_compose_test]]: instead of two LLM
4// clones, runs THREE actors of DIFFERENT modalities concurrently:
5//
6// NARRATOR (LLM, priority 80) -- nx_actor_role_llm
7// NPC (LLM, priority 60) -- nx_actor_role_llm
8// AUDIO_QC (audio scene grader, priority 50) -- nx_actor_role_audio_grader
9//
10// All three share one scheduler + one message bus + one multiplexer.
11// Each actor calls a DIFFERENT other-agent primitive:
12// NARRATOR/NPC -> embedding_lookup + blas_matmul + sample_categorical
13// AUDIO_QC -> audio_scene_grade
14//
15// This is the smallest-honest demonstration that the parallel-companion
16// substrate handles HETEROGENEOUS modalities, not just N copies of the
17// same primitive. The DND companion north star is one of these
18// scenarios (LLM narrative + NPC dialogue + ambient audio grading
19// + game action processing) running in coherent interleaved fashion.
20
21import "nx_syscalls.nx"
22import "nx_tier.nx"
23import "nx_tensor.nx"
24import "nx_actor.nx"
25import "nx_message.nx"
26import "nx_stream_multiplexer.nx"
27import "nx_actor_role_llm.nx"
28import "nx_actor_role_audio_grader.nx"
29
30func _fill_q10(t: *NxTensor, base: i64, stride: i64) -> i64 {
31 let p: *i64 = t.storage as *i64
32 var i: nx_int = 0
33 while i < t.numel {
34 p[i] = base + (i as i64) * stride
35 i = i + 1
36 }
37 return 0
38}
39
40func main() -> i64 {
41 let now: nx_size = 1000000
42
43 // 1: LLM weights (shared between NARRATOR + NPC)
44 let shape_buf: *u8 = sys_mmap(16)
45 let shape: *i64 = shape_buf as *i64
46 shape[0] = 4
47 shape[1] = 4
48 let err_buf: *u8 = sys_mmap(8)
49 let err_p: *i64 = err_buf as *i64
50 let embed_table: *NxTensor = nx_t_alloc(NX_DT_I64, shape, 2, err_p)
51 if err_p[0] != NX_T_OK { return 1 }
52 _fill_q10(embed_table, 100, 5)
53
54 let shape2_buf: *u8 = sys_mmap(16)
55 let shape2: *i64 = shape2_buf as *i64
56 shape2[0] = 4
57 shape2[1] = 4
58 let out_proj: *NxTensor = nx_t_alloc(NX_DT_I64, shape2, 2, err_p)
59 _fill_q10(out_proj, 40, 3)
60
61 // 2: audio scene inputs
62 let bins_buf: *u8 = sys_mmap(64)
63 let bins: *i64 = bins_buf as *i64
64 var b: nx_int = 0
65 while b < 8 {
66 bins[b] = 5000 + ((b * 800) as i64) // ramped spectrum
67 b = b + 1
68 }
69 let rms_buf: *u8 = sys_mmap(128)
70 let rms: *i64 = rms_buf as *i64
71 var f: nx_int = 0
72 while f < 16 {
73 rms[f] = 2500 + ((f * 150) as i64)
74 f = f + 1
75 }
76
77 // 3: shared substrate
78 let sched: *NxActorScheduler = nx_ac_sched_new(8, 1000000, 8, now)
79 let bus: *NxMessageBus = nx_ms_bus_new(8, 16, 8)
80 let mux: *NxStreamMultiplexer = nx_sm_new(16, 50000)
81
82 let NARR: nx_int = 7001
83 let NPC: nx_int = 7002
84 let AUD: nx_int = 7003
85 let LIS: nx_int = 7099
86
87 nx_ac_spawn(sched, NARR, 1, 80, 0, now)
88 nx_ac_spawn(sched, NPC, 1, 60, 0, now)
89 nx_ac_spawn(sched, AUD, 4, 50, 0, now)
90 nx_ac_spawn(sched, LIS, 5, 40, 0, now)
91
92 nx_ms_register(bus, NARR)
93 nx_ms_register(bus, NPC)
94 nx_ms_register(bus, AUD)
95 nx_ms_register(bus, LIS)
96 nx_ms_subscribe(bus, LIS, NX_MS_KIND_LLM_TOKEN)
97 nx_ms_subscribe(bus, LIS, NX_MS_KIND_AUDIO_FRAME)
98
99 // 4: contexts
100 let narr_ctx: *NxLlmActorCtx = nx_lr_actor_new(embed_table, out_proj, 1, 4, 4, 1024, 2, 11)
101 let npc_ctx: *NxLlmActorCtx = nx_lr_actor_new(embed_table, out_proj, 2, 4, 4, 1024, 2, 13)
102 let aud_ctx: *NxAudioGraderCtx = nx_ag_actor_new(bins, 8, rms, 16, 4)
103 if (narr_ctx as i64) == 0 { return 2 }
104 if (npc_ctx as i64) == 0 { return 3 }
105 if (aud_ctx as i64) == 0 { return 4 }
106
107 // 5: pick_next initial state -- NARR is highest priority (80)
108 let first: *NxActor = nx_ac_pick_next(sched)
109 if first.actor_id != NARR { return 5 }
110
111 // 6: interleave 5 LLM-actor steps for NARR, 5 for NPC, 3 for AUD.
112 // All three actors COMPLETE concurrently in this round.
113 var tick: nx_size = now + 100
114 var i: nx_int = 0
115 while i < 5 {
116 nx_lr_actor_step(narr_ctx, sched, bus, NARR, tick)
117 tick = tick + 50
118 i = i + 1
119 }
120 var j: nx_int = 0
121 while j < 5 {
122 nx_lr_actor_step(npc_ctx, sched, bus, NPC, tick)
123 tick = tick + 50
124 j = j + 1
125 }
126 var k: nx_int = 0
127 while k < 3 {
128 nx_ag_actor_step(aud_ctx, sched, bus, AUD, tick)
129 tick = tick + 50
130 k = k + 1
131 }
132
133 // 7: all three actors COMPLETED
134 let na: *NxActor = nx_ac_find(sched, NARR)
135 let nb: *NxActor = nx_ac_find(sched, NPC)
136 let nc: *NxActor = nx_ac_find(sched, AUD)
137 if na.state != NX_AC_STATE_COMPLETED { return 6 }
138 if nb.state != NX_AC_STATE_COMPLETED { return 7 }
139 if nc.state != NX_AC_STATE_COMPLETED { return 8 }
140
141 // 8: LISTENER mailbox has 3 messages: 2 LLM_TOKEN + 1 AUDIO_FRAME
142 if nx_ms_pending(bus, LIS) != 3 { return 9 }
143
144 // 9: tokens valid
145 let narr_tok: nx_int = nx_lr_actor_token(narr_ctx)
146 let npc_tok: nx_int = nx_lr_actor_token(npc_ctx)
147 if narr_tok < 0 { return 10 }
148 if narr_tok >= 4 { return 11 }
149 if npc_tok < 0 { return 12 }
150 if npc_tok >= 4 { return 13 }
151
152 // 10: audio score positive (healthy synthetic scene)
153 if nx_ag_actor_score_q14(aud_ctx) <= 0 { return 14 }
154
155 // 11: drain LISTENER mailbox. Receive order = FIFO = NARR_LLM, NPC_LLM, AUD.
156 let m1: *NxMessage = nx_ms_receive(bus, LIS)
157 if m1.kind != NX_MS_KIND_LLM_TOKEN { return 15 }
158 if m1.sender_actor_id != NARR { return 16 }
159 if (m1.payload_handle as nx_int) != narr_tok { return 17 }
160
161 let m2: *NxMessage = nx_ms_receive(bus, LIS)
162 if m2.kind != NX_MS_KIND_LLM_TOKEN { return 18 }
163 if m2.sender_actor_id != NPC { return 19 }
164
165 let m3: *NxMessage = nx_ms_receive(bus, LIS)
166 if m3.kind != NX_MS_KIND_AUDIO_FRAME { return 20 }
167 if m3.sender_actor_id != AUD { return 21 }
168 if (m3.payload_handle as nx_int) != nx_ag_actor_score_q14(aud_ctx) { return 22 }
169
170 // 12: push to multiplexer with stream classes -- LLM_TEXT > AUDIO_TTS by priority
171 nx_sm_push(mux, NARR, NX_SM_CLASS_LLM_TEXT, 1, 8, narr_tok as nx_size, tick)
172 nx_sm_push(mux, NPC, NX_SM_CLASS_LLM_TEXT, 2, 8, npc_tok as nx_size, tick)
173 nx_sm_push(mux, AUD, NX_SM_CLASS_AUDIO_TTS, 3, 8, nx_ag_actor_score_q14(aud_ctx) as nx_size, tick)
174 if nx_sm_pending_count(mux) != 3 { return 23 }
175
176 // 13: drain -- both LLM_TEXT (priority 80) come before AUDIO_TTS (priority 70)
177 let oc_buf: *u8 = sys_mmap(24)
178 let oc: *i64 = oc_buf as *i64
179 let os: *i64 = (oc_buf + 8) as *i64
180 let oid: *i64 = (oc_buf + 16) as *i64
181
182 nx_sm_next(mux, oc, os, oid, tick + 100)
183 if oc[0] != (NX_SM_CLASS_LLM_TEXT as i64) { return 24 }
184
185 nx_sm_next(mux, oc, os, oid, tick + 200)
186 if oc[0] != (NX_SM_CLASS_LLM_TEXT as i64) { return 25 }
187
188 nx_sm_next(mux, oc, os, oid, tick + 300)
189 if oc[0] != (NX_SM_CLASS_AUDIO_TTS as i64) { return 26 }
190
191 if nx_sm_pending_count(mux) != 0 { return 27 }
192
193 return 0
194}