ludic/packages/ludic.effects/README.md
Orkuncakilkaya d0ad364ad4 feat(ludic.effects): timed modifiers as a mechanic package; ludic.base's q_new is queue_new
ludic.effects: effects_add / effects_add_after / effects_clear / effects_run,
effects_sum / effects_has / effects_at / effects_live, an EffectEnded queue
(ran out, pushed out by the bonus cap, crowded out of a full ring) and a
System with its own save section. Nine test blocks in tests/effects_test.ludic.

ludic.base: q_new collided with render3d's quaternion q_new the moment a game
imported both, so the queue constructor is queue_new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 04:14:09 +03:00

44 lines
2.2 KiB
Markdown

# ludic.effects
Timed modifiers that run down in game time: a meal that holds the cold off for fifty minutes, a
drink that is twenty minutes of legs and then a crash, a soaking that chills you for ten. The
rules of the game ask one question - "what should I add for this kind right now?" - and the
package answers it. Uses [`ludic.base`](../ludic.base/README.md) and nothing else.
```ludic
import "ludic.effects"
```
A kind is the game's own number (`EF_WARMTH_RATE`, `EF_CARRY`, ...); the package never knows what
one means. A magnitude is in that kind's units. Time is game minutes.
## Rules
- **The same kind from the same label replaces itself.** A second soda extends the fizz, it does
not double it.
- **Bonuses are capped** (`effects_config(max, bonus_max)`, 8 and 3 by default): a bonus that would
be one too many pushes out the bonus nearest its end. A menu is a choice.
- **A full ring** drops whatever is nearest its end.
- **A delayed effect** (`effects_add_after`) counts for nothing until it begins, then runs its time.
## API
| | |
| --- | --- |
| `effects_add(kind, mag, minutes, bonus, label)` | start one now |
| `effects_add_after(after, kind, mag, minutes, bonus, label)` | start one `after` game minutes from now |
| `effects_clear()` | stop everything, silently (a new trip, a night's sleep) |
| `effects_run(minutes)` | run them down (the system's tick does this from `Tick.hours`) |
| `effects_sum(kind)`, `effects_has(kind)`, `effects_bonus_count()` | what the rules ask |
| `effects_count()`, `effects_at(i) -> Effect`, `effects_live() -> []int` | what a read-out draws (`effects_at` is a copy) |
| `effects_config(max, bonus_max)`, `effects_capacity()`, `effects_bonus_capacity()` | the ring's size |
| `effects_ended() -> Queue<EffectEnded>` | the facts: `{ kind, label, why, bonus }`, `why` one of `EFFECT_RAN_OUT`, `EFFECT_PUSHED_OUT`, `EFFECT_CROWDED_OUT` |
| `effects_system() -> System` | `"effects"`, `PH_SIMULATE`: reset, tick, and its save section (`effects_save`, `effects_load`) |
No ports: the package asks the world nothing.
## Tests
```bash
ludic build packages/ludic.effects/tests/effects_test.ludic --headless -o /tmp/effects_test && /tmp/effects_test
```