Skip to main content

dotzuki_rules/
lib.rs

1//! # dotzuki-rules — no-code RON authoring for the battle effect-stack (Phase 1)
2//!
3//! A **game-side** loader that turns a
4//! declarative `rules.ron` into runtime [`Effect`](dotzuki_engine::battle::stack::Effect)s
5//! dispatched through **ONE** zero-capture interpreter-bridge `fn`
6//! ([`interpret`]) plus a **closed** primitive-op interpreter ([`run_ops`]).
7//!
8//! ## What this crate is (and is NOT)
9//!
10//! * It depends on the game-agnostic [`dotzuki_engine`] **only** — zero
11//!   pokered / pokered-core / pokered-data / minimon, zero concrete game type in
12//!   non-test code, no `rand`.
13//! * It is a **consumer** of the engine's closed primitive vocabulary
14//!   (doc 11 §1.1 + doc 12 §3). It **amortizes content** (one `InflictStatus`
15//!   covers every secondary-status move) — it does **not extend mechanics**.
16//!   A genuinely new mechanic still needs a Rust primitive + test (doc 11 §5).
17//!
18//! ## The bridge (doc 11 §2 — Option A, ZERO engine change)
19//!
20//! The fold's only handler call site is a zero-capture `fn` pointer
21//! ([`HandlerFn`](dotzuki_engine::battle::stack::HandlerFn)); **data cannot *be* a
22//! `fn` pointer**. So every data hook points its `call` field at the single
23//! generic [`interpret`] `fn`, which on each call looks up its op-list **by the
24//! [`EffectId`] the engine already threads as `source_effect`**
25//! (`dispatch.rs:128`). The loader mints one distinct `EffectId` per
26//! `(effect, event)` hook and registers each as its own tiny runtime
27//! [`Effect`](dotzuki_engine::battle::stack::Effect) **through the existing
28//! defaulted resolvers** — exactly the Option-A shape doc 11 §2.2 recommends,
29//! and the shape minimon already proves with `effectiveness_chart_hook`. **No
30//! engine edit, no new trait method on an engine trait.**
31//!
32//! ## Determinism (doc 11 §4)
33//!
34//! The interpreter has **NO entropy except `ctx.rng`** (a `&mut dyn BattleRng`).
35//! The `chance` gate compiles to `ctx.rng.chance(num, den)`; there is no clock,
36//! no pointer hashing, no `HashMap` iteration affecting draw order. A
37//! [`ScriptedRng`](dotzuki_engine::battle::rng::ScriptedRng) replays a data ruleset
38//! identically (same draw count and order) as the native path — a **structural**
39//! guarantee, proved by [`tests::scripted_rng_replays_identically`].
40//!
41//! ## Dual-mode sourcing (Phase 2, doc 11 §4.2)
42//!
43//! [`RuleSource`] yields the **same** runtime [`Ruleset`] from either a **baked**
44//! `include_str!`'d text (RELEASE; the default build, zero file IO) or a **disk**
45//! path (DEV; behind the `hot-reload` feature it also watches the file and
46//! [`RuleSource::poll_changed`] signals an edit so the game rebuilds the registry
47//! **between turns**). A mid-battle reload is safe because effects are addressed
48//! by [`EffectId`](dotzuki_engine::battle::stack::EffectId) and live state lives in
49//! the engine's `EffectState` arena, not the data — the reload swaps the
50//! *vocabulary*, never the *in-flight state*.
51
52// `forbid(unsafe_code)` holds on hosted builds; the bare-metal build uses one
53// audited `static mut` for the trace sink (std::thread_local! is unavailable
54// there), so it downgrades to `warn`.
55#![cfg_attr(not(target_os = "none"), forbid(unsafe_code))]
56#![cfg_attr(target_os = "none", warn(unsafe_code))]
57
58// no_std port (GBA / thumbv4t): the default build (baked rules, no
59// hot-reload) runs bare-metal — the disk source and `notify` watcher are
60// hosted-only, and RON parsing uses the vendored no_std ron.
61#![cfg_attr(target_os = "none", no_std)]
62#![cfg_attr(target_os = "none", feature(prelude_import))]
63#![cfg_attr(target_os = "none", allow(internal_features))]
64
65extern crate alloc;
66
67#[allow(unused_imports)]
68mod alloc_prelude {
69    pub use core::prelude::v1::*;
70    pub use core::convert::{TryFrom, TryInto};
71    pub use alloc::borrow::ToOwned;
72    pub use core::iter::FromIterator;
73    pub use alloc::boxed::Box;
74    pub use alloc::format;
75    pub use alloc::string::{String, ToString};
76    pub use alloc::vec;
77    pub use alloc::vec::Vec;
78    pub use core::{assert_eq, assert_ne, matches, todo, unimplemented, write, writeln};
79    pub use core::debug_assert;
80}
81
82#[cfg_attr(target_os = "none", prelude_import)]
83#[allow(unused_imports)]
84use alloc_prelude::*;
85
86mod bindings;
87mod interp;
88mod model;
89mod registry;
90mod source;
91mod trace;
92
93#[cfg(feature = "compile-time")]
94pub use dotzuki_rules_macro::rules_ron;
95
96pub use bindings::RuleBindings;
97pub use interp::{interpret, run_ops};
98pub use model::{
99    parse_event, parse_kind, DamageValue, EffectKind, EffectRecord, FinalHitRider, FractionOf,
100    HitCount, HookRecord, LoadError, Op, Predicate, Rational, ResourceCost, Ruleset, Selector,
101    StatRef, TypeChartEntry, TypeName,
102};
103pub use registry::{CompiledHook, CompiledRuleset, ResolverKind, RulesHost, RulesProvider};
104pub use source::RuleSource;
105pub use trace::{enable_trace, take_trace, TraceEvent, TraceSink};
106
107#[cfg(test)]
108mod tests;