A 5e storytelling engine with an LLM DM

Add local semantic vector search for recall master

The DM's recall tool could only do substring and fuzzy word-overlap matching, so "big explosion spell" couldn't find Fireball and "that merchant we met" couldn't find Henrik. This adds a local vector search engine using fastembed (ONNX, ~100MB) and sqlite-vec so recall can handle natural language queries against the full SRD, world entities, player knowledge, and transcripts. The SRD gets embedded once via `storied index srd` / `make search-index`, and that search.db acts as the seed for every world — just a file copy instead of re-embedding 862 files each time. Write-through keeps the index current as the DM establishes entities, marks events, and records player discoveries. Transcript and player knowledge results get age-decayed (3 game-day half-life) so recent conversations rank higher than old ones. The on_empty callback on VectorIndex auto-populates from the SRD seed on first search if the index is missing or was deleted. Also introduced ToolContext to bundle the infrastructure params (world_id, player_id, base_path, campaign_log, entity_index, vector_index) that were threaded through every tool function individually. This cleaned up the tool signatures and removed a bunch of defensive None-checks — all index objects are now required, not optional. Dropped the old ContentResolver.search/fuzzy fallback since vector search subsumes it. ContentResolver still handles direct name→file lookups (find/load) but the keyword search methods are gone. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>