This document describes the technical architecture of Neon Vanguard: Sector Zero.
Neon Vanguard is a single-page React application (SPA) built with TypeScript and Vite, packaged as a desktop application via Electron. The architecture follows a Component-Based model with centralized state management.
graph TD
A[index.tsx] --> B[App.tsx]
B --> C[GameProvider]
C --> D[UI Components]
D --> E[Services]
D --> F[Utils]
E --> G[Browser APIs]
F --> H[GameContext]
H --> I[LocalStorage]
neon-vanguard_-sector-zero/
├── components/ # React UI components (44 files)
├── constants/ # Configuration and static data
├── context/ # React Context providers
├── data/ # Dynamic data manager
├── docs/ # Documentation
├── electron/ # Electron configuration
├── hooks/ # Custom React hooks
├── mods/ # Modding system
├── public/ # Static assets
├── services/ # Services (audio, TTS, voice)
├── src/ # Additional source code
├── types/ # Additional TypeScript definitions
├── utils/ # Utility functions
├── App.tsx # Root component
├── constants.ts # Global constants
├── index.css # Global styles (Tailwind + custom)
├── index.tsx # React entry point
├── types.ts # Main global types
├── package.json # npm configuration
├── tsconfig.json # TypeScript configuration
└── vite.config.ts # Vite configuration
React components organized by functionality:
| Component | Description |
|---|---|
CombatScreen.tsx |
Main combat screen (ATB, enemies, logs) |
HangarScreen.tsx |
Shop and upgrades between sectors |
CharacterSelect.tsx |
Pilot + module + loadout selection |
EndlessWaveScreen.tsx |
Endless mode with upgrades |
AchievementsScreen.tsx |
Achievement gallery |
TalentTreeScreen.tsx |
Talent tree per pilot |
CodexScreen.tsx |
Lore/enemy database |
ReplayViewer.tsx |
Recorded combat player |
StatsScreen.tsx |
Detailed statistics |
SettingsScreen.tsx |
Audio/visual/gameplay configuration |
| ... | + 34 other components |
Patterns Used:
- Functional components with hooks
- Props typed with TypeScript interfaces
- Composition over inheritance
- Separation of concerns (presentation vs logic)
Game configuration and static data:
achievements.ts: Definitions for 18 achievementsaugmentations.ts: 30+ augmentations with effectstalents.ts: Talent trees for each pilotcolors.ts: Color palettes (colorblind support)codex.ts: Lore entries and Codex data
React Context for global state management:
GameContext.tsx: Main context with Zustand- Manages: profile, settings, runState, stats, achievements
- Provides: Actions to modify state
- Persists: Automatic LocalStorage saving
Dynamic data manager:
dataManager.ts: Loads pilots from/mods/pilots/- Allows adding custom pilots via JSON
- Async initialization at startup
Services for external interactions:
-
audioService.ts: Audio management (music, SFX)- Asset preloading
- Master/music/sfx volume control
- Play/stop/pause for each sound
-
ttsService.ts: Text-to-Speech via Web Speech API- Game event text reading
- Voice support per language
- Fallback if TTS unavailable
-
voiceLineService.ts: Pilot voice lines- Contextual voice lines (combat, victory, defeat)
- Priority system
- Playback queue
Pure utility functions:
-
combatUtils.ts: Combat logic- Damage calculation (base, critical, weak point)
- Consumable application
- Status management (stun, burning, etc.)
- ATB charge rate
-
synergyUtils.ts: Synergy system- Active synergy detection
- Combined effect application
-
achievementUtils.ts: Achievement unlocking -
talentUtils.ts: Talent application -
codexUtils.ts: Codex entry unlocking
Modular TypeScript definitions:
codex.ts: Codex system typesreplay.ts: Replay system typestalents.ts: Talent tree types- Complements
types.ts(global types)
Extensible modding system:
mods/
├── enemies/
│ └── berserker.json
├── pilots/
│ └── ghost.json
└── events/
└── derelict-ship.ts
Files are automatically loaded at startup.
sequenceDiagram
participant User
participant App
participant DataManager
participant GameContext
participant LocalStorage
User->>App: Load application
App->>DataManager: initializeDataManager()
DataManager->>DataManager: Load pilots (vanilla + mods)
App->>GameContext: Initialize context
GameContext->>LocalStorage: Load persisted data
LocalStorage-->>GameContext: profile, settings, stats
GameContext-->>App: Context ready
App-->>User: Display main menu
sequenceDiagram
participant Player
participant CombatScreen
participant CombatUtils
participant GameContext
participant AudioService
Player->>CombatScreen: Click on enemy
CombatScreen->>CombatUtils: calculateDamage()
CombatUtils-->>CombatScreen: Damage value
CombatScreen->>GameContext: recordDamageDealt()
CombatScreen->>AudioService: playHitSound()
CombatScreen->>CombatScreen: Update enemy HP
loop Every frame
CombatScreen->>CombatScreen: Update ATB gauges
alt Enemy ATB full
CombatScreen->>CombatUtils: calculateEnemyDamage()
CombatUtils-->>CombatScreen: Damage to player
CombatScreen->>GameContext: recordDamageTaken()
CombatScreen->>AudioService: playDamageSound()
end
end
GameContext uses useEffect to auto-save to LocalStorage:
useEffect(() => {
localStorage.setItem('neonvanguard_profile', JSON.stringify(profile));
localStorage.setItem('neonvanguard_settings', JSON.stringify(settings));
localStorage.setItem('neonvanguard_stats', JSON.stringify(stats));
// ...
}, [profile, settings, stats, ...]);LocalStorage Keys:
neonvanguard_profile: XP, level, killsneonvanguard_settings: Audio/visual settingsneonvanguard_runstate: Active run (allows continue)neonvanguard_stats: Detailed statisticsneonvanguard_achievements: Unlocked achievementsneonvanguard_loadouts: Saved configurationsneonvanguard_codex: Unlocked codex entriesneonvanguard_replays: Recorded replaysneonvanguard_talents: Unlocked talents
Complex screens are composed of sub-components:
CombatScreen
├── EnemyList
│ └── Enemy (x N)
├── PlayerStats
├── AbilityBar
├── ConsumableBar
└── CombatLog
Reusable logic via custom hooks:
// Hook for keyboard navigation
const useKeyboardNavigation = (enabled: boolean) => {
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Logic...
};
if (enabled) {
window.addEventListener('keydown', handleKeyDown);
}
return () => window.removeEventListener('keydown', handleKeyDown);
}, [enabled]);
};GameContext uses Zustand for performant state:
const useGameStore = create<GameState>((set, get) => ({
profile: initialProfile,
settings: initialSettings,
addXp: (amount) => set((state) => ({
profile: { ...state.profile, xp: state.profile.xp + amount }
})),
// ... other actions
}));Benefits:
- ✅ Fewer re-renders than Context alone
- ✅ Simple syntax
- ✅ Integrated DevTools
- ✅ Easy to test
Services encapsulate external interactions:
class AudioService {
private sounds: Map<string, HTMLAudioElement>;
private musicVolume: number;
async preload(urls: string[]) { /* ... */ }
play(soundId: string) { /* ... */ }
setVolume(volume: number) { /* ... */ }
}
export const audio = new AudioService();const { app, BrowserWindow } = require('electron');
const path = require('path');
const isDev = require('electron-is-dev');
function createWindow() {
const win = new BrowserWindow({
width: 1280,
height: 720,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
}
});
if (isDev) {
win.loadURL('http://localhost:5173');
} else {
win.loadFile(path.join(__dirname, '../dist/index.html'));
}
}
app.whenReady().then(createWindow);package.json:
{
"main": "electron/main.cjs",
"scripts": {
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:5173 && electron .\"",
"electron:build": "npm run build && electron-builder"
},
"build": {
"appId": "com.neonvanguard.sectorzero",
"files": ["dist/**/*", "electron/**/*", "package.json"],
"directories": {
"buildResources": "public",
"output": "release"
}
}
}Tests for utils and pure logic:
// combatUtils.test.ts
describe('calculateDamage', () => {
it('calculates base damage correctly', () => {
const result = calculateDamage(100, 1.0, false);
expect(result).toBe(100);
});
it('applies critical multiplier', () => {
const result = calculateDamage(100, 1.0, true);
expect(result).toBe(150); // 1.5x crit
});
});Integration tests for components:
// CharacterSelect.test.tsx
describe('CharacterSelect', () => {
it('displays all unlocked pilots', () => {
render(<CharacterSelect onSelect={vi.fn()} />);
expect(screen.getByText('VANGUARD')).toBeInTheDocument();
expect(screen.getByText('SOLARIS')).toBeInTheDocument();
});
});npm run dev # Vite dev server (port 5173)
npm run electron:dev # Electron + hot reloadnpm run build # Build web (dist/)
npm run electron:build:win # Build Electron WindowsOutputs:
dist/: Web build (deployable to hosting)release/: Electron executables (.exe, .dmg, etc.)
- API Reference - Complete GameContext API
- Features - Game systems documentation
- Modding Guide - Content creation guide
- Contributing - Contribution guide
Last Updated: 2025-12-11