Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ and `drain_sounds` plays whatever the physics queued.

- **`parse`** (core) — hand-written parser: notation → `Roll` (a `Vec<DiceTerm>`
+ flat `i32` modifier + optional `Stake` for staked rolls). Each `DiceTerm` is
count, sides, and modifiers (`TermMod`: keep/drop, explode with a `Compare`,
multiply). Pure and unit-tested. Sizes are capped (≤ 60 dice, ≤ 1000 sides) so
count, sides, and modifiers (`TermMod`: reroll (`r`/`ro`, each a `Reroll` —
a `Compare` plus a once flag), keep/drop, explode with a `Compare`, multiply).
Pure and unit-tested. Sizes are capped (≤ 60 dice, ≤ 1000 sides) so
a huge expression can't wedge the renderer; the `vs` target must come last and
is range-checked into `i32`. A `Stake` bundles that target with a `Goal`
(`Over` for `>` and its word alias `vs`, `Under` for the roll-under `<`) so a
Expand Down Expand Up @@ -116,10 +117,15 @@ and `drain_sounds` plays whatever the physics queued.
so it always converges).
- `evaluate(expr, &Roll, &mut StdRng) -> Outcome` resolves a roll *instantly*
into a full breakdown (`Outcome`/`OutcomeTerm`/`OutcomeDie`, all `serde`). It
**mirrors the animation's semantics exactly** — explode → keep/drop on the
base pool → per-term multiply → flat modifier. This is the shared contract:
the one-shot CLI and the TUI must agree, so changes to roll rules belong here
and in the animated path together.
**mirrors the animation's semantics exactly** — reroll → explode → keep/drop
on the base pool → per-term multiply → flat modifier. This is the shared
contract: the one-shot CLI and the TUI must agree, so changes to roll rules
belong here and in the animated path together.
- Reroll (`r`/`ro`) is decided *up front* like the value it replaces:
`draw_with_reroll()` is the single source both paths draw every face
through, so a `r1` die simply never lands on a 1 — no arena timeline, unlike
exploding. Discarded faces ride along in `OutcomeDie.rerolled` for the `-v`
breakdown; the arena only ever shows the survivor.
- Single-source rule helpers back both paths: `check()` (the `vs` verdict for
either `Goal` — meet-or-beat or roll-under — returning a direction-aware
margin, also used by the stats pane's success odds), `Stake::label()` (the
Expand Down Expand Up @@ -313,7 +319,7 @@ and `drain_sounds` plays whatever the physics queued.
the codebase.
- **Roll semantics live in two places that must stay in lockstep**: the animated
path in `app` and `evaluate`. A test would fail if they diverge, but keep them
together when editing rules (explode/keep-drop/multiply order). Cross-cutting
together when editing rules (reroll/explode/keep-drop/multiply order). Cross-cutting
rules (verdict, crit/fumble) live once in the shared helpers listed above.
- **The RNG stays untouched**: throw power, verdicts, particles, and sound are
all downstream of the same seedable RNG; a test asserts the same seed rolls
Expand Down
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ a `ScheduleRunnerPlugin` at ~60 fps — the core `App` is the single source of
truth, and the Bevy entities are a pure view of it.

- **`parse`** (core) — a hand-written parser that turns notation into a `Roll`: a
list of **dice terms** (each a count, a side count, and its modifiers — keep/drop,
explode, multiply) plus an integer flat modifier and the optional `Stake` (a
list of **dice terms** (each a count, a side count, and its modifiers — reroll,
keep/drop, explode, multiply) plus an integer flat modifier and the optional `Stake` (a
target with a `Goal`: meet-or-beat `> N` / `vs N`, or roll-under `< N`). Pure
and unit-tested.
- **`app`** (core) — the state, the roll evaluator, and the glue that drives the
Expand Down Expand Up @@ -189,8 +189,8 @@ Tests guard most of these, but know them before you lean on a wall:
goes through `scene::convert` — a stray direct assignment is a compile error,
not a silent unit bug.
- **Roll semantics live in two places that must agree**: the animated path in
`app` and the instant `evaluate()`. Editing roll rules (explodekeep/drop
→ multiply order) means editing both together. Cross-cutting rules
`app` and the instant `evaluate()`. Editing roll rules (rerollexplode →
keep/drop → multiply order) means editing both together. Cross-cutting rules
(verdict, crit/fumble) live exactly once, in the shared helpers — never
restate those comparisons inline.
- **No plain-letter hotkeys.** Bare letters must stay typeable — `kh`/`dh`
Expand Down
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ the call with `--graphics kitty` or `--graphics blocks`.

A roll is a sequence of **dice terms** and optional **flat modifiers**,
separated by `+`, `,`, whitespace, or simply written next to each other. A term
can carry **modifiers** (keep/drop, explode, multiply) written right after its
`dN`; these apply in pool order — **explode → keep/drop → multiply** — and
stack. Add **stakes** to check the total against a target.
can carry **modifiers** (keep/drop, explode, reroll, multiply) written right
after its `dN`; these apply in pool order — **reroll → explode → keep/drop →
multiply** — and stack. Add **stakes** to check the total against a target.

### The basics

Expand Down Expand Up @@ -164,6 +164,21 @@ Exploding plays out live: a die that _settles_ on a qualifying face drops one
more die into the arena, which can explode in turn — capped at 40 extra dice
per term so `d2!` can't grow without bound.

### Reroll

| Input | Meaning |
| ---------- | ------------------------------------------------------- |
| `4d6r1` | reroll any 1, repeating until it clears |
| `d20ro1` | `ro` rerolls **once** — one redraw, then live with it |
| `d20r<3` | a compare point works too (`<`, `>`, `=`); bare `N` = `=N` |
| `6d6r2r4r6`| chain compare points to reroll several faces |

A reroll throws the old face out before anything else touches the pool, so a
die you'd have dropped or exploded is settled first. The die lands on its
kept face in the arena; a `-v` breakdown shows what was tossed (`1r4` = rolled
a 1, rerolled, kept the 4). A plain `r` whose compare would match *every* face
is rejected (it could never clear); use `ro` if you really mean one redraw.

### Multiply

| Input | Meaning |
Expand Down
Loading
Loading