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:
+194
-1
@@ -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
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user