← Back to list

RHEA_Tactical_Test_Harness

Sovereign Order of Enigmatic Republics: ZadienLabs · 2026-03-10 09:48 · 0 claps · 17.1 min read
#rhea-ucm #zadien-labs #reb
Open on Medium ↗

# ╔══════════════════════════════════════════════════════════════════════════╗
# ║ RHEA Tactical Reasoning Test Harness                                    ║
# ║ Simulation: RHEA_Tactical_TicTacToe_Curriculum                          ║
# ║ License / Identifier: RHEA-Core Public Grant v2.1                       ║
# ║ Framework: RHEA-UCM / ZADEIAN-RHEA Sentinel                             ║
# ║ Author: Paul M. Roe / EnigmaticGlitch                                   ║
# ╚══════════════════════════════════════════════════════════════════════════╝

from __future__ import annotations

import json
import math
import time
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

# ============================================================
# Core data structures
# ============================================================

@dataclass
class StepRecord:
    step_index: int
    observation: Any
    action: Any
    reward: float
    trust: float
    entropy: float
    reseal_triggered: bool
    glyph: str
    metadata: Dict[str, Any] = field(default_factory=dict)

@dataclass
class EpisodeResult:
    task_name: str
    agent_name: str
    success: bool
    total_reward: float
    steps: int
    duration_ms: float
    entropy_peak: float
    trust_floor_hit: bool
    reseal_count: int
    final_glyph: str
    records: List[StepRecord]

    def to_dict(self) -> Dict[str, Any]:
        payload = asdict(self)
        payload["records"] = [asdict(r) for r in self.records]
        return payload

# ============================================================
# Task interface
# ============================================================

class Task:
    name: str = "task"

    def reset(self) -> None:
        raise NotImplementedError

    def observe(self) -> Any:
        raise NotImplementedError

    def step(self, action: Any) -> None:
        raise NotImplementedError

    def reward(self) -> float:
        raise NotImplementedError

    def done(self) -> bool:
        raise NotImplementedError

    def success(self) -> bool:
        raise NotImplementedError

    def metadata(self) -> Dict[str, Any]:
        return {}

# ============================================================
# Sequence / arithmetic tasks
# ============================================================

class SequenceTask(Task):
    name = "sequence_task"

    def __init__(self, sequence: Optional[List[int]] = None):
        self.sequence = sequence or [2, 4, 6, 8]
        self.expected = self._expected_next(self.sequence)
        self._done = False
        self._correct = False

    @staticmethod
    def _expected_next(sequence: List[int]) -> int:
        return sequence[-1] + (sequence[-1] - sequence[-2])

    def reset(self) -> None:
        self._done = False
        self._correct = False

    def observe(self) -> Dict[str, Any]:
        return {"type": "sequence", "sequence": list(self.sequence), "task": "predict_next"}

    def step(self, action: Any) -> None:
        self._correct = action == self.expected
        self._done = True

    def reward(self) -> float:
        return 1.0 if self._correct else 0.0

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._correct

    def metadata(self) -> Dict[str, Any]:
        return {"expected": self.expected}

class CorruptedSequenceTask(SequenceTask):
    name = "corrupted_sequence_task"

    def __init__(self, sequence: Optional[List[int]] = None, corruption_index: int = 2, corruption_value: int = 99):
        self.original_sequence = list(sequence or [2, 4, 6, 8])
        self.corruption_index = corruption_index
        self.corruption_value = corruption_value
        super().__init__(sequence=list(self.original_sequence))
        self.expected = self._expected_next(self.original_sequence)

    def reset(self) -> None:
        super().reset()
        self.sequence = list(self.original_sequence)
        if 0 <= self.corruption_index < len(self.sequence):
            self.sequence[self.corruption_index] = self.corruption_value

    def observe(self) -> Dict[str, Any]:
        return {"type": "corrupted_sequence", "sequence": list(self.sequence), "task": "repair_and_predict"}

    def metadata(self) -> Dict[str, Any]:
        payload = super().metadata()
        payload.update(
            {
                "original_sequence": list(self.original_sequence),
                "corruption_index": self.corruption_index,
                "corruption_value": self.corruption_value,
            }
        )
        return payload

class ArithmeticTask(Task):
    name = "arithmetic_task"

    def __init__(self, expression: str = "7 * (3 + 2)"):
        self.expression = expression
        self.expected = eval(expression, {"__builtins__": {}}, {})
        self._done = False
        self._correct = False

    def reset(self) -> None:
        self._done = False
        self._correct = False

    def observe(self) -> Dict[str, Any]:
        return {"type": "arithmetic", "expression": self.expression, "task": "evaluate"}

    def step(self, action: Any) -> None:
        self._correct = action == self.expected
        self._done = True

    def reward(self) -> float:
        return 1.0 if self._correct else 0.0

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._correct

    def metadata(self) -> Dict[str, Any]:
        return {"expected": self.expected}

class ModularArithmeticTask(Task):
    name = "modular_arithmetic_task"

    def __init__(self, a: int = 17, b: int = 9, modulus: int = 5, op: str = "+"):
        self.a = a
        self.b = b
        self.modulus = modulus
        self.op = op
        self.expected = self._compute_expected()
        self._done = False
        self._correct = False

    def _compute_expected(self) -> int:
        if self.op == "+":
            return (self.a + self.b) % self.modulus
        if self.op == "-":
            return (self.a - self.b) % self.modulus
        if self.op == "*":
            return (self.a * self.b) % self.modulus
        raise ValueError(f"Unsupported modular op: {self.op}")

    def reset(self) -> None:
        self._done = False
        self._correct = False

    def observe(self) -> Dict[str, Any]:
        return {
            "type": "modular_arithmetic",
            "a": self.a,
            "b": self.b,
            "modulus": self.modulus,
            "op": self.op,
            "task": "evaluate_modular_expression",
        }

    def step(self, action: Any) -> None:
        self._correct = action == self.expected
        self._done = True

    def reward(self) -> float:
        return 1.0 if self._correct else 0.0

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._correct

    def metadata(self) -> Dict[str, Any]:
        return {"expected": self.expected}

