`passage_start` rewinds the rite to `listen` at any time, and PassagePanel offers exactly that button after a resisted release. Every replayed layer re-credited its essence, so one summon funded an endless loop — the 20/60s limiter caps the rate, never the total. Measured at ~110 essence/minute, indefinitely. Each layer now pays the first time it opens for a presence and never again, cleared only by a genuine summon. Re-walking still reveals; it just doesn't mint. The frame reports what was ACTUALLY credited, so the UI's running total can't drift from the ledger. Three further fixes in the same handlers: - judgment -> passage double-paid a crossing. The passage -> cross_over direction was already guarded; the reverse ran free, favor included. - both handlers read `state.entity["id"]` AFTER their DB round-trip. The HTTP telemetry path drives the same SeanceState and can summon concurrently, so a crossing could mark the presence that just arrived. Pinned before the awaits. tests/test_ws_passage.py is new, and covers the gap that let all of this hide: test_passage.py tests the pure module, and nothing exercised these handlers over a real connection. Efficacy proven by reverting the fix — the replay test then reports "minted 280 extra essence". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
56 KiB
56 KiB