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)]
53
54mod bindings;
55mod interp;
56mod model;
57mod registry;
58mod source;
59mod trace;
60
61#[cfg(feature = "compile-time")]
62pub use dotzuki_rules_macro::rules_ron;
63
64pub use bindings::RuleBindings;
65pub use interp::{interpret, run_ops};
66pub use model::{
67 parse_event, parse_kind, DamageValue, EffectKind, EffectRecord, FinalHitRider, FractionOf,
68 HitCount, HookRecord, LoadError, Op, Predicate, Rational, ResourceCost, Ruleset, Selector,
69 StatRef, TypeChartEntry, TypeName,
70};
71pub use registry::{CompiledHook, CompiledRuleset, ResolverKind, RulesHost, RulesProvider};
72pub use source::RuleSource;
73pub use trace::{enable_trace, take_trace, TraceEvent, TraceSink};
74
75#[cfg(test)]
76mod tests;