# ============================================================
# Tic-tac-toe tasks
# ============================================================

class TicTacToeTacticTask(Task):
    name = "tictactoe_tactic_task"

    def __init__(self):
        self.board = ["X", "O", "X", "O", "X", ".", ".", ".", "O"]
        self.expected_moves = {6}
        self._done = False
        self._correct = False

    def reset(self) -> None:
        self._done = False
        self._correct = False

    def observe(self) -> Dict[str, Any]:
        return {"type": "tictactoe", "board": list(self.board), "player": "X", "task": "find_winning_move"}

    def step(self, action: Any) -> None:
        self._correct = action in self.expected_moves
        self._done = True

    def reward(self) -> float:
        return 1.0 if self._correct else 0.0

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._correct

    def metadata(self) -> Dict[str, Any]:
        return {"expected_moves": sorted(self.expected_moves)}

class TicTacToeBlockTask(Task):
    name = "tictactoe_block_task"

    def __init__(self):
        self.board = ["X", "O", ".", ".", "O", ".", "X", ".", "."]
        self.expected_moves = {7}
        self._done = False
        self._correct = False

    def reset(self) -> None:
        self._done = False
        self._correct = False

    def observe(self) -> Dict[str, Any]:
        return {
            "type": "tictactoe_block",
            "board": list(self.board),
            "player": "X",
            "opponent": "O",
            "task": "find_blocking_move",
        }

    def step(self, action: Any) -> None:
        self._correct = action in self.expected_moves
        self._done = True

    def reward(self) -> float:
        return 1.0 if self._correct else 0.0

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._correct

    def metadata(self) -> Dict[str, Any]:
        return {"expected_moves": sorted(self.expected_moves)}

class TicTacToeForkTask(Task):
    name = "tictactoe_fork_task"

    def __init__(self):
        self.board = ["X", ".", ".", ".", "O", ".", ".", ".", "X"]
        self.expected_moves = {2, 6}
        self._done = False
        self._correct = False

    def reset(self) -> None:
        self._done = False
        self._correct = False

    def observe(self) -> Dict[str, Any]:
        return {
            "type": "tictactoe_fork",
            "board": list(self.board),
            "player": "X",
            "opponent": "O",
            "task": "find_fork_move",
        }

    def step(self, action: Any) -> None:
        self._correct = action in self.expected_moves
        self._done = True

    def reward(self) -> float:
        return 1.0 if self._correct else 0.0

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._correct

    def metadata(self) -> Dict[str, Any]:
        return {"expected_moves": sorted(self.expected_moves)}

class TicTacToeCenterPreferenceTask(Task):
    name = "tictactoe_center_preference_task"

    def __init__(self):
        self.board = ["X", ".", ".", ".", ".", ".", ".", ".", "O"]
        self.expected_moves = {4}
        self._done = False
        self._correct = False

    def reset(self) -> None:
        self._done = False
        self._correct = False

    def observe(self) -> Dict[str, Any]:
        return {
            "type": "tictactoe_center",
            "board": list(self.board),
            "player": "X",
            "opponent": "O",
            "task": "prefer_center",
        }

    def step(self, action: Any) -> None:
        self._correct = action in self.expected_moves
        self._done = True

    def reward(self) -> float:
        return 1.0 if self._correct else 0.0

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._correct

    def metadata(self) -> Dict[str, Any]:
        return {"expected_moves": sorted(self.expected_moves)}

class TicTacToeNoiseTask(Task):
    name = "tictactoe_noise_task"

    def __init__(self):
        self.board = ["X", "O", "X", "?", "O", ".", "X", ".", "."]
        self.expected_moves = {7}
        self._done = False
        self._correct = False

    def reset(self) -> None:
        self._done = False
        self._correct = False

    def observe(self) -> Dict[str, Any]:
        return {
            "type": "tictactoe_noise",
            "board": list(self.board),
            "player": "X",
            "opponent": "O",
            "task": "resolve_under_normalized_corruption",
        }

    def step(self, action: Any) -> None:
        self._correct = action in self.expected_moves
        self._done = True

    def reward(self) -> float:
        return 1.0 if self._correct else 0.0

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._correct

    def metadata(self) -> Dict[str, Any]:
        return {"expected_moves": sorted(self.expected_moves), "corrupted_token": "?", "policy": "normalize_to_empty"}

class TicTacToeAmbiguousTask(Task):
    name = "tictactoe_ambiguous_task"

    def __init__(self):
        self.board = ["X", "O", "X", "?", "O", ".", "X", ".", "."]
        self.expected_moves = {7}
        self._done = False
        self._correct = False

    def reset(self) -> None:
        self._done = False
        self._correct = False

    def observe(self) -> Dict[str, Any]:
        return {
            "type": "tictactoe_ambiguous",
            "board": list(self.board),
            "player": "X",
            "opponent": "O",
            "task": "resolve_under_ambiguity",
        }

    def step(self, action: Any) -> None:
        self._correct = action in self.expected_moves
        self._done = True

    def reward(self) -> float:
        return 1.0 if self._correct else 0.0

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._correct

    def metadata(self) -> Dict[str, Any]:
        return {"expected_moves": sorted(self.expected_moves), "corrupted_token": "?", "policy": "conservative_under_ambiguity"}

