Skip to main content

dotzuki_rules/
trace.rs

1//! The two-layer-debugging trace (doc 11 §5, last bullet): "the interpreter
2//! **must** log `(EffectId, Event, op, relay before/after)` from day one or the
3//! data author is pushed back into Rust." A wrong outcome may be in the data OR
4//! the primitive; this trace tells them apart.
5//!
6//! The trace is **opt-in** (a [`TraceSink`] installed thread-locally by a debug
7//! flag) and records NO entropy — it never draws RNG, never reads a clock, never
8//! affects draw order. It is a pure observer.
9
10use std::cell::RefCell;
11
12use dotzuki_engine::battle::stack::{EffectId, Event, RelayVar};
13
14use crate::model::Op;
15
16/// One recorded interpreter step (doc 11 §5).
17#[derive(Debug, Clone, PartialEq)]
18pub struct TraceEvent {
19    /// The firing hook's synthesized id (the `source_effect`).
20    pub effect: EffectId,
21    /// The closed event being folded.
22    pub event: Event,
23    /// The op that ran (cloned for inspection).
24    pub op: Op,
25    /// The relay BEFORE the op.
26    pub before: RelayVar,
27    /// The relay AFTER the op (the op's `HandlerResult` applied locally).
28    pub after: RelayVar,
29}
30
31/// A collector of [`TraceEvent`]s. Installed thread-locally by a debug flag so
32/// the interpreter's hot path stays branch-light when tracing is off.
33#[derive(Debug, Default, Clone)]
34pub struct TraceSink {
35    /// The recorded steps, in fold order.
36    pub events: Vec<TraceEvent>,
37}
38
39thread_local! {
40    static SINK: RefCell<Option<TraceSink>> = const { RefCell::new(None) };
41}
42
43/// Enable tracing for the current thread (the debug flag, doc 11 §5).
44pub fn enable_trace() {
45    SINK.with(|s| *s.borrow_mut() = Some(TraceSink::default()));
46}
47
48/// Disable tracing and take the recorded sink (`None` if tracing was off).
49pub fn take_trace() -> Option<TraceSink> {
50    SINK.with(|s| s.borrow_mut().take())
51}
52
53/// Record one step IFF tracing is enabled. Pure: no RNG, no clock, no draw-order
54/// effect.
55pub(crate) fn record(effect: EffectId, event: Event, op: &Op, before: RelayVar, after: RelayVar) {
56    SINK.with(|s| {
57        if let Some(sink) = s.borrow_mut().as_mut() {
58            sink.events.push(TraceEvent {
59                effect,
60                event,
61                op: op.clone(),
62                before,
63                after,
64            });
65        }
66    });
67}