Files
tekton/docs/gauntlet-technical-docs.html
adtpdn 7380161743 feat: add Candy Cannon Survival game mode with collectible tiles
Version bump to 2.3.6. New game mode features 20×20 arena with central cannon obstacle, three escalating phases (Open Arena, Route Pressure, Survival), and collectible tiles (Hearts, Diamonds, Stars, Coins) with pattern-matching missions. Players dodge candy volleys while completing collection goals.

Updated export paths and version strings across all platforms (Windows, Android, Web, Linux).
2026-05-24 06:56:57 +08:00

1367 lines
55 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Candy Cannon Survival — Technical Documentation</title>
<meta name="description" content="Technical implementation documentation for the Candy Cannon Survival (Gauntlet) game mode in Tekton Dash">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #111118;
--bg-card: #16161f;
--bg-card-hover: #1c1c28;
--bg-code: #1a1a26;
--border: #2a2a3a;
--border-glow: #ff6bb5;
--text-primary: #e8e6f0;
--text-secondary: #8b89a0;
--text-muted: #5a586e;
--accent-pink: #ff6bb5;
--accent-pink-dim: #ff6bb540;
--accent-candy: #ff85c8;
--accent-purple: #a855f7;
--accent-blue: #6366f1;
--accent-cyan: #22d3ee;
--accent-green: #34d399;
--accent-yellow: #fbbf24;
--accent-orange: #fb923c;
--accent-red: #f87171;
--new-badge: #34d399;
--existing-badge: #6366f1;
--adapt-badge: #fbbf24;
--glass: rgba(22, 22, 31, 0.7);
--glass-border: rgba(255, 107, 181, 0.12);
--shadow-xl: 0 25px 50px -12px rgba(0,0,0,0.5);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html {
scroll-behavior: smooth;
scrollbar-width: thin;
scrollbar-color: var(--accent-pink-dim) transparent;
}
body {
font-family: 'Inter', -apple-system, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
overflow-x: hidden;
}
/* === AMBIENT BG === */
.ambient-bg {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
overflow: hidden;
}
.ambient-bg .orb {
position: absolute;
border-radius: 50%;
filter: blur(120px);
opacity: 0.15;
animation: orbFloat 20s ease-in-out infinite;
}
.ambient-bg .orb:nth-child(1) {
width: 600px; height: 600px;
background: var(--accent-pink);
top: -200px; left: -100px;
animation-delay: 0s;
}
.ambient-bg .orb:nth-child(2) {
width: 500px; height: 500px;
background: var(--accent-purple);
bottom: -150px; right: -100px;
animation-delay: -7s;
}
.ambient-bg .orb:nth-child(3) {
width: 400px; height: 400px;
background: var(--accent-blue);
top: 50%; left: 50%;
transform: translate(-50%, -50%);
animation-delay: -14s;
}
@keyframes orbFloat {
0%, 100% { transform: translate(0, 0) scale(1); }
25% { transform: translate(30px, -40px) scale(1.05); }
50% { transform: translate(-20px, 20px) scale(0.95); }
75% { transform: translate(40px, 30px) scale(1.02); }
}
/* === LAYOUT === */
.wrapper {
position: relative;
z-index: 1;
max-width: 1280px;
margin: 0 auto;
padding: 0 24px;
}
/* === HERO === */
.hero {
padding: 80px 0 60px;
text-align: center;
position: relative;
}
.hero-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 16px;
border-radius: 100px;
background: var(--accent-pink-dim);
border: 1px solid var(--accent-pink);
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--accent-candy);
margin-bottom: 24px;
}
.hero-badge .dot {
width: 6px; height: 6px;
border-radius: 50%;
background: var(--accent-pink);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.hero h1 {
font-size: clamp(2.5rem, 5vw, 4rem);
font-weight: 900;
letter-spacing: -0.03em;
line-height: 1.1;
background: linear-gradient(135deg, #fff 0%, var(--accent-candy) 50%, var(--accent-purple) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 16px;
}
.hero .subtitle {
font-size: 18px;
color: var(--text-secondary);
max-width: 640px;
margin: 0 auto 40px;
}
.hero-stats {
display: flex;
justify-content: center;
gap: 32px;
flex-wrap: wrap;
}
.hero-stat {
text-align: center;
}
.hero-stat .value {
font-size: 2rem;
font-weight: 800;
color: var(--accent-candy);
}
.hero-stat .label {
font-size: 12px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
}
/* === NAV === */
.sticky-nav {
position: sticky;
top: 0;
z-index: 100;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
background: rgba(10,10,15,0.85);
border-bottom: 1px solid var(--border);
padding: 0;
margin-bottom: 48px;
}
.nav-inner {
display: flex;
gap: 4px;
padding: 8px 0;
overflow-x: auto;
scrollbar-width: none;
}
.nav-inner::-webkit-scrollbar { display: none; }
.nav-link {
padding: 8px 16px;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
text-decoration: none;
white-space: nowrap;
transition: all 0.2s;
}
.nav-link:hover, .nav-link.active {
background: var(--accent-pink-dim);
color: var(--accent-candy);
}
/* === SECTIONS === */
section {
margin-bottom: 64px;
}
.section-header {
margin-bottom: 32px;
}
.section-header h2 {
font-size: 1.75rem;
font-weight: 800;
letter-spacing: -0.02em;
display: flex;
align-items: center;
gap: 12px;
}
.section-header h2 .icon {
width: 36px; height: 36px;
display: flex; align-items: center; justify-content: center;
border-radius: 10px;
font-size: 18px;
}
.section-header p {
color: var(--text-secondary);
margin-top: 8px;
max-width: 700px;
}
/* === CARDS === */
.card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
transition: all 0.3s;
}
.card:hover {
border-color: var(--glass-border);
background: var(--bg-card-hover);
box-shadow: 0 0 30px var(--accent-pink-dim);
}
/* === GLOSSARY === */
.glossary-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 16px;
}
.glossary-item {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
padding: 20px;
display: flex;
gap: 16px;
transition: all 0.3s;
position: relative;
overflow: hidden;
}
.glossary-item::before {
content: '';
position: absolute;
top: 0; left: 0;
width: 4px; height: 100%;
border-radius: 4px 0 0 4px;
}
.glossary-item.new::before { background: var(--new-badge); }
.glossary-item.adapt::before { background: var(--adapt-badge); }
.glossary-item.existing::before { background: var(--existing-badge); }
.glossary-item:hover {
border-color: var(--glass-border);
transform: translateY(-2px);
box-shadow: 0 8px 30px rgba(0,0,0,0.3);
}
.glossary-icon {
width: 44px; height: 44px;
min-width: 44px;
border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-size: 20px;
}
.glossary-item.new .glossary-icon { background: rgba(52,211,153,0.12); }
.glossary-item.adapt .glossary-icon { background: rgba(251,191,36,0.12); }
.glossary-item.existing .glossary-icon { background: rgba(99,102,241,0.12); }
.glossary-content h3 {
font-size: 15px;
font-weight: 700;
margin-bottom: 4px;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.glossary-content p {
font-size: 13px;
color: var(--text-secondary);
line-height: 1.5;
}
.glossary-content .code-ref {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--accent-cyan);
background: rgba(34,211,238,0.08);
padding: 2px 8px;
border-radius: 4px;
margin-top: 6px;
display: inline-block;
}
/* === BADGES === */
.badge {
display: inline-flex;
align-items: center;
padding: 2px 10px;
border-radius: 100px;
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.8px;
}
.badge-new { background: rgba(52,211,153,0.15); color: var(--new-badge); }
.badge-adapt { background: rgba(251,191,36,0.15); color: var(--adapt-badge); }
.badge-existing { background: rgba(99,102,241,0.15); color: var(--existing-badge); }
/* === LEGEND === */
.legend {
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-bottom: 24px;
padding: 16px 20px;
background: var(--bg-secondary);
border-radius: 12px;
border: 1px solid var(--border);
}
.legend-item {
display: flex; align-items: center; gap: 8px;
font-size: 13px; color: var(--text-secondary);
}
.legend-dot {
width: 10px; height: 10px;
border-radius: 3px;
}
.legend-dot.new { background: var(--new-badge); }
.legend-dot.adapt { background: var(--adapt-badge); }
.legend-dot.existing { background: var(--existing-badge); }
/* === ARCHITECTURE DIAGRAM === */
.arch-diagram {
background: var(--bg-code);
border: 1px solid var(--border);
border-radius: 16px;
padding: 32px;
overflow-x: auto;
}
.arch-tree {
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
line-height: 2;
}
.arch-tree .node { color: var(--accent-cyan); font-weight: 600; }
.arch-tree .new-node { color: var(--accent-green); font-weight: 600; }
.arch-tree .label { color: var(--text-muted); }
.arch-tree .connector { color: var(--text-muted); }
.arch-tree .tag-new {
font-size: 10px;
background: rgba(52,211,153,0.15);
color: var(--new-badge);
padding: 1px 6px;
border-radius: 4px;
margin-left: 4px;
font-weight: 700;
}
/* === TABLES === */
.table-wrap {
overflow-x: auto;
border-radius: 14px;
border: 1px solid var(--border);
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
thead th {
background: var(--bg-secondary);
padding: 14px 16px;
text-align: left;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-muted);
border-bottom: 1px solid var(--border);
white-space: nowrap;
}
tbody td {
padding: 12px 16px;
border-bottom: 1px solid rgba(42,42,58,0.5);
vertical-align: top;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: rgba(255,107,181,0.03); }
tbody td:first-child { font-weight: 600; }
.td-code {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--accent-cyan);
}
/* === REUSE TABLE === */
.reuse-direct { color: var(--accent-green); font-weight: 600; }
.reuse-heavy { color: var(--accent-cyan); font-weight: 600; }
.reuse-partial { color: var(--accent-yellow); font-weight: 600; }
.reuse-adapt { color: var(--accent-orange); font-weight: 600; }
.reuse-pattern { color: var(--accent-purple); font-weight: 600; }
/* === PHASE TIMELINE === */
.timeline {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
@media (max-width: 768px) { .timeline { grid-template-columns: 1fr; } }
.phase-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 16px;
padding: 24px;
position: relative;
overflow: hidden;
}
.phase-card::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 3px;
}
.phase-card.phase-1::after { background: linear-gradient(90deg, var(--accent-green), var(--accent-cyan)); }
.phase-card.phase-2::after { background: linear-gradient(90deg, var(--accent-yellow), var(--accent-orange)); }
.phase-card.phase-3::after { background: linear-gradient(90deg, var(--accent-orange), var(--accent-red)); }
.phase-time {
font-size: 11px;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 8px;
}
.phase-card h3 {
font-size: 18px;
font-weight: 800;
margin-bottom: 12px;
}
.phase-card ul {
list-style: none;
padding: 0;
}
.phase-card li {
font-size: 13px;
color: var(--text-secondary);
padding: 4px 0;
padding-left: 16px;
position: relative;
}
.phase-card li::before {
content: '';
position: absolute;
left: 0;
color: var(--accent-pink);
font-weight: 700;
}
/* === FILE LISTS === */
.file-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 12px;
}
.file-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px;
display: flex;
align-items: center;
gap: 12px;
transition: all 0.2s;
}
.file-card:hover {
border-color: var(--glass-border);
transform: translateY(-1px);
}
.file-icon {
width: 36px; height: 36px; min-width: 36px;
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 16px;
}
.file-card.new-file .file-icon { background: rgba(52,211,153,0.12); }
.file-card.mod-file .file-icon { background: rgba(251,191,36,0.12); }
.file-info h4 {
font-size: 13px;
font-weight: 600;
font-family: 'JetBrains Mono', monospace;
}
.file-info p {
font-size: 12px;
color: var(--text-muted);
}
/* === CODE BLOCKS === */
.code-block {
background: var(--bg-code);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px 24px;
overflow-x: auto;
font-family: 'JetBrains Mono', monospace;
font-size: 12.5px;
line-height: 1.8;
}
.code-block .kw { color: var(--accent-purple); }
.code-block .fn { color: var(--accent-cyan); }
.code-block .str { color: var(--accent-green); }
.code-block .cm { color: var(--text-muted); font-style: italic; }
.code-block .num { color: var(--accent-orange); }
.code-block .type { color: var(--accent-yellow); }
/* === RISK CARDS === */
.risk-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
gap: 12px;
}
.risk-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
display: grid;
grid-template-columns: auto 1fr;
gap: 14px;
}
.risk-indicator {
width: 36px; height: 36px;
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 16px;
background: rgba(248,113,113,0.12);
}
.risk-content h4 { font-size: 14px; font-weight: 700; margin-bottom: 4px; }
.risk-content p { font-size: 12px; color: var(--text-secondary); }
/* === PRIORITY LIST === */
.priority-list {
counter-reset: priority;
}
.priority-item {
display: flex;
gap: 16px;
padding: 16px 0;
border-bottom: 1px solid rgba(42,42,58,0.4);
align-items: flex-start;
}
.priority-item:last-child { border-bottom: none; }
.priority-num {
counter-increment: priority;
width: 36px; height: 36px; min-width: 36px;
border-radius: 10px;
background: var(--accent-pink-dim);
color: var(--accent-candy);
display: flex; align-items: center; justify-content: center;
font-size: 14px;
font-weight: 800;
}
.priority-info h4 { font-size: 14px; font-weight: 700; }
.priority-info p {
font-size: 12px;
color: var(--text-secondary);
font-family: 'JetBrains Mono', monospace;
}
/* === NETWORK TABLE ACCENTS === */
.sync-method {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--accent-cyan);
}
/* === FOOTER === */
footer {
text-align: center;
padding: 48px 0;
border-top: 1px solid var(--border);
color: var(--text-muted);
font-size: 13px;
}
/* === SEARCH === */
.search-bar {
position: relative;
margin-bottom: 20px;
}
.search-bar input {
width: 100%;
padding: 12px 16px 12px 44px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
color: var(--text-primary);
font-size: 14px;
font-family: 'Inter', sans-serif;
outline: none;
transition: border-color 0.2s;
}
.search-bar input:focus {
border-color: var(--accent-pink);
}
.search-bar input::placeholder {
color: var(--text-muted);
}
.search-bar .search-icon {
position: absolute;
left: 14px; top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
font-size: 16px;
pointer-events: none;
}
/* === FILTER BTNS === */
.filter-btns {
display: flex; gap: 8px; margin-bottom: 20px; flex-wrap: wrap;
}
.filter-btn {
padding: 6px 14px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
border: 1px solid var(--border);
background: transparent;
color: var(--text-secondary);
transition: all 0.2s;
}
.filter-btn:hover, .filter-btn.active {
border-color: var(--accent-pink);
color: var(--accent-candy);
background: var(--accent-pink-dim);
}
/* === SCROLL TOP === */
.scroll-top {
position: fixed;
bottom: 32px; right: 32px;
width: 44px; height: 44px;
border-radius: 12px;
background: var(--accent-pink);
color: #fff;
border: none;
cursor: pointer;
font-size: 18px;
display: none;
align-items: center; justify-content: center;
box-shadow: 0 4px 20px var(--accent-pink-dim);
transition: transform 0.2s;
z-index: 200;
}
.scroll-top:hover { transform: translateY(-2px); }
.scroll-top.show { display: flex; }
</style>
</head>
<body>
<div class="ambient-bg">
<div class="orb"></div>
<div class="orb"></div>
<div class="orb"></div>
</div>
<div class="wrapper">
<!-- ============ HERO ============ -->
<header class="hero">
<div class="hero-badge"><span class="dot"></span> Technical Documentation</div>
<h1>Candy Cannon Survival</h1>
<p class="subtitle">Gauntlet Mode — Implementation blueprint mapping GDD mechanics to existing Tekton Dash systems</p>
<div class="hero-stats">
<div class="hero-stat"><div class="value">70%</div><div class="label">Code Reuse</div></div>
<div class="hero-stat"><div class="value">4</div><div class="label">New Files</div></div>
<div class="hero-stat"><div class="value">7</div><div class="label">Modified Files</div></div>
<div class="hero-stat"><div class="value">12</div><div class="label">New Terms</div></div>
<div class="hero-stat"><div class="value">10</div><div class="label">Reused Terms</div></div>
</div>
</header>
<!-- ============ NAV ============ -->
<div class="sticky-nav">
<nav class="nav-inner wrapper">
<a class="nav-link" href="#glossary">Glossary</a>
<a class="nav-link" href="#architecture">Architecture</a>
<a class="nav-link" href="#reuse">Reuse Map</a>
<a class="nav-link" href="#phases">Phases</a>
<a class="nav-link" href="#systems">Systems</a>
<a class="nav-link" href="#files">Files</a>
<a class="nav-link" href="#network">Network</a>
<a class="nav-link" href="#priority">Priority</a>
<a class="nav-link" href="#risks">Risks</a>
</nav>
</div>
<!-- ============ GLOSSARY ============ -->
<section id="glossary">
<div class="section-header">
<h2><span class="icon" style="background:rgba(255,107,181,0.12)">📖</span> Glossary</h2>
<p>All terms used in Gauntlet mode — categorized by whether they're new, adapted, or already implemented in Tekton Dash.</p>
</div>
<div class="legend">
<div class="legend-item"><div class="legend-dot new"></div> New — unique to Gauntlet</div>
<div class="legend-item"><div class="legend-dot adapt"></div> Adapted — modified from existing mechanic</div>
<div class="legend-item"><div class="legend-dot existing"></div> Existing — already in game, reused as-is</div>
</div>
<div class="search-bar">
<span class="search-icon">🔍</span>
<input type="text" id="glossarySearch" placeholder="Search terms..." oninput="filterGlossary()">
</div>
<div class="filter-btns">
<button class="filter-btn active" onclick="setFilter('all', this)">All</button>
<button class="filter-btn" onclick="setFilter('new', this)">🟢 New Only</button>
<button class="filter-btn" onclick="setFilter('adapt', this)">🟡 Adapted</button>
<button class="filter-btn" onclick="setFilter('existing', this)">🔵 Existing</button>
</div>
<div class="glossary-grid" id="glossaryGrid">
<!-- ===== NEW TERMS ===== -->
<div class="glossary-item new" data-type="new" data-name="sticky cell">
<div class="glossary-icon">🍬</div>
<div class="glossary-content">
<h3>Sticky Cell <span class="badge badge-new">New</span></h3>
<p>A grid cell hit by the Candy Cannon that becomes impassable. Players stepping onto or pushed into a sticky cell are <em>trapped</em>. Remains until cleansed or round ends. Rendered as Layer 2 overlay (pink translucent mesh, ID 17).</p>
<span class="code-ref">TILE_STICKY = 17 → GridMap Layer 2</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="telegraph">
<div class="glossary-icon"></div>
<div class="glossary-content">
<h3>Telegraph <span class="badge badge-new">New</span></h3>
<p>1-second warning before cannon impact. Target cell glows pink/candy color with a shadow preview and charge-up sound. Uses temporary overlay tile (ID 18) on Layer 2, animated alpha 0→1 over 0.8s, then replaced by Sticky Cell on impact.</p>
<span class="code-ref">TILE_TELEGRAPH = 18 → rpc("sync_telegraph")</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="candy cannon">
<div class="glossary-icon">💥</div>
<div class="glossary-content">
<h3>Candy Cannon <span class="badge badge-new">New</span></h3>
<p>Central NPC occupying a permanent 3×3 zone at arena center. Fires volleys of 5 candy shots every 5 seconds, creating sticky cells. Static body — cannot be grabbed, thrown, or interacted with. Not a Tekton — it's a dedicated hazard entity.</p>
<span class="code-ref">CandyCannonController → candy_cannon.tscn</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="volley">
<div class="glossary-icon">🎯</div>
<div class="glossary-content">
<h3>Volley <span class="badge badge-new">New</span></h3>
<p>A batch of 5 simultaneous cannon shots fired at different target cells. One volley fires every 5 seconds (36 total over 3 minutes = 180 impacts). Each shot in a volley has an independent impact size roll (1×1, 1×2, or 2×2).</p>
<span class="code-ref">_fire_volley() → cannon_interval = 5.0</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="impact size">
<div class="glossary-icon">📐</div>
<div class="glossary-content">
<h3>Impact Size <span class="badge badge-new">New</span></h3>
<p>The footprint of each cannon shot. Three sizes: 1×1 (single cell), 1×2 (two adjacent), 2×2 (four cells square). Distribution changes per phase — early favors 1×1, endgame favors 2×2.</p>
<span class="code-ref">phase_weights[phase_idx]["2x2"]</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="trapped">
<div class="glossary-icon">🪤</div>
<div class="glossary-content">
<h3>Trapped <span class="badge badge-new">New</span></h3>
<p>Player state when standing on a sticky cell. Cannot move normally. Escape only via Cleanser power-up. Players can be trapped by stepping onto sticky, being pushed into sticky, or direct cannon hit. Trapped players keep their score but are out of active play.</p>
<span class="code-ref">trapped_players: Dict → rpc("sync_trapped")</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="cleanser">
<div class="glossary-icon"></div>
<div class="glossary-content">
<h3>Cleanser <span class="badge badge-new">New</span></h3>
<p>Power-up earned by completing 2 missions. Allows 5 cells of movement through sticky candy, cleansing traversed cells back to walkable. Inventory limit: 1. Cannot activate while stunned. 0.3s activation delay.</p>
<span class="code-ref">player_cleansers[peer_id] → GoalsCycleManager signal</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="clash">
<div class="glossary-icon">💫</div>
<div class="glossary-content">
<h3>Clash <span class="badge badge-new">New</span></h3>
<p>When two players activate Smack simultaneously (within 0.5s) and are in range of each other. Both get stunned for 1.0s, no push occurs, both smack bars are consumed. Server-authoritative timestamp comparison.</p>
<span class="code-ref">clash detection → 0.5s window, server authority</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="charged state">
<div class="glossary-icon">🔋</div>
<div class="glossary-content">
<h3>Charged State <span class="badge badge-new">New</span></h3>
<p>3-second window after Smack activation where the player model turns pink. If a target enters range during this window, the smack triggers. If no target is hit within 3s, energy is consumed with no effect.</p>
<span class="code-ref">smack_charged[player_id] → 3.0s window</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="anti-unfairness">
<div class="glossary-icon">⚖️</div>
<div class="glossary-content">
<h3>Anti-Unfairness <span class="badge badge-new">New</span></h3>
<p>Targeting rules preventing the cannon from feeling random/cheap. No same-player twice in a row, 2×2 never directly on player, path validation ensures escape routes exist (except final 30s). Uses AStar pathfinding.</p>
<span class="code-ref">last_targeted_player_id → EnhancedGridMap.initialize_astar()</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="route blocking">
<div class="glossary-icon">🚧</div>
<div class="glossary-content">
<h3>Route Blocking <span class="badge badge-new">New</span></h3>
<p>Cannon targeting strategy (25% chance) that places sticky cells on pathfinding bottlenecks — narrow corridors between sticky regions. Forces players to reroute. Calculated using EnhancedGridMap neighbor analysis.</p>
<span class="code-ref">_get_route_blocking_target() → 25% weight</span>
</div>
</div>
<div class="glossary-item new" data-type="new" data-name="gauntlet arena">
<div class="glossary-icon">🏟️</div>
<div class="glossary-content">
<h3>Gauntlet Arena <span class="badge badge-new">New</span></h3>
<p>20×20 cell arena with 391 playable cells (400 minus 3×3 NPC zone). Players spawn at outer edges/corners. Target: 80% sticky coverage by round end (313 cells), leaving ~78 safe cells.</p>
<span class="code-ref">ARENA_SIZE = 20 → gauntlet.tscn</span>
</div>
</div>
<!-- ===== ADAPTED TERMS ===== -->
<div class="glossary-item adapt" data-type="adapt" data-name="smack">
<div class="glossary-icon">👊</div>
<div class="glossary-content">
<h3>Smack <span class="badge badge-adapt">Adapted</span></h3>
<p>Gauntlet-specific melee push. Adapts existing <code>try_push()</code> from Attack Mode but replaces boost-meter gating with 8s auto-refill cooldown, adds 3s charged window, sticky landing trap, and clash detection. Push distance: 3 cells.</p>
<span class="code-ref">PlayerMovementManager.try_push() → smack_cooldowns</span>
</div>
</div>
<div class="glossary-item adapt" data-type="adapt" data-name="phase">
<div class="glossary-icon">⏱️</div>
<div class="glossary-content">
<h3>Phase <span class="badge badge-adapt">Adapted</span></h3>
<p>Three escalation phases in Gauntlet: <strong>Open Arena</strong> (060s), <strong>Route Pressure</strong> (60120s), <strong>Survival Endgame</strong> (120180s). Adapts StopNGoManager's Go/Stop phase pattern but uses time-elapsed triggers instead of cycle signals.</p>
<span class="code-ref">enum Phase { OPEN_ARENA, ROUTE_PRESSURE, SURVIVAL_ENDGAME }</span>
</div>
</div>
<div class="glossary-item adapt" data-type="adapt" data-name="bot ai cannon avoidance">
<div class="glossary-icon">🤖</div>
<div class="glossary-content">
<h3>Bot AI — Cannon Avoidance <span class="badge badge-adapt">Adapted</span></h3>
<p>Extends BotStrategicPlanner with Gauntlet-specific logic: telegraph awareness, sticky path planning, safe-zone pathfinding. Adapts existing bot movement heuristics to factor in shrinking arena.</p>
<span class="code-ref">BotStrategicPlanner → new evaluate_gauntlet()</span>
</div>
</div>
<!-- ===== EXISTING TERMS ===== -->
<div class="glossary-item existing" data-type="existing" data-name="attack mode">
<div class="glossary-icon">⚔️</div>
<div class="glossary-content">
<h3>Attack Mode <span class="badge badge-existing">Existing</span></h3>
<p>Existing player state toggled via PowerUpManager when boost bar is full. In Gauntlet, <em>not used directly</em> — replaced by Smack mechanic. The push physics from <code>try_push()</code> are reused but the activation logic differs.</p>
<span class="code-ref">PowerUpManager.is_attack_mode → NOT used in Gauntlet</span>
</div>
</div>
<div class="glossary-item existing" data-type="existing" data-name="stagger">
<div class="glossary-icon">😵</div>
<div class="glossary-content">
<h3>Stagger <span class="badge badge-existing">Existing</span></h3>
<p>Existing 1.5s movement disable after being push-attacked. Gauntlet's Smack uses a shorter 1.0s stun, but the underlying <code>apply_stagger()</code> function is reused with a duration parameter.</p>
<span class="code-ref">PlayerMovementManager.apply_stagger(duration)</span>
</div>
</div>
<div class="glossary-item existing" data-type="existing" data-name="mission / goals">
<div class="glossary-icon">🎯</div>
<div class="glossary-content">
<h3>Mission / Goals <span class="badge badge-existing">Existing</span></h3>
<p>3×3 pattern-matching tile collection system. Reused as-is from GoalManager + GoalsCycleManager. In Gauntlet, completing every 2 missions also triggers Cleanser unlock (new hook on existing signal).</p>
<span class="code-ref">GoalManager → GoalsCycleManager.goal_count_updated</span>
</div>
</div>
<div class="glossary-item existing" data-type="existing" data-name="layer 2 overlay">
<div class="glossary-icon">🗂️</div>
<div class="glossary-content">
<h3>Layer 2 Overlay <span class="badge badge-existing">Existing</span></h3>
<p>GridMap's Y=2 layer used for visual overlays (safe zones in Stop N Go, freeze in Freemode, highlights). Gauntlet uses it for Sticky Cell and Telegraph meshes. No conflict — modes are mutually exclusive.</p>
<span class="code-ref">GridMap.set_cell_item(Vector3i(x, 2, z), id)</span>
</div>
</div>
<div class="glossary-item existing" data-type="existing" data-name="try push">
<div class="glossary-icon">🫸</div>
<div class="glossary-content">
<h3>try_push() <span class="badge badge-existing">Existing</span></h3>
<p>Player push mechanic in PlayerMovementManager. Pushes target 3 cells backward. Gauntlet's Smack wraps this with direction-based push, sticky landing detection, and clash rules.</p>
<span class="code-ref">PlayerMovementManager.try_push(target, direction)</span>
</div>
</div>
<div class="glossary-item existing" data-type="existing" data-name="screen shake">
<div class="glossary-icon">📳</div>
<div class="glossary-content">
<h3>Screen Shake <span class="badge badge-existing">Existing</span></h3>
<p>Camera shake effect triggered via RPC. Used on cannon impact with "medium" intensity. Already implemented system-wide.</p>
<span class="code-ref">player.rpc("trigger_screen_shake", "medium")</span>
</div>
</div>
<div class="glossary-item existing" data-type="existing" data-name="tekton projectile">
<div class="glossary-icon">🎪</div>
<div class="glossary-content">
<h3>Tekton Projectile <span class="badge badge-existing">Existing</span></h3>
<p>Arc-tween projectile from Tekton NPC. Candy Cannon reuses this exact visual pattern (spawn_projectile_rpc) — creating a mesh, arc-tweening position, then freeing on arrival.</p>
<span class="code-ref">tekton.gd → spawn_projectile_rpc(target, duration)</span>
</div>
</div>
<div class="glossary-item existing" data-type="existing" data-name="rpc sync pattern">
<div class="glossary-icon">📡</div>
<div class="glossary-content">
<h3>RPC Sync Pattern <span class="badge badge-existing">Existing</span></h3>
<p>Server-authoritative state sync via <code>@rpc("authority", "call_local", "reliable")</code>. All Gauntlet state changes (sticky, phase, trap, cleanser) use this identical pattern.</p>
<span class="code-ref">@rpc("authority", "call_local", "reliable")</span>
</div>
</div>
<div class="glossary-item existing" data-type="existing" data-name="timed match">
<div class="glossary-icon"></div>
<div class="glossary-content">
<h3>Timed Match <span class="badge badge-existing">Existing</span></h3>
<p>Global match timer from GoalsCycleManager. Gauntlet passes 180s duration. System handles countdown, HUD timer, and match-end trigger.</p>
<span class="code-ref">goals_cycle_manager.start_match(180.0)</span>
</div>
</div>
<div class="glossary-item existing" data-type="existing" data-name="special tiles manager">
<div class="glossary-icon">💎</div>
<div class="glossary-content">
<h3>SpecialTilesManager <span class="badge badge-existing">Existing</span></h3>
<p>Handles power-up tiles, inventory, and effects. Gauntlet restricts certain powerups (like Stop N Go restrictions) and adds Cleanser as a new inventory slot via the existing signal/slot system.</p>
<span class="code-ref">SpecialTilesManager.inventory → mode-based restrictions</span>
</div>
</div>
</div>
</section>
<!-- ============ ARCHITECTURE ============ -->
<section id="architecture">
<div class="section-header">
<h2><span class="icon" style="background:rgba(99,102,241,0.12)">🏗️</span> Architecture</h2>
<p>How GauntletManager slots into the existing manager tree, following the StopNGoManager pattern exactly.</p>
</div>
<div class="arch-diagram">
<div class="arch-tree">
<span class="node">main.gd</span><br>
<span class="connector">├──</span> <span class="node">_init_managers()</span> <span class="label">← instantiate GauntletManager</span><br>
<span class="connector">├──</span> <span class="node">_setup_host_game()</span> <span class="label">← arena setup branch</span><br>
<span class="connector">├──</span> <span class="node">_start_game()</span> <span class="label">← start_game_mode() call</span><br>
<span class="connector"></span><br>
<span class="new-node">GauntletManager</span> <span class="tag-new">NEW</span><br>
<span class="connector">├──</span> <span class="new-node">_setup_arena()</span> <span class="label">← 20×20 grid, center 3×3 NPC zone</span><br>
<span class="connector">├──</span> <span class="new-node">_setup_hud()</span> <span class="label">← mission label, cleanser indicator</span><br>
<span class="connector">├──</span> <span class="new-node">start_game_mode()</span> <span class="label">← start cannon timer, spawn tiles</span><br>
<span class="connector">├──</span> <span class="new-node">_process()</span> <span class="label">← cannon volley timer, phase escalation</span><br>
<span class="connector">├──</span> <span class="new-node">CandyCannonController</span> <span class="tag-new">NEW</span> <span class="label">← targeting, volley fire</span><br>
<span class="connector">├──</span> <span class="new-node">StickyCell system</span> <span class="tag-new">NEW</span> <span class="label">← Layer 2 overlay, trap logic</span><br>
<span class="connector">├──</span> <span class="new-node">Cleanser system</span> <span class="tag-new">NEW</span> <span class="label">← powerup via missions</span><br>
<span class="connector">├──</span> <span class="new-node">Smack system</span> <span class="tag-new">NEW</span> <span class="label">← modified push with charge/cooldown</span><br>
<span class="connector">└──</span> <span class="new-node">Win condition</span> <span class="label">← highest score at timer end</span><br>
</div>
</div>
</section>
<!-- ============ REUSE MAP ============ -->
<section id="reuse">
<div class="section-header">
<h2><span class="icon" style="background:rgba(52,211,153,0.12)">♻️</span> Reuse Map</h2>
<p>How each GDD feature maps to existing systems — showing what's reused vs what's new.</p>
</div>
<div class="table-wrap">
<table>
<thead>
<tr><th>GDD Feature</th><th>Existing System</th><th>Reuse</th><th>New Work</th></tr>
</thead>
<tbody>
<tr><td>Game Mode Registration</td><td class="td-code">GameMode.gd + LobbyManager</td><td><span class="reuse-direct">Direct</span></td><td>Add enum + strings</td></tr>
<tr><td>20×20 Arena</td><td class="td-code">StopNGoManager._setup_arena()</td><td><span class="reuse-heavy">Heavy</span></td><td>Custom layout, same API</td></tr>
<tr><td>Tile Collection / Scoring</td><td class="td-code">GoalsCycleManager</td><td><span class="reuse-direct">Direct</span></td><td>Reuse as-is</td></tr>
<tr><td>Mission System</td><td class="td-code">GoalManager + goals_cycle_manager</td><td><span class="reuse-direct">Direct</span></td><td>Same 3×3 pattern matching</td></tr>
<tr><td>Timed Match</td><td class="td-code">GoalsCycleManager.start_match()</td><td><span class="reuse-direct">Direct</span></td><td>Pass 180s duration</td></tr>
<tr><td>Player Movement</td><td class="td-code">PlayerMovementManager</td><td><span class="reuse-direct">Direct</span></td><td>No changes</td></tr>
<tr><td>Powerup System</td><td class="td-code">SpecialTilesManager</td><td><span class="reuse-partial">Partial</span></td><td>Cleanser = new type</td></tr>
<tr><td>Smack Mechanic</td><td class="td-code">PlayerMovementManager.try_push()</td><td><span class="reuse-adapt">Adapt</span></td><td>Modified push rules</td></tr>
<tr><td>Candy Cannon NPC</td><td class="td-code">tekton.gd + TektonController</td><td><span class="reuse-pattern">Pattern</span></td><td>New NPC, reuses projectile</td></tr>
<tr><td>Sticky Cells</td><td class="td-code">StopNGoManager safe zone overlay</td><td><span class="reuse-pattern">Pattern</span></td><td>New tile type, same layer</td></tr>
<tr><td>Telegraph VFX</td><td class="td-code">VFXManager / animation.gd</td><td><span class="reuse-pattern">Pattern</span></td><td>New animations, same system</td></tr>
<tr><td>HUD</td><td class="td-code">StopNGoManager._setup_hud()</td><td><span class="reuse-direct">Direct</span></td><td>Mode-specific labels</td></tr>
<tr><td>Network Sync</td><td class="td-code">RPC patterns</td><td><span class="reuse-direct">Direct</span></td><td>Same patterns</td></tr>
<tr><td>Lobby Settings</td><td class="td-code">LobbyManager signal/sync</td><td><span class="reuse-direct">Direct</span></td><td>Gauntlet settings</td></tr>
<tr><td>Bot AI</td><td class="td-code">BotController + BotStrategicPlanner</td><td><span class="reuse-adapt">Adapt</span></td><td>Cannon avoidance strategy</td></tr>
</tbody>
</table>
</div>
</section>
<!-- ============ PHASES ============ -->
<section id="phases">
<div class="section-header">
<h2><span class="icon" style="background:rgba(251,191,36,0.12)">🌊</span> Phase Timeline</h2>
<p>Three escalation phases that control cannon intensity and impact size distribution.</p>
</div>
<div class="timeline">
<div class="phase-card phase-1">
<div class="phase-time">0:00 — 1:00</div>
<h3>Open Arena</h3>
<ul>
<li>Collect tiles, learn the mission</li>
<li>Slow candy pressure</li>
<li>1×1 shots: <strong>60%</strong></li>
<li>1×2 shots: <strong>40%</strong></li>
<li>2×2 shots: <strong>0%</strong></li>
<li>~60 impacts total</li>
</ul>
</div>
<div class="phase-card phase-2">
<div class="phase-time">1:00 — 2:00</div>
<h3>Route Pressure</h3>
<ul>
<li>Candy shapes arena topology</li>
<li>Smack becomes dangerous</li>
<li>1×1 shots: <strong>30%</strong></li>
<li>1×2 shots: <strong>55%</strong></li>
<li>2×2 shots: <strong>15%</strong></li>
<li>Cleanser used strategically</li>
</ul>
</div>
<div class="phase-card phase-3">
<div class="phase-time">2:00 — 3:00</div>
<h3>Survival Endgame</h3>
<ul>
<li>~80% arena is sticky</li>
<li>Safe zones limited, high tension</li>
<li>1×1 shots: <strong>15%</strong></li>
<li>1×2 shots: <strong>55%</strong></li>
<li>2×2 shots: <strong>30%</strong></li>
<li>Aggressive route-blocking allowed</li>
</ul>
</div>
</div>
</section>
<!-- ============ CORE SYSTEMS ============ -->
<section id="systems">
<div class="section-header">
<h2><span class="icon" style="background:rgba(168,85,247,0.12)">⚙️</span> Core Systems</h2>
<p>Deep-dive into the four new systems and how they integrate.</p>
</div>
<!-- Sticky Cell -->
<div class="card" style="margin-bottom: 16px;">
<h3 style="margin-bottom:16px; display:flex; align-items:center; gap:8px;">🍬 Sticky Cell System</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Feature</th><th>Implementation</th></tr></thead>
<tbody>
<tr><td>Visual</td><td>Layer 2 overlay — transparent candy-pink mesh (ID 17)</td></tr>
<tr><td>Movement Block</td><td class="td-code">PlayerMovementManager.simple_move_to() — add sticky check alongside wall check</td></tr>
<tr><td>Trap on Step</td><td class="td-code">GauntletManager._check_player_on_sticky() in _process()</td></tr>
<tr><td>Trap on Push</td><td class="td-code">PlayerMovementManager.try_push() — check landing cell</td></tr>
<tr><td>Cleanser Bypass</td><td>Temporary flag (like <code>is_invisible</code> wall bypass)</td></tr>
<tr><td>Network Sync</td><td class="td-code">main.rpc("sync_grid_item", x, 2, z, 17)</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Smack -->
<div class="card" style="margin-bottom: 16px;">
<h3 style="margin-bottom:16px; display:flex; align-items:center; gap:8px;">👊 Smack vs Attack Mode</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Property</th><th>Current Attack Mode</th><th>Gauntlet Smack</th></tr></thead>
<tbody>
<tr><td>Charge Source</td><td>Boost bar fills to 100</td><td>8s auto-refill cooldown</td></tr>
<tr><td>Activation</td><td>Toggle <code>is_attack_mode</code></td><td>3s charged window (pink model)</td></tr>
<tr><td>Push Distance</td><td>3 cells backward</td><td>3 cells in push direction</td></tr>
<tr><td>Stagger Duration</td><td>1.5s <code>apply_stagger()</code></td><td>1.0s stun</td></tr>
<tr><td>Sticky Landing</td><td>N/A</td><td>Trapped on first sticky cell</td></tr>
<tr><td>Clash Rule</td><td>N/A</td><td>Both stunned, no push, bars consumed</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Cleanser -->
<div class="card" style="margin-bottom: 16px;">
<h3 style="margin-bottom:16px; display:flex; align-items:center; gap:8px;">✨ Cleanser Power-Up</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Property</th><th>Value</th></tr></thead>
<tbody>
<tr><td>Unlock Trigger</td><td class="td-code">GoalsCycleManager.goal_count_updated → count % 2 == 0</td></tr>
<tr><td>Storage</td><td class="td-code">GauntletManager.player_cleansers[peer_id] = 1</td></tr>
<tr><td>Effect</td><td>5 cells movement through sticky — crossed cells become passable</td></tr>
<tr><td>Sync</td><td class="td-code">rpc("sync_cleanser_state", peer_id, count)</td></tr>
<tr><td>Clear Sticky</td><td class="td-code">main.rpc("sync_grid_item", x, 2, z, -1)</td></tr>
<tr><td>Inventory Limit</td><td>1 per player</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Cannon Targeting -->
<div class="card">
<h3 style="margin-bottom:16px; display:flex; align-items:center; gap:8px;">🎯 Cannon Targeting Intelligence</h3>
<div class="table-wrap">
<table>
<thead><tr><th>Roll %</th><th>Target Strategy</th><th>Purpose</th></tr></thead>
<tbody>
<tr><td><strong>60%</strong></td><td>Near a player (not same as last)</td><td>Direct pressure</td></tr>
<tr><td><strong>25%</strong></td><td>Route-blocking bottleneck</td><td>Cut escape paths</td></tr>
<tr><td><strong>10%</strong></td><td>Random non-sticky area</td><td>Spread coverage</td></tr>
<tr><td><strong>5%</strong></td><td>Previously sticky / chaos</td><td>Unpredictability</td></tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- ============ FILES ============ -->
<section id="files">
<div class="section-header">
<h2><span class="icon" style="background:rgba(34,211,238,0.12)">📁</span> File Changes</h2>
</div>
<h3 style="margin-bottom:12px; font-size:14px; color:var(--accent-green);">New Files</h3>
<div class="file-grid" style="margin-bottom:28px">
<div class="file-card new-file">
<div class="file-icon">📜</div>
<div class="file-info"><h4>gauntlet_manager.gd</h4><p>Core mode logic, phases, sticky cells, cleanser, smack</p></div>
</div>
<div class="file-card new-file">
<div class="file-icon">📜</div>
<div class="file-info"><h4>candy_cannon_controller.gd</h4><p>Cannon targeting, volley fire, telegraph</p></div>
</div>
<div class="file-card new-file">
<div class="file-icon">🎬</div>
<div class="file-info"><h4>gauntlet.tscn</h4><p>3D arena environment scene</p></div>
</div>
<div class="file-card new-file">
<div class="file-icon">🎬</div>
<div class="file-info"><h4>candy_cannon.tscn</h4><p>Candy Cannon NPC (3×3, static)</p></div>
</div>
</div>
<h3 style="margin-bottom:12px; font-size:14px; color:var(--accent-yellow);">Modified Files</h3>
<div class="file-grid">
<div class="file-card mod-file">
<div class="file-icon">✏️</div>
<div class="file-info"><h4>game_mode.gd</h4><p>Add GAUNTLET = 3 enum, string mappings</p></div>
</div>
<div class="file-card mod-file">
<div class="file-icon">✏️</div>
<div class="file-info"><h4>lobby_manager.gd</h4><p>Mode list, gauntlet settings, area mapping</p></div>
</div>
<div class="file-card mod-file">
<div class="file-icon">✏️</div>
<div class="file-info"><h4>main.gd</h4><p>Manager init, arena setup branch, start branch</p></div>
</div>
<div class="file-card mod-file">
<div class="file-icon">✏️</div>
<div class="file-info"><h4>player_movement_manager.gd</h4><p>Sticky check in move + push</p></div>
</div>
<div class="file-card mod-file">
<div class="file-icon">✏️</div>
<div class="file-info"><h4>goals_cycle_manager.gd</h4><p>Cleanser grant on 2nd goal</p></div>
</div>
<div class="file-card mod-file">
<div class="file-icon">✏️</div>
<div class="file-info"><h4>special_tiles_manager.gd</h4><p>Gauntlet powerup restrictions</p></div>
</div>
<div class="file-card mod-file">
<div class="file-icon">✏️</div>
<div class="file-info"><h4>MeshLibrary .tres</h4><p>Add TILE_STICKY (17) and TILE_TELEGRAPH (18)</p></div>
</div>
</div>
</section>
<!-- ============ NETWORK ============ -->
<section id="network">
<div class="section-header">
<h2><span class="icon" style="background:rgba(248,113,113,0.12)">📡</span> Network Sync</h2>
<p>All sync follows existing RPC patterns — no new networking paradigms needed.</p>
</div>
<div class="table-wrap">
<table>
<thead><tr><th>Data</th><th>Sync Method</th><th>Existing Pattern</th></tr></thead>
<tbody>
<tr><td>Sticky Cells</td><td class="sync-method">main.rpc("sync_grid_item", x, 2, z, 17)</td><td>Safe zone / freeze overlay</td></tr>
<tr><td>Telegraph</td><td class="sync-method">rpc("sync_telegraph", targets_array)</td><td>StopNGoManager.sync_phase()</td></tr>
<tr><td>Phase Changes</td><td class="sync-method">rpc("sync_gauntlet_phase", idx, elapsed)</td><td>StopNGoManager.sync_phase()</td></tr>
<tr><td>Trap State</td><td class="sync-method">player.rpc("sync_trapped", true)</td><td>player.rpc("sync_stop_freeze")</td></tr>
<tr><td>Cleanser Grant</td><td class="sync-method">rpc("sync_cleanser", peer_id, count)</td><td>goals_cycle_manager.sync_goal_count()</td></tr>
<tr><td>Smack State</td><td class="sync-method">player.rpc("sync_smack_state", charged)</td><td>player.rpc("sync_modulate")</td></tr>
<tr><td>Cannon NPC</td><td colspan="2" style="color:var(--text-muted)">Static scene — no movement sync needed</td></tr>
</tbody>
</table>
</div>
</section>
<!-- ============ PRIORITY ============ -->
<section id="priority">
<div class="section-header">
<h2><span class="icon" style="background:rgba(251,191,36,0.12)">📋</span> Implementation Priority</h2>
</div>
<div class="card">
<div class="priority-list">
<div class="priority-item"><div class="priority-num">1</div><div class="priority-info"><h4>Game Mode Registration</h4><p>game_mode.gd, lobby_manager.gd, main.gd</p></div></div>
<div class="priority-item"><div class="priority-num">2</div><div class="priority-info"><h4>Arena Setup</h4><p>gauntlet_manager._setup_arena(), 20×20 grid</p></div></div>
<div class="priority-item"><div class="priority-num">3</div><div class="priority-info"><h4>Tile Spawning</h4><p>StopNGoManager._spawn_mission_tiles() pattern</p></div></div>
<div class="priority-item"><div class="priority-num">4</div><div class="priority-info"><h4>Cannon Timer + Volley</h4><p>5s interval, 5 shots, 1×1 only</p></div></div>
<div class="priority-item"><div class="priority-num">5</div><div class="priority-info"><h4>Sticky Cell System</h4><p>Layer 2 overlay, movement block, trap detection</p></div></div>
<div class="priority-item"><div class="priority-num">6</div><div class="priority-info"><h4>Telegraph VFX</h4><p>Warning glow → impact transition</p></div></div>
<div class="priority-item"><div class="priority-num">7</div><div class="priority-info"><h4>Impact Sizes</h4><p>1×2 and 2×2 shapes, phase weights</p></div></div>
<div class="priority-item"><div class="priority-num">8</div><div class="priority-info"><h4>Smack Mechanic</h4><p>Modified push with cooldown/charge</p></div></div>
<div class="priority-item"><div class="priority-num">9</div><div class="priority-info"><h4>Cleanser</h4><p>Unlock tracking, sticky bypass</p></div></div>
<div class="priority-item"><div class="priority-num">10</div><div class="priority-info"><h4>Targeting Intelligence</h4><p>Player proximity, route blocking, anti-unfairness</p></div></div>
<div class="priority-item"><div class="priority-num">11</div><div class="priority-info"><h4>Bot AI</h4><p>Cannon avoidance, sticky path planning</p></div></div>
<div class="priority-item"><div class="priority-num">12</div><div class="priority-info"><h4>Polish</h4><p>VFX, SFX, HUD animations, 3D scene</p></div></div>
</div>
</div>
</section>
<!-- ============ RISKS ============ -->
<section id="risks">
<div class="section-header">
<h2><span class="icon" style="background:rgba(248,113,113,0.12)">⚠️</span> Risk Assessment</h2>
</div>
<div class="risk-grid">
<div class="risk-card">
<div class="risk-indicator">🗂️</div>
<div class="risk-content">
<h4>Layer 2 Conflict</h4>
<p>GridMap Layer 2 used by freeze/safe overlays. <strong>Mitigated:</strong> Gauntlet mode is exclusive — no freeze/safe tiles exist.</p>
</div>
</div>
<div class="risk-card">
<div class="risk-indicator">📊</div>
<div class="risk-content">
<h4>20×20 Grid Performance</h4>
<p>400 cells + overlays. <strong>Mitigated:</strong> Existing 23×12 and 14×14 arenas work fine; 20×20 comparable.</p>
</div>
</div>
<div class="risk-card">
<div class="risk-indicator">🚫</div>
<div class="risk-content">
<h4>Impossible Arenas</h4>
<p>Cannon could seal all paths. <strong>Mitigated:</strong> AStar pathfinding check before each volley.</p>
</div>
</div>
<div class="risk-card">
<div class="risk-indicator">🔢</div>
<div class="risk-content">
<h4>MeshLibrary ID Collision</h4>
<p>IDs 1718 might exist. <strong>Mitigated:</strong> Verify max ID in .tres before adding.</p>
</div>
</div>
<div class="risk-card">
<div class="risk-indicator">⏱️</div>
<div class="risk-content">
<h4>Smack Clash Timing</h4>
<p>Network latency affects clash detection. <strong>Mitigated:</strong> Server-authoritative timestamp, 0.5s window.</p>
</div>
</div>
</div>
</section>
</div>
<footer>
<div class="wrapper">
Tekton Dash — Candy Cannon Survival Technical Docs · Generated from gauntlet-technical-implementation.md
</div>
</footer>
<button class="scroll-top" id="scrollTop" onclick="window.scrollTo({top:0,behavior:'smooth'})"></button>
<script>
// Scroll-to-top button
window.addEventListener('scroll', () => {
document.getElementById('scrollTop').classList.toggle('show', window.scrollY > 400);
});
// Active nav link
const sections = document.querySelectorAll('section[id]');
const navLinks = document.querySelectorAll('.nav-link');
window.addEventListener('scroll', () => {
let current = '';
sections.forEach(s => {
if (window.scrollY >= s.offsetTop - 120) current = s.id;
});
navLinks.forEach(l => {
l.classList.toggle('active', l.getAttribute('href') === '#' + current);
});
});
// Glossary filter
let activeFilter = 'all';
function setFilter(type, btn) {
activeFilter = type;
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
filterGlossary();
}
function filterGlossary() {
const q = document.getElementById('glossarySearch').value.toLowerCase();
document.querySelectorAll('.glossary-item').forEach(item => {
const name = item.dataset.name;
const type = item.dataset.type;
const text = item.textContent.toLowerCase();
const matchFilter = activeFilter === 'all' || type === activeFilter;
const matchSearch = !q || text.includes(q);
item.style.display = matchFilter && matchSearch ? '' : 'none';
});
}
</script>
</body>
</html>