class TicTacToeTwoPlyTrapTask(Task):
    name = "tictactoe_two_ply_trap_task"

    def __init__(self):
        self.initial_board = ["X", "O", ".", ".", "X", ".", ".", ".", "O"]
        self.player = "X"
        self.opponent = "O"
        self.expected_first_moves = {6}
        self.board: List[str] = []
        self.phase = 0
        self._done = False
        self._success = False
        self._reward = 0.0
        self.history: List[int] = []

    def reset(self) -> None:
        self.board = list(self.initial_board)
        self.phase = 0
        self._done = False
        self._success = False
        self._reward = 0.0
        self.history = []

    def observe(self) -> Dict[str, Any]:
        return {
            "type": "tictactoe_two_ply_trap",
            "board": list(self.board),
            "player": self.player,
            "opponent": self.opponent,
            "task": "avoid_two_ply_trap",
            "phase": self.phase,
            "history": list(self.history),
        }

    @staticmethod
    def _wins() -> List[Tuple[int, int, int]]:
        return [
            (0, 1, 2), (3, 4, 5), (6, 7, 8),
            (0, 3, 6), (1, 4, 7), (2, 5, 8),
            (0, 4, 8), (2, 4, 6),
        ]

    @classmethod
    def _is_winner(cls, board: List[str], player: str) -> bool:
        return any(all(board[i] == player for i in line) for line in cls._wins())

    def _scripted_opponent_reply(self) -> None:
        move = BaselineAgent._winning_move(self.board, self.opponent)
        if move is None:
            move = BaselineAgent._blocking_move(self.board, self.player)
        if move is None:
            move = next(i for i, c in enumerate(self.board) if c == ".")
        self.board[move] = self.opponent
        self.history.append(move)

    def step(self, action: Any) -> None:
        if self._done:
            return
        if not (0 <= int(action) < 9) or self.board[int(action)] != ".":
            self._reward = 0.0
            self._success = False
            self._done = True
            return

        action = int(action)
        self.board[action] = self.player
        self.history.append(action)

        if self.phase == 0:
            if action not in self.expected_first_moves:
                self._reward = 0.0
                self._success = False
                self._done = True
                return
            if self._is_winner(self.board, self.player):
                self._reward = 1.0
                self._success = True
                self._done = True
                return
            self._scripted_opponent_reply()
            if self._is_winner(self.board, self.opponent):
                self._reward = 0.0
                self._success = False
                self._done = True
                return
            self.phase = 1
            return

        if self._is_winner(self.board, self.player):
            self._reward = 1.0
            self._success = True
        else:
            next_win = BaselineAgent._winning_move(self.board, self.player)
            self._reward = 0.75 if next_win is not None else 0.5
            self._success = True
        self._done = True

    def reward(self) -> float:
        return self._reward

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._success

    def metadata(self) -> Dict[str, Any]:
        return {"expected_first_moves": sorted(self.expected_first_moves), "phases": 2}

class TicTacToeAmbiguityForkTask(Task):
    name = "tictactoe_ambiguity_fork_task"

    def __init__(self):
        self.initial_board = ["X", ".", "?", ".", "O", ".", ".", ".", "X"]
        self.player = "X"
        self.opponent = "O"
        self.expected_moves = {6}
        self.board: List[str] = []
        self._done = False
        self._success = False
        self._reward = 0.0

    def reset(self) -> None:
        self.board = list(self.initial_board)
        self._done = False
        self._success = False
        self._reward = 0.0

    def observe(self) -> Dict[str, Any]:
        return {
            "type": "tictactoe_ambiguity_fork",
            "board": list(self.board),
            "player": self.player,
            "opponent": self.opponent,
            "task": "resolve_adversarial_ambiguity",
        }

    def step(self, action: Any) -> None:
        self._success = int(action) in self.expected_moves
        self._reward = 1.0 if self._success else 0.0
        self._done = True

    def reward(self) -> float:
        return self._reward

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._success

    def metadata(self) -> Dict[str, Any]:
        return {
            "expected_moves": sorted(self.expected_moves),
            "corrupted_token": "?",
            "policy": "conservative_branch_under_ambiguity",
        }

