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

2.2 KiB

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 and nothing else.

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

ludic build packages/ludic.effects/tests/effects_test.ludic --headless -o /tmp/effects_test && /tmp/effects_test