"""Enhanced cybersecurity-themed replay HTML renderer for SentinelOps Arena.""" import html as _html def _esc(text): """HTML-escape a string.""" return _html.escape(str(text)) def format_replay_html(log, scores): """Format replay log as visually stunning cybersecurity-themed HTML.""" # --- Phase definitions --- PHASES = [ (0, 6, "RECONNAISSANCE PHASE", "#00ff41", "\u25c9", "Scanning enterprise systems..."), (7, 13, "SCHEMA DRIFT ATTACK", "#ff2a2a", "\u26a0", "Database schemas compromised!"), (14, 19, "POLICY DRIFT ATTACK", "#ff8c00", "\u2622", "Business policies mutating!"), (20, 24, "SOCIAL ENGINEERING", "#bf5fff", "\u2620", "Manipulation attempt detected!"), (25, 29, "RATE LIMITING", "#ffd700", "\u26a1", "API throttle engaged!"), ] def get_phase(tick): for start, end, name, color, icon, desc in PHASES: if start <= tick <= end: return name, color, icon, desc return "UNKNOWN PHASE", "#888", "\u2022", "" # Agent colors AGENT_COLORS = { "attacker": "#ff4444", "worker": "#4d9fff", "oversight": "#00ff41", } AGENT_ICONS = { "attacker": "\u2694", "worker": "\u2699", "oversight": "\u2691", } AGENT_BG = { "attacker": "rgba(255,68,68,0.08)", "worker": "rgba(77,159,255,0.08)", "oversight": "rgba(0,255,65,0.08)", } # --- Build HTML --- html = f"""
\u2588\u2588 SENTINELOPS ARENA \u2588\u2588
EPISODE REPLAY \u2502 MULTI-AGENT SECURITY SIMULATION
\u250c\u2500 RED TEAM vs BLUE TEAM vs AUDITOR \u2500\u2510
""" for agent_key, label in [("attacker", "RED TEAM"), ("worker", "BLUE TEAM"), ("oversight", "AUDITOR")]: color = AGENT_COLORS[agent_key] icon = AGENT_ICONS[agent_key] html += f"""
{icon} {label}
""" html += """
""" # --- Render log entries grouped by tick --- current_tick = -1 current_phase = None for entry in log: tick = entry["tick"] phase_name, phase_color, phase_icon, phase_desc = get_phase(tick) # Phase header when phase changes if phase_name != current_phase: current_phase = phase_name # Determine banner style if phase_name == "RECONNAISSANCE PHASE": banner_bg = "linear-gradient(90deg, rgba(0,255,65,0.12), rgba(0,255,65,0.03))" border_col = "#00ff4155" elif phase_name == "SCHEMA DRIFT ATTACK": banner_bg = "linear-gradient(90deg, rgba(255,42,42,0.15), rgba(255,42,42,0.03))" border_col = "#ff2a2a66" elif phase_name == "POLICY DRIFT ATTACK": banner_bg = "linear-gradient(90deg, rgba(255,140,0,0.15), rgba(255,140,0,0.03))" border_col = "#ff8c0066" elif phase_name == "SOCIAL ENGINEERING": banner_bg = "linear-gradient(90deg, rgba(191,95,255,0.15), rgba(191,95,255,0.03))" border_col = "#bf5fff66" elif phase_name == "RATE LIMITING": banner_bg = "linear-gradient(90deg, rgba(255,215,0,0.15), rgba(255,215,0,0.03))" border_col = "#ffd70066" else: banner_bg = "rgba(50,50,50,0.3)" border_col = "#333" html += f"""
{phase_icon}
{phase_name}
{phase_desc}
TICKS {next((f'{p[0]:02d}-{p[1]:02d}' for p in PHASES if p[2] == phase_name), '??-??')}
""" # Tick divider if tick != current_tick: current_tick = tick html += f"""
\u25c8 TICK {tick:02d}
""" agent = entry["agent"] color = AGENT_COLORS.get(agent, "#888") icon = AGENT_ICONS.get(agent, "\u2022") bg = AGENT_BG.get(agent, "rgba(50,50,50,0.1)") reward = entry["reward"] action_type = entry["action_type"] is_flagged = entry.get("flag", False) # Detect special events is_attack_launch = action_type == "launch_attack" is_recovery = action_type in ("get_schema", "get_current_policy") # Full-width attack launch banner if is_attack_launch: details_str = str(entry.get("details", "")) # Determine attack type from details if "schema_drift" in details_str: atk_label = "SCHEMA DRIFT" atk_icon = "\u26a0" atk_color = "#ff2a2a" atk_bg = "rgba(255,42,42,0.12)" elif "policy_drift" in details_str: atk_label = "POLICY DRIFT" atk_icon = "\u2622" atk_color = "#ff8c00" atk_bg = "rgba(255,140,0,0.12)" elif "social_engineering" in details_str: atk_label = "SOCIAL ENGINEERING" atk_icon = "\u2620" atk_color = "#bf5fff" atk_bg = "rgba(191,95,255,0.12)" elif "rate_limit" in details_str: atk_label = "RATE LIMIT" atk_icon = "\u26a1" atk_color = "#ffd700" atk_bg = "rgba(255,215,0,0.12)" else: atk_label = "UNKNOWN ATTACK" atk_icon = "\u2753" atk_color = "#ff4444" atk_bg = "rgba(255,68,68,0.12)" html += f"""
{atk_icon}
ATTACK LAUNCHED: {atk_label}
{_esc(str(entry.get('details', ''))[:100])}
{icon} RED TEAM \u2502 Reward: {reward:.1f}
""" continue # Regular action card # Build badges badges = "" if is_recovery: badges += f"""\u2714 RECOVERY""" if is_flagged: badges += f"""\u26a0 FLAGGED""" # Reward badge color if reward > 0: reward_color = "#00ff41" reward_bg = "rgba(0,255,65,0.1)" elif reward < 0: reward_color = "#ff4444" reward_bg = "rgba(255,68,68,0.1)" else: reward_color = "#484f58" reward_bg = "rgba(72,79,88,0.1)" reward_str = f"+{reward:.1f}" if reward > 0 else f"{reward:.1f}" html += f"""
{icon}
{entry['agent_label']} \u25b8 {action_type} {badges}
""" details = entry.get("details", "") if details: html += f"""
{_esc(str(details)[:150])}
""" explanation = entry.get("explanation", "") if explanation: exp_color = "#ff4444" if is_flagged else "#00ff41" exp_icon = "\u26a0" if is_flagged else "\u2714" html += f"""
{exp_icon} {_esc(explanation)}
""" html += f"""
{reward_str}
""" # --- Final Scores --- html += """
\u2588 FINAL SCORES \u2588
""" max_score = max(abs(s) for s in scores.values()) if scores else 1 if max_score == 0: max_score = 1 for agent_key, score in scores.items(): color = AGENT_COLORS.get(agent_key, "#888") icon = AGENT_ICONS.get(agent_key, "\u2022") label_map = {"attacker": "RED TEAM", "worker": "BLUE TEAM", "oversight": "AUDITOR"} label = label_map.get(agent_key, agent_key.upper()) # Bar width as percentage (handle negative scores) bar_pct = max(0, min((score / max_score) * 100, 100)) if score > 0 else 0 # Score display color if score > 0: score_display_color = "#00ff41" elif score < 0: score_display_color = "#ff4444" else: score_display_color = "#484f58" html += f"""
{icon} {label}
{score:.1f}
""" html += """
SENTINELOPS ARENA \u2502 OPENENV HACKATHON SF 2026
""" return html