class TicTacToeRolloutTask(Task):
    name = "tictactoe_rollout_task"

    def __init__(self, board: Optional[List[str]] = None, player: str = "X", opponent: str = "O", max_turns: int = 3):
        self.initial_board = list(board or ["X", ".", ".", ".", "O", ".", ".", ".", "."])
        self.player = player
        self.opponent = opponent
        self.max_turns = max_turns
        self.board: List[str] = []
        self.turn_count = 0
        self._done = False
        self._success = False
        self._reward = 0.0

    def reset(self) -> None:
        self.board = list(self.initial_board)
        self.turn_count = 0
        self._done = False
        self._success = False
        self._reward = 0.0

    @staticmethod
    def _wins() -> List[Tuple[int, int, int]]:
        return [
            (0, 1, 2), (3, 4, 5), (6, 7, 8),
            (0, 3, 6), (1, 4, 7), (2, 5, 8),
            (0, 4, 8), (2, 4, 6),
        ]

    @classmethod
    def _is_winner(cls, board: List[str], player: str) -> bool:
        return any(all(board[i] == player for i in line) for line in cls._wins())

    def observe(self) -> Dict[str, Any]:
        return {
            "type": "tictactoe_rollout",
            "board": list(self.board),
            "player": self.player,
            "opponent": self.opponent,
            "task": "stateful_rollout",
            "turn_count": self.turn_count,
            "max_turns": self.max_turns,
        }

    def _opponent_move(self) -> Optional[int]:
        move = BaselineAgent._winning_move(self.board, self.opponent)
        if move is None:
            move = BaselineAgent._blocking_move(self.board, self.player)
        if move is None:
            empties = [i for i, c in enumerate(self.board) if c == "."]
            move = empties[0] if empties else None
        return move

    def step(self, action: Any) -> None:
        if self._done:
            return
        if not (0 <= int(action) < 9) or self.board[int(action)] != ".":
            self._reward = 0.0
            self._success = False
            self._done = True
            return

        action = int(action)
        self.board[action] = self.player
        self.turn_count += 1

        if self._is_winner(self.board, self.player):
            self._reward = 1.0
            self._success = True
            self._done = True
            return

        if self.turn_count >= self.max_turns:
            self._reward = 0.5
            self._success = True
            self._done = True
            return

        opp = self._opponent_move()
        if opp is not None:
            self.board[opp] = self.opponent

        if self._is_winner(self.board, self.opponent):
            self._reward = 0.0
            self._success = False
            self._done = True
            return

        if all(c != "." for c in self.board):
            self._reward = 0.5
            self._success = True
            self._done = True

    def reward(self) -> float:
        return self._reward

    def done(self) -> bool:
        return self._done

    def success(self) -> bool:
        return self._success

    def metadata(self) -> Dict[str, Any]:
        return {"max_turns": self.max_turns}

# ============================================================
# Glyph + entropy/trust core
# ============================================================

class GlyphMemory:
    def __init__(self) -> None:
        self.history: List[str] = []

    def encode(self, observation: Any, trust: float, entropy: float) -> str:
        obs_type = observation.get("type", "unknown") if isinstance(observation, dict) else type(observation).__name__
        if entropy > 0.8:
            glyph = "♇TIK-IGNIS"
        elif trust < 0.35:
            glyph = "♛ROAR-CORE"
        elif obs_type.startswith("tictactoe"):
            glyph = "∆DOLPH-SYNC"
        elif obs_type in {"arithmetic", "modular_arithmetic"}:
            glyph = "ΞISLAND-FRAME"
        elif obs_type == "reseal":
            glyph = "⟁RESEAL-LOCK"
        else:
            glyph = "⸸SUNSET-VANE"
        self.history.append(glyph)
        return glyph

class TrustEntropyCore:
    def __init__(self, trust: float = 0.8, entropy: float = 0.1, trust_floor: float = 0.25):
        self.trust = trust
        self.entropy = entropy
        self.trust_floor = trust_floor
        self.reseal_count = 0
        self.trust_floor_hit = False

    def update_from_error(self, error_signal: float) -> bool:
        self.entropy = min(1.0, max(0.0, self.entropy + 0.15 * abs(error_signal)))
        self.trust = max(0.0, min(1.0, self.trust * math.exp(-0.35 * abs(error_signal))))
        reseal = False
        if self.trust < self.trust_floor or self.entropy > 0.9:
            reseal = True
            self.reseal()
        self.trust_floor_hit = self.trust_floor_hit or (self.trust <= self.trust_floor)
        return reseal

    def reward_reinforce(self, reward: float) -> None:
        if reward > 0:
            self.trust = min(1.0, self.trust + 0.1 * reward)
            self.entropy = max(0.0, self.entropy - 0.08 * reward)

    def reseal(self) -> None:
        self.reseal_count += 1
        self.trust = max(self.trust, 0.55)
        self.entropy = min(self.entropy, 0.35)

# ============================================================
# Agent interface
# ============================================================

class Agent:
    name: str = "agent"

    def reset(self) -> None:
        raise NotImplementedError

    def act(self, observation: Any) -> Any:
        raise NotImplementedError

    def update(self, observation: Any, action: Any, reward: float) -> Dict[str, Any]:
        raise NotImplementedError

    @property
    def trust(self) -> float:
        raise NotImplementedError

    @property
    def entropy(self) -> float:
        raise NotImplementedError

    @property
    def reseal_count(self) -> int:
        raise NotImplementedError

    @property
    def trust_floor_hit(self) -> bool:
        raise NotImplementedError

    @property
    def current_glyph(self) -> str:
        raise NotImplementedError

