From 72c4469fd9a020c73b1f6f48fdac1501cdf3bcea Mon Sep 17 00:00:00 2001 From: Claude Code Date: Wed, 18 Mar 2026 16:05:09 -0400 Subject: [PATCH] 5 validator fixes, [Not Secure] regex, interventions 48/day MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- mc_aigod_paper.json | 2 +- mc_aigod_paper.py | 206 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 198 insertions(+), 10 deletions(-) diff --git a/mc_aigod_paper.json b/mc_aigod_paper.json index 6f955a8..9013bdf 100644 --- a/mc_aigod_paper.json +++ b/mc_aigod_paper.json @@ -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, diff --git a/mc_aigod_paper.py b/mc_aigod_paper.py index a020b9f..617e136 100644 --- a/mc_aigod_paper.py +++ b/mc_aigod_paper.py @@ -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)