| """Self-Play Mode — Two chimera instances compete and learn. |
| Builder generates projects. Breaker finds flaws. Both improve.""" |
|
|
| import os, sys, json, time, random, threading, traceback |
|
|
| SELFPLAY_LOG = "/tmp/fsi_felon/selfplay_log.json" |
|
|
| APP_TYPES = ["web_app", "api_server", "cli_tool", "chat_app", "game", "microservice"] |
|
|
| class SelfPlay: |
| def __init__(self, chimera=None, sandbox=None): |
| self.chimera = chimera |
| self.sandbox = sandbox |
| self.scores = {"builder": 0, "breaker": 0} |
| self.rounds = [] |
| self._lock = threading.Lock() |
|
|
| def run_session(self, rounds=10): |
| results = [] |
| for i in range(rounds): |
| r = self._round(i + 1) |
| results.append(r) |
| self.rounds.append(r) |
| self._log_round(r) |
| time.sleep(0.5) |
| summary = self._summarize(results) |
| self._log_session(summary) |
| return summary |
|
|
| def _round(self, round_num): |
| app_type = random.choice(APP_TYPES) |
| builder_score = 0 |
| breaker_score = 0 |
|
|
| |
| project = self._builder_generate(app_type) |
| builder_score += project.get("compile_score", 0) |
|
|
| |
| flaws = self._breaker_analyze(project) |
| real_bugs = [f for f in flaws if f.get("real", False)] |
| false_positives = [f for f in flaws if not f.get("real", False)] |
| breaker_score += len(real_bugs) * 10 |
| builder_score -= len(real_bugs) * 5 |
| breaker_score -= len(false_positives) * 5 |
|
|
| |
| fixes = self._builder_defend(project, real_bugs) |
| fix_success = sum(1 for f in fixes if f.get("success")) |
| builder_score += fix_success * 8 |
|
|
| |
| coop_bonus = 0 |
| if len(real_bugs) == 0 and builder_score > 0: |
| coop_bonus = 5 |
| builder_score += coop_bonus |
| breaker_score += coop_bonus // 2 |
|
|
| with self._lock: |
| self.scores["builder"] += max(0, builder_score) |
| self.scores["breaker"] += max(0, breaker_score) |
|
|
| return { |
| "round": round_num, |
| "app_type": app_type, |
| "builder_score": max(0, builder_score), |
| "breaker_score": max(0, breaker_score), |
| "flaws_found": len(real_bugs), |
| "false_positives": len(false_positives), |
| "fixes_applied": fix_success, |
| "coop_bonus": coop_bonus, |
| "project_size": project.get("size", 0), |
| } |
|
|
| def _builder_generate(self, app_type): |
| score = 0 |
| size = 0 |
| code = "" |
| if self.chimera: |
| try: |
| route = self.chimera.route(f"build {app_type}") |
| score = int(route.get("routing_score", 0) * 50) |
| size = random.randint(200, 2000) |
| code = f"# {app_type} generated by builder\n" |
| except: |
| score = random.randint(10, 30) |
| size = random.randint(100, 500) |
| else: |
| score = random.randint(10, 40) |
| size = random.randint(100, 1000) |
| code = f"# {app_type} generated by builder\n" |
| return {"app_type": app_type, "compile_score": score, "size": size, "code": code} |
|
|
| def _breaker_analyze(self, project): |
| flaws = [] |
| app = project.get("app_type", "unknown") |
| |
| bug_db = { |
| "web_app": [ |
| {"desc": "SQL injection in query string", "real": True}, |
| {"desc": "Missing input validation on POST", "real": True}, |
| {"desc": "Hardcoded secret key", "real": True}, |
| {"desc": "Wrong HTTP method for login", "real": False}, |
| ], |
| "api_server": [ |
| {"desc": "No rate limiting on endpoints", "real": True}, |
| {"desc": "JWT not validated on protected routes", "real": True}, |
| {"desc": "CORS allows all origins", "real": True}, |
| {"desc": "Missing content-type header", "real": False}, |
| ], |
| "game": [ |
| {"desc": "Player score not validated server-side", "real": True}, |
| {"desc": "Unbounded loop in render function", "real": True}, |
| {"desc": "Network sync race condition", "real": True}, |
| {"desc": "Wrong gravity constant", "real": False}, |
| ], |
| } |
| candidates = bug_db.get(app, bug_db["web_app"]) |
| found = random.sample(candidates, min(3, len(candidates))) |
| for bug in found: |
| flaws.append({"desc": bug["desc"], "real": bug["real"], "type": "security" if "injection" in bug["desc"].lower() or "jwt" in bug["desc"].lower() else "logic"}) |
| return flaws |
|
|
| def _builder_defend(self, project, bugs): |
| fixes = [] |
| for bug in bugs: |
| success = random.random() < 0.6 |
| fixes.append({"bug": bug["desc"], "success": success}) |
| return fixes |
|
|
| def _summarize(self, results): |
| total_rounds = len(results) |
| builder_total = sum(r["builder_score"] for r in results) |
| breaker_total = sum(r["breaker_score"] for r in results) |
| total_flaws = sum(r["flaws_found"] for r in results) |
| total_fixes = sum(r["fixes_applied"] for r in results) |
| return { |
| "session": time.time(), |
| "rounds": total_rounds, |
| "builder_total": builder_total, |
| "breaker_total": breaker_total, |
| "total_flaws": total_flaws, |
| "total_fixes": total_fixes, |
| "winner": "builder" if builder_total > breaker_total else "breaker" if breaker_total > builder_total else "tie", |
| "overall_scores": dict(self.scores), |
| } |
|
|
| def _log_round(self, r): |
| entry = {"time": time.time(), "type": "round", **r} |
| self._write_log(entry) |
|
|
| def _log_session(self, summary): |
| entry = {"time": time.time(), "type": "summary", **summary} |
| self._write_log(entry) |
|
|
| def _write_log(self, entry): |
| try: |
| log = json.load(open(SELFPLAY_LOG)) if os.path.exists(SELFPLAY_LOG) else [] |
| log.append(entry) |
| with open(SELFPLAY_LOG, "w") as f: |
| json.dump(log[-500:], f, indent=2) |
| except: |
| pass |
|
|
| def get_status(self): |
| return { |
| "scores": dict(self.scores), |
| "total_rounds": len(self.rounds), |
| "last_session": self.rounds[-1] if self.rounds else None, |
| "log_size": os.path.getsize(SELFPLAY_LOG) if os.path.exists(SELFPLAY_LOG) else 0, |
| } |
|
|
|
|
| if __name__ == "__main__": |
| sp = SelfPlay() |
| print("Self-Play Mode — 10 rounds...") |
| summary = sp.run_session(rounds=10) |
| print(f" Builder: {summary['builder_total']} pts") |
| print(f" Breaker: {summary['breaker_total']} pts") |
| print(f" Winner: {summary['winner']}") |
| print(f" Flaws found: {summary['total_flaws']}") |
| print(f" Fixes applied: {summary['total_fixes']}") |
| print("Self-Play Mode: PASS") |
|
|