class BaselineAgent(Agent):
    name = "baseline_agent"

    def __init__(self) -> None:
        self._trust = 1.0
        self._entropy = 0.0
        self._reseal_count = 0
        self._trust_floor_hit = False
        self._glyph = "BASELINE"

    def reset(self) -> None:
        self._trust = 1.0
        self._entropy = 0.0
        self._reseal_count = 0
        self._trust_floor_hit = False
        self._glyph = "BASELINE"

    @staticmethod
    def _wins() -> List[Tuple[int, int, int]]:
        return [
            (0, 1, 2), (3, 4, 5), (6, 7, 8),
            (0, 3, 6), (1, 4, 7), (2, 5, 8),
            (0, 4, 8), (2, 4, 6),
        ]

    @classmethod
    def _is_winner(cls, board: List[str], player: str) -> bool:
        return any(all(board[i] == player for i in line) for line in cls._wins())

    @classmethod
    def _winning_move(cls, board: List[str], player: str) -> Optional[int]:
        for idx, cell in enumerate(board):
            if cell != ".":
                continue
            trial = board.copy()
            trial[idx] = player
            if cls._is_winner(trial, player):
                return idx
        return None

    @classmethod
    def _blocking_move(cls, board: List[str], opponent: str) -> Optional[int]:
        for idx, cell in enumerate(board):
            if cell != ".":
                continue
            trial = board.copy()
            trial[idx] = opponent
            if cls._is_winner(trial, opponent):
                return idx
        return None

    @classmethod
    def _fork_move(cls, board: List[str], player: str) -> Optional[int]:
        for idx, cell in enumerate(board):
            if cell != ".":
                continue
            trial = board.copy()
            trial[idx] = player
            winning_replies = 0
            for jdx, cell2 in enumerate(trial):
                if cell2 != ".":
                    continue
                trial2 = trial.copy()
                trial2[jdx] = player
                if cls._is_winner(trial2, player):
                    winning_replies += 1
            if winning_replies >= 2:
                return idx
        return None

    @staticmethod
    def _center_or_corner(board: List[str]) -> int:
        if board[4] == ".":
            return 4
        for idx in (0, 2, 6, 8):
            if board[idx] == ".":
                return idx
        return next(i for i, c in enumerate(board) if c == ".")

    def act(self, observation: Any) -> Any:
        obs_type = observation["type"]
        if obs_type in {"sequence", "corrupted_sequence"}:
            seq = observation["sequence"]
            return seq[-1] + (seq[-1] - seq[-2])
        if obs_type == "arithmetic":
            return eval(observation["expression"], {"__builtins__": {}}, {})
        if obs_type == "modular_arithmetic":
            a, b, m, op = observation["a"], observation["b"], observation["modulus"], observation["op"]
            if op == "+":
                return (a + b) % m
            if op == "-":
                return (a - b) % m
            if op == "*":
                return (a * b) % m
        if obs_type == "tictactoe":
            move = self._winning_move(observation["board"], observation["player"])
            return move if move is not None else self._center_or_corner(observation["board"])
        if obs_type == "tictactoe_block":
            move = self._blocking_move(observation["board"], observation["opponent"])
            return move if move is not None else self._center_or_corner(observation["board"])
        if obs_type == "tictactoe_fork":
            move = self._fork_move(observation["board"], observation["player"])
            return move if move is not None else self._center_or_corner(observation["board"])
        if obs_type == "tictactoe_center":
            return self._center_or_corner(observation["board"])
        if obs_type == "tictactoe_noise":
            board = ["." if c == "?" else c for c in observation["board"]]
            move = self._blocking_move(board, observation["opponent"])
            return move if move is not None else self._center_or_corner(board)
        if obs_type == "tictactoe_ambiguous":
            board = ["." if c == "?" else c for c in observation["board"]]
            move = self._blocking_move(board, observation["opponent"])
            return move if move is not None else self._center_or_corner(board)
        if obs_type == "tictactoe_two_ply_trap":
            board = observation["board"]
            move = self._winning_move(board, observation["player"])
            if move is not None:
                return move
            move = self._fork_move(board, observation["player"])
            if move is not None:
                return move
            move = self._blocking_move(board, observation["opponent"])
            if move is not None:
                return move
            return self._center_or_corner(board)
        if obs_type == "tictactoe_ambiguity_fork":
            board = ["." if c == "?" else c for c in observation["board"]]
            move = self._blocking_move(board, observation["opponent"])
            if move is not None:
                return move
            move = self._fork_move(board, observation["player"])
            if move is not None:
                return move
            return self._center_or_corner(board)
        if obs_type == "tictactoe_rollout":
            board = observation["board"]
            move = self._winning_move(board, observation["player"])
            if move is not None:
                return move
            move = self._blocking_move(board, observation["opponent"])
            if move is not None:
                return move
            move = self._fork_move(board, observation["player"])
            if move is not None:
                return move
            return self._center_or_corner(board)
        raise ValueError(f"Unsupported observation type: {obs_type}")

    def update(self, observation: Any, action: Any, reward: float) -> Dict[str, Any]:
        return {"reseal_triggered": False, "error_signal": 0.0}

    @property
    def trust(self) -> float:
        return self._trust

    @property
    def entropy(self) -> float:
        return self._entropy

    @property
    def reseal_count(self) -> int:
        return self._reseal_count

    @property
    def trust_floor_hit(self) -> bool:
        return self._trust_floor_hit

    @property
    def current_glyph(self) -> str:
        return self._glyph

