5 validator fixes, [Not Secure] regex, interventions 48/day

Validator improvements (deployed to paper-ai):
- Fix 1: @s → player name (RCON has no executor entity)
- Fix 2: Old NBT {Enchantments:[...]} → 1.21 [enchantments={...}] converter
- Fix 3: Strip invalid item components (display, durability, enc)
- Fix 4: Hallucinated effect repair (invincibility→resistance, etc.)
- Fix 5: Hallucinated command repair (setworldborder→worldborder set, etc.)
- All fixes applied in execute-tail chains too

Chat patterns updated for online-mode=false ([Not Secure] prefix)
Divine interventions set to 48/day

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-03-18 16:05:09 -04:00
parent 1e9bda4a15
commit 72c4469fd9
2 changed files with 198 additions and 10 deletions
+1 -1
View File
@@ -32,7 +32,7 @@
],
"cooldown_seconds": 10,
"max_commands_per_response": 8,
"interventions_per_day": 24,
"interventions_per_day": 48,
"god_chat_prefix": "[§b§lPAPER GOD§r]",
"debug_commands": true,
"sudo_enabled": true,
+197 -9
View File
@@ -27,19 +27,19 @@ log = logging.getLogger(__name__)
CONFIG_PATH = '/etc/mc_aigod_paper.json'
PRAY_PATTERNS = [
re.compile(r'\[.*?\]: <(\w+)> [Pp]ray (.+)'),
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Pp]ray (.+)'),
]
BIBLE_PATTERNS = [
re.compile(r'\[.*?\]: <(\w+)> [Bb]ible\s*$'),
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Bb]ible\s*$'),
]
SUDO_PATTERNS = [
re.compile(r'\[.*?\]: <(\w+)> [Ss]udo (.+)'),
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Ss]udo (.+)'),
]
BUG_LOG_PATTERNS = [
re.compile(r'\[.*?\]: <(\w+)> [Bb]ug[_ ]?[Ll]og(?:\s+(.+))?\s*$'),
re.compile(r'\[.*?\]: (?:\[Not Secure\] )?<(\w+)> [Bb]ug[_ ]?[Ll]og(?:\s+(.+))?\s*$'),
]
JOIN_PATTERN = re.compile(r'\[.*?\]: (\w+) joined the game')
@@ -1162,10 +1162,6 @@ def process_bug_log(player: str, description: str, config):
bf.write("(no raw lines available)\n")
log.info(f"BUG_LOG recorded by {player}: {desc} -> {bug_path}")
# Also write structured feedback to the training audit log
write_training_feedback(player, desc, config)
rcon(
f'tellraw {player} {{"text":"[BUG_LOG] Logged. Thank you.","color":"green"}}',
config["rcon_host"], config["rcon_port"], config["rcon_password"]
@@ -1177,6 +1173,12 @@ def process_bug_log(player: str, description: str, config):
config["rcon_host"], config["rcon_port"], config["rcon_password"]
)
# Training audit — separate try block so bug_log success isn't affected
try:
write_training_feedback(player, desc, config)
except Exception as e:
log.error(f"Training feedback write failed for {player}: {e}", exc_info=True)
# ---------------------------------------------------------------------------
# Training Audit Log — structured JSONL for dataset expansion
# ---------------------------------------------------------------------------
@@ -1271,7 +1273,7 @@ def write_training_feedback(player: str, description: str, config: dict):
"description": description,
"last_sudo_context": {
"prompt": last_sudo.get("prompt", ""),
"results": last_sudo.get("results", []),
"results": [list(r) if isinstance(r, tuple) else r for r in last_sudo.get("results", [])],
"ineffective": last_sudo.get("ineffective", False),
} if last_sudo else None,
"last_prayer_context": {
@@ -2395,6 +2397,172 @@ def _tp_safety_prefix_commands(resolved_cmd: str, config) -> list:
f"effect give {target} minecraft:resistance 8 1",
]
# ---------------------------------------------------------------------------
# Fix 1: @s → player name in RCON context
# ---------------------------------------------------------------------------
def fix_at_s_selector(cmd: str, fallback_player: str) -> str:
"""Replace @s with the requesting player's name.
RCON has no executor entity, so @s always fails with 'No entity was found'."""
if '@s' not in cmd:
return cmd
fixed = cmd.replace('@s', fallback_player)
if fixed != cmd:
log.warning(f"Fixed @s selector: '{cmd}' -> '{fixed}'")
return fixed
# ---------------------------------------------------------------------------
# Fix 2: Old NBT enchantment → 1.21 component syntax
# ---------------------------------------------------------------------------
def fix_nbt_enchantment_syntax(cmd: str) -> str:
"""Convert old NBT {Enchantments:[{id:...,lvl:...}]} and {Enchantments:{...}}
to 1.21 component syntax item[enchantments={name:level}]."""
if 'Enchantments' not in cmd and 'enchantments' not in cmd:
return cmd
# Pattern 1: item{Enchantments:[{id:"name",lvl:N}, ...]}
m = re.match(r'^(give\s+\S+\s+)(minecraft:\w+)\{Enchantments:\[(.+?)\]\}(.*)$', cmd, re.I)
if m:
pre, item, body, rest = m.groups()
enchants = {}
for eid, lvl in re.findall(r'id:[\"\']?(?:minecraft:)?([a-z_]+)[\"\']?\s*,\s*lvl[\"\']?:?\s*[\"\']?(\d+)', body):
enchants[eid] = lvl
if enchants:
ench_str = ','.join(f'{k}:{v}' for k, v in enchants.items())
fixed = f"{pre}{item}[enchantments={{{ench_str}}}]{rest}"
log.warning(f"Fixed NBT enchant (list): '{cmd}' -> '{fixed}'")
return fixed
# Pattern 2: item{Enchantments:{minecraft:name:N, ...}} (non-standard)
m = re.match(r'^(give\s+\S+\s+)(?:minecraft:)?(\w+)\{Enchantments:\{(.+?)\}\}(.*)$', cmd, re.I)
if m:
pre, item, body, rest = m.groups()
enchants = {}
for eid, lvl in re.findall(r'(?:minecraft:)?([a-z_]+):(\d+)', body):
enchants[eid] = lvl
if enchants:
ench_str = ','.join(f'{k}:{v}' for k, v in enchants.items())
fixed = f"{pre}minecraft:{item}[enchantments={{{ench_str}}}]{rest}"
log.warning(f"Fixed NBT enchant (dict): '{cmd}' -> '{fixed}'")
return fixed
return cmd
# ---------------------------------------------------------------------------
# Fix 3: Strip invalid item components
# ---------------------------------------------------------------------------
_INVALID_COMPONENTS = re.compile(
r',?\s*(?:display|durability|enc|Durability|Display|Lore|CustomModelData|HideFlags'
r'|Paper|Unbreakable|Displayname|CustomName)(?::\{[^}]*\}|=[^,\]}\s]+|:[^,\]}\s]+)',
re.I,
)
def fix_invalid_item_components(cmd: str) -> str:
"""Strip unrecognized item components from give commands.
Removes display:{...}, durability=N, enc=N, etc."""
if not cmd.startswith('give ') or '[' not in cmd:
return cmd
# Find the component section: item[...]
m = re.search(r'(\[.+?\])', cmd)
if not m:
return cmd
bracket = m.group(1)
cleaned = _INVALID_COMPONENTS.sub('', bracket)
# Clean up dangling commas
cleaned = re.sub(r'\{,', '{', cleaned)
cleaned = re.sub(r',\}', '}', cleaned)
cleaned = re.sub(r',\]', ']', cleaned)
if cleaned != bracket:
# Remove empty brackets entirely
if cleaned in ('[]', '[,]'):
cleaned = ''
fixed = cmd[:m.start()] + cleaned + cmd[m.end():]
log.warning(f"Stripped invalid components: '{cmd}' -> '{fixed}'")
return fixed
return cmd
# ---------------------------------------------------------------------------
# Fix 4: Hallucinated effect/item validation with fuzzy matching
# ---------------------------------------------------------------------------
VALID_EFFECTS = {
'speed', 'slowness', 'haste', 'mining_fatigue', 'strength', 'instant_health',
'instant_damage', 'jump_boost', 'nausea', 'regeneration', 'resistance',
'fire_resistance', 'water_breathing', 'invisibility', 'blindness', 'night_vision',
'hunger', 'weakness', 'poison', 'wither', 'health_boost', 'absorption',
'saturation', 'glowing', 'levitation', 'luck', 'unluck', 'slow_falling',
'conduit_power', 'dolphins_grace', 'bad_omen', 'hero_of_the_village',
'darkness', 'trial_omen', 'raid_omen', 'wind_charged', 'weaving', 'oozing',
'infested',
}
EFFECT_ALIASES = {
'invincibility': 'resistance', 'invulnerability': 'resistance',
'invulnerable': 'resistance', 'invincible': 'resistance',
'experience_boost': 'luck', 'xp_boost': 'luck',
'slow_speed': 'slowness', 'slow': 'slowness',
'fast': 'speed', 'swift': 'speed', 'quick': 'speed',
'do_not_breed': 'weakness',
'fire': 'fire_resistance',
'healing': 'instant_health', 'heal': 'instant_health',
'damage': 'instant_damage', 'harm': 'instant_damage',
'swim': 'dolphins_grace', 'swimming': 'dolphins_grace',
'flying': 'levitation', 'float': 'slow_falling',
}
def fix_hallucinated_effect(cmd: str) -> str:
"""Replace hallucinated effect names with the closest valid effect."""
m = re.match(r'^(effect\s+give\s+\S+\s+minecraft:)(\w+)(.*)$', cmd)
if not m:
return cmd
pre, effect, rest = m.groups()
if effect in VALID_EFFECTS:
return cmd
replacement = EFFECT_ALIASES.get(effect)
if replacement:
fixed = f"{pre}{replacement}{rest}"
log.warning(f"Fixed hallucinated effect: '{effect}' -> '{replacement}' in '{cmd}'")
return fixed
# No known alias — log but don't block (let RCON report the error for training data)
log.warning(f"Unknown effect '{effect}' in: {cmd}")
return cmd
# ---------------------------------------------------------------------------
# Fix 5: Hallucinated command repair
# ---------------------------------------------------------------------------
COMMAND_ALIASES = {
r'^setworldborder\s+(\d+)': lambda m: f"worldborder set {m.group(1)}",
r'^setspawn\b': lambda m: f"spawnpoint",
r'^heal\s+(.+)': lambda m: f"effect give {m.group(1)} minecraft:instant_health 1 1",
r'^difficulty set\s+(\w+)': lambda m: f"difficulty {m.group(1)}",
r'^commands?\b': lambda m: None, # drop the garbage 'commands' token
r'^gamerule\s+timeSpeed\s+': lambda m: None, # not a real gamerule
}
def fix_hallucinated_command(cmd: str) -> str:
"""Rewrite commonly hallucinated commands to their real equivalents."""
for pattern, fixer in COMMAND_ALIASES.items():
m = re.match(pattern, cmd, re.I)
if m:
result = fixer(m)
if result is None:
log.warning(f"Dropped hallucinated command: '{cmd}'")
return ""
log.warning(f"Fixed hallucinated command: '{cmd}' -> '{result}'")
return result
return cmd
# ---------------------------------------------------------------------------
# Existing fix functions
# ---------------------------------------------------------------------------
def fix_give_command(cmd: str) -> str:
"""
Correct common LLM give command mistakes:
@@ -2574,6 +2742,13 @@ def _repair_execute_tail(cmd: str, fallback_player: str) -> str:
tail = tail[idx + len(marker):].strip()
fixed_tail = tail
fixed_tail = fix_hallucinated_command(fixed_tail)
if not fixed_tail:
return ""
fixed_tail = fix_at_s_selector(fixed_tail, fallback_player)
fixed_tail = fix_nbt_enchantment_syntax(fixed_tail)
fixed_tail = fix_invalid_item_components(fixed_tail)
fixed_tail = fix_hallucinated_effect(fixed_tail)
fixed_tail = fix_give_command(fixed_tail)
fixed_tail = fix_effect_command(fixed_tail)
fixed_tail = fix_gamemode_command(fixed_tail, fallback_player)
@@ -2594,6 +2769,19 @@ def validate_command(cmd, online_players, fallback_player, config=None):
resolved = resolved.strip()
if resolved.startswith("/"):
resolved = resolved[1:]
# Fix 5: hallucinated commands (must run first — may drop or rewrite entire command)
resolved = fix_hallucinated_command(resolved)
if not resolved:
return "", False
# Fix 1: @s → player name (RCON has no executor entity)
resolved = fix_at_s_selector(resolved, fallback_player)
# Fix 2: old NBT enchantment → 1.21 component syntax
resolved = fix_nbt_enchantment_syntax(resolved)
# Fix 3: strip invalid item components (display, durability, enc, etc.)
resolved = fix_invalid_item_components(resolved)
# Fix 4: hallucinated effect names → closest valid effect
resolved = fix_hallucinated_effect(resolved)
# Existing fixes
resolved = fix_give_command(resolved)
resolved = fix_effect_command(resolved)
resolved = fix_gamemode_command(resolved, fallback_player)