Port validator fixes, training audit, God Soul to shrink-world

Ported from mc_aigod_paper.py (vanilla-compatible, no Paper/plugins):
- 5 validator fixes: @s→player, NBT enchant→components, strip invalid
  components, hallucinated effects, hallucinated commands
- Training audit logger → /var/log/mc_training_audit_shrink.jsonl
- God Soul document loading (/etc/god_soul.md)
- Prayer title flash ("Your prayers have been answered!")

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-03-18 22:04:08 -04:00
parent 066aef3c39
commit 0556d0ca16
+194 -1
View File
@@ -1373,9 +1373,169 @@ def fix_weather_command(cmd: str) -> str:
log.warning(f"Fixed weather syntax: '{cmd}' -> '{fixed}'") log.warning(f"Fixed weather syntax: '{cmd}' -> '{fixed}'")
return fixed return fixed
# --- Validator fixes (ported from mc_aigod_paper.py) ---
def fix_at_s_selector(cmd: str, fallback_player: str) -> str:
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
def fix_nbt_enchantment_syntax(cmd: str) -> str:
if 'Enchantments' not in cmd: return cmd
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: '{cmd}' -> '{fixed}'")
return fixed
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: '{cmd}' -> '{fixed}'")
return fixed
return cmd
_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:
if not cmd.startswith('give ') or '[' not in cmd: return cmd
m = re.search(r'(\[.+?\])', cmd)
if not m: return cmd
bracket = m.group(1)
cleaned = _INVALID_COMPONENTS.sub('', bracket)
cleaned = re.sub(r'\{,', '{', cleaned)
cleaned = re.sub(r',\}', '}', cleaned)
if cleaned in ('[]', '[,]'): cleaned = ''
if cleaned != bracket:
fixed = cmd[:m.start()] + cleaned + cmd[m.end():]
log.warning(f"Stripped invalid components: '{cmd}' -> '{fixed}'")
return fixed
return cmd
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',
}
EFFECT_ALIASES = {
'invincibility': 'resistance', 'invulnerability': 'resistance',
'invulnerable': 'resistance', 'invincible': 'resistance',
'experience_boost': 'luck', 'slow_speed': 'slowness',
'fire': 'fire_resistance', 'healing': 'instant_health',
'damage': 'instant_damage',
}
def fix_hallucinated_effect(cmd: str) -> str:
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}'")
return fixed
return cmd
COMMAND_ALIASES = {
r'^setworldborder\s+(\d+)': lambda m: f"worldborder set {m.group(1)}",
r'^setspawn\b': lambda m: "spawnpoint",
r'^heal\s+(.+)': lambda m: f"effect give {m.group(1)} minecraft:instant_health 1 1",
r'^commands?\b': lambda m: None,
}
def fix_hallucinated_command(cmd: str) -> str:
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
# --- God Soul ---
_GOD_SOUL = ""
try:
with open(os.path.join(os.path.dirname(__file__) or ".", "god_soul.md")) as _f:
_GOD_SOUL = _f.read()
except FileNotFoundError:
try:
with open("/etc/god_soul.md") as _f:
_GOD_SOUL = _f.read()
except FileNotFoundError:
pass
# --- Training Audit ---
_audit_lock = threading.Lock()
def write_training_audit(player, mode, user_message, commands_generated,
commands_executed, message, context, config, rcon_results=None):
audit_path = config.get("training_audit_path", "/var/log/mc_training_audit.jsonl")
server_ctx = {
"server_type": config.get("server_type", "vanilla"),
"version": "1.21.x",
"online_players": context.get("online_players", []),
}
entry = {
"timestamp": time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
"source": "live_playtest",
"category": "command_gen",
"mode": mode,
"player": player,
"input": {"user_message": user_message, "server_context": server_ctx},
"output": {
"commands_generated": commands_generated or [],
"commands_executed": commands_executed or [],
"message": message or "",
},
"rcon_results": rcon_results or [],
"needs_review": True,
}
try:
parent = os.path.dirname(audit_path)
if parent: os.makedirs(parent, exist_ok=True)
with _audit_lock:
with open(audit_path, 'a', encoding='utf-8') as f:
f.write(json.dumps(entry, ensure_ascii=False) + '\n')
except Exception as e:
log.error(f"Training audit write failed: {e}")
def validate_command(cmd, online_players, fallback_player, config=None): def validate_command(cmd, online_players, fallback_player, config=None):
"""Replace placeholders, auto-fix common give syntax errors, check safe prefix.""" """Replace placeholders, auto-fix common syntax errors, check safe prefix."""
resolved = cmd.replace("{player}", fallback_player).replace("{target}", fallback_player) resolved = cmd.replace("{player}", fallback_player).replace("{target}", fallback_player)
resolved = resolved.strip()
if resolved.startswith("/"): resolved = resolved[1:]
# New fixes
resolved = fix_hallucinated_command(resolved)
if not resolved: return "", False
resolved = fix_at_s_selector(resolved, fallback_player)
resolved = fix_nbt_enchantment_syntax(resolved)
resolved = fix_invalid_item_components(resolved)
resolved = fix_hallucinated_effect(resolved)
# Existing fixes
resolved = fix_give_command(resolved) resolved = fix_give_command(resolved)
resolved = fix_effect_command(resolved) resolved = fix_effect_command(resolved)
resolved = fix_weather_command(resolved) resolved = fix_weather_command(resolved)
@@ -1433,6 +1593,20 @@ def execute_response(response, context, config, praying_player=None):
) )
time.sleep(0.2) time.sleep(0.2)
# Flash title on the praying player's screen
if praying_player and (commands or message):
try:
rcon(
f'title {praying_player} times 5 40 15',
config["rcon_host"], config["rcon_port"], config["rcon_password"]
)
rcon(
f'title {praying_player} title {{"text":"Your prayers have been answered!","color":"gold","bold":true}}',
config["rcon_host"], config["rcon_port"], config["rcon_password"]
)
except Exception:
pass
fallback = praying_player or (context["online_players"][0] if context["online_players"] else "") fallback = praying_player or (context["online_players"][0] if context["online_players"] else "")
max_cmds = config.get("max_commands_per_response", 6) max_cmds = config.get("max_commands_per_response", 6)
@@ -1680,6 +1854,16 @@ def process_sudo(player, prompt, config):
add_sudo_history(player, prompt, commands, executed) add_sudo_history(player, prompt, commands, executed)
# Training audit
online = players_online(config)
write_training_audit(
player=player, mode="sudo",
user_message=f"sudo {prompt}",
commands_generated=commands,
commands_executed=executed,
message="", context={"online_players": online}, config=config,
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Prayer handler # Prayer handler
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -1784,6 +1968,15 @@ def process_prayer(player, prayer, config, cooldowns):
if god_msg: if god_msg:
add_prayer_memory(player, prayer, god_msg, config) add_prayer_memory(player, prayer, god_msg, config)
# Training audit
write_training_audit(
player=player, mode="god",
user_message=f"pray {prayer}",
commands_generated=response.get("commands") or [],
commands_executed=response.get("commands") or [],
message=god_msg, context=context, config=config,
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Divine intervention timer # Divine intervention timer
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------