class RHEAAgent(Agent):
    name = "rhea_agent"

    def __init__(self) -> None:
        self.core = TrustEntropyCore()
        self.memory = GlyphMemory()
        self._glyph = "ΞINIT"
        self.last_error_signal = 0.0

    def reset(self) -> None:
        self.core = TrustEntropyCore()
        self.memory = GlyphMemory()
        self._glyph = "ΞINIT"
        self.last_error_signal = 0.0

    def _center_or_corner(self, board: List[str]) -> int:
        return BaselineAgent._center_or_corner(board)

    def act(self, observation: Any) -> Any:
        self._glyph = self.memory.encode(observation, self.core.trust, self.core.entropy)
        obs_type = observation["type"]

        if obs_type in {"sequence", "corrupted_sequence"}:
            seq = observation["sequence"]
            return seq[-1] + (seq[-1] - seq[-2]) if len(seq) >= 2 else seq[-1]
        if obs_type == "arithmetic":
            return eval(observation["expression"], {"__builtins__": {}}, {})
        if obs_type == "modular_arithmetic":
            a, b, m, op = observation["a"], observation["b"], observation["modulus"], observation["op"]
            if op == "+":
                return (a + b) % m
            if op == "-":
                return (a - b) % m
            if op == "*":
                return (a * b) % m
        if obs_type == "tictactoe":
            move = BaselineAgent._winning_move(observation["board"], observation["player"])
            return move if move is not None else self._center_or_corner(observation["board"])
        if obs_type == "tictactoe_block":
            move = BaselineAgent._blocking_move(observation["board"], observation["opponent"])
            return move if move is not None else self._center_or_corner(observation["board"])
        if obs_type == "tictactoe_fork":
            move = BaselineAgent._fork_move(observation["board"], observation["player"])
            return move if move is not None else self._center_or_corner(observation["board"])
        if obs_type == "tictactoe_center":
            return self._center_or_corner(observation["board"])
        if obs_type == "tictactoe_noise":
            board = ["." if c == "?" else c for c in observation["board"]]
            move = BaselineAgent._blocking_move(board, observation["opponent"])
            return move if move is not None else self._center_or_corner(board)
        if obs_type == "tictactoe_ambiguous":
            board = observation["board"]
            ambiguity = sum(1 for c in board if c == "?")
            if ambiguity > 0:
                self.core.update_from_error(0.4)
            normalized = ["." if c == "?" else c for c in board]
            move = BaselineAgent._blocking_move(normalized, observation["opponent"])
            return move if move is not None else self._center_or_corner(normalized)
        if obs_type == "tictactoe_two_ply_trap":
            board = observation["board"]
            move = BaselineAgent._winning_move(board, observation["player"])
            if move is not None:
                return move
            move = BaselineAgent._fork_move(board, observation["player"])
            if move is not None:
                return move
            move = BaselineAgent._blocking_move(board, observation["opponent"])
            if move is not None:
                return move
            return self._center_or_corner(board)
        if obs_type == "tictactoe_ambiguity_fork":
            raw_board = observation["board"]
            ambiguity = sum(1 for c in raw_board if c == "?")
            if ambiguity > 0:
                self.core.update_from_error(0.45)
            board = ["." if c == "?" else c for c in raw_board]
            move = BaselineAgent._blocking_move(board, observation["opponent"])
            if move is not None:
                return move
            move = BaselineAgent._fork_move(board, observation["player"])
            if move is not None:
                return move
            if board[6] == ".":
                return 6
            return self._center_or_corner(board)
        if obs_type == "tictactoe_rollout":
            board = observation["board"]
            move = BaselineAgent._winning_move(board, observation["player"])
            if move is not None:
                return move
            move = BaselineAgent._blocking_move(board, observation["opponent"])
            if move is not None:
                return move
            move = BaselineAgent._fork_move(board, observation["player"])
            if move is not None:
                return move
            return self._center_or_corner(board)
        raise ValueError(f"Unsupported observation type: {obs_type}")

    def update(self, observation: Any, action: Any, reward: float) -> Dict[str, Any]:
        error_signal = 1.0 - reward
        reseal_triggered = self.core.update_from_error(error_signal)
        self.core.reward_reinforce(reward)
        self.last_error_signal = error_signal
        self._glyph = self.memory.encode(observation, self.core.trust, self.core.entropy)
        return {"reseal_triggered": reseal_triggered, "error_signal": error_signal}

    @property
    def trust(self) -> float:
        return self.core.trust

    @property
    def entropy(self) -> float:
        return self.core.entropy

    @property
    def reseal_count(self) -> int:
        return self.core.reseal_count

    @property
    def trust_floor_hit(self) -> bool:
        return self.core.trust_floor_hit

    @property
    def current_glyph(self) -> str:
        return self._glyph

class PersistentRHEAAgent(RHEAAgent):
    name = "persistent_rhea_agent"

    def __init__(self) -> None:
        super().__init__()
        self.episode_memory: Dict[str, Dict[str, Any]] = {}

    def reset(self) -> None:
        self.core.trust = max(0.6, self.core.trust)
        self.core.entropy = min(0.2, self.core.entropy)
        self._glyph = "ΞPERSIST"
        self.last_error_signal = 0.0

    @staticmethod
    def _looks_corrupted(seq: List[int]) -> bool:
        if len(seq) < 3:
            return False
        diffs = [seq[i + 1] - seq[i] for i in range(len(seq) - 1)]
        return len(set(diffs)) > 1

    @staticmethod
    def _repair_arithmetic_sequence(seq: List[int]) -> Optional[List[int]]:
        if len(seq) < 4:
            return None
        for idx in range(1, len(seq) - 1):
            repaired = list(seq)
            expected = (repaired[idx - 1] + repaired[idx + 1]) // 2
            repaired[idx] = expected
            diffs = [repaired[i + 1] - repaired[i] for i in range(len(repaired) - 1)]
            if len(set(diffs)) == 1:
                return repaired
        return None

    def act(self, observation: Any) -> Any:
        obs_type = observation["type"]
        self._glyph = self.memory.encode(observation, self.core.trust, self.core.entropy)

        if obs_type in {"sequence", "corrupted_sequence"}:
            seq = observation["sequence"]
            if self._looks_corrupted(seq):
                repaired = self._repair_arithmetic_sequence(seq)
                if repaired is not None:
                    self.episode_memory[obs_type] = {"repaired": repaired}
                    return repaired[-1] + (repaired[-1] - repaired[-2])
            return super().act({**observation, "type": "sequence"})

        if obs_type == "tictactoe_ambiguous":
            board = observation["board"]
            ambiguity = sum(1 for c in board if c == "?")
            if ambiguity > 0:
                self.core.update_from_error(0.5)
                self._glyph = self.memory.encode({"type": "reseal"}, self.core.trust, self.core.entropy)
            normalized = ["." if c == "?" else c for c in board]
            move = BaselineAgent._blocking_move(normalized, observation["opponent"])
            return move if move is not None else self._center_or_corner(normalized)

        if obs_type == "tictactoe_ambiguity_fork":
            raw_board = observation["board"]
            ambiguity = sum(1 for c in raw_board if c == "?")
            if ambiguity > 0:
                self.core.update_from_error(0.55)
                self._glyph = self.memory.encode({"type": "reseal"}, self.core.trust, self.core.entropy)
            board = ["." if c == "?" else c for c in raw_board]
            move = BaselineAgent._blocking_move(board, observation["opponent"])
            if move is not None:
                return move
            if board[6] == ".":
                return 6
            move = BaselineAgent._fork_move(board, observation["player"])
            if move is not None:
                return move
            return self._center_or_corner(board)

        return super().act(observation)

    def update(self, observation: Any, action: Any, reward: float) -> Dict[str, Any]:
        payload = super().update(observation, action, reward)
        if reward <= 0:
            payload["reseal_triggered"] = self.core.update_from_error(0.75) or payload["reseal_triggered"]
            self._glyph = self.memory.encode({"type": "reseal"}, self.core.trust, self.core.entropy)
        return payload

