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).
This commit is contained in:
@@ -0,0 +1,1366 @@
|
||||
<!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> (0–60s), <strong>Route Pressure</strong> (60–120s), <strong>Survival Endgame</strong> (120–180s). 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 17–18 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>
|
||||
Reference in New Issue
Block a user