425 lines
12 KiB
Markdown
425 lines
12 KiB
Markdown
# System Architecture
|
|
|
|
High-level architecture guide for the Skelly project, explaining system design, architectural patterns, and design decisions.
|
|
|
|
**Quick Links**:
|
|
- **Coding Standards**: See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
|
|
- **Testing Protocols**: See [TESTING.md](TESTING.md)
|
|
- **Component APIs**: See [UI_COMPONENTS.md](UI_COMPONENTS.md)
|
|
|
|
## Overview
|
|
|
|
Skelly uses a service-oriented architecture with **autoload managers** providing global services, **scene-based components** implementing gameplay, and **signal-based communication** for loose coupling.
|
|
|
|
**Key Architectural Principles**:
|
|
- **Instance-based design**: No global static state for testability
|
|
- **Signal-driven communication**: Loose coupling between systems
|
|
- **Service layer pattern**: Autoloads as singleton services
|
|
- **State machines**: Explicit state management for complex flows
|
|
- **Defensive programming**: Input validation, error recovery, fallback mechanisms
|
|
|
|
## Autoload System
|
|
|
|
Autoloads provide singleton services accessible globally. Each has a specific responsibility.
|
|
|
|
### GameManager
|
|
**Responsibility**: Scene transitions and game state coordination
|
|
|
|
**Key Features**:
|
|
- Centralized scene loading with error handling
|
|
- Race condition protection via `is_changing_scene` flag
|
|
- Scene path constants for maintainability
|
|
- Gameplay mode validation with whitelist
|
|
- Never use `get_tree().change_scene_to_file()` directly
|
|
|
|
**Usage**:
|
|
```gdscript
|
|
# ✅ Correct
|
|
GameManager.start_game_with_mode("match3")
|
|
|
|
# ❌ Wrong
|
|
get_tree().change_scene_to_file("res://scenes/game/Game.tscn")
|
|
```
|
|
|
|
**Files**: `src/autoloads/GameManager.gd`
|
|
|
|
### SaveManager
|
|
**Responsibility**: Save/load game state with security and data integrity
|
|
|
|
**Key Features**:
|
|
- **Tamper Detection**: Deterministic checksums detect save file modification
|
|
- **Race Condition Protection**: Save operation locking prevents concurrent conflicts
|
|
- **Permissive Validation**: Auto-repair system fixes corrupted data
|
|
- **Type Safety**: NaN/Infinity/bounds checking for numeric values
|
|
- **Memory Protection**: File size limits prevent memory exhaustion
|
|
- **Version Migration**: Backward-compatible save format upgrades
|
|
- **Error Recovery**: Multi-layered backup and fallback systems
|
|
|
|
**Security Model**:
|
|
```gdscript
|
|
# Checksum validates data integrity
|
|
save_data["_checksum"] = _calculate_checksum(save_data)
|
|
|
|
# Auto-repair fixes corrupted fields
|
|
if not _validate_save_data(data):
|
|
data = _repair_save_data(data)
|
|
|
|
# Race condition protection
|
|
if _is_saving:
|
|
return # Prevent concurrent saves
|
|
```
|
|
|
|
**Testing**: See [TESTING.md](TESTING.md#save-system-testing-protocols) for comprehensive test suites validating checksums, migration, and integration.
|
|
|
|
**Files**: `src/autoloads/SaveManager.gd`
|
|
|
|
### SettingsManager
|
|
**Responsibility**: User settings persistence and validation
|
|
|
|
**Key Features**:
|
|
- Input validation with NaN/Infinity checks
|
|
- Bounds checking for numeric values
|
|
- Security hardening against invalid inputs
|
|
- Default fallback values
|
|
- Type coercion with validation
|
|
|
|
**Files**: `src/autoloads/SettingsManager.gd`
|
|
|
|
### DebugManager
|
|
**Responsibility**: Unified logging and debug UI coordination
|
|
|
|
**Key Features**:
|
|
- Structured logging with log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)
|
|
- Category-based log organization
|
|
- Global debug UI toggle (F12 key)
|
|
- Log level filtering for development/production
|
|
- Replaces all `print()` and `push_error()` calls
|
|
|
|
**Usage**: See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#logging-standards) for logging best practices.
|
|
|
|
**Files**: `src/autoloads/DebugManager.gd`
|
|
|
|
### AudioManager
|
|
**Responsibility**: Music and sound effect playback
|
|
|
|
**Key Features**:
|
|
- Audio bus system (Music, SFX)
|
|
- Volume control per bus
|
|
- Loop configuration for music
|
|
- UI click sounds
|
|
|
|
**Files**: `src/autoloads/AudioManager.gd`
|
|
|
|
### LocalizationManager
|
|
**Responsibility**: Language switching and translation management
|
|
|
|
**Key Features**:
|
|
- Multi-language support (English, Russian)
|
|
- Runtime language switching
|
|
- Translation file management
|
|
|
|
**Files**: `src/autoloads/LocalizationManager.gd`
|
|
|
|
## Scene Management Pattern
|
|
|
|
All scene transitions flow through **GameManager** to ensure consistent error handling and state management.
|
|
|
|
### Scene Loading Flow
|
|
|
|
```
|
|
User Action
|
|
↓
|
|
GameManager.start_game_with_mode(mode)
|
|
↓
|
|
Validate mode (whitelist check)
|
|
↓
|
|
Check is_changing_scene flag
|
|
↓
|
|
Load PackedScene with validation
|
|
↓
|
|
change_scene_to_packed()
|
|
↓
|
|
Reset is_changing_scene flag
|
|
```
|
|
|
|
### Race Condition Prevention
|
|
|
|
```gdscript
|
|
var is_changing_scene: bool = false
|
|
|
|
func start_game_with_mode(gameplay_mode: String) -> void:
|
|
# Prevent concurrent scene changes
|
|
if is_changing_scene:
|
|
DebugManager.log_warn("Scene change already in progress", "GameManager")
|
|
return
|
|
|
|
is_changing_scene = true
|
|
# ... scene loading logic ...
|
|
is_changing_scene = false
|
|
```
|
|
|
|
**Why This Matters**: Multiple rapid button clicks or input events could trigger concurrent scene loads, causing crashes or undefined state.
|
|
|
|
## Modular Gameplay System
|
|
|
|
Game modes are implemented as separate gameplay modules in `scenes/game/gameplays/`.
|
|
|
|
### Gameplay Architecture
|
|
|
|
```
|
|
Game.tscn (Main scene)
|
|
↓
|
|
├─> Match3Gameplay.tscn (Match-3 mode)
|
|
├─> ClickomaniaGameplay.tscn (Clickomania mode)
|
|
└─> [Future gameplay modes]
|
|
```
|
|
|
|
**Pattern**: Each gameplay mode:
|
|
- Extends Control or Node2D
|
|
- Emits `score_changed` signal
|
|
- Implements `_ready()` for initialization
|
|
- Handles input independently
|
|
- Includes optional debug menu
|
|
|
|
**Files**: `scenes/game/gameplays/`
|
|
|
|
## Design Patterns Used
|
|
|
|
### Instance-Based Architecture
|
|
|
|
**Problem**: Static variables prevent testing and create hidden dependencies.
|
|
|
|
**Solution**: Instance-based architecture with explicit dependencies.
|
|
|
|
```gdscript
|
|
# ❌ Bad: Static global state
|
|
static var current_gem_pool = [0, 1, 2, 3, 4]
|
|
|
|
# ✅ Good: Instance-based
|
|
var active_gem_types: Array = []
|
|
|
|
func set_active_gem_types(gem_indices: Array) -> void:
|
|
active_gem_types = gem_indices.duplicate()
|
|
```
|
|
|
|
**Benefits**:
|
|
- Each instance isolated for testing
|
|
- No hidden global state
|
|
- Explicit dependencies
|
|
- Thread-safe by default
|
|
|
|
### Signal-Based Communication
|
|
|
|
**Pattern**: Use signals for loose coupling between systems.
|
|
|
|
```gdscript
|
|
# Component emits signal
|
|
signal score_changed(new_score: int)
|
|
|
|
# Parent connects to signal
|
|
gameplay.score_changed.connect(_on_score_changed)
|
|
```
|
|
|
|
**Benefits**:
|
|
- Loose coupling
|
|
- Easy to test components in isolation
|
|
- Clear event flow
|
|
- Flexible subscription model
|
|
|
|
### Service Layer Pattern
|
|
|
|
Autoloads act as singleton services providing global functionality.
|
|
|
|
**Pattern**:
|
|
- Autoloads expose public API methods
|
|
- Components call autoload methods
|
|
- Autoloads emit signals for state changes
|
|
- Never nest autoload calls deeply
|
|
|
|
### State Machine Pattern
|
|
|
|
Complex workflows use explicit state machines.
|
|
|
|
**Example**: Match-3 tile swapping
|
|
```
|
|
WAITING → SELECTING → SWAPPING → PROCESSING → WAITING
|
|
```
|
|
|
|
**Benefits**:
|
|
- Clear state transitions
|
|
- Easy to debug
|
|
- Prevents invalid state combinations
|
|
- Self-documenting code
|
|
|
|
## Critical Architecture Decisions
|
|
|
|
### Memory Management: queue_free() Over free()
|
|
|
|
**Decision**: Always use `queue_free()` instead of `free()` for node cleanup.
|
|
|
|
**Rationale**:
|
|
- `free()` causes immediate deletion (crashes if referenced)
|
|
- `queue_free()` waits until safe deletion point
|
|
- Prevents use-after-free bugs
|
|
|
|
**Implementation**:
|
|
```gdscript
|
|
# ✅ Correct
|
|
for child in children_to_remove:
|
|
child.queue_free()
|
|
await get_tree().process_frame # Wait for cleanup
|
|
|
|
# ❌ Dangerous
|
|
for child in children_to_remove:
|
|
child.free() # Can crash
|
|
```
|
|
|
|
**Impact**: Eliminated multiple potential crash points. See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#memory-management-standards) for standards.
|
|
|
|
### Race Condition Prevention: State Flags
|
|
|
|
**Decision**: Use state flags to prevent concurrent operations.
|
|
|
|
**Rationale**:
|
|
- Async operations can overlap without protection
|
|
- Multiple rapid inputs can trigger race conditions
|
|
- State flags provide simple, effective protection
|
|
|
|
**Implementation**: Used in GameManager (scene loading), SaveManager (save operations), and gameplay systems (tile swapping).
|
|
|
|
### Error Recovery: Fallback Mechanisms
|
|
|
|
**Decision**: Provide fallback behavior for all critical failures.
|
|
|
|
**Rationale**:
|
|
- Games should degrade gracefully, not crash
|
|
- User experience > strict validation
|
|
- Log errors but continue operation
|
|
|
|
**Examples**:
|
|
- Settings file corrupted? Load defaults
|
|
- Scene load failed? Return to main menu
|
|
- Audio file missing? Continue without sound
|
|
|
|
### Input Validation: Whitelist Approach
|
|
|
|
**Decision**: Validate all user inputs against known-good values.
|
|
|
|
**Rationale**:
|
|
- Security hardening
|
|
- Prevent invalid state
|
|
- Clear error messages
|
|
- Self-documenting code
|
|
|
|
**Implementation**: Used in SettingsManager (volume, language), GameManager (gameplay modes), Match3Gameplay (grid movements).
|
|
|
|
## System Interactions
|
|
|
|
### Typical Game Flow
|
|
|
|
```
|
|
Main Menu
|
|
↓
|
|
[GameManager.start_game_with_mode("match3")]
|
|
↓
|
|
Game Scene Loads
|
|
↓
|
|
Match3Gameplay initializes
|
|
↓
|
|
├─> SettingsManager (load difficulty)
|
|
├─> AudioManager (play background music)
|
|
└─> DebugManager (setup debug UI)
|
|
↓
|
|
Gameplay Loop
|
|
↓
|
|
├─> Input handling
|
|
├─> Score updates (emit score_changed signal)
|
|
├─> SaveManager (autosave high score)
|
|
└─> DebugManager (log important events)
|
|
```
|
|
|
|
### Signal Flow Example: Score Change
|
|
|
|
```
|
|
Match3Gameplay detects match
|
|
↓
|
|
[emit score_changed(new_score)]
|
|
↓
|
|
Game.gd receives signal
|
|
↓
|
|
Updates score display UI
|
|
↓
|
|
SaveManager.save_high_score(score)
|
|
```
|
|
|
|
### Debug System Integration
|
|
|
|
```
|
|
User presses F12
|
|
↓
|
|
DebugManager.toggle_debug_ui()
|
|
↓
|
|
[emit debug_ui_toggled(visible)]
|
|
↓
|
|
All debug menus receive signal
|
|
↓
|
|
Show/hide debug panels
|
|
```
|
|
|
|
## Quality Improvements
|
|
|
|
The project implements high-quality code standards from the start:
|
|
|
|
**Key Quality Features**:
|
|
- **Memory Safety**: Uses `queue_free()` pattern for safe node cleanup
|
|
- **Error Handling**: Comprehensive error handling with fallbacks
|
|
- **Race Condition Protection**: State flag protection for async operations
|
|
- **Instance-Based Architecture**: No global static state for testability
|
|
- **Code Reuse**: Base class architecture (DebugMenuBase) for common functionality
|
|
- **Input Validation**: Complete validation coverage for all user inputs
|
|
|
|
These standards are enforced through the [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md#code-quality-checklist) quality checklist.
|
|
|
|
## Best Practices
|
|
|
|
### Extending the Architecture
|
|
|
|
When adding new features:
|
|
|
|
1. **New autoload?**
|
|
- Only if truly global and used by many systems
|
|
- Consider dependency injection instead
|
|
- Keep autoloads focused on single responsibility
|
|
|
|
2. **New gameplay mode?**
|
|
- Create in `scenes/game/gameplays/`
|
|
- Extend appropriate base class
|
|
- Emit `score_changed` signal
|
|
- Add to GameManager mode whitelist
|
|
|
|
3. **New UI component?**
|
|
- Create in `scenes/ui/components/`
|
|
- Follow [UI_COMPONENTS.md](UI_COMPONENTS.md) patterns
|
|
- Support keyboard/gamepad navigation
|
|
- Emit signals for state changes
|
|
|
|
### Architecture Review Checklist
|
|
|
|
Before committing architectural changes:
|
|
|
|
- [ ] No new global static state introduced
|
|
- [ ] All autoload access justified and documented
|
|
- [ ] Signal-based communication used appropriately
|
|
- [ ] Error handling with fallbacks implemented
|
|
- [ ] Input validation in place
|
|
- [ ] Memory management uses `queue_free()`
|
|
- [ ] Race condition protection if async operations
|
|
- [ ] Testing strategy defined
|
|
|
|
## Related Documentation
|
|
|
|
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)**: Coding standards and best practices
|
|
- **[TESTING.md](TESTING.md)**: Testing protocols and procedures
|
|
- **[UI_COMPONENTS.md](UI_COMPONENTS.md)**: Component API reference
|
|
- **[CLAUDE.md](CLAUDE.md)**: LLM assistant quick start guide
|