# ============================================================
# Runner
# ============================================================

class ExperimentRunner:
    def __init__(self, log_dir: str = "logs") -> None:
        self.log_dir = Path(log_dir)
        self.log_dir.mkdir(parents=True, exist_ok=True)

    def run_episode(self, task: Task, agent: Agent) -> EpisodeResult:
        task.reset()
        agent.reset()
        records: List[StepRecord] = []
        total_reward = 0.0
        entropy_peak = agent.entropy
        start = time.perf_counter()
        step_index = 0

        while not task.done():
            observation = task.observe()
            action = agent.act(observation)
            task.step(action)
            reward = task.reward()
            total_reward += reward
            update_payload = agent.update(observation, action, reward)
            entropy_peak = max(entropy_peak, agent.entropy)
            records.append(
                StepRecord(
                    step_index=step_index,
                    observation=observation,
                    action=action,
                    reward=reward,
                    trust=agent.trust,
                    entropy=agent.entropy,
                    reseal_triggered=bool(update_payload.get("reseal_triggered", False)),
                    glyph=agent.current_glyph,
                    metadata={"task_metadata": task.metadata(), **update_payload},
                )
            )
            step_index += 1

        duration_ms = (time.perf_counter() - start) * 1000.0
        return EpisodeResult(
            task_name=task.name,
            agent_name=agent.name,
            success=task.success(),
            total_reward=total_reward,
            steps=step_index,
            duration_ms=duration_ms,
            entropy_peak=entropy_peak,
            trust_floor_hit=agent.trust_floor_hit,
            reseal_count=agent.reseal_count,
            final_glyph=agent.current_glyph,
            records=records,
        )

    def write_result(self, result: EpisodeResult, filename: Optional[str] = None) -> Path:
        stamp = int(time.time() * 1000)
        out_name = filename or f"{result.task_name}_{result.agent_name}_{stamp}.json"
        path = self.log_dir / out_name
        with path.open("w", encoding="utf-8") as fh:
            json.dump(result.to_dict(), fh, indent=2)
        return path

class CurriculumRunner(ExperimentRunner):
    def run_curriculum(self, tasks: List[Task], agent: Agent, curriculum_name: str) -> List[EpisodeResult]:
        results: List[EpisodeResult] = []
        for task in tasks:
            result = self.run_episode(task, agent)
            self.write_result(result, filename=f"{curriculum_name}_{task.name}_{agent.name}_{int(time.time() * 1000)}.json")
            results.append(result)
        return results

# ============================================================
# Display helpers
# ============================================================

def print_summary(result: EpisodeResult) -> None:
    print("-" * 72)
    print(f"Task:            {result.task_name}")
    print(f"Agent:           {result.agent_name}")
    print(f"Success:         {result.success}")
    print(f"Total reward:    {result.total_reward:.2f}")
    print(f"Steps:           {result.steps}")
    print(f"Duration (ms):   {result.duration_ms:.3f}")
    print(f"Entropy peak:    {result.entropy_peak:.3f}")
    print(f"Trust floor hit: {result.trust_floor_hit}")
    print(f"Reseal count:    {result.reseal_count}")
    print(f"Final glyph:     {result.final_glyph}")
    if result.records:
        final = result.records[-1]
        print(f"Last action:     {final.action}")
        print(f"Expected:        {final.metadata.get('task_metadata')}")

def print_curriculum_summary(results: List[EpisodeResult]) -> None:
    print("=" * 72)
    print("CURRICULUM SUMMARY")
    print("=" * 72)
    successes = sum(1 for r in results if r.success)
    total = len(results)
    avg_reward = sum(r.total_reward for r in results) / max(1, total)
    avg_entropy = sum(r.entropy_peak for r in results) / max(1, total)
    reseals = sum(r.reseal_count for r in results)
    print(f"Episodes:        {total}")
    print(f"Successes:       {successes}/{total}")
    print(f"Average reward:  {avg_reward:.3f}")
    print(f"Average entropy: {avg_entropy:.3f}")
    print(f"Total reseals:   {reseals}")
    print("Per-episode:")
    for r in results:
        print(
            f"  - {r.task_name:30s} | success={str(r.success):5s} | "
            f"entropy_peak={r.entropy_peak:.3f} | reseals={r.reseal_count} | glyph={r.final_glyph}"
        )

# ============================================================
# Lightweight self-tests
# ============================================================

