============== visual effects ============== the overlay system for transient visual effects like cloud trails. overview ======== effects are temporary overlays displayed on top of terrain in the viewport. each effect has a position, character, color, and expiration time. they're rendered last-wins on overlap, cleaned up passively in the game loop. data model ========== effect dataclass ---------------- defined in ``effects.py``:: @dataclass class Effect: x: int y: int char: str color: str # ANSI code expires_at: float # monotonic time global state ------------ single global list:: active_effects: list[Effect] = [] no per-zone partitioning. simple. core functions ============== ``add_effect(x, y, char, color, ttl)`` creates effect with expiry at ``time.monotonic() + ttl``, appends to list ``get_effects_at(x, y)`` returns active effects at position, auto-filters expired ones ``clear_expired()`` in-place list mutation to drop expired effects. called once per tick (10/sec) in ``server.py`` game loop rendering ========= in ``look.py``, for each viewport tile: 1. check ``get_effects_at(x, y)`` 2. if effects exist, take ``effects[-1]`` (most recent) 3. display its ``char`` and ``color``, overlaying terrain last-added effect wins on overlap. timing ====== uses ``time.monotonic()`` for consistent async timing. ttl in seconds. current usage: fly command creates cloud trail ("~" bright white) with staggered ttls (1.5s base + 0.4s per step) so clouds fade origin to destination. design decisions ================ - global state, not per-zone (simplicity) - passive cleanup via tick-based ``clear_expired()`` - last-wins overlay (no z-index, no blending) - just char + ANSI color (minimal rendering) - monotonic time (no wall clock drift) code ==== - ``src/mudlib/effects.py`` - dataclass, functions, global state - ``src/mudlib/look.py`` - viewport rendering with effect overlay - ``src/mudlib/server.py`` - game loop calls ``clear_expired()`` - ``content/commands/fly.toml`` - cloud trail effect usage