Skip to main content

ternlang_core/vm/
mod.rs

1pub mod bet;
2
3use crate::trit::Trit;
4use crate::vm::bet::{unpack_trits, BetFault};
5
6use std::fmt;
7use std::sync::Arc;
8
9// ─── Remote transport trait ───────────────────────────────────────────────────
10
11pub trait RemoteTransport: Send + Sync {
12    fn remote_send(&self, node_addr: &str, agent_id: usize, trit: i8) -> std::io::Result<()>;
13    fn remote_await(&self, node_addr: &str, agent_id: usize) -> std::io::Result<i8>;
14}
15
16/// Maximum call depth before the VM returns a `CallStackOverflow` error.
17/// Prevents OOM/freeze when programs contain unbounded recursion or
18/// mutual recursion across imported modules.
19const MAX_CALL_DEPTH: usize = 4096;
20
21#[derive(Debug, PartialEq, Eq)]
22pub enum VmError {
23    StackUnderflow,
24    BetFault(BetFault),
25    Halt,
26    InvalidOpcode(u8),
27    InvalidRegister(u8),
28    PcOutOfBounds(usize),
29    TypeMismatch { expected: String, found: String },
30    // ── Tensor errors ────────────────────────────────────────────────────────
31    TensorIndexOutOfBounds { tensor_id: usize, index: usize, size: usize },
32    TensorNotAllocated(usize),
33    // ── Agent errors ─────────────────────────────────────────────────────────
34    AgentTypeNotRegistered(u16),
35    AgentIdInvalid(usize),
36    RuntimeError(String),
37    CallStackOverflow,
38    // ── File I/O errors ──────────────────────────────────────────────────────
39    FileOpenError(String),
40    FileReadError(String),
41    FileWriteError(String),
42    FileNotOpen(usize),
43    AssertionFailed,
44}
45
46impl fmt::Display for VmError {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match self {
49            VmError::StackUnderflow =>
50                write!(f, "[BET-001] Stack underflow — you tried to pop a truth that wasn't there.\n          → details: stdlib/errors/BET-001.tern  |  ternlang errors BET-001"),
51            VmError::BetFault(fault) =>
52                write!(f, "[BET-002] BET encoding fault: {fault:?}. The 0b00 state is forbidden — only 01/10/11 are valid trit bits.\n          → details: stdlib/errors/BET-002.tern  |  ternlang errors BET-002"),
53            VmError::Halt =>
54                write!(f, "[BET-003] VM halted cleanly. Execution reached the end. This is not an error — this is peace.\n          → details: stdlib/errors/BET-003.tern  |  ternlang errors BET-003"),
55            VmError::InvalidOpcode(op) =>
56                write!(f, "[BET-004] Unknown opcode 0x{op:02x} — the machine has never seen this instruction. Delete cached .ternbc files and recompile.\n          → details: stdlib/errors/BET-004.tern  |  ternlang errors BET-004"),
57            VmError::InvalidRegister(reg) =>
58                write!(f, "[BET-005] Register {reg} is out of range. The BET has exactly 27 registers (0–26). That's 3³. No more.\n          → details: stdlib/errors/BET-005.tern  |  ternlang errors BET-005"),
59            VmError::PcOutOfBounds(pc) =>
60                write!(f, "[BET-006] PC {pc} is out of bounds — you jumped outside the known universe. Recompile from source.\n          → details: stdlib/errors/BET-006.tern  |  ternlang errors BET-006"),
61            VmError::TypeMismatch { expected, found } =>
62                write!(f, "[BET-007] Runtime type mismatch — expected {expected} but found {found}. Square peg, round hole.\n          → details: stdlib/errors/BET-007.tern  |  ternlang errors BET-007"),
63            VmError::TensorIndexOutOfBounds { tensor_id, index, size } =>
64                write!(f, "[BET-008] Tensor[{tensor_id}]: index {index} is out of bounds — tensor only has {size} element(s). Trittensors don't grow on access.\n          → details: stdlib/errors/BET-008.tern  |  ternlang errors BET-008"),
65            VmError::TensorNotAllocated(idx) =>
66                write!(f, "[BET-009] TensorRef({idx}) doesn't exist — you never allocated it. TALLOC first, then TIDX.\n          → details: stdlib/errors/BET-009.tern  |  ternlang errors BET-009"),
67            VmError::AgentTypeNotRegistered(type_id) =>
68                write!(f, "[BET-010] Agent type_id 0x{type_id:04x} was never registered. You can't spawn what was never declared.\n          → details: stdlib/errors/BET-010.tern  |  ternlang errors BET-010"),
69            VmError::AgentIdInvalid(id) =>
70                write!(f, "[BET-011] Agent #{id} doesn't exist — no agent was spawned at this ID. TSEND and TAWAIT require a live agent.\n          → details: stdlib/errors/BET-011.tern  |  ternlang errors BET-011"),
71            VmError::RuntimeError(msg) =>
72                write!(f, "[BET-012] Runtime error: {msg}"),
73            VmError::CallStackOverflow =>
74                write!(f, "[BET-013] Call stack overflow — max depth ({MAX_CALL_DEPTH}) exceeded. Infinite recursion or unbounded cross-module mutual calls detected.\n          → details: stdlib/errors/BET-013.tern  |  ternlang errors BET-013"),
75            VmError::FileOpenError(e) =>
76                write!(f, "[IO-001] File open error: {e}"),
77            VmError::FileReadError(e) =>
78                write!(f, "[IO-002] File read error: {e}"),
79            VmError::FileWriteError(e) =>
80                write!(f, "[IO-003] File write error: {e}"),
81            VmError::FileNotOpen(id) =>
82                write!(f, "[IO-004] File handle {id} is not open or was closed."),
83            VmError::AssertionFailed =>
84                write!(f, "[ASSERT-001] Assertion failed: an assert() condition evaluated to reject or tend."),
85        }
86    }
87}
88
89#[derive(Debug, Clone, PartialEq)]
90pub enum Value {
91    Trit(Trit),
92    Int(i64),
93    Float(f64),
94    String(String),
95    TensorRef(usize),
96    AgentRef(usize, Option<String>),
97    Struct(std::collections::HashMap<String, Value>),
98}
99
100impl Default for Value {
101    fn default() -> Self {
102        Value::Trit(Trit::Tend)
103    }
104}
105
106enum TensorData {
107    Trit(Vec<Trit>),
108    Float(Vec<f64>),
109    Int(Vec<i64>),
110}
111
112impl TensorData {
113    fn len(&self) -> usize {
114        match self {
115            TensorData::Trit(v) => v.len(),
116            TensorData::Float(v) => v.len(),
117            TensorData::Int(v) => v.len(),
118        }
119    }
120}
121
122struct TensorInstance {
123    data: TensorData,
124    rows: usize,
125    cols: usize,
126}
127
128struct AgentInstance {
129    handler_addr: usize,
130    mailbox: std::collections::VecDeque<Value>,
131}
132
133pub struct BetVm {
134    /// Dynamic register file — grows on demand so programs with > 27 locals work correctly
135    /// instead of silently dropping stores and returning zero on reads.
136    registers: Vec<Value>,
137    register_stack: Vec<Vec<Value>>,
138    carry_reg: Trit,
139    stack: Vec<Value>,
140    call_stack: Vec<usize>,
141    tensors: Vec<TensorInstance>,
142    agents: Vec<AgentInstance>,
143    agent_types: std::collections::HashMap<u16, usize>,
144    pc: usize,
145    code: Vec<u8>,
146    node_id: String,
147    pub sparse_dropped: bool,
148    remote: Option<Arc<dyn RemoteTransport>>,
149    open_files: Vec<Option<std::fs::File>>,
150    _instructions_count: u64,
151    pub print_log: Vec<String>,
152}
153
154impl BetVm {
155    pub fn new(code: Vec<u8>) -> Self {
156        Self {
157            registers: vec![Value::default(); 27],
158            register_stack: Vec::new(),
159            carry_reg: Trit::Tend,
160            stack: Vec::new(),
161            call_stack: Vec::new(),
162            tensors: Vec::new(),
163            agents: Vec::new(),
164            agent_types: std::collections::HashMap::new(),
165            pc: 0,
166            code,
167            node_id: "127.0.0.1".into(),
168            sparse_dropped: false,
169            remote: None,
170            open_files: Vec::new(),
171            _instructions_count: 0,
172            print_log: Vec::new(),
173        }
174    }
175
176
177    /// Drain all lines printed by `print()`/`println()` during execution.
178    pub fn take_output(&mut self) -> Vec<String> {
179        std::mem::take(&mut self.print_log)
180    }
181
182    pub fn set_node_id(&mut self, node_id: String) {
183        self.node_id = node_id;
184    }
185
186    pub fn set_remote(&mut self, transport: Arc<dyn RemoteTransport>) {
187        self.remote = Some(transport);
188    }
189
190    pub fn register_agent_type(&mut self, type_id: u16, handler_addr: usize) {
191        self.agent_types.insert(type_id, handler_addr);
192    }
193
194    pub fn peek_stack(&self) -> Option<Value> {
195        self.stack.last().cloned()
196    }
197
198    pub fn get_registers(&self) -> Vec<Value> {
199        self.registers.clone()
200    }
201
202    pub fn get_register(&self, reg: u8) -> Value {
203        self.registers.get(reg as usize).cloned().unwrap_or_default()
204    }
205
206    pub fn node_id(&self) -> &str {
207        &self.node_id
208    }
209
210    pub fn run(&mut self) -> Result<(), VmError> {
211        loop {
212            if self.pc >= self.code.len() { break; }
213            let opcode = self.code[self.pc];
214            self.pc += 1;
215
216            match opcode {
217                0x01 => { // Tpush
218                    let packed = self.read_u8()?;
219                    let trits = unpack_trits(&[packed], 1).map_err(VmError::BetFault)?;
220                    self.stack.push(Value::Trit(trits[0]));
221                }
222                0x02 => { // Tadd
223                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
224                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
225                    match (a.clone(), b.clone()) {
226                        (Value::Trit(av), Value::Trit(bv)) => {
227                            let (sum, carry) = av + bv;
228                            self.stack.push(Value::Trit(sum));
229                            self.carry_reg = carry;
230                        }
231                        (Value::Int(av), Value::Int(bv)) => self.stack.push(Value::Int(av + bv)),
232                        (Value::Float(av), Value::Float(bv)) => self.stack.push(Value::Float(av + bv)),
233                        (Value::Int(av), Value::Trit(bv)) => self.stack.push(Value::Int(av + bv as i64)),
234                        (Value::Trit(av), Value::Int(bv)) => self.stack.push(Value::Int(av as i64 + bv)),
235                        (Value::Float(av), Value::Trit(bv)) => self.stack.push(Value::Float(av + (bv as i8 as f64))),
236                        (Value::Trit(av), Value::Float(bv)) => self.stack.push(Value::Float((av as i8 as f64) + bv)),
237                        (Value::Float(av), Value::Int(bv)) => self.stack.push(Value::Float(av + (bv as f64))),
238                        (Value::Int(av), Value::Float(bv)) => self.stack.push(Value::Float((av as f64) + bv)),
239                        // PARSER-STR-001: string concatenation via + operator
240                        (Value::String(av), Value::String(bv)) => self.stack.push(Value::String(av + &bv)),
241                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
242                    }
243                }
244                0x03 => { // Tmul
245                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
246                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
247                    match (a.clone(), b.clone()) {
248                        (Value::Trit(av), Value::Trit(bv)) => self.stack.push(Value::Trit(av * bv)),
249                        (Value::Int(av), Value::Int(bv)) => self.stack.push(Value::Int(av * bv)),
250                        (Value::Float(av), Value::Float(bv)) => self.stack.push(Value::Float(av * bv)),
251                        (Value::Int(av), Value::Trit(bv)) => self.stack.push(Value::Int(av * bv as i64)),
252                        (Value::Trit(av), Value::Int(bv)) => self.stack.push(Value::Int(av as i64 * bv)),
253                        (Value::Float(av), Value::Trit(bv)) => self.stack.push(Value::Float(av * (bv as i8 as f64))),
254                        (Value::Trit(av), Value::Float(bv)) => self.stack.push(Value::Float((av as i8 as f64) * bv)),
255                        (Value::Float(av), Value::Int(bv)) => self.stack.push(Value::Float(av * (bv as f64))),
256                        (Value::Int(av), Value::Float(bv)) => self.stack.push(Value::Float((av as f64) * bv)),
257                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
258                    }
259                }
260                0x04 => { // Tneg
261                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
262                    match a.clone() {
263                        Value::Trit(av) => self.stack.push(Value::Trit(-av)),
264                        Value::Int(av) => self.stack.push(Value::Int(-av)),
265                        Value::Float(av) => self.stack.push(Value::Float(-av)),
266                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", a) }),
267                    }
268                }
269                0x05 => { // TjmpPos — jumps if top is positive (Affirm / any int > 0)
270                    let addr = self.read_u16()?;
271                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
272                    let is_pos = match val {
273                        Value::Trit(Trit::Affirm) => true,
274                        Value::Int(v) => *v > 0,
275                        Value::Float(f) => *f > 0.0,
276                        _ => false,
277                    };
278                    if is_pos { self.pc = addr as usize; }
279                }
280                0x06 => { // TjmpZero — jumps if top is zero (Tend / 0)
281                    let addr = self.read_u16()?;
282                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
283                    let is_zero = match val {
284                        Value::Trit(Trit::Tend) => true,
285                        Value::Int(v) => *v == 0,
286                        Value::Float(f) => *f == 0.0,
287                        _ => false,
288                    };
289                    if is_zero { self.pc = addr as usize; }
290                }
291                0x07 => { // TjmpNeg — jumps if top is negative (Reject / any int < 0)
292                    let addr = self.read_u16()?;
293                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
294                    let is_neg = match val {
295                        Value::Trit(Trit::Reject) => true,
296                        Value::Int(v) => *v < 0,
297                        Value::Float(f) => *f < 0.0,
298                        _ => false,
299                    };
300                    if is_neg { self.pc = addr as usize; }
301                }
302                0x08 => { // Tstore
303                    let reg = self.read_u8()? as usize;
304                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
305                    if reg >= self.registers.len() { self.registers.resize(reg + 1, Value::default()); }
306                    self.registers[reg] = val;
307                }
308                0x09 => { // Tload
309                    let reg = self.read_u8()? as usize;
310                    if reg >= self.registers.len() { self.registers.resize(reg + 1, Value::default()); }
311                    self.stack.push(self.registers[reg].clone());
312                }
313                0x0a => { // Tdup
314                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
315                    self.stack.push(val.clone());
316                }
317                0x0b => { // Tjmp
318                    let addr = self.read_u16()?;
319                    self.pc = addr as usize;
320                }
321                0x0c => { // Tpop
322                    self.stack.pop().ok_or(VmError::StackUnderflow)?;
323                }
324                0x0e => { // Tcons
325                    let b_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
326                    let a_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
327                    
328                    let a = match a_val {
329                        Value::Trit(t) => t,
330                        Value::Int(v) if v == 1 => Trit::Affirm,
331                        Value::Int(v) if v == 0 => Trit::Tend,
332                        Value::Int(v) if v == -1 => Trit::Reject,
333                        _ => return Err(VmError::TypeMismatch { expected: "Trit or Int(-1..1)".into(), found: format!("{:?}", a_val) }),
334                    };
335                    let b = match b_val {
336                        Value::Trit(t) => t,
337                        Value::Int(v) if v == 1 => Trit::Affirm,
338                        Value::Int(v) if v == 0 => Trit::Tend,
339                        Value::Int(v) if v == -1 => Trit::Reject,
340                        _ => return Err(VmError::TypeMismatch { expected: "Trit or Int(-1..1)".into(), found: format!("{:?}", b_val) }),
341                    };
342
343                    let result = match (a, b) {
344                        (Trit::Affirm, Trit::Affirm) => Trit::Affirm,
345                        (Trit::Reject, Trit::Reject) => Trit::Reject,
346                        (Trit::Tend, x) => x,
347                        (x, Trit::Tend) => x,
348                        _ => Trit::Tend,
349                    };
350                    self.stack.push(Value::Trit(result));
351                }
352                0x0f => { // Talloc (trit tensor)
353                    let rows = self.read_u32()? as usize;
354                    let cols = self.read_u32()? as usize;
355                    let size = rows * cols;
356                    let idx = self.tensors.len();
357                    self.tensors.push(TensorInstance {
358                        data: TensorData::Trit(vec![Trit::Tend; size]),
359                        rows,
360                        cols,
361                    });
362                    self.stack.push(Value::TensorRef(idx));
363                }
364                0x3c => { // Talloc_Int (int tensor)
365                    let rows = self.read_u32()? as usize;
366                    let cols = self.read_u32()? as usize;
367                    let size = rows * cols;
368                    let idx = self.tensors.len();
369                    self.tensors.push(TensorInstance {
370                        data: TensorData::Int(vec![0i64; size]),
371                        rows,
372                        cols,
373                    });
374                    self.stack.push(Value::TensorRef(idx));
375                }
376                0x3d => { // Talloc_Float (float tensor)
377                    let rows = self.read_u32()? as usize;
378                    let cols = self.read_u32()? as usize;
379                    let size = rows * cols;
380                    let idx = self.tensors.len();
381                    self.tensors.push(TensorInstance {
382                        data: TensorData::Float(vec![0.0f64; size]),
383                        rows,
384                        cols,
385                    });
386                    self.stack.push(Value::TensorRef(idx));
387                }
388                0x10 => { // Tcall
389                    if self.call_stack.len() >= MAX_CALL_DEPTH {
390                        return Err(VmError::CallStackOverflow);
391                    }
392                    let addr = self.read_u16()? as usize;
393                    self.register_stack.push(self.registers.clone());
394                    self.call_stack.push(self.pc);
395                    self.pc = addr;
396                }
397                0x11 => { // Tret
398                    if let Some(prev) = self.register_stack.pop() {
399                        self.registers = prev;
400                    }
401                    match self.call_stack.pop() {
402                        Some(ret) => self.pc = ret,
403                        None => return Ok(()),
404                    }
405                }
406                0x14 => { // Tless
407                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
408                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
409                    match (a.clone(), b.clone()) {
410                        (Value::Int(x), Value::Int(y)) => {
411                            let r = if x < y { Trit::Affirm } else if x == y { Trit::Tend } else { Trit::Reject };
412                            self.stack.push(Value::Trit(r));
413                        }
414                        (Value::Float(x), Value::Float(y)) => {
415                            let r = if x < y { Trit::Affirm } else if (x - y).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
416                            self.stack.push(Value::Trit(r));
417                        }
418                        (Value::Int(x), Value::Trit(y)) => {
419                            let bv = y as i64;
420                            let r = if x < bv { Trit::Affirm } else if x == bv { Trit::Tend } else { Trit::Reject };
421                            self.stack.push(Value::Trit(r));
422                        }
423                        (Value::Trit(x), Value::Int(y)) => {
424                            let av = x as i64;
425                            let r = if av < y { Trit::Affirm } else if av == y { Trit::Tend } else { Trit::Reject };
426                            self.stack.push(Value::Trit(r));
427                        }
428                        (Value::Int(av), Value::Float(bv)) => {
429                            let a_val = av as f64;
430                            let r = if a_val < bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
431                            self.stack.push(Value::Trit(r));
432                        }
433                        (Value::Float(av), Value::Int(bv)) => {
434                            let b_val = bv as f64;
435                            let r = if av < b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
436                            self.stack.push(Value::Trit(r));
437                        }
438                        (Value::Trit(av), Value::Float(bv)) => {
439                            let a_val = av as i8 as f64;
440                            let r = if a_val < bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
441                            self.stack.push(Value::Trit(r));
442                        }
443                        (Value::Float(av), Value::Trit(bv)) => {
444                            let b_val = bv as i8 as f64;
445                            let r = if av < b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
446                            self.stack.push(Value::Trit(r));
447                        }
448                        (Value::Trit(x), Value::Trit(y)) => {
449                            let av = x as i64;
450                            let bv = y as i64;
451                            let r = if av < bv { Trit::Affirm } else if av == bv { Trit::Tend } else { Trit::Reject };
452                            self.stack.push(Value::Trit(r));
453                        }
454                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
455                    }
456                }
457                0x15 => { // Tgreater
458                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
459                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
460                    match (a.clone(), b.clone()) {
461                        (Value::Int(x), Value::Int(y)) => {
462                            let r = if x > y { Trit::Affirm } else if x == y { Trit::Tend } else { Trit::Reject };
463                            self.stack.push(Value::Trit(r));
464                        }
465                        (Value::Float(x), Value::Float(y)) => {
466                            let r = if x > y { Trit::Affirm } else if (x - y).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
467                            self.stack.push(Value::Trit(r));
468                        }
469                        (Value::Int(x), Value::Trit(y)) => {
470                            let bv = y as i64;
471                            let r = if x > bv { Trit::Affirm } else if x == bv { Trit::Tend } else { Trit::Reject };
472                            self.stack.push(Value::Trit(r));
473                        }
474                        (Value::Trit(x), Value::Int(y)) => {
475                            let av = x as i64;
476                            let r = if av > y { Trit::Affirm } else if av == y { Trit::Tend } else { Trit::Reject };
477                            self.stack.push(Value::Trit(r));
478                        }
479                        (Value::Int(av), Value::Float(bv)) => {
480                            let a_val = av as f64;
481                            let r = if a_val > bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
482                            self.stack.push(Value::Trit(r));
483                        }
484                        (Value::Float(av), Value::Int(bv)) => {
485                            let b_val = bv as f64;
486                            let r = if av > b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
487                            self.stack.push(Value::Trit(r));
488                        }
489                        (Value::Trit(av), Value::Float(bv)) => {
490                            let a_val = av as i8 as f64;
491                            let r = if a_val > bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
492                            self.stack.push(Value::Trit(r));
493                        }
494                        (Value::Float(av), Value::Trit(bv)) => {
495                            let b_val = bv as i8 as f64;
496                            let r = if av > b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
497                            self.stack.push(Value::Trit(r));
498                        }
499                        (Value::Trit(x), Value::Trit(y)) => {
500                            let av = x as i64;
501                            let bv = y as i64;
502                            let r = if av > bv { Trit::Affirm } else if av == bv { Trit::Tend } else { Trit::Reject };
503                            self.stack.push(Value::Trit(r));
504                        }
505                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
506                    }
507                }
508                0x16 => { // Teq
509                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
510                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
511                    let is_eq = match (a.clone(), b.clone()) {
512                        (Value::Int(av), Value::Trit(bv)) => av == bv as i64,
513                        (Value::Trit(av), Value::Int(bv)) => av as i64 == bv,
514                        (Value::Float(av), Value::Float(bv)) => (av - bv).abs() < f64::EPSILON,
515                        (Value::Float(av), Value::Trit(bv)) => (av - (bv as i8 as f64)).abs() < f64::EPSILON,
516                        (Value::Trit(av), Value::Float(bv)) => ((av as i8 as f64) - bv).abs() < f64::EPSILON,
517                        (Value::Float(av), Value::Int(bv)) => (av - (bv as f64)).abs() < f64::EPSILON,
518                        (Value::Int(av), Value::Float(bv)) => ((av as f64) - bv).abs() < f64::EPSILON,
519                        _ => a == b,
520                    };
521                    let r = if is_eq { Trit::Affirm } else { Trit::Reject };
522                    self.stack.push(Value::Trit(r));
523                }
524                0x17 => { // TpushInt
525                    let mut b = [0u8; 8];
526                    for i in 0..8 { b[i] = self.read_u8()?; }
527                    self.stack.push(Value::Int(i64::from_le_bytes(b)));
528                }
529                0x18 => { // TaddInt
530                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
531                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
532                    match (a.clone(), b.clone()) {
533                        (Value::Int(x), Value::Int(y)) => self.stack.push(Value::Int(x + y)),
534                        _ => return Err(VmError::TypeMismatch { expected: "Int".into(), found: format!("{:?}", (a, b)) }),
535                    }
536                }
537                0x19 => { // TpushFloat
538                    let mut b = [0u8; 8];
539                    for i in 0..8 { b[i] = self.read_u8()?; }
540                    self.stack.push(Value::Float(f64::from_le_bytes(b)));
541                }
542                0x1e => { // Tdiv
543                    let b_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
544                    let a_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
545                    match (a_val.clone(), b_val.clone()) {
546                        (Value::Int(av), Value::Int(bv)) => {
547                            if bv == 0 { return Err(VmError::RuntimeError("Division by zero".into())); }
548                            self.stack.push(Value::Int(av / bv));
549                        }
550                        (Value::Float(av), Value::Float(bv)) => {
551                            if bv == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
552                            self.stack.push(Value::Float(av / bv));
553                        }
554                        (Value::Int(av), Value::Trit(bv)) => {
555                            let b = bv as i64;
556                            if b == 0 { return Err(VmError::RuntimeError("Division by zero".into())); }
557                            self.stack.push(Value::Int(av / b));
558                        }
559                        (Value::Trit(av), Value::Int(bv)) => {
560                            if bv == 0 { return Err(VmError::RuntimeError("Division by zero".into())); }
561                            self.stack.push(Value::Int(av as i64 / bv));
562                        }
563                        (Value::Float(av), Value::Trit(bv)) => {
564                            let b = bv as i8 as f64;
565                            if b == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
566                            self.stack.push(Value::Float(av / b));
567                        }
568                        (Value::Trit(av), Value::Float(bv)) => {
569                            if bv == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
570                            self.stack.push(Value::Float(av as i8 as f64 / bv));
571                        }
572                        (Value::Float(av), Value::Int(bv)) => {
573                            let b = bv as f64;
574                            if b == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
575                            self.stack.push(Value::Float(av / b));
576                        }
577                        (Value::Int(av), Value::Float(bv)) => {
578                            if bv == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
579                            self.stack.push(Value::Float(av as f64 / bv));
580                        }
581                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a_val, b_val)) }),
582                    }
583                }
584                0x1f => { // Tmod
585                    let b_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
586                    let a_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
587                    match (a_val.clone(), b_val.clone()) {
588                        (Value::Int(av), Value::Int(bv)) => {
589                            if bv == 0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
590                            self.stack.push(Value::Int(av % bv));
591                        }
592                        (Value::Int(av), Value::Trit(bv)) => {
593                            let b = bv as i64;
594                            if b == 0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
595                            self.stack.push(Value::Int(av % b));
596                        }
597                        (Value::Trit(av), Value::Int(bv)) => {
598                            if bv == 0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
599                            self.stack.push(Value::Int(av as i64 % bv));
600                        }
601                        (Value::Float(av), Value::Float(bv)) => {
602                             if bv == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
603                             self.stack.push(Value::Float(av % bv));
604                        }
605                        (Value::Float(av), Value::Trit(bv)) => {
606                             let b = bv as i8 as f64;
607                             if b == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
608                             self.stack.push(Value::Float(av % b));
609                        }
610                        (Value::Trit(av), Value::Float(bv)) => {
611                             if bv == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
612                             self.stack.push(Value::Float(av as i8 as f64 % bv));
613                        }
614                        (Value::Float(av), Value::Int(bv)) => {
615                             let b = bv as f64;
616                             if b == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
617                             self.stack.push(Value::Float(av % b));
618                        }
619                        (Value::Int(av), Value::Float(bv)) => {
620                             if bv == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
621                             self.stack.push(Value::Float(av as f64 % bv));
622                        }
623                        _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", (a_val, b_val)) }),
624                    }
625                }
626                0x20 => { // Tprint
627                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
628                    let line = match &val {
629                        Value::Trit(t) => format!("{:?}", t),
630                        Value::Int(i) => format!("{}", i),
631                        Value::Float(f) => format!("{}", f),
632                        Value::String(s) => s.clone(),
633                        Value::TensorRef(idx) => format!("TensorRef({})", idx),
634                        Value::AgentRef(idx, addr) => format!("AgentRef({}, {:?})", idx, addr),
635                        Value::Struct(fields) => format!("Struct({:?})", fields),
636                    };
637                    println!("{}", line);
638                    self.print_log.push(line);
639                }
640                0x21 => { // TpushString
641                    let len = self.read_u16()? as usize;
642                    let mut bytes = vec![0u8; len];
643                    for i in 0..len { bytes[i] = self.read_u8()?; }
644                    let s = String::from_utf8(bytes).map_err(|_| VmError::RuntimeError("Invalid UTF-8 string".into()))?;
645                    self.stack.push(Value::String(s));
646                }
647                0x22 => { // Tidx
648                    let col = self.stack.pop().ok_or(VmError::StackUnderflow)?;
649                    let row = self.stack.pop().ok_or(VmError::StackUnderflow)?;
650                    let rf = self.stack.pop().ok_or(VmError::StackUnderflow)?;
651                    let r = match row { Value::Int(v) => v, Value::Trit(t) => t as i64, _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", row) }) };
652                    let c = match col { Value::Int(v) => v, Value::Trit(t) => t as i64, _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", col) }) };
653                    match rf {
654                        Value::TensorRef(idx) => {
655                            if idx >= self.tensors.len() {
656                                return Err(VmError::TensorNotAllocated(idx));
657                            }
658                            let tensor = &self.tensors[idx];
659                            let data_len = tensor.data.len();
660                            let pos = if tensor.cols > 1 && c >= 0 { r as usize * tensor.cols + c as usize } else { r as usize };
661                            if pos >= data_len {
662                                return Err(VmError::TensorIndexOutOfBounds { tensor_id: idx, index: pos, size: data_len });
663                            }
664                            let pushed = match &tensor.data {
665                                TensorData::Trit(v) => Value::Trit(v[pos]),
666                                TensorData::Float(v) => Value::Float(v[pos]),
667                                TensorData::Int(v) => Value::Int(v[pos]),
668                            };
669                            self.stack.push(pushed);
670                        }
671                        _ => return Err(VmError::TypeMismatch { expected: "TensorRef".into(), found: format!("{:?}", rf) }),
672                    }
673                }
674                0x23 => { // Tset
675                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
676                    let col = self.stack.pop().ok_or(VmError::StackUnderflow)?;
677                    let row = self.stack.pop().ok_or(VmError::StackUnderflow)?;
678                    let rf = self.stack.pop().ok_or(VmError::StackUnderflow)?;
679                    let r = match row { Value::Int(v) => v, Value::Trit(t) => t as i64, _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", row) }) };
680                    let c = match col { Value::Int(v) => v, Value::Trit(t) => t as i64, _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", col) }) };
681                    if let Value::TensorRef(idx) = rf.clone() {
682                        if idx >= self.tensors.len() { return Err(VmError::TensorNotAllocated(idx)); }
683                        let tensor = &mut self.tensors[idx];
684                        let data_len = tensor.data.len();
685                        let pos = if tensor.cols > 1 && c >= 0 { r as usize * tensor.cols + c as usize } else { r as usize };
686                        if pos >= data_len { return Err(VmError::TensorIndexOutOfBounds { tensor_id: idx, index: pos, size: data_len }); }
687                        match (&mut tensor.data, val.clone()) {
688                            (TensorData::Trit(v), Value::Trit(t)) => v[pos] = t,
689                            (TensorData::Trit(v), Value::Int(i)) => v[pos] = if i > 0 { Trit::Affirm } else if i < 0 { Trit::Reject } else { Trit::Tend },
690                            (TensorData::Float(v), Value::Float(f)) => v[pos] = f,
691                            (TensorData::Float(v), Value::Int(i)) => v[pos] = i as f64,
692                            (TensorData::Int(v), Value::Int(i)) => v[pos] = i,
693                            (TensorData::Int(v), Value::Float(f)) => v[pos] = f as i64,
694                            (TensorData::Int(v), Value::Trit(t)) => v[pos] = t as i64,
695                            _ => return Err(VmError::TypeMismatch { expected: "compatible value for tensor type".into(), found: format!("{:?}", val) }),
696                        }
697                    } else {
698                        return Err(VmError::TypeMismatch { expected: "TensorRef".into(), found: format!("{:?}", rf) });
699                    }
700                }
701                0x24 => { // Tshape — TensorRef → (rows, cols); String → (len, 1)
702                    let rf = self.stack.pop().ok_or(VmError::StackUnderflow)?;
703                    match rf {
704                        Value::TensorRef(idx) => {
705                            if idx >= self.tensors.len() {
706                                return Err(VmError::TensorNotAllocated(idx));
707                            }
708                            let tensor = &self.tensors[idx];
709                            self.stack.push(Value::Int(tensor.rows as i64));
710                            self.stack.push(Value::Int(tensor.cols as i64));
711                        }
712                        Value::String(s) => {
713                            // len(string) — returns character count as rows, cols=1
714                            let n = s.chars().count() as i64;
715                            self.stack.push(Value::Int(n));
716                            self.stack.push(Value::Int(1));
717                        }
718                        _ => return Err(VmError::TypeMismatch { expected: "TensorRef or String".into(), found: format!("{:?}", rf) }),
719                    }
720                }
721                0x30 => { // Tspawn — (type_id) → AgentRef
722                    let type_id = self.read_u16()?;
723                    if let Some(&handler_addr) = self.agent_types.get(&type_id) {
724                        let id = self.agents.len();
725                        self.agents.push(AgentInstance { handler_addr, mailbox: Default::default() });
726                        self.stack.push(Value::AgentRef(id, None));
727                    } else {
728                        return Err(VmError::AgentTypeNotRegistered(type_id));
729                    }
730                }
731                0x31 => { // Tsend — msg, target → void
732                    let msg = self.stack.pop().ok_or(VmError::StackUnderflow)?;
733                    let target = self.stack.pop().ok_or(VmError::StackUnderflow)?;
734                    if let Value::AgentRef(id, None) = target {
735                        if id < self.agents.len() {
736                            self.agents[id].mailbox.push_back(msg);
737                        } else {
738                            return Err(VmError::AgentIdInvalid(id));
739                        }
740                    } else {
741                        return Err(VmError::TypeMismatch { expected: "Local AgentRef".into(), found: format!("{:?}", target) });
742                    }
743                }
744                0x32 => { // Tawait — target → result
745                    let target = self.stack.pop().ok_or(VmError::StackUnderflow)?;
746                    if let Value::AgentRef(id, None) = target {
747                        if id < self.agents.len() {
748                            if self.call_stack.len() >= MAX_CALL_DEPTH {
749                                return Err(VmError::CallStackOverflow);
750                            }
751                            let handler_addr = self.agents[id].handler_addr;
752                            let msg = self.agents[id].mailbox.pop_front().unwrap_or(Value::default());
753                            // Synchronous handler dispatch — identical to TCALL
754                            self.register_stack.push(self.registers.clone());
755                            self.call_stack.push(self.pc);
756                            self.pc = handler_addr;
757                            self.stack.push(msg);
758                        } else {
759                            return Err(VmError::AgentIdInvalid(id));
760                        }
761                    } else {
762                        return Err(VmError::TypeMismatch { expected: "Local AgentRef".into(), found: format!("{:?}", target) });
763                    }
764                }
765                0x25 => { // TjmpEqInt — imm_int, imm_addr → peek, jumps if eq
766                    let mut b = [0u8; 8];
767                    for i in 0..8 { b[i] = self.read_u8()?; }
768                    let target_val = i64::from_le_bytes(b);
769                    let addr = self.read_u16()?;
770                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
771                    let is_eq = match val {
772                        Value::Int(v) => *v == target_val,
773                        Value::Trit(t) => (*t as i8) as i64 == target_val,
774                        _ => false,
775                    };
776                    if is_eq { self.pc = addr as usize; }
777                }
778                0x26 => { // TlessEqual
779                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
780                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
781                    let is_le = match (a.clone(), b.clone()) {
782                        (Value::Int(x), Value::Int(y)) => x <= y,
783                        (Value::Float(x), Value::Float(y)) => x <= y || (x - y).abs() < f64::EPSILON,
784                        (Value::Int(x), Value::Trit(y)) => x <= y as i64,
785                        (Value::Trit(x), Value::Int(y)) => (x as i64) <= y,
786                        (Value::Trit(x), Value::Trit(y)) => (x as i64) <= (y as i64),
787                        _ => false,
788                    };
789                    self.stack.push(Value::Trit(if is_le { Trit::Affirm } else { Trit::Reject }));
790                }
791                0x27 => { // TgreaterEqual
792                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
793                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
794                    let is_ge = match (a.clone(), b.clone()) {
795                        (Value::Int(x), Value::Int(y)) => x >= y,
796                        (Value::Float(x), Value::Float(y)) => x >= y || (x - y).abs() < f64::EPSILON,
797                        (Value::Int(x), Value::Trit(y)) => x >= y as i64,
798                        (Value::Trit(x), Value::Int(y)) => (x as i64) >= y,
799                        (Value::Trit(x), Value::Trit(y)) => (x as i64) >= (y as i64),
800                        _ => false,
801                    };
802                    self.stack.push(Value::Trit(if is_ge { Trit::Affirm } else { Trit::Reject }));
803                }
804                0x28 => { // Tand — min(a, b) in balanced ternary (logical AND)
805                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
806                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
807                    let to_trit = |v: Value| -> Result<Trit, VmError> {
808                        match v {
809                            Value::Trit(t) => Ok(t),
810                            Value::Int(n) if n > 0 => Ok(Trit::Affirm),
811                            Value::Int(0) => Ok(Trit::Tend),
812                            Value::Int(_) => Ok(Trit::Reject),
813                            other => Err(VmError::TypeMismatch { expected: "Trit or Int".into(), found: format!("{:?}", other) }),
814                        }
815                    };
816                    let ta = to_trit(a)?;
817                    let tb = to_trit(b)?;
818                    let result = if (ta as i8) <= (tb as i8) { ta } else { tb };
819                    self.stack.push(Value::Trit(result));
820                }
821                0x29 => { // Tor — max(a, b) in balanced ternary (logical OR)
822                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
823                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
824                    let to_trit = |v: Value| -> Result<Trit, VmError> {
825                        match v {
826                            Value::Trit(t) => Ok(t),
827                            Value::Int(n) if n > 0 => Ok(Trit::Affirm),
828                            Value::Int(0) => Ok(Trit::Tend),
829                            Value::Int(_) => Ok(Trit::Reject),
830                            other => Err(VmError::TypeMismatch { expected: "Trit or Int".into(), found: format!("{:?}", other) }),
831                        }
832                    };
833                    let ta = to_trit(a)?;
834                    let tb = to_trit(b)?;
835                    let result = if (ta as i8) >= (tb as i8) { ta } else { tb };
836                    self.stack.push(Value::Trit(result));
837                }
838                0x2a => { // TjmpEqFloat — peek stack, jump if top Float equals embedded f64 literal
839                    // Emitted by betbc.rs for float match-arm patterns (PARSER-002 fix).
840                    // Layout: [opcode: u8] [target_f64: 8 bytes LE] [jump_addr: u16 LE]
841                    // Peeks the top of stack (does NOT consume it), jumps if value matches
842                    // within machine epsilon.
843                    let mut fb = [0u8; 8];
844                    for i in 0..8 { fb[i] = self.read_u8()?; }
845                    let target_f = f64::from_le_bytes(fb);
846                    let addr = self.read_u16()?;
847                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
848                    if let Value::Float(f) = val {
849                        if (f - target_f).abs() < 1e-9 {
850                            self.pc = addr as usize;
851                        }
852                    }
853                }
854                0x33 => { // Topent — path_str, mode_int → handle_int
855                    let mode = self.stack.pop().ok_or(VmError::StackUnderflow)?;
856                    let path = self.stack.pop().ok_or(VmError::StackUnderflow)?;
857                    if let (Value::String(p), Value::Int(m)) = (path, mode) {
858                        use std::fs::OpenOptions;
859                        let mut options = OpenOptions::new();
860                        match m {
861                            0 => { options.read(true); } // Read
862                            1 => { options.write(true).create(true).truncate(true); } // Write
863                            2 => { options.append(true).create(true); } // Append
864                            _ => return Err(VmError::RuntimeError(format!("Invalid file mode: {m}"))),
865                        }
866                        let file = options.open(&p).map_err(|e| VmError::FileOpenError(e.to_string()))?;
867                        let handle = self.open_files.len();
868                        self.open_files.push(Some(file));
869                        self.stack.push(Value::Int(handle as i64));
870                    } else {
871                        return Err(VmError::TypeMismatch { expected: "String, Int".into(), found: "Unknown".into() });
872                    }
873                }
874                0x34 => { // Treadt — handle_int → trit
875                    let handle_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
876                    if let Value::Int(h) = handle_val {
877                        let h = h as usize;
878                        if h >= self.open_files.len() || self.open_files[h].is_none() {
879                            return Err(VmError::FileNotOpen(h));
880                        }
881                        let file = self.open_files[h].as_mut().unwrap();
882                        let mut buf = [0u8; 1];
883                        use std::io::Read;
884                        match file.read_exact(&mut buf) {
885                            Ok(_) => {
886                                let t = match buf[0] {
887                                    b'+' | b'1' => Trit::Affirm,
888                                    b'-' => Trit::Reject,
889                                    _ => Trit::Tend,
890                                };
891                                self.stack.push(Value::Trit(t));
892                            }
893                            Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
894                                self.stack.push(Value::Trit(Trit::Tend)); // EOF as Tend
895                            }
896                            Err(e) => return Err(VmError::FileReadError(e.to_string())),
897                        }
898                    } else {
899                        return Err(VmError::TypeMismatch { expected: "Int".into(), found: format!("{:?}", handle_val) });
900                    }
901                }
902                0x35 => { // Twritet — handle_int, trit → void
903                    let t_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
904                    let h_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
905                    if let (Value::Int(h), Value::Trit(t)) = (h_val, t_val) {
906                        let h = h as usize;
907                        if h >= self.open_files.len() || self.open_files[h].is_none() {
908                            return Err(VmError::FileNotOpen(h));
909                        }
910                        let file = self.open_files[h].as_mut().unwrap();
911                        let out = match t {
912                            Trit::Affirm => b'+',
913                            Trit::Reject => b'-',
914                            Trit::Tend   => b'0',
915                        };
916                        use std::io::Write;
917                        file.write_all(&[out]).map_err(|e| VmError::FileWriteError(e.to_string()))?;
918                    } else {
919                        return Err(VmError::TypeMismatch { expected: "Int, Trit".into(), found: "Unknown".into() });
920                    }
921                }
922                0x36 => { // Tnodeid — push this node's runtime address as a String
923                    // Defers the binding to runtime so --node-addr is respected.
924                    // Previously, Expr::NodeId emitted a hardcoded "127.0.0.1:7373"
925                    // string at compile time, ignoring vm.set_node_id().
926                    self.stack.push(Value::String(self.node_id.clone()));
927                }
928                0x37 => { // Tassert
929                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
930                    let is_affirm = match val {
931                        Value::Trit(Trit::Affirm) => true,
932                        Value::Int(1) => true,
933                        _ => false,
934                    };
935                    if !is_affirm {
936                        return Err(VmError::AssertionFailed);
937                    }
938                }
939                0x38 => { // TSparseMatmul (@sparseskip)
940                    // Layout: [opcode] [a_rows: u8] [a_cols: u8] [b_cols: u8]
941                    // Pops: A_tensor, B_tensor
942                    // Pushes: Result_tensor
943                    let a_rows = self.read_u8()? as usize;
944                    let a_cols = self.read_u8()? as usize;
945                    let b_cols = self.read_u8()? as usize;
946                    let b_ref = self.stack.pop().ok_or(VmError::StackUnderflow)?;
947                    let a_ref = self.stack.pop().ok_or(VmError::StackUnderflow)?;
948                    
949                    if let (Value::TensorRef(a_idx), Value::TensorRef(b_idx)) = (a_ref, b_ref) {
950                        let (a_data, b_data) = {
951                            let a = self.tensors.get(a_idx).ok_or(VmError::TensorNotAllocated(a_idx))?;
952                            let b = self.tensors.get(b_idx).ok_or(VmError::TensorNotAllocated(b_idx))?;
953                            
954                            let a_data = match &a.data {
955                                TensorData::Trit(v) => v,
956                                _ => return Err(VmError::TypeMismatch { expected: "TritTensor".into(), found: "Other".into() }),
957                            };
958                            let b_data = match &b.data {
959                                TensorData::Trit(v) => v,
960                                _ => return Err(VmError::TypeMismatch { expected: "TritTensor".into(), found: "Other".into() }),
961                            };
962                            (a_data.clone(), b_data.clone())
963                        };
964
965                        let mut result = vec![Trit::Tend; a_rows * b_cols];
966                        let mut skipped = false;
967
968                        for i in 0..a_rows {
969                            for k in 0..a_cols {
970                                let a_val = a_data[i * a_cols + k];
971                                if a_val == Trit::Tend {
972                                    skipped = true;
973                                    continue;
974                                }
975                                for j in 0..b_cols {
976                                    let b_val = b_data[k * b_cols + j];
977                                    if b_val == Trit::Tend { continue; }
978                                    let prod = a_val * b_val;
979                                    let (sum, _) = result[i * b_cols + j] + prod;
980                                    result[i * b_cols + j] = sum;
981                                }
982                            }
983                        }
984
985                        if skipped { self.sparse_dropped = true; }
986                        let res_idx = self.tensors.len();
987                        self.tensors.push(TensorInstance {
988                            data: TensorData::Trit(result),
989                            rows: a_rows,
990                            cols: b_cols,
991                        });
992                        self.stack.push(Value::TensorRef(res_idx));
993                    } else {
994                        return Err(VmError::TypeMismatch { expected: "TensorRef, TensorRef".into(), found: "Unknown".into() });
995                    }
996                }
997                0x40 => { // Tstruct
998                    let num_fields = self.read_u8()? as usize;
999                    let mut fields = std::collections::HashMap::new();
1000                    for _ in 0..num_fields {
1001                        let name_len = self.read_u8()? as usize;
1002                        let mut name_bytes = vec![0u8; name_len];
1003                        for i in 0..name_len { name_bytes[i] = self.read_u8()?; }
1004                        let name = String::from_utf8(name_bytes).unwrap();
1005                        let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1006                        fields.insert(name, val);
1007                    }
1008                    self.stack.push(Value::Struct(fields));
1009                }
1010                0x41 => { // Tfield
1011                    let name_len = self.read_u8()? as usize;
1012                    let mut name_bytes = vec![0u8; name_len];
1013                    for i in 0..name_len { name_bytes[i] = self.read_u8()?; }
1014                    let name = String::from_utf8(name_bytes).unwrap();
1015                    let obj = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1016                    if let Value::Struct(fields) = obj {
1017                        let val = fields.get(&name).cloned().unwrap_or_default();
1018                        self.stack.push(val);
1019                    } else {
1020                        return Err(VmError::TypeMismatch { expected: "Struct".into(), found: format!("{:?}", obj) });
1021                    }
1022                }
1023                0x00 => return Ok(()),
1024                _ => return Err(VmError::InvalidOpcode(opcode)),
1025            }
1026        }
1027        Ok(())
1028    }
1029
1030    fn read_u8(&mut self) -> Result<u8, VmError> {
1031        if self.pc >= self.code.len() { return Err(VmError::PcOutOfBounds(self.pc)); }
1032        let val = self.code[self.pc];
1033        self.pc += 1;
1034        Ok(val)
1035    }
1036
1037    fn read_u16(&mut self) -> Result<u16, VmError> {
1038        if self.pc + 1 >= self.code.len() { return Err(VmError::PcOutOfBounds(self.pc)); }
1039        let val = u16::from_le_bytes([self.code[self.pc], self.code[self.pc + 1]]);
1040        self.pc += 2;
1041        Ok(val)
1042    }
1043
1044    fn read_u32(&mut self) -> Result<u32, VmError> {
1045        if self.pc + 3 >= self.code.len() { return Err(VmError::PcOutOfBounds(self.pc)); }
1046        let val = u32::from_le_bytes([
1047            self.code[self.pc], self.code[self.pc + 1],
1048            self.code[self.pc + 2], self.code[self.pc + 3]
1049        ]);
1050        self.pc += 4;
1051        Ok(val)
1052    }
1053}