"""Bark selection with phase cooldown (design R5/R11).""" from __future__ import annotations import random from typing import Dict, List, Optional, Set, Tuple class BarkSelector: def __init__(self, rng: Optional[random.Random] = None): self.rng = rng or random.Random() # (npc_id, phase) -> used bark ids this phase instance self._used: Dict[Tuple[str, str], Set[str]] = {} def reset_phase(self, phase: str) -> None: keys = [k for k in self._used if k[1] == phase] for k in keys: del self._used[k] def pick(self, npc_id: str, phase: str, barks: List[dict]) -> Optional[str]: if not barks: return None key = (npc_id, phase) used = self._used.setdefault(key, set()) candidates = [] for b in barks: bid = b.get("id") or b.get("text") bphase = b.get("phase") if bid in used: continue if bphase and bphase != phase: continue candidates.append(b) if not candidates: # allow phase-agnostic leftovers candidates = [b for b in barks if (b.get("id") or b.get("text")) not in used] if not candidates: return None choice = self.rng.choice(candidates) bid = choice.get("id") or choice.get("text") used.add(bid) return choice.get("text") # reseal-product R387