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/// Maximum instructions the VM will execute per `run()` call.
22/// Prevents infinite loops from hanging the MCP server process.
23const MAX_STEPS: u64 = 10_000_000;
24
25#[derive(Debug, PartialEq, Eq)]
26pub enum VmError {
27    StackUnderflow,
28    BetFault(BetFault),
29    Halt,
30    InvalidOpcode(u8),
31    InvalidRegister(u8),
32    PcOutOfBounds(usize),
33    TypeMismatch { expected: String, found: String },
34    // ── Tensor errors ────────────────────────────────────────────────────────
35    TensorIndexOutOfBounds { tensor_id: usize, index: usize, size: usize },
36    TensorNotAllocated(usize),
37    // ── Agent errors ─────────────────────────────────────────────────────────
38    AgentTypeNotRegistered(u16),
39    AgentIdInvalid(usize),
40    RuntimeError(String),
41    CallStackOverflow,
42    StepLimitExceeded,
43    // ── File I/O errors ──────────────────────────────────────────────────────
44    FileOpenError(String),
45    FileReadError(String),
46    FileWriteError(String),
47    FileNotOpen(usize),
48    AssertionFailed,
49}
50
51impl fmt::Display for VmError {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        match self {
54            VmError::StackUnderflow =>
55                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"),
56            VmError::BetFault(fault) =>
57                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"),
58            VmError::Halt =>
59                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"),
60            VmError::InvalidOpcode(op) =>
61                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"),
62            VmError::InvalidRegister(reg) =>
63                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"),
64            VmError::PcOutOfBounds(pc) =>
65                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"),
66            VmError::TypeMismatch { expected, found } =>
67                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"),
68            VmError::TensorIndexOutOfBounds { tensor_id, index, size } =>
69                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"),
70            VmError::TensorNotAllocated(idx) =>
71                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"),
72            VmError::AgentTypeNotRegistered(type_id) =>
73                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"),
74            VmError::AgentIdInvalid(id) =>
75                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"),
76            VmError::RuntimeError(msg) =>
77                write!(f, "[BET-012] Runtime error: {msg}"),
78            VmError::CallStackOverflow =>
79                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"),
80            VmError::StepLimitExceeded =>
81                write!(f, "[BET-014] Step limit ({MAX_STEPS}) exceeded — program did not halt within the allowed instruction budget. Check for infinite loops.\n          → details: stdlib/errors/BET-014.tern  |  ternlang errors BET-014"),
82            VmError::FileOpenError(e) =>
83                write!(f, "[IO-001] File open error: {e}"),
84            VmError::FileReadError(e) =>
85                write!(f, "[IO-002] File read error: {e}"),
86            VmError::FileWriteError(e) =>
87                write!(f, "[IO-003] File write error: {e}"),
88            VmError::FileNotOpen(id) =>
89                write!(f, "[IO-004] File handle {id} is not open or was closed."),
90            VmError::AssertionFailed =>
91                write!(f, "[ASSERT-001] Assertion failed: an assert() condition evaluated to reject or tend."),
92        }
93    }
94}
95
96#[derive(Debug, Clone, PartialEq)]
97pub enum Value {
98    Trit(Trit),
99    Int(i64),
100    Float(f64),
101    String(String),
102    TensorRef(usize),
103    TensorView {
104        tensor_id: usize,
105        offset: usize,
106        length: usize,
107        stride: usize,
108    },
109    AgentRef(usize, Option<String>),
110    Struct(std::collections::HashMap<String, Value>),
111}
112
113impl Default for Value {
114    fn default() -> Self {
115        Value::Trit(Trit::Tend)
116    }
117}
118
119enum TensorData {
120    Trit(Vec<Trit>),
121    PackedTrit(Vec<u8>, usize),
122    Float(Vec<f64>),
123    Int(Vec<i64>),
124}
125
126impl TensorData {
127    fn len(&self) -> usize {
128        match self {
129            TensorData::Trit(v) => v.len(),
130            TensorData::PackedTrit(_, len) => *len,
131            TensorData::Float(v) => v.len(),
132            TensorData::Int(v) => v.len(),
133        }
134    }
135}
136
137struct TensorInstance {
138    data: TensorData,
139    rows: usize,
140    cols: usize,
141}
142
143struct AgentInstance {
144    handler_addr: usize,
145    mailbox: std::collections::VecDeque<Value>,
146}
147
148pub struct BetVm {
149    /// Dynamic register file — grows on demand so programs with > 27 locals work correctly
150    /// instead of silently dropping stores and returning zero on reads.
151    registers: Vec<Value>,
152    register_stack: Vec<Vec<Value>>,
153    carry_reg: Trit,
154    stack: Vec<Value>,
155    call_stack: Vec<usize>,
156    tensors: Vec<TensorInstance>,
157    agents: Vec<AgentInstance>,
158    agent_types: std::collections::HashMap<u16, usize>,
159    pc: usize,
160    code: Vec<u8>,
161    node_id: String,
162    pub sparse_dropped: bool,
163    remote: Option<Arc<dyn RemoteTransport>>,
164    open_files: Vec<Option<std::fs::File>>,
165    bindings: std::collections::HashMap<usize, Value>,
166    _instructions_count: u64,
167    pub print_log: Vec<String>,
168    /// Structured XAI decision trace, populated by the 0x60 TEXPLAIN opcode.
169    pub trace_log: Vec<String>,
170}
171
172impl BetVm {
173    pub fn new(code: Vec<u8>) -> Self {
174        Self {
175            registers: vec![Value::default(); 27],
176            register_stack: Vec::new(),
177            carry_reg: Trit::Tend,
178            stack: Vec::new(),
179            call_stack: Vec::new(),
180            tensors: Vec::new(),
181            agents: Vec::new(),
182            agent_types: std::collections::HashMap::new(),
183            pc: 0,
184            code,
185            node_id: "127.0.0.1".into(),
186            sparse_dropped: false,
187            remote: None,
188            open_files: Vec::new(),
189            bindings: std::collections::HashMap::new(),
190            _instructions_count: 0,
191            print_log: Vec::new(),
192            trace_log: Vec::new(),
193        }
194    }
195
196    /// Drain all lines printed by `print()`/`println()` during execution.
197    pub fn take_output(&mut self) -> Vec<String> {
198        std::mem::take(&mut self.print_log)
199    }
200
201    /// Drain the XAI decision trace accumulated by `explain()` calls during execution.
202    pub fn take_trace_log(&mut self) -> Vec<String> {
203        std::mem::take(&mut self.trace_log)
204    }
205
206    pub fn set_node_id(&mut self, node_id: String) {
207        self.node_id = node_id;
208    }
209
210    pub fn set_remote(&mut self, transport: Arc<dyn RemoteTransport>) {
211        self.remote = Some(transport);
212    }
213
214    pub fn register_agent_type(&mut self, type_id: u16, handler_addr: usize) {
215        self.agent_types.insert(type_id, handler_addr);
216    }
217
218    pub fn peek_stack(&self) -> Option<Value> {
219        self.stack.last().cloned()
220    }
221
222    pub fn get_registers(&self) -> Vec<Value> {
223        self.registers.clone()
224    }
225
226    pub fn get_register(&self, reg: u8) -> Value {
227        self.registers.get(reg as usize).cloned().unwrap_or_default()
228    }
229
230    pub fn node_id(&self) -> &str {
231        &self.node_id
232    }
233
234    pub fn run(&mut self) -> Result<(), VmError> {
235        loop {
236            if self.pc >= self.code.len() { break; }
237            self._instructions_count += 1;
238            if self._instructions_count > MAX_STEPS {
239                return Err(VmError::StepLimitExceeded);
240            }
241            let opcode = self.code[self.pc];
242            self.pc += 1;
243
244            match opcode {
245                0x01 => { // Tpush
246                    let packed = self.read_u8()?;
247                    let trits = unpack_trits(&[packed], 1).map_err(VmError::BetFault)?;
248                    self.stack.push(Value::Trit(trits[0]));
249                }
250                0x02 => { // Tadd
251                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
252                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
253                    match (a.clone(), b.clone()) {
254                        (Value::Trit(av), Value::Trit(bv)) => {
255                            let (sum, carry) = av + bv;
256                            self.stack.push(Value::Trit(sum));
257                            self.carry_reg = carry;
258                        }
259                        (Value::Int(av), Value::Int(bv)) => self.stack.push(Value::Int(av + bv)),
260                        (Value::Float(av), Value::Float(bv)) => self.stack.push(Value::Float(av + bv)),
261                        (Value::Int(av), Value::Trit(bv)) => self.stack.push(Value::Int(av + bv as i64)),
262                        (Value::Trit(av), Value::Int(bv)) => self.stack.push(Value::Int(av as i64 + bv)),
263                        (Value::Float(av), Value::Trit(bv)) => self.stack.push(Value::Float(av + (bv as i8 as f64))),
264                        (Value::Trit(av), Value::Float(bv)) => self.stack.push(Value::Float((av as i8 as f64) + bv)),
265                        (Value::Float(av), Value::Int(bv)) => self.stack.push(Value::Float(av + (bv as f64))),
266                        (Value::Int(av), Value::Float(bv)) => self.stack.push(Value::Float((av as f64) + bv)),
267                        // PARSER-STR-001: string concatenation via + operator
268                        (Value::String(av), Value::String(bv)) => self.stack.push(Value::String(av + &bv)),
269                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
270                    }
271                }
272                0x03 => { // Tmul
273                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
274                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
275                    match (a.clone(), b.clone()) {
276                        (Value::Trit(av), Value::Trit(bv)) => self.stack.push(Value::Trit(av * bv)),
277                        (Value::Int(av), Value::Int(bv)) => self.stack.push(Value::Int(av * bv)),
278                        (Value::Float(av), Value::Float(bv)) => self.stack.push(Value::Float(av * bv)),
279                        (Value::Int(av), Value::Trit(bv)) => self.stack.push(Value::Int(av * bv as i64)),
280                        (Value::Trit(av), Value::Int(bv)) => self.stack.push(Value::Int(av as i64 * bv)),
281                        (Value::Float(av), Value::Trit(bv)) => self.stack.push(Value::Float(av * (bv as i8 as f64))),
282                        (Value::Trit(av), Value::Float(bv)) => self.stack.push(Value::Float((av as i8 as f64) * bv)),
283                        (Value::Float(av), Value::Int(bv)) => self.stack.push(Value::Float(av * (bv as f64))),
284                        (Value::Int(av), Value::Float(bv)) => self.stack.push(Value::Float((av as f64) * bv)),
285                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
286                    }
287                }
288                0x04 => { // Tneg
289                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
290                    match a.clone() {
291                        Value::Trit(av) => self.stack.push(Value::Trit(-av)),
292                        Value::Int(av) => self.stack.push(Value::Int(-av)),
293                        Value::Float(av) => self.stack.push(Value::Float(-av)),
294                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", a) }),
295                    }
296                }
297                0x05 => { // TjmpPos — jumps if top is positive (Affirm / any int > 0)
298                    let addr = self.read_u16()?;
299                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
300                    let is_pos = match val {
301                        Value::Trit(Trit::Affirm) => true,
302                        Value::Int(v) => *v > 0,
303                        Value::Float(f) => *f > 0.0,
304                        _ => false,
305                    };
306                    if is_pos { self.pc = addr as usize; }
307                }
308                0x06 => { // TjmpZero — jumps if top is zero (Tend / 0)
309                    let addr = self.read_u16()?;
310                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
311                    let is_zero = match val {
312                        Value::Trit(Trit::Tend) => true,
313                        Value::Int(v) => *v == 0,
314                        Value::Float(f) => *f == 0.0,
315                        _ => false,
316                    };
317                    if is_zero { self.pc = addr as usize; }
318                }
319                0x07 => { // TjmpNeg — jumps if top is negative (Reject / any int < 0)
320                    let addr = self.read_u16()?;
321                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
322                    let is_neg = match val {
323                        Value::Trit(Trit::Reject) => true,
324                        Value::Int(v) => *v < 0,
325                        Value::Float(f) => *f < 0.0,
326                        _ => false,
327                    };
328                    if is_neg { self.pc = addr as usize; }
329                }
330                0x08 => { // Tstore
331                    let reg = self.read_u8()? as usize;
332                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
333                    self.bindings.remove(&reg);
334                    if reg >= self.registers.len() { self.registers.resize(reg + 1, Value::default()); }
335                    self.registers[reg] = val;
336                }
337                0x09 => { // Tload
338                    let reg = self.read_u8()? as usize;
339                    let val = self.bindings.get(&reg).cloned().unwrap_or_else(|| {
340                        if reg >= self.registers.len() { self.registers.resize(reg + 1, Value::default()); }
341                        self.registers[reg].clone()
342                    });
343                    self.stack.push(val);
344                }
345                0x0a => { // Tdup
346                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
347                    self.stack.push(val.clone());
348                }
349                0x0b => { // Tjmp
350                    let addr = self.read_u16()?;
351                    self.pc = addr as usize;
352                }
353                0x0c => { // Tpop
354                    self.stack.pop().ok_or(VmError::StackUnderflow)?;
355                }
356                0x0e => { // Tcons
357                    let b_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
358                    let a_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
359                    
360                    let a = match a_val {
361                        Value::Trit(t) => t,
362                        Value::Int(v) if v == 1 => Trit::Affirm,
363                        Value::Int(v) if v == 0 => Trit::Tend,
364                        Value::Int(v) if v == -1 => Trit::Reject,
365                        _ => return Err(VmError::TypeMismatch { expected: "Trit or Int(-1..1)".into(), found: format!("{:?}", a_val) }),
366                    };
367                    let b = match b_val {
368                        Value::Trit(t) => t,
369                        Value::Int(v) if v == 1 => Trit::Affirm,
370                        Value::Int(v) if v == 0 => Trit::Tend,
371                        Value::Int(v) if v == -1 => Trit::Reject,
372                        _ => return Err(VmError::TypeMismatch { expected: "Trit or Int(-1..1)".into(), found: format!("{:?}", b_val) }),
373                    };
374
375                    let result = match (a, b) {
376                        (Trit::Affirm, Trit::Affirm) => Trit::Affirm,
377                        (Trit::Reject, Trit::Reject) => Trit::Reject,
378                        (Trit::Tend, x) => x,
379                        (x, Trit::Tend) => x,
380                        _ => Trit::Tend,
381                    };
382                    self.stack.push(Value::Trit(result));
383                }
384                0x0f => { // Talloc (trit tensor)
385                    let rows = self.read_u32()? as usize;
386                    let cols = self.read_u32()? as usize;
387                    let size = rows * cols;
388                    let idx = self.tensors.len();
389                    self.tensors.push(TensorInstance {
390                        data: TensorData::Trit(vec![Trit::Tend; size]),
391                        rows,
392                        cols,
393                    });
394                    self.stack.push(Value::TensorRef(idx));
395                }
396                0x3c => { // Talloc_Int (int tensor)
397                    let rows = self.read_u32()? as usize;
398                    let cols = self.read_u32()? as usize;
399                    let size = rows * cols;
400                    let idx = self.tensors.len();
401                    self.tensors.push(TensorInstance {
402                        data: TensorData::Int(vec![0i64; size]),
403                        rows,
404                        cols,
405                    });
406                    self.stack.push(Value::TensorRef(idx));
407                }
408                0x3d => { // Talloc_Float (float tensor)
409                    let rows = self.read_u32()? as usize;
410                    let cols = self.read_u32()? as usize;
411                    let size = rows * cols;
412                    let idx = self.tensors.len();
413                    self.tensors.push(TensorInstance {
414                        data: TensorData::Float(vec![0.0f64; size]),
415                        rows,
416                        cols,
417                    });
418                    self.stack.push(Value::TensorRef(idx));
419                }
420                0x10 => { // Tcall
421                    if self.call_stack.len() >= MAX_CALL_DEPTH {
422                        return Err(VmError::CallStackOverflow);
423                    }
424                    let addr = self.read_u16()? as usize;
425                    self.register_stack.push(self.registers.clone());
426                    self.call_stack.push(self.pc);
427                    self.pc = addr;
428                }
429                0x11 => { // Tret
430                    if let Some(prev) = self.register_stack.pop() {
431                        self.registers = prev;
432                    }
433                    match self.call_stack.pop() {
434                        Some(ret) => self.pc = ret,
435                        None => return Ok(()),
436                    }
437                }
438                0x14 => { // Tless
439                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
440                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
441                    match (a.clone(), b.clone()) {
442                        (Value::Int(x), Value::Int(y)) => {
443                            let r = if x < y { Trit::Affirm } else if x == y { Trit::Tend } else { Trit::Reject };
444                            self.stack.push(Value::Trit(r));
445                        }
446                        (Value::Float(x), Value::Float(y)) => {
447                            let r = if x < y { Trit::Affirm } else if (x - y).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
448                            self.stack.push(Value::Trit(r));
449                        }
450                        (Value::Int(x), Value::Trit(y)) => {
451                            let bv = y as i64;
452                            let r = if x < bv { Trit::Affirm } else if x == bv { Trit::Tend } else { Trit::Reject };
453                            self.stack.push(Value::Trit(r));
454                        }
455                        (Value::Trit(x), Value::Int(y)) => {
456                            let av = x as i64;
457                            let r = if av < y { Trit::Affirm } else if av == y { Trit::Tend } else { Trit::Reject };
458                            self.stack.push(Value::Trit(r));
459                        }
460                        (Value::Int(av), Value::Float(bv)) => {
461                            let a_val = av as f64;
462                            let r = if a_val < bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
463                            self.stack.push(Value::Trit(r));
464                        }
465                        (Value::Float(av), Value::Int(bv)) => {
466                            let b_val = bv as f64;
467                            let r = if av < b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
468                            self.stack.push(Value::Trit(r));
469                        }
470                        (Value::Trit(av), Value::Float(bv)) => {
471                            let a_val = av as i8 as f64;
472                            let r = if a_val < bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
473                            self.stack.push(Value::Trit(r));
474                        }
475                        (Value::Float(av), Value::Trit(bv)) => {
476                            let b_val = bv as i8 as f64;
477                            let r = if av < b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
478                            self.stack.push(Value::Trit(r));
479                        }
480                        (Value::Trit(x), Value::Trit(y)) => {
481                            let av = x as i64;
482                            let bv = y as i64;
483                            let r = if av < bv { Trit::Affirm } else if av == bv { Trit::Tend } else { Trit::Reject };
484                            self.stack.push(Value::Trit(r));
485                        }
486                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
487                    }
488                }
489                0x15 => { // Tgreater
490                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
491                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
492                    match (a.clone(), b.clone()) {
493                        (Value::Int(x), Value::Int(y)) => {
494                            let r = if x > y { Trit::Affirm } else if x == y { Trit::Tend } else { Trit::Reject };
495                            self.stack.push(Value::Trit(r));
496                        }
497                        (Value::Float(x), Value::Float(y)) => {
498                            let r = if x > y { Trit::Affirm } else if (x - y).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
499                            self.stack.push(Value::Trit(r));
500                        }
501                        (Value::Int(x), Value::Trit(y)) => {
502                            let bv = y as i64;
503                            let r = if x > bv { Trit::Affirm } else if x == bv { Trit::Tend } else { Trit::Reject };
504                            self.stack.push(Value::Trit(r));
505                        }
506                        (Value::Trit(x), Value::Int(y)) => {
507                            let av = x as i64;
508                            let r = if av > y { Trit::Affirm } else if av == y { Trit::Tend } else { Trit::Reject };
509                            self.stack.push(Value::Trit(r));
510                        }
511                        (Value::Int(av), Value::Float(bv)) => {
512                            let a_val = av as f64;
513                            let r = if a_val > bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
514                            self.stack.push(Value::Trit(r));
515                        }
516                        (Value::Float(av), Value::Int(bv)) => {
517                            let b_val = bv as f64;
518                            let r = if av > b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
519                            self.stack.push(Value::Trit(r));
520                        }
521                        (Value::Trit(av), Value::Float(bv)) => {
522                            let a_val = av as i8 as f64;
523                            let r = if a_val > bv { Trit::Affirm } else if (a_val - bv).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
524                            self.stack.push(Value::Trit(r));
525                        }
526                        (Value::Float(av), Value::Trit(bv)) => {
527                            let b_val = bv as i8 as f64;
528                            let r = if av > b_val { Trit::Affirm } else if (av - b_val).abs() < f64::EPSILON { Trit::Tend } else { Trit::Reject };
529                            self.stack.push(Value::Trit(r));
530                        }
531                        (Value::Trit(x), Value::Trit(y)) => {
532                            let av = x as i64;
533                            let bv = y as i64;
534                            let r = if av > bv { Trit::Affirm } else if av == bv { Trit::Tend } else { Trit::Reject };
535                            self.stack.push(Value::Trit(r));
536                        }
537                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a, b)) }),
538                    }
539                }
540                0x16 => { // Teq
541                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
542                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
543                    let is_eq = match (a.clone(), b.clone()) {
544                        (Value::Int(av), Value::Trit(bv)) => av == bv as i64,
545                        (Value::Trit(av), Value::Int(bv)) => av as i64 == bv,
546                        (Value::Float(av), Value::Float(bv)) => (av - bv).abs() < f64::EPSILON,
547                        (Value::Float(av), Value::Trit(bv)) => (av - (bv as i8 as f64)).abs() < f64::EPSILON,
548                        (Value::Trit(av), Value::Float(bv)) => ((av as i8 as f64) - bv).abs() < f64::EPSILON,
549                        (Value::Float(av), Value::Int(bv)) => (av - (bv as f64)).abs() < f64::EPSILON,
550                        (Value::Int(av), Value::Float(bv)) => ((av as f64) - bv).abs() < f64::EPSILON,
551                        _ => a == b,
552                    };
553                    let r = if is_eq { Trit::Affirm } else { Trit::Reject };
554                    self.stack.push(Value::Trit(r));
555                }
556                0x17 => { // TpushInt
557                    let mut b = [0u8; 8];
558                    for i in 0..8 { b[i] = self.read_u8()?; }
559                    self.stack.push(Value::Int(i64::from_le_bytes(b)));
560                }
561                0x18 => { // TaddInt
562                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
563                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
564                    match (a.clone(), b.clone()) {
565                        (Value::Int(x), Value::Int(y)) => self.stack.push(Value::Int(x + y)),
566                        _ => return Err(VmError::TypeMismatch { expected: "Int".into(), found: format!("{:?}", (a, b)) }),
567                    }
568                }
569                0x19 => { // TpushFloat
570                    let mut b = [0u8; 8];
571                    for i in 0..8 { b[i] = self.read_u8()?; }
572                    self.stack.push(Value::Float(f64::from_le_bytes(b)));
573                }
574                0x1e => { // Tdiv
575                    let b_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
576                    let a_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
577                    match (a_val.clone(), b_val.clone()) {
578                        (Value::Int(av), Value::Int(bv)) => {
579                            if bv == 0 { return Err(VmError::RuntimeError("Division by zero".into())); }
580                            self.stack.push(Value::Int(av / bv));
581                        }
582                        (Value::Float(av), Value::Float(bv)) => {
583                            if bv == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
584                            self.stack.push(Value::Float(av / bv));
585                        }
586                        (Value::Int(av), Value::Trit(bv)) => {
587                            let b = bv as i64;
588                            if b == 0 { return Err(VmError::RuntimeError("Division by zero".into())); }
589                            self.stack.push(Value::Int(av / b));
590                        }
591                        (Value::Trit(av), Value::Int(bv)) => {
592                            if bv == 0 { return Err(VmError::RuntimeError("Division by zero".into())); }
593                            self.stack.push(Value::Int(av as i64 / bv));
594                        }
595                        (Value::Float(av), Value::Trit(bv)) => {
596                            let b = bv as i8 as f64;
597                            if b == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
598                            self.stack.push(Value::Float(av / b));
599                        }
600                        (Value::Trit(av), Value::Float(bv)) => {
601                            if bv == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
602                            self.stack.push(Value::Float(av as i8 as f64 / bv));
603                        }
604                        (Value::Float(av), Value::Int(bv)) => {
605                            let b = bv as f64;
606                            if b == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
607                            self.stack.push(Value::Float(av / b));
608                        }
609                        (Value::Int(av), Value::Float(bv)) => {
610                            if bv == 0.0 { return Err(VmError::RuntimeError("Division by zero".into())); }
611                            self.stack.push(Value::Float(av as f64 / bv));
612                        }
613                        _ => return Err(VmError::TypeMismatch { expected: "Numeric".into(), found: format!("{:?}", (a_val, b_val)) }),
614                    }
615                }
616                0x1f => { // Tmod
617                    let b_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
618                    let a_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
619                    match (a_val.clone(), b_val.clone()) {
620                        (Value::Int(av), Value::Int(bv)) => {
621                            if bv == 0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
622                            self.stack.push(Value::Int(av % bv));
623                        }
624                        (Value::Int(av), Value::Trit(bv)) => {
625                            let b = bv as i64;
626                            if b == 0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
627                            self.stack.push(Value::Int(av % b));
628                        }
629                        (Value::Trit(av), Value::Int(bv)) => {
630                            if bv == 0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
631                            self.stack.push(Value::Int(av as i64 % bv));
632                        }
633                        (Value::Float(av), Value::Float(bv)) => {
634                             if bv == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
635                             self.stack.push(Value::Float(av % bv));
636                        }
637                        (Value::Float(av), Value::Trit(bv)) => {
638                             let b = bv as i8 as f64;
639                             if b == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
640                             self.stack.push(Value::Float(av % b));
641                        }
642                        (Value::Trit(av), Value::Float(bv)) => {
643                             if bv == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
644                             self.stack.push(Value::Float(av as i8 as f64 % bv));
645                        }
646                        (Value::Float(av), Value::Int(bv)) => {
647                             let b = bv as f64;
648                             if b == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
649                             self.stack.push(Value::Float(av % b));
650                        }
651                        (Value::Int(av), Value::Float(bv)) => {
652                             if bv == 0.0 { return Err(VmError::RuntimeError("Modulo by zero".into())); }
653                             self.stack.push(Value::Float(av as f64 % bv));
654                        }
655                        _ => return Err(VmError::TypeMismatch { expected: "Int or Trit".into(), found: format!("{:?}", (a_val, b_val)) }),
656                    }
657                }
658                0x20 => { // Tprint
659                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
660                    let line = match &val {
661                        Value::Trit(t) => format!("{}", t),
662                        Value::Int(i) => format!("{}", i),
663                        Value::Float(f) => format!("{}", f),
664                        Value::String(s) => s.clone(),
665                        Value::TensorRef(idx) => format!("TensorRef({})", idx),
666                        Value::TensorView { tensor_id, offset, length, .. } => format!("TensorView({}[{}..{}])", tensor_id, offset, offset + length),
667                        Value::AgentRef(idx, addr) => format!("AgentRef({}, {:?})", idx, addr),
668                        Value::Struct(fields) => format!("Struct({:?})", fields),
669                    };
670                    println!("{}", line);
671                    self.print_log.push(line);
672                }
673                0x21 => { // TpushString
674                    let len = self.read_u16()? as usize;
675                    let mut bytes = vec![0u8; len];
676                    for i in 0..len { bytes[i] = self.read_u8()?; }
677                    let s = String::from_utf8(bytes).map_err(|_| VmError::RuntimeError("Invalid UTF-8 string".into()))?;
678                    self.stack.push(Value::String(s));
679                }
680                0x22 => { // Tidx
681                    let col = self.stack.pop().ok_or(VmError::StackUnderflow)?;
682                    let row = self.stack.pop().ok_or(VmError::StackUnderflow)?;
683                    let rf = self.stack.pop().ok_or(VmError::StackUnderflow)?;
684                    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) }) };
685                    // String indexing: s[i] → single-char string (fixes BET-007)
686                    if let Value::String(s) = &rf {
687                        let chars: Vec<char> = s.chars().collect();
688                        if r < 0 || r as usize >= chars.len() {
689                            return Err(VmError::RuntimeError(format!(
690                                "[BET-007] String index {} out of bounds (len={}). Use len(s) to guard.",
691                                r, chars.len()
692                            )));
693                        }
694                        self.stack.push(Value::String(chars[r as usize].to_string()));
695                    } else {
696                        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) }) };
697                        let (idx, pos) = self.get_pos(&rf, r, c)?;
698                        let tensor = &self.tensors[idx];
699                        let data_len = tensor.data.len();
700                        if pos >= data_len {
701                            return Err(VmError::TensorIndexOutOfBounds { tensor_id: idx, index: pos, size: data_len });
702                        }
703                        let pushed = match &tensor.data {
704                            TensorData::Trit(v) => Value::Trit(v[pos]),
705                            TensorData::PackedTrit(v, _) => {
706                                let byte_idx = pos / 5;
707                                let trit_idx = pos % 5;
708                                let trits = crate::trit::unpack_5_trits(v[byte_idx]);
709                                Value::Trit(trits[trit_idx])
710                            }
711                            TensorData::Float(v) => Value::Float(v[pos]),
712                            TensorData::Int(v) => Value::Int(v[pos]),
713                        };
714                        self.stack.push(pushed);
715                    }
716                }
717                0x23 => { // Tset
718                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
719                    let col = self.stack.pop().ok_or(VmError::StackUnderflow)?;
720                    let row = self.stack.pop().ok_or(VmError::StackUnderflow)?;
721                    let rf = self.stack.pop().ok_or(VmError::StackUnderflow)?;
722                    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) }) };
723                    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) }) };
724                    
725                    let (idx, pos) = self.get_pos(&rf, r, c)?;
726                    let tensor = &mut self.tensors[idx];
727                    let data_len = tensor.data.len();
728                    if pos >= data_len { return Err(VmError::TensorIndexOutOfBounds { tensor_id: idx, index: pos, size: data_len }); }
729                    match (&mut tensor.data, val.clone()) {
730                        (TensorData::Trit(v), Value::Trit(t)) => v[pos] = t,
731                        (TensorData::Trit(v), Value::Int(i)) => v[pos] = if i > 0 { Trit::Affirm } else if i < 0 { Trit::Reject } else { Trit::Tend },
732                        (TensorData::PackedTrit(v, _), val_v) => {
733                            let byte_idx = pos / 5;
734                            let trit_idx = pos % 5;
735                            let mut trits = crate::trit::unpack_5_trits(v[byte_idx]);
736                            trits[trit_idx] = match val_v {
737                                Value::Trit(t) => t,
738                                Value::Int(i) => if i > 0 { Trit::Affirm } else if i < 0 { Trit::Reject } else { Trit::Tend },
739                                _ => return Err(VmError::TypeMismatch { expected: "Trit or Int".into(), found: format!("{:?}", val_v) }),
740                            };
741                            v[byte_idx] = crate::trit::pack_5_trits(trits);
742                        }
743                        (TensorData::Float(v), Value::Float(f)) => v[pos] = f,
744                        (TensorData::Float(v), Value::Int(i)) => v[pos] = i as f64,
745                        (TensorData::Int(v), Value::Int(i)) => v[pos] = i,
746                        (TensorData::Int(v), Value::Float(f)) => v[pos] = f as i64,
747                        (TensorData::Int(v), Value::Trit(t)) => v[pos] = t as i64,
748                        _ => return Err(VmError::TypeMismatch { expected: "compatible value for tensor type".into(), found: format!("{:?}", val) }),
749                    }
750                }
751                0x24 => { // Tshape — TensorRef/View → (rows, cols); String → (len, 1)
752                    let rf = self.stack.pop().ok_or(VmError::StackUnderflow)?;
753                    match rf {
754                        Value::TensorRef(idx) => {
755                            if idx >= self.tensors.len() { return Err(VmError::TensorNotAllocated(idx)); }
756                            let tensor = &self.tensors[idx];
757                            self.stack.push(Value::Int(tensor.rows as i64));
758                            self.stack.push(Value::Int(tensor.cols as i64));
759                        }
760                        Value::TensorView { length, .. } => {
761                            self.stack.push(Value::Int(length as i64));
762                            self.stack.push(Value::Int(1));
763                        }
764                        Value::String(s) => {
765                            let n = s.chars().count() as i64;
766                            self.stack.push(Value::Int(n));
767                            self.stack.push(Value::Int(1));
768                        }
769                        _ => return Err(VmError::TypeMismatch { expected: "TensorRef, TensorView, or String".into(), found: format!("{:?}", rf) }),
770                    }
771                }
772                0x30 => { // Tspawn — (type_id) → AgentRef
773                    let type_id = self.read_u16()?;
774                    if let Some(&handler_addr) = self.agent_types.get(&type_id) {
775                        let id = self.agents.len();
776                        self.agents.push(AgentInstance { handler_addr, mailbox: Default::default() });
777                        self.stack.push(Value::AgentRef(id, None));
778                    } else {
779                        return Err(VmError::AgentTypeNotRegistered(type_id));
780                    }
781                }
782                0x31 => { // Tsend — msg, target → void
783                    let msg = self.stack.pop().ok_or(VmError::StackUnderflow)?;
784                    let target = self.stack.pop().ok_or(VmError::StackUnderflow)?;
785                    if let Value::AgentRef(id, None) = target {
786                        if id < self.agents.len() {
787                            self.agents[id].mailbox.push_back(msg);
788                        } else {
789                            return Err(VmError::AgentIdInvalid(id));
790                        }
791                    } else {
792                        return Err(VmError::TypeMismatch { expected: "Local AgentRef".into(), found: format!("{:?}", target) });
793                    }
794                }
795                0x32 => { // Tawait — target → result
796                    let target = self.stack.pop().ok_or(VmError::StackUnderflow)?;
797                    if let Value::AgentRef(id, None) = target {
798                        if id < self.agents.len() {
799                            if self.call_stack.len() >= MAX_CALL_DEPTH {
800                                return Err(VmError::CallStackOverflow);
801                            }
802                            let handler_addr = self.agents[id].handler_addr;
803                            let msg = self.agents[id].mailbox.pop_front().unwrap_or(Value::default());
804                            // Synchronous handler dispatch — identical to TCALL
805                            self.register_stack.push(self.registers.clone());
806                            self.call_stack.push(self.pc);
807                            self.pc = handler_addr;
808                            self.stack.push(msg);
809                        } else {
810                            return Err(VmError::AgentIdInvalid(id));
811                        }
812                    } else {
813                        return Err(VmError::TypeMismatch { expected: "Local AgentRef".into(), found: format!("{:?}", target) });
814                    }
815                }
816                0x25 => { // TjmpEqInt — imm_int, imm_addr → peek, jumps if eq
817                    let mut b = [0u8; 8];
818                    for i in 0..8 { b[i] = self.read_u8()?; }
819                    let target_val = i64::from_le_bytes(b);
820                    let addr = self.read_u16()?;
821                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
822                    let is_eq = match val {
823                        Value::Int(v) => *v == target_val,
824                        Value::Trit(t) => (*t as i8) as i64 == target_val,
825                        _ => false,
826                    };
827                    if is_eq { self.pc = addr as usize; }
828                }
829                0x26 => { // TlessEqual
830                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
831                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
832                    let is_le = match (a.clone(), b.clone()) {
833                        (Value::Int(x), Value::Int(y)) => x <= y,
834                        (Value::Float(x), Value::Float(y)) => x <= y || (x - y).abs() < f64::EPSILON,
835                        (Value::Int(x), Value::Trit(y)) => x <= y as i64,
836                        (Value::Trit(x), Value::Int(y)) => (x as i64) <= y,
837                        (Value::Trit(x), Value::Trit(y)) => (x as i64) <= (y as i64),
838                        _ => false,
839                    };
840                    self.stack.push(Value::Trit(if is_le { Trit::Affirm } else { Trit::Reject }));
841                }
842                0x27 => { // TgreaterEqual
843                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
844                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
845                    let is_ge = match (a.clone(), b.clone()) {
846                        (Value::Int(x), Value::Int(y)) => x >= y,
847                        (Value::Float(x), Value::Float(y)) => x >= y || (x - y).abs() < f64::EPSILON,
848                        (Value::Int(x), Value::Trit(y)) => x >= y as i64,
849                        (Value::Trit(x), Value::Int(y)) => (x as i64) >= y,
850                        (Value::Trit(x), Value::Trit(y)) => (x as i64) >= (y as i64),
851                        _ => false,
852                    };
853                    self.stack.push(Value::Trit(if is_ge { Trit::Affirm } else { Trit::Reject }));
854                }
855                0x28 => { // Tand — min(a, b) in balanced ternary (logical AND)
856                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
857                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
858                    let to_trit = |v: Value| -> Result<Trit, VmError> {
859                        match v {
860                            Value::Trit(t) => Ok(t),
861                            Value::Int(n) if n > 0 => Ok(Trit::Affirm),
862                            Value::Int(0) => Ok(Trit::Tend),
863                            Value::Int(_) => Ok(Trit::Reject),
864                            other => Err(VmError::TypeMismatch { expected: "Trit or Int".into(), found: format!("{:?}", other) }),
865                        }
866                    };
867                    let ta = to_trit(a)?;
868                    let tb = to_trit(b)?;
869                    let result = if (ta as i8) <= (tb as i8) { ta } else { tb };
870                    self.stack.push(Value::Trit(result));
871                }
872                0x29 => { // Tor — max(a, b) in balanced ternary (logical OR)
873                    let b = self.stack.pop().ok_or(VmError::StackUnderflow)?;
874                    let a = self.stack.pop().ok_or(VmError::StackUnderflow)?;
875                    let to_trit = |v: Value| -> Result<Trit, VmError> {
876                        match v {
877                            Value::Trit(t) => Ok(t),
878                            Value::Int(n) if n > 0 => Ok(Trit::Affirm),
879                            Value::Int(0) => Ok(Trit::Tend),
880                            Value::Int(_) => Ok(Trit::Reject),
881                            other => Err(VmError::TypeMismatch { expected: "Trit or Int".into(), found: format!("{:?}", other) }),
882                        }
883                    };
884                    let ta = to_trit(a)?;
885                    let tb = to_trit(b)?;
886                    let result = if (ta as i8) >= (tb as i8) { ta } else { tb };
887                    self.stack.push(Value::Trit(result));
888                }
889                0x2a => { // TjmpEqFloat — peek stack, jump if top Float equals embedded f64 literal
890                    // Emitted by betbc.rs for float match-arm patterns (PARSER-002 fix).
891                    // Layout: [opcode: u8] [target_f64: 8 bytes LE] [jump_addr: u16 LE]
892                    // Peeks the top of stack (does NOT consume it), jumps if value matches
893                    // within machine epsilon.
894                    let mut fb = [0u8; 8];
895                    for i in 0..8 { fb[i] = self.read_u8()?; }
896                    let target_f = f64::from_le_bytes(fb);
897                    let addr = self.read_u16()?;
898                    let val = self.stack.last().ok_or(VmError::StackUnderflow)?;
899                    if let Value::Float(f) = val {
900                        if (f - target_f).abs() < 1e-9 {
901                            self.pc = addr as usize;
902                        }
903                    }
904                }
905                0x33 => { // Topent — path_str, mode_int → handle_int
906                    let mode = self.stack.pop().ok_or(VmError::StackUnderflow)?;
907                    let path = self.stack.pop().ok_or(VmError::StackUnderflow)?;
908                    if let (Value::String(p), Value::Int(m)) = (path, mode) {
909                        use std::fs::OpenOptions;
910                        let mut options = OpenOptions::new();
911                        match m {
912                            0 => { options.read(true); } // Read
913                            1 => { options.write(true).create(true).truncate(true); } // Write
914                            2 => { options.append(true).create(true); } // Append
915                            _ => return Err(VmError::RuntimeError(format!("Invalid file mode: {m}"))),
916                        }
917                        let file = options.open(&p).map_err(|e| VmError::FileOpenError(e.to_string()))?;
918                        let handle = self.open_files.len();
919                        self.open_files.push(Some(file));
920                        self.stack.push(Value::Int(handle as i64));
921                    } else {
922                        return Err(VmError::TypeMismatch { expected: "String, Int".into(), found: "Unknown".into() });
923                    }
924                }
925                0x34 => { // Treadt — handle_int → trit
926                    let handle_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
927                    if let Value::Int(h) = handle_val {
928                        let h = h as usize;
929                        if h >= self.open_files.len() || self.open_files[h].is_none() {
930                            return Err(VmError::FileNotOpen(h));
931                        }
932                        let file = self.open_files[h].as_mut().unwrap();
933                        let mut buf = [0u8; 1];
934                        use std::io::Read;
935                        match file.read_exact(&mut buf) {
936                            Ok(_) => {
937                                let t = match buf[0] {
938                                    b'+' | b'1' => Trit::Affirm,
939                                    b'-' => Trit::Reject,
940                                    _ => Trit::Tend,
941                                };
942                                self.stack.push(Value::Trit(t));
943                            }
944                            Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
945                                self.stack.push(Value::Trit(Trit::Tend)); // EOF as Tend
946                            }
947                            Err(e) => return Err(VmError::FileReadError(e.to_string())),
948                        }
949                    } else {
950                        return Err(VmError::TypeMismatch { expected: "Int".into(), found: format!("{:?}", handle_val) });
951                    }
952                }
953                0x35 => { // Twritet — handle_int, trit → void
954                    let t_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
955                    let h_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
956                    if let (Value::Int(h), Value::Trit(t)) = (h_val, t_val) {
957                        let h = h as usize;
958                        if h >= self.open_files.len() || self.open_files[h].is_none() {
959                            return Err(VmError::FileNotOpen(h));
960                        }
961                        let file = self.open_files[h].as_mut().unwrap();
962                        let out = match t {
963                            Trit::Affirm => b'+',
964                            Trit::Reject => b'-',
965                            Trit::Tend   => b'0',
966                        };
967                        use std::io::Write;
968                        file.write_all(&[out]).map_err(|e| VmError::FileWriteError(e.to_string()))?;
969                    } else {
970                        return Err(VmError::TypeMismatch { expected: "Int, Trit".into(), found: "Unknown".into() });
971                    }
972                }
973                0x36 => { // Tnodeid — push this node's runtime address as a String
974                    // Defers the binding to runtime so --node-addr is respected.
975                    // Previously, Expr::NodeId emitted a hardcoded "127.0.0.1:7373"
976                    // string at compile time, ignoring vm.set_node_id().
977                    self.stack.push(Value::String(self.node_id.clone()));
978                }
979                0x37 => { // Tassert
980                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
981                    let is_affirm = match val {
982                        Value::Trit(Trit::Affirm) => true,
983                        Value::Int(1) => true,
984                        _ => false,
985                    };
986                    if !is_affirm {
987                        return Err(VmError::AssertionFailed);
988                    }
989                }
990                0x38 => { // TSparseMatmul (@sparseskip)
991                    // Layout: [opcode] [a_rows: u8] [a_cols: u8] [b_cols: u8]
992                    // Dim bytes = 0 mean "wildcard" — read actual dims from the tensor at runtime.
993                    // Codegen emits 0x00 0x00 0x00 when the sizes are generic (not statically known).
994                    // Pops: A_tensor, B_tensor
995                    // Pushes: Result_tensor
996                    let bc_a_rows = self.read_u8()? as usize;
997                    let bc_a_cols = self.read_u8()? as usize;
998                    let bc_b_cols = self.read_u8()? as usize;
999                    let b_ref = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1000                    let a_ref = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1001
1002                    if let (Value::TensorRef(a_idx), Value::TensorRef(b_idx)) = (a_ref, b_ref) {
1003                        let (a_rows, a_cols, b_cols, a_data, b_data) = {
1004                            let a = self.tensors.get(a_idx).ok_or(VmError::TensorNotAllocated(a_idx))?;
1005                            let b = self.tensors.get(b_idx).ok_or(VmError::TensorNotAllocated(b_idx))?;
1006
1007                            let a_data = match &a.data {
1008                                TensorData::Trit(v) => v,
1009                                _ => return Err(VmError::TypeMismatch { expected: "TritTensor".into(), found: "Other".into() }),
1010                            };
1011                            let b_data = match &b.data {
1012                                TensorData::Trit(v) => v,
1013                                _ => return Err(VmError::TypeMismatch { expected: "TritTensor".into(), found: "Other".into() }),
1014                            };
1015                            // Use bytecode dims if specified; fall back to tensor metadata (wildcard=0).
1016                            let a_rows = if bc_a_rows > 0 { bc_a_rows } else { a.rows };
1017                            let a_cols = if bc_a_cols > 0 { bc_a_cols } else { a.cols };
1018                            let b_cols = if bc_b_cols > 0 { bc_b_cols } else { b.cols };
1019                            (a_rows, a_cols, b_cols, a_data.clone(), b_data.clone())
1020                        };
1021
1022                        let mut result = vec![Trit::Tend; a_rows * b_cols];
1023                        let mut skipped = false;
1024
1025                        for i in 0..a_rows {
1026                            for k in 0..a_cols {
1027                                let a_val = a_data[i * a_cols + k];
1028                                if a_val == Trit::Tend {
1029                                    skipped = true;
1030                                    continue;
1031                                }
1032                                for j in 0..b_cols {
1033                                    let b_val = b_data[k * b_cols + j];
1034                                    if b_val == Trit::Tend { continue; }
1035                                    let prod = a_val * b_val;
1036                                    let (sum, _) = result[i * b_cols + j] + prod;
1037                                    result[i * b_cols + j] = sum;
1038                                }
1039                            }
1040                        }
1041
1042                        if skipped { self.sparse_dropped = true; }
1043                        let res_idx = self.tensors.len();
1044                        self.tensors.push(TensorInstance {
1045                            data: TensorData::Trit(result),
1046                            rows: a_rows,
1047                            cols: b_cols,
1048                        });
1049                        self.stack.push(Value::TensorRef(res_idx));
1050                    } else {
1051                        return Err(VmError::TypeMismatch { expected: "TensorRef, TensorRef".into(), found: "Unknown".into() });
1052                    }
1053                }
1054                0x40 => { // Tstruct
1055                    let num_fields = self.read_u8()? as usize;
1056                    let mut fields = std::collections::HashMap::new();
1057                    for _ in 0..num_fields {
1058                        let name_len = self.read_u8()? as usize;
1059                        let mut name_bytes = vec![0u8; name_len];
1060                        for i in 0..name_len { name_bytes[i] = self.read_u8()?; }
1061                        let name = String::from_utf8(name_bytes).unwrap();
1062                        let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1063                        fields.insert(name, val);
1064                    }
1065                    self.stack.push(Value::Struct(fields));
1066                }
1067                0x41 => { // Tfield
1068                    let name_len = self.read_u8()? as usize;
1069                    let mut name_bytes = vec![0u8; name_len];
1070                    for i in 0..name_len { name_bytes[i] = self.read_u8()?; }
1071                    let name = String::from_utf8(name_bytes).unwrap();
1072                    let obj = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1073                    if let Value::Struct(fields) = obj {
1074                        let val = fields.get(&name).cloned().unwrap_or_default();
1075                        self.stack.push(val);
1076                    } else {
1077                        return Err(VmError::TypeMismatch { expected: "Struct".into(), found: format!("{:?}", obj) });
1078                    }
1079                }
1080                0x42 => { // TBIND reg, view_reg
1081                    let reg = self.read_u8()? as usize;
1082                    let view_val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1083                    if let Value::TensorView { .. } = view_val {
1084                        self.bindings.insert(reg, view_val);
1085                    } else {
1086                        return Err(VmError::TypeMismatch { expected: "TensorView".into(), found: format!("{:?}", view_val) });
1087                    }
1088                }
1089                0x55 => { // TVIEW tensor_ref/view, offset, length, stride → TensorView
1090                    let stride = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1091                    let length = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1092                    let offset = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1093                    let rf = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1094                    
1095                    if let (Value::Int(o), Value::Int(l), Value::Int(s)) = (&offset, &length, &stride) {
1096                        match rf {
1097                            Value::TensorRef(id) => {
1098                                self.stack.push(Value::TensorView {
1099                                    tensor_id: id,
1100                                    offset: *o as usize,
1101                                    length: *l as usize,
1102                                    stride: *s as usize,
1103                                });
1104                            }
1105                            Value::TensorView { tensor_id, offset: v_off, stride: v_stride, .. } => {
1106                                // Slicing a view: absolute offset = view_offset + (slice_offset * view_stride)
1107                                // New stride = view_stride * slice_stride
1108                                self.stack.push(Value::TensorView {
1109                                    tensor_id,
1110                                    offset: v_off + (*o as usize * v_stride),
1111                                    length: *l as usize,
1112                                    stride: v_stride * (*s as usize),
1113                                });
1114                            }
1115                            _ => return Err(VmError::TypeMismatch { expected: "TensorRef or TensorView".into(), found: format!("{:?}", rf) }),
1116                        }
1117                    } else {
1118                        return Err(VmError::TypeMismatch { expected: "Int, Int, Int".into(), found: format!("{:?}, {:?}, {:?}", offset, length, stride) });
1119                    }
1120                }
1121                0x50 => { // TPACK: pops 5 trits, pushes 1 packed byte (as Int)
1122                    let mut trits = [Trit::Tend; 5];
1123                    for i in (0..5).rev() {
1124                        let t = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1125                        trits[i] = match t {
1126                            Value::Trit(tv) => tv,
1127                            _ => return Err(VmError::TypeMismatch { expected: "Trit".into(), found: format!("{:?}", t) }),
1128                        };
1129                    }
1130                    let packed = crate::trit::pack_5_trits(trits);
1131                    self.stack.push(Value::Int(packed as i64));
1132                }
1133                0x51 => { // TUNPACK: pops 1 packed byte, pushes 5 trits
1134                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1135                    if let Value::Int(packed) = val {
1136                        let trits = crate::trit::unpack_5_trits(packed as u8);
1137                        for t in trits {
1138                            self.stack.push(Value::Trit(t));
1139                        }
1140                    } else {
1141                        return Err(VmError::TypeMismatch { expected: "Int (packed byte)".into(), found: format!("{:?}", val) });
1142                    }
1143                }
1144                0x56 => { // TALLOC_PACKED (packed trit tensor)
1145                    let rows = self.read_u32()? as usize;
1146                    let cols = self.read_u32()? as usize;
1147                    let size = rows * cols;
1148                    let num_bytes = (size + 4) / 5;
1149                    let idx = self.tensors.len();
1150                    self.tensors.push(TensorInstance {
1151                        data: TensorData::PackedTrit(vec![0x00; num_bytes], size), // 0x00 is Reject Reject Reject Reject Reject
1152                        rows,
1153                        cols,
1154                    });
1155                    self.stack.push(Value::TensorRef(idx));
1156                }
1157                0x57 => { // TtoInt — coerce any numeric value to Int (fixes cast<int>)
1158                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1159                    let int_val = match val {
1160                        Value::Int(i) => i,
1161                        Value::Trit(t) => t as i64,
1162                        Value::Float(f) => f as i64,
1163                        _ => return Err(VmError::TypeMismatch { expected: "numeric (int/trit/float)".into(), found: format!("{:?}", val) }),
1164                    };
1165                    self.stack.push(Value::Int(int_val));
1166                }
1167                0x58 => { // TtoFloat — coerce any numeric value to Float (fixes cast<float>)
1168                    let val = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1169                    let float_val = match val {
1170                        Value::Float(f) => f,
1171                        Value::Int(i) => i as f64,
1172                        Value::Trit(t) => t as i64 as f64,
1173                        _ => return Err(VmError::TypeMismatch { expected: "numeric (int/trit/float)".into(), found: format!("{:?}", val) }),
1174                    };
1175                    self.stack.push(Value::Float(float_val));
1176                }
1177                0x52 => { // TV_ADD: Vectorized addition of two packed tensors
1178                    let b_ref = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1179                    let a_ref = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1180                    if let (Value::TensorRef(a_idx), Value::TensorRef(b_idx)) = (a_ref, b_ref) {
1181                        let res_idx = self.tensors.len();
1182                        let (rows, cols, data) = {
1183                            let a = self.tensors.get(a_idx).ok_or(VmError::TensorNotAllocated(a_idx))?;
1184                            let b = self.tensors.get(b_idx).ok_or(VmError::TensorNotAllocated(b_idx))?;
1185                            if a.rows != b.rows || a.cols != b.cols {
1186                                return Err(VmError::RuntimeError("Tensor dimension mismatch in TV_ADD".into()));
1187                            }
1188                            match (&a.data, &b.data) {
1189                                (TensorData::PackedTrit(av, alen), TensorData::PackedTrit(bv, _)) => {
1190                                    let mut res_v = vec![0u8; av.len()];
1191                                    for i in 0..av.len() {
1192                                        res_v[i] = crate::trit::packed_add(av[i], bv[i]);
1193                                    }
1194                                    (a.rows, a.cols, TensorData::PackedTrit(res_v, *alen))
1195                                }
1196                                _ => return Err(VmError::TypeMismatch { expected: "PackedTrit tensors".into(), found: "Other".into() }),
1197                            }
1198                        };
1199                        self.tensors.push(TensorInstance { data, rows, cols });
1200                        self.stack.push(Value::TensorRef(res_idx));
1201                    } else {
1202                        return Err(VmError::TypeMismatch { expected: "TensorRef, TensorRef".into(), found: "Unknown".into() });
1203                    }
1204                }
1205                0x53 => { // TV_NEG: Vectorized negation of a packed tensor
1206                    let a_ref = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1207                    if let Value::TensorRef(idx) = a_ref {
1208                        let res_idx = self.tensors.len();
1209                        let (rows, cols, data) = {
1210                            let a = self.tensors.get(idx).ok_or(VmError::TensorNotAllocated(idx))?;
1211                            match &a.data {
1212                                TensorData::PackedTrit(v, len) => {
1213                                    let mut res_v = vec![0u8; v.len()];
1214                                    for i in 0..v.len() {
1215                                        res_v[i] = crate::trit::packed_neg(v[i]);
1216                                    }
1217                                    (a.rows, a.cols, TensorData::PackedTrit(res_v, *len))
1218                                }
1219                                _ => return Err(VmError::TypeMismatch { expected: "PackedTrit tensor".into(), found: "Other".into() }),
1220                            }
1221                        };
1222                        self.tensors.push(TensorInstance { data, rows, cols });
1223                        self.stack.push(Value::TensorRef(res_idx));
1224                    } else {
1225                        return Err(VmError::TypeMismatch { expected: "TensorRef".into(), found: format!("{:?}", a_ref) });
1226                    }
1227                }
1228                0x54 => { // TV_CON: Vectorized consensus of two packed tensors
1229                    let b_ref = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1230                    let a_ref = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1231                    if let (Value::TensorRef(a_idx), Value::TensorRef(b_idx)) = (a_ref, b_ref) {
1232                        let res_idx = self.tensors.len();
1233                        let (rows, cols, data) = {
1234                            let a = self.tensors.get(a_idx).ok_or(VmError::TensorNotAllocated(a_idx))?;
1235                            let b = self.tensors.get(b_idx).ok_or(VmError::TensorNotAllocated(b_idx))?;
1236                            if a.rows != b.rows || a.cols != b.cols {
1237                                return Err(VmError::RuntimeError("Tensor dimension mismatch in TV_CON".into()));
1238                            }
1239                            match (&a.data, &b.data) {
1240                                (TensorData::PackedTrit(av, alen), TensorData::PackedTrit(bv, _)) => {
1241                                    let mut res_v = vec![0u8; av.len()];
1242                                    for i in 0..av.len() {
1243                                        res_v[i] = crate::trit::packed_consensus(av[i], bv[i]);
1244                                    }
1245                                    (a.rows, a.cols, TensorData::PackedTrit(res_v, *alen))
1246                                }
1247                                _ => return Err(VmError::TypeMismatch { expected: "PackedTrit tensors".into(), found: "Other".into() }),
1248                            }
1249                        };
1250                        self.tensors.push(TensorInstance { data, rows, cols });
1251                        self.stack.push(Value::TensorRef(res_idx));
1252                    } else {
1253                        return Err(VmError::TypeMismatch { expected: "TensorRef, TensorRef".into(), found: "Unknown".into() });
1254                    }
1255                }
1256                0x60 => { // TEXPLAIN — XAI decision trace
1257                    // Layout: [opcode]
1258                    // Stack (top → bottom): value, label
1259                    // Pops label (String) and value, formats to "label: value", appends to trace_log.
1260                    // Return value is pushed by codegen's hold() stub — this opcode pushes nothing.
1261                    let val   = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1262                    let label = self.stack.pop().ok_or(VmError::StackUnderflow)?;
1263                    let label_str = match label {
1264                        Value::String(s) => s,
1265                        other => format!("{:?}", other),
1266                    };
1267                    let val_str = match &val {
1268                        Value::Trit(t)   => format!("{:?}", t),
1269                        Value::Int(i)    => format!("{}", i),
1270                        Value::Float(f)  => format!("{}", f),
1271                        Value::String(s) => s.clone(),
1272                        other            => format!("{:?}", other),
1273                    };
1274                    self.trace_log.push(format!("{}: {}", label_str, val_str));
1275                }
1276                0x00 => return Ok(()),
1277                _ => return Err(VmError::InvalidOpcode(opcode)),
1278            }
1279        }
1280        Ok(())
1281    }
1282
1283    fn read_u8(&mut self) -> Result<u8, VmError> {
1284        if self.pc >= self.code.len() { return Err(VmError::PcOutOfBounds(self.pc)); }
1285        let val = self.code[self.pc];
1286        self.pc += 1;
1287        Ok(val)
1288    }
1289
1290    fn read_u16(&mut self) -> Result<u16, VmError> {
1291        if self.pc + 1 >= self.code.len() { return Err(VmError::PcOutOfBounds(self.pc)); }
1292        let val = u16::from_le_bytes([self.code[self.pc], self.code[self.pc + 1]]);
1293        self.pc += 2;
1294        Ok(val)
1295    }
1296
1297    fn read_u32(&mut self) -> Result<u32, VmError> {
1298        if self.pc + 3 >= self.code.len() { return Err(VmError::PcOutOfBounds(self.pc)); }
1299        let val = u32::from_le_bytes([
1300            self.code[self.pc], self.code[self.pc + 1],
1301            self.code[self.pc + 2], self.code[self.pc + 3]
1302        ]);
1303        self.pc += 4;
1304        Ok(val)
1305    }
1306
1307    fn get_pos(&self, rf: &Value, row: i64, col: i64) -> Result<(usize, usize), VmError> {
1308        match rf {
1309            Value::TensorRef(idx) => {
1310                let tensor = self.tensors.get(*idx).ok_or(VmError::TensorNotAllocated(*idx))?;
1311                let pos = if tensor.cols > 1 && col >= 0 {
1312                    row as usize * tensor.cols + col as usize
1313                } else {
1314                    row as usize
1315                };
1316                Ok((*idx, pos))
1317            }
1318            Value::TensorView { tensor_id, offset, stride, .. } => {
1319                let pos = *offset + (row as usize * *stride);
1320                Ok((*tensor_id, pos))
1321            }
1322            _ => Err(VmError::TypeMismatch { expected: "TensorRef or TensorView".into(), found: format!("{:?}", rf) }),
1323        }
1324    }
1325}