Implement per-prompt metadata filtering and fix battleship feedback system

Major improvements to battleship game feedback accuracy and user experience:

## New Multi-Prompt Feedback System
- Replaced single feedback with 3 specialized prompts: Shot Report, Ship Status, Game Over
- Each prompt has individual metadata filtering to see only relevant data
- Shot Report only sees hit/miss data, Ship Status only sees ship destruction data
- Added STFU token system to suppress empty messages (filtered out automatically)

## Technical Implementation
- Added per-prompt metadata_filter support in YAML structure
- Updated app.py and guarded_ai.py to handle prompt-specific filtering
- Legacy single-prompt system still works with transition-level filtering
- Added comprehensive test suite for feedback system validation

## User Experience Fixes
- Fixed TTS queue blocking JavaScript execution (async promises instead of await)
- Ship Status now correctly reports who destroyed which ship (role confusion fixed)
- Game Over only appears when game actually ends (no more random messages)
- Maintained dramatic storytelling while ensuring factual accuracy

## Battleship-Specific Improvements
- Ship destruction messages only appear when ships actually sink
- Clear separation of concerns: hits/misses vs ship destruction vs game over
- Eliminated false positive ship destruction reports
- Fixed role reversal where wrong player got credit for destruction

The battleship narrator now provides accurate, contextual feedback while preserving the dramatic naval warfare atmosphere.
This commit is contained in:
Russell Ballestrini 2025-08-11 11:39:49 -04:00
parent e28dc11f04
commit d4d697db59
10 changed files with 1448 additions and 107 deletions

View file

@ -242,6 +242,10 @@ class ActivityYAMLValidator:
f"Section {section_id}, step {step_id}: 'feedback_tokens_for_ai' must be a string"
)
# Validate feedback_prompts (new multi-prompt system)
if "feedback_prompts" in step:
self._validate_feedback_prompts(step["feedback_prompts"], section_id, step_id)
# Validate buckets and transitions
if "buckets" in step:
self._validate_buckets(step["buckets"], section_id, step_id)
@ -251,6 +255,73 @@ class ActivityYAMLValidator:
step["transitions"], step.get("buckets", []), section_id, step_id
)
def _validate_feedback_prompts(self, feedback_prompts: List[Dict[str, Any]], section_id: str, step_id: str):
"""Validate feedback_prompts structure"""
if not isinstance(feedback_prompts, list):
self.errors.append(
f"Section {section_id}, step {step_id}: 'feedback_prompts' must be a list"
)
return
if len(feedback_prompts) == 0:
self.errors.append(
f"Section {section_id}, step {step_id}: 'feedback_prompts' cannot be empty"
)
return
prompt_names = set()
for i, prompt in enumerate(feedback_prompts):
if not isinstance(prompt, dict):
self.errors.append(
f"Section {section_id}, step {step_id}: feedback_prompts[{i}] must be a dictionary"
)
continue
# Required fields for each prompt
required_fields = ["name", "tokens_for_ai"]
for field in required_fields:
if field not in prompt:
self.errors.append(
f"Section {section_id}, step {step_id}: feedback_prompts[{i}] missing required field '{field}'"
)
# Validate name uniqueness
if "name" in prompt:
if not isinstance(prompt["name"], str):
self.errors.append(
f"Section {section_id}, step {step_id}: feedback_prompts[{i}].name must be a string"
)
else:
if prompt["name"] in prompt_names:
self.errors.append(
f"Section {section_id}, step {step_id}: duplicate feedback prompt name '{prompt['name']}'"
)
prompt_names.add(prompt["name"])
# Validate tokens_for_ai
if "tokens_for_ai" in prompt:
if not isinstance(prompt["tokens_for_ai"], str):
self.errors.append(
f"Section {section_id}, step {step_id}: feedback_prompts[{i}].tokens_for_ai must be a string"
)
# Check for STFU token usage (informational)
elif "STFU" in prompt["tokens_for_ai"]:
# This is valid - STFU token is used to suppress empty feedback messages
pass
# Validate metadata_filter (optional)
if "metadata_filter" in prompt:
if not isinstance(prompt["metadata_filter"], list):
self.errors.append(
f"Section {section_id}, step {step_id}: feedback_prompts[{i}].metadata_filter must be a list"
)
else:
for j, filter_key in enumerate(prompt["metadata_filter"]):
if not isinstance(filter_key, str):
self.errors.append(
f"Section {section_id}, step {step_id}: feedback_prompts[{i}].metadata_filter[{j}] must be a string"
)
def _validate_buckets(self, buckets: List[str], section_id: str, step_id: str):
"""Validate buckets list"""
if not isinstance(buckets, list):