def _run_self_tests() -> None:
    seq = SequenceTask([2, 4, 6, 8])
    seq.reset()
    assert seq.metadata()["expected"] == 10

    corrupt = CorruptedSequenceTask([2, 4, 6, 8], corruption_index=2, corruption_value=11)
    corrupt.reset()
    assert corrupt.observe()["sequence"] == [2, 4, 11, 8]
    assert corrupt.metadata()["expected"] == 10

    ar = ArithmeticTask("7 * (3 + 2)")
    assert ar.metadata()["expected"] == 35

    mod = ModularArithmeticTask(a=23, b=6, modulus=7, op="*")
    assert mod.metadata()["expected"] == (23 * 6) % 7

    win_task = TicTacToeTacticTask()
    assert 6 in win_task.metadata()["expected_moves"]

    block_task = TicTacToeBlockTask()
    assert 7 in block_task.metadata()["expected_moves"]

    fork_task = TicTacToeForkTask()
    assert set(fork_task.metadata()["expected_moves"]) == {2, 6}

    center_task = TicTacToeCenterPreferenceTask()
    assert center_task.metadata()["expected_moves"] == [4]

    noise_task = TicTacToeNoiseTask()
    assert noise_task.metadata()["expected_moves"] == [7]

    ambiguous_task = TicTacToeAmbiguousTask()
    assert ambiguous_task.metadata()["expected_moves"] == [7]

    two_ply = TicTacToeTwoPlyTrapTask()
    two_ply.reset()
    assert two_ply.metadata()["phases"] == 2

    amb_fork = TicTacToeAmbiguityForkTask()
    assert amb_fork.metadata()["expected_moves"] == [6]

    rollout = TicTacToeRolloutTask(["X", ".", ".", ".", "O", ".", ".", ".", "."], max_turns=3)
    rollout.reset()
    assert rollout.metadata()["max_turns"] == 3

    base = BaselineAgent()
    base.reset()
    assert base.act(mod.observe()) == (23 * 6) % 7
    assert base.act(block_task.observe()) == 7
    assert base.act(center_task.observe()) == 4

    rhea = PersistentRHEAAgent()
    rhea.reset()
    repaired_action = rhea.act(corrupt.observe())
    assert repaired_action == 10

    rhea_basic = RHEAAgent()
    rhea_basic.reset()
    assert rhea_basic.act(TicTacToeTacticTask().observe()) == 6
    assert rhea_basic.act(TicTacToeBlockTask().observe()) == 7

    runner = ExperimentRunner(log_dir="logs")
    result = runner.run_episode(ArithmeticTask(), RHEAAgent())
    assert result.success is True
    assert result.total_reward == 1.0

# ============================================================
# Demo execution
# ============================================================

if __name__ == "__main__":
    _run_self_tests()

    runner = ExperimentRunner()

    tasks: List[Task] = [SequenceTask(), ArithmeticTask(), TicTacToeTacticTask()]
    agents: List[Agent] = [BaselineAgent(), RHEAAgent()]

    for task in tasks:
        for agent in agents:
            result = runner.run_episode(task, agent)
            path = runner.write_result(result)
            print_summary(result)
            print(f"Log written to:  {path}")

    print("\n" + "#" * 72)
    print("ADVANCED CURRICULUM: persistence + corruption + modular arithmetic + tactics")
    print("#" * 72)

    curriculum_runner = CurriculumRunner()
    curriculum_tasks: List[Task] = [
        SequenceTask([3, 6, 9, 12]),
        CorruptedSequenceTask([2, 4, 6, 8], corruption_index=2, corruption_value=11),
        ModularArithmeticTask(a=17, b=9, modulus=5, op="+"),
        ModularArithmeticTask(a=23, b=6, modulus=7, op="*"),
        TicTacToeBlockTask(),
        TicTacToeForkTask(),
        TicTacToeCenterPreferenceTask(),
        TicTacToeNoiseTask(),
        TicTacToeAmbiguousTask(),
        TicTacToeTwoPlyTrapTask(),
        TicTacToeAmbiguityForkTask(),
        TicTacToeRolloutTask(["X", ".", ".", ".", "O", ".", ".", ".", "."], max_turns=3),
    ]
    advanced_agents: List[Agent] = [BaselineAgent(), PersistentRHEAAgent()]

    for agent in advanced_agents:
        results = curriculum_runner.run_curriculum(curriculum_tasks, agent, curriculum_name="advanced_curriculum")
        print_curriculum_summary(results)

    print("\n" + "#" * 72)
    print("TACTICAL TIC-TAC-TOE SUITE")
    print("#" * 72)

    tictactoe_tasks: List[Task] = [
        TicTacToeTacticTask(),
        TicTacToeBlockTask(),
        TicTacToeForkTask(),
        TicTacToeCenterPreferenceTask(),
        TicTacToeNoiseTask(),
        TicTacToeAmbiguousTask(),
        TicTacToeTwoPlyTrapTask(),
        TicTacToeAmbiguityForkTask(),
        TicTacToeRolloutTask(["X", ".", ".", ".", "O", ".", ".", ".", "."], max_turns=3),
    ]

    for agent in advanced_agents:
        results = curriculum_runner.run_curriculum(tictactoe_tasks, agent, curriculum_name="tictactoe_suite")
        print_curriculum_summary(results)

Test Harness Terminal Output

Test Harness Terminal Output


메타데이터
post_id
63cb50f23ee0
slug
rhea-tactical-test-harness-63cb50f23ee0
url
https://medium.com/@ZadienLabs/rhea-tactical-test-harness-63cb50f23ee0
canonical_url
https://medium.com/@ZadienLabs/rhea-tactical-test-harness-63cb50f23ee0
author_url
https://medium.com/@ZadienLabs
status
ok
fetched_at
2026-08-10 10:15:22