1879 lines
43 KiB
Markdown
1879 lines
43 KiB
Markdown
# Cat Haven - Comprehensive Instructions
|
||
|
||
## Table of Contents
|
||
1. [Codebase Overview](#codebase-overview)
|
||
2. [Build Instructions](#build-instructions)
|
||
3. [Testing Instructions](#testing-instructions)
|
||
4. [Development Workflow](#development-workflow)
|
||
5. [Architecture Documentation](#architecture-documentation)
|
||
6. [API Reference](#api-reference)
|
||
7. [Troubleshooting](#troubleshooting)
|
||
|
||
---
|
||
|
||
## Codebase Overview
|
||
|
||
### Project Structure
|
||
|
||
```
|
||
cat_game/
|
||
├── main.lua # LOVR entry point - initializes all modules
|
||
├── README.md # Project overview and quick start
|
||
├── INSTRUCTIONS.md # This file - comprehensive documentation
|
||
├── src/
|
||
│ ├── core.lua # Game state, configuration, save/load system
|
||
│ ├── json.lua # Custom JSON encoder/decoder (DKJSON)
|
||
│ ├── voxel/
|
||
│ │ └── init.lua # Voxel rendering, procedural textures, mesh generation
|
||
│ ├── cat/
|
||
│ │ └── init.lua # Cat AI, behavior, personality logic, state machine
|
||
│ ├── house/
|
||
│ │ └── init.lua # Grid system, furniture placement, collision detection
|
||
│ ├── ui/
|
||
│ │ └── init.lua # HUD rendering, inventory, cat log, interaction
|
||
│ └── audio/
|
||
│ └── init.lua # Procedural audio generation (purr, meow, ambient)
|
||
├── test/
|
||
│ ├── test_helper.lua # LOVR API mocks and test utilities
|
||
│ ├── run_tests.lua # Test runner with TAP reporter and XML generation
|
||
│ ├── spec/ # Unit tests (per module)
|
||
│ │ ├── core_spec.lua
|
||
│ │ ├── json_spec.lua
|
||
│ │ ├── voxel_spec.lua
|
||
│ │ ├── cat_spec.lua
|
||
│ │ ├── house_spec.lua
|
||
│ │ ├── ui_spec.lua
|
||
│ │ └── audio_spec.lua
|
||
│ ├── integration/ # Integration tests
|
||
│ │ └── game_flow_spec.lua
|
||
│ └── e2e/ # End-to-end edge case tests
|
||
│ └── edge_cases_spec.lua
|
||
└── test-report.xml # Generated JUnit XML report (after tests)
|
||
```
|
||
|
||
### Module Descriptions
|
||
|
||
#### Core Module (`src/core.lua`)
|
||
**Responsibilities:**
|
||
- Global game state management
|
||
- Configuration constants and defaults
|
||
- Personality definitions (5 types)
|
||
- Furniture type definitions (5 types)
|
||
- Cat spawning and removal
|
||
- Save/load system using JSON
|
||
- Happiness calculation
|
||
- Game loop updates
|
||
- Pause state management
|
||
|
||
**Key Components:**
|
||
- `core.CONFIG` - Game configuration (grid size, voxel size, spawn intervals)
|
||
- `core.state` - Mutable game state (cats, furniture, inventory, cat log)
|
||
- `core.personalities` - 5 personality definitions with traits and compatibility
|
||
- `core.furnitureTypes` - 5 furniture types with properties and bonuses
|
||
|
||
#### JSON Module (`src/json.lua`)
|
||
**Responsibilities:**
|
||
- Custom JSON encoding/decoding
|
||
- Support for all Lua data types (nil, number, string, boolean, table)
|
||
- Round-trip validation
|
||
- Error handling for malformed JSON
|
||
|
||
**Features:**
|
||
- Deterministic encoding (sorted keys)
|
||
- Handles nested tables
|
||
- Proper string escaping
|
||
- Robust parsing with position tracking
|
||
|
||
#### Voxel Module (`src/voxel/init.lua`)
|
||
**Responsibilities:**
|
||
- Procedural texture generation with noise
|
||
- Voxel cube rendering
|
||
- Furniture and cat rendering
|
||
- Raycast for interaction detection
|
||
- Mesh generation with proper normals and UVs
|
||
|
||
**Features:**
|
||
- 64×64 pixel procedural textures
|
||
- Texture caching by ID
|
||
- AABB collision detection for raycasting
|
||
- Support for multiple pass rendering
|
||
|
||
#### Cat Module (`src/cat/init.lua`)
|
||
**Responsibilities:**
|
||
- Cat object creation with personality
|
||
- AI behavior state machine
|
||
- Need management (comfort, social, hunger, fun)
|
||
- Animation and state updates
|
||
- Serialization for save/load
|
||
- Personality compatibility checking
|
||
|
||
**AI Behavior:**
|
||
- Seeks furniture that satisfies lowest need
|
||
- Avoids incompatible personalities
|
||
- Sleeps when comfort is low
|
||
- Moves toward targets based on needs
|
||
|
||
#### House Module (`src/house/init.lua`)
|
||
**Responsibilities:**
|
||
- Grid-based placement system
|
||
- Furniture creation and management
|
||
- Collision detection
|
||
- Furniture upgrades
|
||
- Serialization/deserialization
|
||
|
||
**Features:**
|
||
- 10×6×10 grid system
|
||
- Voxel-based collision detection
|
||
- 3-level upgrade system
|
||
- Occupant tracking
|
||
|
||
#### UI Module (`src/ui/init.lua`)
|
||
**Responsibilities:**
|
||
- HUD rendering (happiness meter, cat count, time)
|
||
- Inventory display and interaction
|
||
- Cat log display
|
||
- Placement preview
|
||
- Mouse interaction handling
|
||
|
||
**Features:**
|
||
- Dynamic scaling based on window size
|
||
- 3D world raycasting for placement
|
||
- Interactive inventory selection
|
||
- Real-time updates
|
||
|
||
#### Audio Module (`src/audio/init.lua`)
|
||
**Responsibilities:**
|
||
- Procedural sound generation
|
||
- Purr, meow, click, and ambient sound creation
|
||
- Spatial audio support
|
||
- Sound caching and playback
|
||
|
||
**Features:**
|
||
- 44.1kHz sample rate
|
||
- Frequency modulation for realistic sounds
|
||
- Spatial positioning
|
||
- Looping ambient sound
|
||
|
||
### Key Components and Responsibilities
|
||
|
||
| Component | File | Primary Responsibility |
|
||
|-----------|------|----------------------|
|
||
| Game State | `core.lua` | Global state, configuration, save/load |
|
||
| Rendering | `voxel/init.lua` | 3D voxel rendering, textures, meshes |
|
||
| AI System | `cat/init.lua` | Cat behavior, state machine, needs |
|
||
| Environment | `house/init.lua` | Grid, furniture, collision |
|
||
| Interface | `ui/init.lua` | HUD, inventory, interaction |
|
||
| Audio | `audio/init.lua` | Procedural sound generation |
|
||
| Serialization | `json.lua` | JSON encoding/decoding |
|
||
|
||
### Data Flow and Module Interactions
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ LOVR Engine │
|
||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||
│ │ lovr.load │ │ lovr.update │ │ lovr.draw │ │
|
||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||
│ │ │ │ │
|
||
│ └──────────────────┴──────────────────┘ │
|
||
│ │ │
|
||
│ ┌───────▼───────┐ │
|
||
│ │ main.lua │ │
|
||
│ └───────┬───────┘ │
|
||
│ │ │
|
||
│ ┌──────────────────┼──────────────────┐ │
|
||
│ │ │ │ │
|
||
│ ┌────▼────┐ ┌──────▼──────┐ ┌────▼────┐ │
|
||
│ │ core │◄─────┤ house │◄─────┤ cat │ │
|
||
│ └────┬────┘ └─────────────┘ └────┬────┘ │
|
||
│ │ │ │
|
||
│ ┌────▼────┐ ┌─────────────┐ ┌────▼────┐ │
|
||
│ │ audio │ │ voxel │ │ ui │ │
|
||
│ └─────────┘ └─────────────┘ └─────────┘ │
|
||
│ │ │
|
||
│ ┌───────▼───────┐ │
|
||
│ │ json.lua │ │
|
||
│ └───────────────┘ │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Data Flow:**
|
||
1. `lovr.load()` → core.init() → loads all modules
|
||
2. `lovr.update(dt)` → core.update(dt) → updates cats, checks departures, calculates happiness
|
||
3. `lovr.draw(pass)` → renders voxel world, furniture, cats, UI
|
||
4. `lovr.mousepressed()` → UI handles interaction
|
||
5. `lovr.keypressed()` → Save/Load with RThumb+L/S, Pause with ESC
|
||
6. `lovr.quit()` → autosave game state
|
||
|
||
### Configuration and State Management
|
||
|
||
#### Configuration (`core.CONFIG`)
|
||
|
||
```lua
|
||
{
|
||
version = '1.0.0',
|
||
voxelSize = 0.5, -- Size of each voxel cube
|
||
gridSize = { x = 10, y = 6, z = 10 }, -- Grid dimensions
|
||
catSpawnInterval = 30, -- Seconds between spawn attempts
|
||
maxCats = 8, -- Maximum concurrent cats
|
||
roomDimensions = { width = 5, height = 3, depth = 5 },
|
||
lighting = {
|
||
ambient = { 0.8, 0.75, 0.7 },
|
||
sunbeamIntensity = 0.6,
|
||
warmColor = { 1.0, 0.95, 0.85 }
|
||
},
|
||
happinessFormulas = {
|
||
comfortWeight = 0.3,
|
||
socialWeight = 0.2,
|
||
hungerWeight = 0.2,
|
||
funWeight = 0.3
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Game State (`core.state`)
|
||
|
||
```lua
|
||
{
|
||
cats = {}, -- Array of cat objects
|
||
furniture = {}, -- Array of furniture objects
|
||
house = {
|
||
rooms = {}, -- 3D grid (x,y,z)
|
||
totalHappiness = 0, -- Current happiness score
|
||
totalCats = 0, -- Current cat count
|
||
history = {} -- Historical events
|
||
},
|
||
inventory = {}, -- Inventory items
|
||
catLog = {}, -- Cat arrival/departure log
|
||
gameTime = 0, -- Elapsed game time
|
||
isPaused = false, -- Pause state
|
||
camera = {
|
||
position = { 0, 2, 0 },
|
||
orientation = { 0, 0, 0, 1 },
|
||
fov = 90
|
||
},
|
||
selectedFurniture = nil, -- Currently selected inventory item
|
||
placementMode = false -- Whether in placement mode
|
||
}
|
||
```
|
||
|
||
#### Personality System
|
||
|
||
**5 Personalities:**
|
||
1. **Lone Wolf** - Independent, quiet, territorial
|
||
- High comfort need, low social need
|
||
- Compatible with other lone wolves
|
||
2. **Social Butterfly** - Friendly, outgoing, curious
|
||
- High social and fun needs
|
||
- Compatible with social and clown personalities
|
||
3. **Playful Clown** - Mischievous, energetic, attention seeker
|
||
- High social, hunger, and fun needs
|
||
- Compatible with social and clown
|
||
4. **Greedy Eater** - Foodie, persistent, demanding
|
||
- Very high hunger need
|
||
- Moderate compatibility with others
|
||
5. **Lazy Sleeper** - Relaxed, comfort lover, slow
|
||
- Very high comfort need, low social and fun needs
|
||
- Compatible with other lazy cats
|
||
|
||
#### Furniture System
|
||
|
||
**5 Furniture Types:**
|
||
1. **Cat Tree** - High comfort, social, and fun bonus
|
||
2. **Scratching Post** - High fun bonus
|
||
3. **Cozy Bed** - High comfort bonus
|
||
4. **Window Seat** - Good comfort and social bonus
|
||
5. **Food Bowl** - Base happiness only (hunger handled separately)
|
||
|
||
---
|
||
|
||
## Build Instructions
|
||
|
||
### Prerequisites
|
||
|
||
#### Required Software
|
||
|
||
1. **LOVR 2023 (v1.4)**
|
||
- Download: https://lovr.org/
|
||
- Required for running the game
|
||
- Must support Lua 5.4
|
||
- Required for VR features (optional but recommended)
|
||
|
||
2. **Lua 5.4**
|
||
- Required for running tests
|
||
- Available on most Linux distributions
|
||
- Windows: Download from https://luabinaries.sourceforge.net/
|
||
- macOS: `brew install lua`
|
||
|
||
3. **Busted (Test Framework)**
|
||
- Required for running tests
|
||
- Install via LuaRocks: `luarocks install busted`
|
||
- Or download from https://lunarmodules.github.io/busted/
|
||
|
||
#### Optional Dependencies
|
||
|
||
- **LuaRocks** - Package manager for Lua modules
|
||
- **LuaJIT** - Faster Lua execution (optional, for testing)
|
||
|
||
### Building and Running
|
||
|
||
#### Running the Game
|
||
|
||
**Method 1: Using LOVR executable**
|
||
```bash
|
||
# From project root
|
||
lovr ./
|
||
|
||
# Or specify path explicitly
|
||
lovr /path/to/cat_game/
|
||
```
|
||
|
||
**Method 2: Using LOVR API**
|
||
```bash
|
||
# If you have LOVR installed as a library
|
||
lua -e "require('lovr').boot('main.lua')"
|
||
```
|
||
|
||
**Method 3: Development mode**
|
||
```bash
|
||
# Edit and run without restarting
|
||
# Just press R in LOVR to reload (if supported)
|
||
```
|
||
|
||
#### Running Tests
|
||
|
||
**All tests:**
|
||
```bash
|
||
lua test/run_tests.lua
|
||
```
|
||
|
||
**Specific test file:**
|
||
```bash
|
||
lua test/spec/json_spec.lua
|
||
lua test/spec/core_spec.lua
|
||
```
|
||
|
||
**With LuaJIT (faster):**
|
||
```bash
|
||
luajit test/run_tests.lua
|
||
```
|
||
|
||
**With Busted directly:**
|
||
```bash
|
||
busted test/spec/
|
||
busted test/integration/
|
||
busted test/e2e/
|
||
```
|
||
|
||
### Development Setup
|
||
|
||
#### IDE Configuration
|
||
|
||
**VS Code (Recommended):**
|
||
```json
|
||
// .vscode/settings.json
|
||
{
|
||
"lua.diagnostics.globals": [
|
||
"lovr",
|
||
"describe",
|
||
"it",
|
||
"assert",
|
||
"assert_truthy",
|
||
"assert_falsy"
|
||
],
|
||
"lua.workspace.library": [
|
||
"/path/to/lovr/include",
|
||
"test"
|
||
]
|
||
}
|
||
```
|
||
|
||
**Neovim:**
|
||
```vim
|
||
" Add to init.vim
|
||
set omnifunc=lua#complete#Complete
|
||
let g:lua_language_server = {
|
||
\ 'workspace': {'library': [
|
||
\ expand('$VIMRUNTIME'),
|
||
\ 'test',
|
||
\ 'src'
|
||
\ ]},
|
||
\ 'runtime': {'version': 'LuaJIT'},
|
||
\ }
|
||
```
|
||
|
||
#### Testing Setup
|
||
|
||
**Install Busted:**
|
||
```bash
|
||
# Using LuaRocks
|
||
luarocks install busted
|
||
|
||
# Or download manually
|
||
wget https://github.com/Olivine-Labs/busted/archive/master.zip
|
||
unzip master.zip
|
||
cd busted-master
|
||
sudo make install
|
||
```
|
||
|
||
**Verify installation:**
|
||
```bash
|
||
busted --version
|
||
```
|
||
|
||
#### Environment Variables
|
||
|
||
No environment variables required, but you can set:
|
||
|
||
```bash
|
||
# For debugging
|
||
export LOVR_DEBUG=1
|
||
|
||
# For specific graphics backend
|
||
export LOVR_BACKEND=vulkan
|
||
```
|
||
|
||
### Common Build Issues and Solutions
|
||
|
||
#### Issue 1: "module 'lovr' not found"
|
||
|
||
**Error:**
|
||
```
|
||
no field package.preload['lovr']
|
||
no file './lovr.lua'
|
||
no file '/usr/local/share/lua/5.4/lovr.lua'
|
||
```
|
||
|
||
**Solution:**
|
||
- Ensure LOVR is installed and in PATH
|
||
- Run with `lovr .` instead of `lua main.lua`
|
||
- Check LOVR version: `lovr --version`
|
||
|
||
#### Issue 2: "busted: command not found"
|
||
|
||
**Solution:**
|
||
```bash
|
||
# Install LuaRocks first
|
||
sudo apt install luarocks # Ubuntu/Debian
|
||
brew install luarocks # macOS
|
||
|
||
# Then install busted
|
||
luarocks install busted
|
||
|
||
# Or use Lua directly
|
||
lua test/run_tests.lua
|
||
```
|
||
|
||
#### Issue 3: "attempt to call field 'newImage' (a nil value)"
|
||
|
||
**Solution:**
|
||
- This indicates LOVR API mismatch
|
||
- Ensure you're using LOVR 2023 (v1.4)
|
||
- Update LOVR: `lovr update`
|
||
|
||
#### Issue 4: Tests fail with "attempt to index global 'core'"
|
||
|
||
**Solution:**
|
||
- This is expected in some edge case tests
|
||
- Tests should handle nil core gracefully
|
||
- Check test_helper.lua for proper mocking
|
||
|
||
#### Issue 5: Audio not working
|
||
|
||
**Solution:**
|
||
- Check audio device is selected
|
||
- Verify LOVR audio is enabled
|
||
- Test with: `lovr.audio.getSources()`
|
||
|
||
#### Issue 6: Graphics rendering issues
|
||
|
||
**Solution:**
|
||
- Update graphics drivers
|
||
- Try different backend: `lovr --backend opengl`
|
||
- Check GPU supports OpenGL 3.3+
|
||
|
||
---
|
||
|
||
## Testing Instructions
|
||
|
||
### Running All Tests
|
||
|
||
**Basic test run:**
|
||
```bash
|
||
lua test/run_tests.lua
|
||
```
|
||
|
||
**Expected output:**
|
||
```
|
||
========================================
|
||
Test Summary
|
||
========================================
|
||
Suites: 7
|
||
Tests: 150
|
||
Passed: 150
|
||
Failed: 0
|
||
Errors: 0
|
||
Skipped: 0
|
||
|
||
✅ All tests passed
|
||
```
|
||
|
||
**With verbose output:**
|
||
```bash
|
||
lua test/run_tests.lua 2>&1 | grep -A 1 "describe\|it\|passed\|failed"
|
||
```
|
||
|
||
### Running Specific Test Suites
|
||
|
||
**Unit tests only:**
|
||
```bash
|
||
lua test/spec/core_spec.lua
|
||
lua test/spec/cat_spec.lua
|
||
lua test/spec/house_spec.lua
|
||
lua test/spec/ui_spec.lua
|
||
lua test/spec/audio_spec.lua
|
||
lua test/spec/voxel_spec.lua
|
||
lua test/spec/json_spec.lua
|
||
```
|
||
|
||
**Integration tests:**
|
||
```bash
|
||
lua test/integration/game_flow_spec.lua
|
||
```
|
||
|
||
**End-to-end tests:**
|
||
```bash
|
||
lua test/e2e/edge_cases_spec.lua
|
||
```
|
||
|
||
**With Busted filters:**
|
||
```bash
|
||
# Run tests matching pattern
|
||
busted test/spec/ -m "cat.*spawn"
|
||
|
||
# Run specific test file
|
||
busted test/spec/core_spec.lua
|
||
|
||
# Run with coverage
|
||
busted test/ --coverage
|
||
```
|
||
|
||
### Test Coverage Expectations
|
||
|
||
**Coverage Areas:**
|
||
|
||
| Module | Coverage Focus | Expected Coverage |
|
||
|--------|---------------|-------------------|
|
||
| JSON | Encoding/decoding, edge cases, round-trip | 100% |
|
||
| Voxel | Texture generation, mesh creation, raycast | 95% |
|
||
| Cat | Creation, AI, needs, serialization | 95% |
|
||
| House | Grid system, collision, upgrades | 95% |
|
||
| UI | HUD, inventory, interaction | 90% |
|
||
| Audio | Sound generation, playback | 90% |
|
||
| Core | State management, save/load, happiness | 95% |
|
||
|
||
**Coverage Tools:**
|
||
```bash
|
||
# Install luacov for coverage analysis
|
||
luarocks install luacov
|
||
|
||
# Run with coverage
|
||
luacov test/run_tests.lua
|
||
|
||
# Generate report
|
||
lua -luacov
|
||
```
|
||
|
||
### How to Add New Tests
|
||
|
||
**Step 1: Create test file**
|
||
```bash
|
||
# For new module
|
||
touch test/spec/newmodule_spec.lua
|
||
|
||
# For new feature
|
||
touch test/integration/new_feature_spec.lua
|
||
```
|
||
|
||
**Step 2: Write test using Busted syntax**
|
||
```lua
|
||
-- test/spec/newmodule_spec.lua
|
||
local test_helper = require('test.test_helper')
|
||
local mocks = test_helper.mocks
|
||
local utils = test_helper.utils
|
||
|
||
describe('New Module', function()
|
||
local newmodule
|
||
|
||
setup(function()
|
||
mocks.setupFilesystem()
|
||
newmodule = require('src.newmodule')
|
||
end)
|
||
|
||
it('should do something', function()
|
||
local result = newmodule.doSomething()
|
||
assert.equals('expected', result)
|
||
end)
|
||
|
||
it('should handle edge cases', function()
|
||
local result = newmodule.doSomething(nil)
|
||
assert_falsy(result)
|
||
end)
|
||
|
||
it('should integrate with other modules', function()
|
||
local core = require('src.core')
|
||
core.init()
|
||
|
||
local result = newmodule.interactWithCore()
|
||
assert_truthy(result)
|
||
end)
|
||
end)
|
||
```
|
||
|
||
**Step 3: Add to test runner (if needed)**
|
||
```lua
|
||
-- test/run_tests.lua
|
||
local testDirs = {
|
||
'test/spec',
|
||
'test/integration',
|
||
'test/e2e',
|
||
'test/new_tests' -- Add new directory here
|
||
}
|
||
```
|
||
|
||
**Step 4: Run and verify**
|
||
```bash
|
||
lua test/spec/newmodule_spec.lua
|
||
```
|
||
|
||
### Test Reporting and Interpretation
|
||
|
||
**Test Output Format:**
|
||
|
||
```
|
||
describe('Core Module')
|
||
it('should initialize game state')............. passed (0.001s)
|
||
it('should spawn cats')........................ passed (0.001s)
|
||
it('should handle nil values')................. passed (0.001s)
|
||
it('should handle edge cases')................. passed (0.001s)
|
||
|
||
Suites: 7
|
||
Tests: 150
|
||
Passed: 150
|
||
Failed: 0
|
||
Errors: 0
|
||
Skipped: 0
|
||
```
|
||
|
||
**XML Report Structure:**
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<testsuites name="Cat Haven Tests">
|
||
<testsuite name="Core Module" tests="16" failures="0" errors="0">
|
||
<testcase name="should initialize game state" classname="core_spec.lua"/>
|
||
<testcase name="should spawn cats" classname="core_spec.lua"/>
|
||
</testsuite>
|
||
</testsuites>
|
||
```
|
||
|
||
**Interpreting Results:**
|
||
|
||
| Status | Meaning | Action |
|
||
|--------|---------|--------|
|
||
| Passed | Test executed successfully | No action needed |
|
||
| Failed | Assertion failed | Fix code or test |
|
||
| Error | Exception thrown | Fix code or test setup |
|
||
| Skipped | Test explicitly skipped | Review if intentional |
|
||
|
||
**CI/CD Integration:**
|
||
|
||
```yaml
|
||
# .github/workflows/test.yml
|
||
name: Run Tests
|
||
on: [push, pull_request]
|
||
jobs:
|
||
test:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
- name: Install Lua
|
||
uses: leafo/gh-actions-lua@v9
|
||
- name: Install Busted
|
||
uses: leafo/gh-actions-luarocks@v4
|
||
- name: Run Tests
|
||
run: lua test/run_tests.lua
|
||
- name: Upload Test Report
|
||
uses: actions/upload-artifact@v3
|
||
with:
|
||
name: test-report
|
||
path: test-report.xml
|
||
```
|
||
|
||
---
|
||
|
||
## Development Workflow
|
||
|
||
### Adding New Features
|
||
|
||
**Step 1: Plan the feature**
|
||
- Determine which module(s) need modification
|
||
- Define the feature's scope and requirements
|
||
- Consider test coverage needs
|
||
|
||
**Step 2: Create test-first**
|
||
```lua
|
||
-- Write tests first
|
||
describe('New Feature', function()
|
||
it('should implement feature X')
|
||
it('should handle edge cases')
|
||
end)
|
||
```
|
||
|
||
**Step 3: Implement in module**
|
||
- Follow existing code patterns
|
||
- Use proper error handling
|
||
- Add comments only if requested
|
||
|
||
**Step 4: Update documentation**
|
||
- Update this INSTRUCTIONS.md if needed
|
||
- Update README.md for user-facing changes
|
||
|
||
**Step 5: Test and iterate**
|
||
```bash
|
||
# Run specific test
|
||
lua test/spec/newfeature_spec.lua
|
||
|
||
# Run all tests
|
||
lua test/run_tests.lua
|
||
```
|
||
|
||
### Modifying Existing Modules
|
||
|
||
**Guidelines:**
|
||
|
||
1. **Read existing code first**
|
||
- Understand the current implementation
|
||
- Identify side effects
|
||
- Check test coverage
|
||
|
||
2. **Update tests before code**
|
||
- Add tests for new behavior
|
||
- Update existing tests if behavior changes
|
||
|
||
3. **Maintain consistency**
|
||
- Follow naming conventions
|
||
- Use existing patterns
|
||
- Preserve error handling
|
||
|
||
4. **Test thoroughly**
|
||
```bash
|
||
# Test the specific module
|
||
lua test/spec/core_spec.lua
|
||
|
||
# Test integration
|
||
lua test/integration/game_flow_spec.lua
|
||
|
||
# Test edge cases
|
||
lua test/e2e/edge_cases_spec.lua
|
||
```
|
||
|
||
### Debugging Tips
|
||
|
||
**1. Use print statements**
|
||
```lua
|
||
-- Debug module loading
|
||
print('Loading core module')
|
||
|
||
-- Debug values
|
||
print('Current state:', json.encode(core.state))
|
||
```
|
||
|
||
**2. LOVR console output**
|
||
```bash
|
||
# Enable verbose output
|
||
lovr . --verbose
|
||
|
||
# Check for errors
|
||
lovr . 2>&1 | grep -i error
|
||
```
|
||
|
||
**3. Test helper utilities**
|
||
```lua
|
||
-- Use test helper for debugging
|
||
local utils = test_helper.utils
|
||
utils.assertDeepEqual(actual, expected, 'Debug message')
|
||
```
|
||
|
||
**4. Check module loading**
|
||
```lua
|
||
-- Verify module is loaded
|
||
assert(core, 'Core module not loaded')
|
||
assert(cat, 'Cat module not loaded')
|
||
```
|
||
|
||
**5. Validate data structures**
|
||
```lua
|
||
-- Check table structure
|
||
for k, v in pairs(core.state) do
|
||
print(string.format('%s: %s', k, type(v)))
|
||
end
|
||
```
|
||
|
||
### Code Style Guidelines
|
||
|
||
**Naming Conventions:**
|
||
- **Modules**: lowercase with underscores (`core.lua`, `json.lua`)
|
||
- **Functions**: camelCase (`updateCats`, `createFurniture`)
|
||
- **Constants**: UPPER_SNAKE_CASE (`MAX_CATS`, `VOXEL_SIZE`)
|
||
- **Variables**: camelCase (`catCount`, `happinessScore`)
|
||
- **Classes**: PascalCase (`HouseFurniture`, `Cat`)
|
||
|
||
**File Structure:**
|
||
```lua
|
||
-- 1. Module declaration
|
||
local moduleName = {}
|
||
|
||
-- 2. Private variables (local)
|
||
local privateVar = nil
|
||
|
||
-- 3. Class definitions
|
||
local ClassName = {}
|
||
ClassName.__index = ClassName
|
||
|
||
-- 4. Public functions
|
||
function moduleName.publicFunction()
|
||
-- implementation
|
||
end
|
||
|
||
-- 5. Return module
|
||
return moduleName
|
||
```
|
||
|
||
**Error Handling:**
|
||
```lua
|
||
-- Check for nil
|
||
if not core then return end
|
||
|
||
-- Check parameters
|
||
if not position then return nil end
|
||
|
||
-- Return error info
|
||
return false, 'error message'
|
||
|
||
-- Handle gracefully
|
||
local result, err = someOperation()
|
||
if not result then
|
||
print('Operation failed:', err)
|
||
return nil
|
||
end
|
||
```
|
||
|
||
**Comments:**
|
||
- Use comments only when requested
|
||
- Explain why, not what
|
||
- Keep comments up to date
|
||
|
||
**Line Length:**
|
||
- Maximum 100 characters
|
||
- Break long lines logically
|
||
|
||
### Commit Conventions
|
||
|
||
**Format:**
|
||
```
|
||
<type>: <description>
|
||
|
||
[optional body]
|
||
[optional footer]
|
||
```
|
||
|
||
**Types:**
|
||
- `feat`: New feature
|
||
- `fix`: Bug fix
|
||
- `docs`: Documentation changes
|
||
- `test`: Test additions/changes
|
||
- `refactor`: Code refactoring
|
||
- `perf`: Performance improvements
|
||
- `chore`: Maintenance tasks
|
||
|
||
**Examples:**
|
||
```
|
||
feat: add new furniture type
|
||
fix: handle nil cat in happiness calculation
|
||
test: add edge case tests for audio
|
||
docs: update INSTRUCTIONS.md with testing info
|
||
refactor: simplify JSON encoding logic
|
||
```
|
||
|
||
---
|
||
|
||
## Architecture Documentation
|
||
|
||
### Module Dependency Diagram
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ LOVR Runtime │
|
||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||
│ │ lovr.load() │ │lovr.update()│ │ lovr.draw() │ │lovr.quit() │ │
|
||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||
│ │ │ │ │ │
|
||
│ └─────────────────┴─────────────────┴─────────────────┘ │
|
||
│ │ │
|
||
│ ┌──────▼───────┐ │
|
||
│ │ main.lua │ │
|
||
│ └──────┬───────┘ │
|
||
└───────────────────────────┼───────────────────────────────────────────┘
|
||
│
|
||
┌───────────────────┼───────────────────┐
|
||
│ │ │
|
||
┌────▼────┐ ┌─────▼─────┐ ┌────▼────┐
|
||
│ core │◄───────┤ house │◄───────┤ cat │
|
||
└────┬────┘ └─────┬─────┘ └────┬────┘
|
||
│ │ │
|
||
┌────▼────┐ ┌─────▼─────┐ ┌────▼────┐
|
||
│ audio │ │ voxel │ │ ui │
|
||
└─────────┘ └─────┬─────┘ └────┬────┘
|
||
│ │
|
||
┌───────▼───────┐ │
|
||
│ json.lua │◄──────────┘
|
||
└───────────────┘
|
||
```
|
||
|
||
**Dependency Directions:**
|
||
- **core** → house, cat, ui, voxel, audio (loads them)
|
||
- **house** → core (reads furniture types, state)
|
||
- **cat** → core (reads personalities, config), house (placement)
|
||
- **ui** → core (reads state, config), voxel (raycast), house (interaction)
|
||
- **voxel** → core (reads furniture, cats, config)
|
||
- **audio** → core (reads position, config)
|
||
- **json** → standalone (no dependencies)
|
||
|
||
### State Management Patterns
|
||
|
||
**Global State (`core.state`):**
|
||
- Single source of truth for game data
|
||
- Mutable but controlled via functions
|
||
- Saved to disk using JSON
|
||
|
||
**State Access Pattern:**
|
||
```lua
|
||
-- Read state
|
||
local catCount = #core.state.cats
|
||
|
||
-- Modify state via functions
|
||
core.spawnCat()
|
||
core.addFurniture('bed', position)
|
||
|
||
-- Access nested state
|
||
local happiness = core.state.house.totalHappiness
|
||
```
|
||
|
||
**State Isolation:**
|
||
- Each module has its own state table
|
||
- Modules don't share state directly
|
||
- Communication via core module
|
||
|
||
**State Persistence:**
|
||
```lua
|
||
-- Save
|
||
core.saveGame() → JSON.encode() → file write
|
||
|
||
-- Load
|
||
file read → JSON.decode() → core.restoreState()
|
||
```
|
||
|
||
### Rendering Pipeline
|
||
|
||
**LOVR Rendering Flow:**
|
||
```
|
||
1. lovr.load()
|
||
└─> core.init()
|
||
└─> voxel.init() -- Create textures
|
||
└─> audio.init() -- Create sounds
|
||
|
||
2. lovr.update(dt)
|
||
└─> core.update(dt)
|
||
└─> cat.updateCats() -- Update cat positions
|
||
└─> house.update() -- Update house state
|
||
|
||
3. lovr.draw(pass)
|
||
└─> voxel.renderWorld(pass) -- Floor, walls
|
||
└─> house.renderFurniture(pass) -- Furniture
|
||
└─> cat.renderCats(pass) -- Cats
|
||
└─> ui.renderHUD(pass) -- HUD overlay
|
||
|
||
4. lovr.quit()
|
||
└─> core.saveGame() -- Autosave
|
||
```
|
||
|
||
**Voxel Rendering Details:**
|
||
```lua
|
||
-- Texture generation
|
||
createVoxelTexture(color) → 64×64 RGBA image → LOVR texture
|
||
|
||
-- Mesh generation
|
||
createCubeMesh() → 24 vertices, 36 indices → LOVR mesh
|
||
|
||
-- Rendering
|
||
pass:box(mode, x, y, z, w, h, d, color, thickness)
|
||
```
|
||
|
||
### AI Behavior Flow
|
||
|
||
**Cat AI State Machine:**
|
||
```
|
||
┌─────────────┐
|
||
│ Sleeping │
|
||
│ (comfort │
|
||
│ < 0.3) │
|
||
└──────┬──────┘
|
||
│
|
||
│ Timer expires
|
||
▼
|
||
┌─────────────┐
|
||
│ Idle │
|
||
│ (waking │
|
||
│ up) │
|
||
└──────┬──────┘
|
||
│
|
||
│ Need unmet
|
||
▼
|
||
┌─────────────┐
|
||
│ Moving │
|
||
│ (seeking │
|
||
│ furniture) │
|
||
└──────┬──────┘
|
||
│
|
||
│ Reached target
|
||
▼
|
||
┌─────────────┐
|
||
│ Active │
|
||
│ (interacting│
|
||
│ with │
|
||
│ furniture) │
|
||
└─────────────┘
|
||
```
|
||
|
||
**Need-Based Behavior:**
|
||
```lua
|
||
-- Update needs over time
|
||
comfort = comfort - dt * 0.02
|
||
social = social - dt * 0.015
|
||
hunger = hunger - dt * 0.025
|
||
fun = fun - dt * 0.02
|
||
|
||
-- AI seeks furniture that satisfies lowest need
|
||
if comfort < 0.5 then
|
||
seek comfort furniture
|
||
elseif fun < 0.5 then
|
||
seek fun furniture
|
||
elseif social < 0.5 then
|
||
seek social furniture
|
||
end
|
||
```
|
||
|
||
**Personality Compatibility:**
|
||
```lua
|
||
-- Check nearby cats
|
||
for each nearby cat:
|
||
compatibility = personality.compatibility[other.personality]
|
||
|
||
if compatibility < -0.3:
|
||
reduce social need (stress)
|
||
elseif compatibility > 0.3:
|
||
increase social need (pleasure)
|
||
```
|
||
|
||
### Save/Load Mechanism
|
||
|
||
**Save Process:**
|
||
```lua
|
||
1. core.saveGame()
|
||
├─> Serialize cats (cat:serialize())
|
||
├─> Serialize furniture (furn:serialize())
|
||
├─> Encode to JSON (json.encode())
|
||
└─> Write to file (lovr.filesystem)
|
||
|
||
2. JSON structure:
|
||
{
|
||
"version": "1.0.0",
|
||
"gameTime": 1234.56,
|
||
"cats": [...],
|
||
"furniture": [...],
|
||
"house": {...},
|
||
"catLog": [...]
|
||
}
|
||
```
|
||
|
||
**Load Process:**
|
||
```lua
|
||
1. core.loadGame()
|
||
├─> Read file (lovr.filesystem)
|
||
├─> Decode JSON (json.decode())
|
||
├─> Deserialize cats (cat.deserialize())
|
||
├─> Deserialize furniture (house.deserializeFurniture())
|
||
└─> Restore state
|
||
|
||
2. Validation:
|
||
- Check version compatibility
|
||
- Handle missing fields
|
||
- Default values for old saves
|
||
```
|
||
|
||
**Data Serialization:**
|
||
```lua
|
||
-- Cat serialization
|
||
function Cat:serialize()
|
||
return {
|
||
id = self.id,
|
||
name = self.name,
|
||
personalityId = self.personality.id,
|
||
position = self.position,
|
||
state = self.state,
|
||
mood = self.mood,
|
||
needs = self.needs,
|
||
-- ... other fields
|
||
}
|
||
end
|
||
|
||
-- Furniture serialization
|
||
function HouseFurniture:serialize()
|
||
return {
|
||
typeId = self.type.id,
|
||
position = self.position,
|
||
upgradeLevel = self.upgradeLevel,
|
||
-- ... other fields
|
||
}
|
||
end
|
||
```
|
||
|
||
---
|
||
|
||
## API Reference
|
||
|
||
### Core Module Functions
|
||
|
||
#### `core.init()`
|
||
Initializes the game state and all modules.
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Side Effects:**
|
||
- Initializes house grid
|
||
- Spawns initial cats
|
||
- Creates voxel textures
|
||
- Loads audio
|
||
|
||
**Example:**
|
||
```lua
|
||
core.init()
|
||
-- Game is now ready to play
|
||
```
|
||
|
||
#### `core.update(dt)`
|
||
Updates the game state each frame.
|
||
|
||
**Parameters:**
|
||
- `dt` (number): Delta time in seconds
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
core.update(0.016) -- ~60 FPS
|
||
```
|
||
|
||
#### `core.spawnCat()`
|
||
Spawns a new cat with random personality.
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
core.spawnCat()
|
||
-- New cat added to core.state.cats
|
||
```
|
||
|
||
#### `core.removeCat(catId)`
|
||
Removes a cat by ID.
|
||
|
||
**Parameters:**
|
||
- `catId` (number): Cat ID to remove
|
||
|
||
**Returns:** `boolean` - true if cat was removed
|
||
|
||
**Example:**
|
||
```lua
|
||
local success = core.removeCat(1)
|
||
if success then
|
||
print('Cat removed')
|
||
end
|
||
```
|
||
|
||
#### `core.addFurniture(typeId, position)`
|
||
Adds furniture to the game.
|
||
|
||
**Parameters:**
|
||
- `typeId` (string): Furniture type ID
|
||
- `position` (table): {x, y, z} position
|
||
|
||
**Returns:** `table|false` - Furniture object or false
|
||
|
||
**Example:**
|
||
```lua
|
||
local furn = core.addFurniture('bed', {x=0, y=0, z=0})
|
||
if furn then
|
||
print('Furniture added')
|
||
end
|
||
```
|
||
|
||
#### `core.saveGame()`
|
||
Saves the current game state.
|
||
|
||
**Returns:** `boolean` - true if save successful
|
||
|
||
**Example:**
|
||
```lua
|
||
local success = core.saveGame()
|
||
```
|
||
|
||
#### `core.loadGame()`
|
||
Loads a saved game state.
|
||
|
||
**Returns:** `boolean` - true if load successful
|
||
|
||
**Example:**
|
||
```lua
|
||
local success = core.loadGame()
|
||
```
|
||
|
||
#### `core.togglePause()`
|
||
Toggles pause state.
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
core.togglePause()
|
||
```
|
||
|
||
#### `core.getHappinessPercentage()`
|
||
Gets current house happiness as percentage.
|
||
|
||
**Returns:** `number` - 0-100
|
||
|
||
**Example:**
|
||
```lua
|
||
local happiness = core.getHappinessPercentage()
|
||
print(string.format('Happiness: %d%%', happiness))
|
||
```
|
||
|
||
#### `core.calculateHouseHappiness()`
|
||
Calculates overall house happiness.
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
core.calculateHouseHappiness()
|
||
print(core.state.house.totalHappiness)
|
||
```
|
||
|
||
#### `core.checkCatDepartures()`
|
||
Checks for cats that should leave (low happiness).
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
core.checkCatDepartures()
|
||
```
|
||
|
||
### Cat Module Functions
|
||
|
||
#### `cat.createCat(id, name, personality)`
|
||
Creates a new cat object.
|
||
|
||
**Parameters:**
|
||
- `id` (number): Unique cat ID
|
||
- `name` (string): Cat name
|
||
- `personality` (table): Personality definition
|
||
|
||
**Returns:** `table` - Cat object
|
||
|
||
**Example:**
|
||
```lua
|
||
local personality = core.personalities[1]
|
||
local cat = cat.createCat(1, 'Mittens', personality)
|
||
```
|
||
|
||
#### `cat.updateCats(dt)`
|
||
Updates all cats.
|
||
|
||
**Parameters:**
|
||
- `dt` (number): Delta time
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
cat.updateCats(0.016)
|
||
```
|
||
|
||
#### `cat.deserialize(data)`
|
||
Deserializes a cat from save data.
|
||
|
||
**Parameters:**
|
||
- `data` (table): Serialized cat data
|
||
|
||
**Returns:** `table|nil` - Cat object or nil
|
||
|
||
**Example:**
|
||
```lua
|
||
local cat = cat.deserialize(savedData)
|
||
```
|
||
|
||
#### `cat.getCatAnimation(catObj, time)`
|
||
Gets animation values for a cat.
|
||
|
||
**Parameters:**
|
||
- `catObj` (table): Cat object
|
||
- `time` (number): Animation time
|
||
|
||
**Returns:** `table` - {bounce, rotate}
|
||
|
||
**Example:**
|
||
```lua
|
||
local anim = cat.getCatAnimation(cat, lovr.timer.getTime())
|
||
cat.position.y = baseY + anim.bounce
|
||
```
|
||
|
||
### House Module Functions
|
||
|
||
#### `house.createFurniture(type, position)`
|
||
Creates a furniture object.
|
||
|
||
**Parameters:**
|
||
- `type` (table): Furniture type definition
|
||
- `position` (table): {x, y, z} position
|
||
|
||
**Returns:** `table|nil` - Furniture object or nil
|
||
|
||
**Example:**
|
||
```lua
|
||
local type = core.furnitureTypes[1]
|
||
local furn = house.createFurniture(type, {x=0, y=0, z=0})
|
||
```
|
||
|
||
#### `house.canPlaceFurniture(position, size)`
|
||
Checks if furniture can be placed at position.
|
||
|
||
**Parameters:**
|
||
- `position` (table): {x, y, z} position
|
||
- `size` (table): {w, h, d} size
|
||
|
||
**Returns:** `boolean, string` - Can place, reason
|
||
|
||
**Example:**
|
||
```lua
|
||
local canPlace, reason = house.canPlaceFurniture(pos, size)
|
||
if not canPlace then
|
||
print('Cannot place:', reason)
|
||
end
|
||
```
|
||
|
||
#### `house.placeFurniture(typeId, position)`
|
||
Places furniture at position.
|
||
|
||
**Parameters:**
|
||
- `typeId` (string): Furniture type ID
|
||
- `position` (table): {x, y, z} position
|
||
|
||
**Returns:** `table|nil` - Furniture object or nil
|
||
|
||
**Example:**
|
||
```lua
|
||
local furn = house.placeFurniture('bed', {x=0, y=0, z=0})
|
||
```
|
||
|
||
#### `house.checkCollision(position, size)`
|
||
Checks for collision with existing furniture.
|
||
|
||
**Parameters:**
|
||
- `position` (table): {x, y, z} position
|
||
- `size` (table): {w, h, d} size
|
||
|
||
**Returns:** `boolean` - true if collision
|
||
|
||
**Example:**
|
||
```lua
|
||
local collision = house.checkCollision(pos, size)
|
||
```
|
||
|
||
#### `house.interactWithFurniture(position)`
|
||
Gets furniture at position.
|
||
|
||
**Parameters:**
|
||
- `position` (table): {x, y, z} position
|
||
|
||
**Returns:** `table|nil` - Furniture object or nil
|
||
|
||
**Example:**
|
||
```lua
|
||
local furn = house.interactWithFurniture(pos)
|
||
if furn then
|
||
print('Clicked:', furn.type.name)
|
||
end
|
||
```
|
||
|
||
#### `house.deserializeFurniture(data)`
|
||
Deserializes furniture from save data.
|
||
|
||
**Parameters:**
|
||
- `data` (table): Serialized furniture data
|
||
|
||
**Returns:** `table|nil` - Furniture object or nil
|
||
|
||
**Example:**
|
||
```lua
|
||
local furn = house.deserializeFurniture(savedData)
|
||
```
|
||
|
||
### Voxel Module Functions
|
||
|
||
#### `voxel.init()`
|
||
Initializes voxel textures.
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
voxel.init()
|
||
```
|
||
|
||
#### `voxel.renderWorld(pass)`
|
||
Renders the voxel world.
|
||
|
||
**Parameters:**
|
||
- `pass` (table): LOVR render pass
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
function lovr.draw(pass)
|
||
voxel.renderWorld(pass)
|
||
end
|
||
```
|
||
|
||
#### `voxel.renderFurniture(pass)`
|
||
Renders all furniture.
|
||
|
||
**Parameters:**
|
||
- `pass` (table): LOVR render pass
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
function lovr.draw(pass)
|
||
voxel.renderFurniture(pass)
|
||
end
|
||
```
|
||
|
||
#### `voxel.renderCats(pass)`
|
||
Renders all cats.
|
||
|
||
**Parameters:**
|
||
- `pass` (table): LOVR render pass
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
function lovr.draw(pass)
|
||
voxel.renderCats(pass)
|
||
end
|
||
```
|
||
|
||
#### `voxel.raycast(start, direction, maxDistance)`
|
||
Performs raycast through world.
|
||
|
||
**Parameters:**
|
||
- `start` (table): {x, y, z} ray start
|
||
- `direction` (table): {x, y, z} ray direction
|
||
- `maxDistance` (number): Maximum distance
|
||
|
||
**Returns:** `table` - Array of hit results
|
||
|
||
**Example:**
|
||
```lua
|
||
local results = voxel.raycast(start, direction, 10)
|
||
if #results > 0 then
|
||
local hit = results[1]
|
||
print('Hit:', hit.type)
|
||
end
|
||
```
|
||
|
||
### UI Module Functions
|
||
|
||
#### `ui.renderHUD(pass)`
|
||
Renders the HUD.
|
||
|
||
**Parameters:**
|
||
- `pass` (table): LOVR render pass
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
function lovr.draw(pass)
|
||
ui.renderHUD(pass)
|
||
end
|
||
```
|
||
|
||
#### `ui.handleMousePress(x, y, button)`
|
||
Handles mouse click.
|
||
|
||
**Parameters:**
|
||
- `x` (number): Mouse X position
|
||
- `y` (number): Mouse Y position
|
||
- `button` (number): Button index
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
function lovr.mousepressed(x, y, button)
|
||
ui.handleMousePress(x, y, button)
|
||
end
|
||
```
|
||
|
||
#### `ui.updateHUD()`
|
||
Updates dynamic HUD elements.
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
function core.update(dt)
|
||
ui.updateHUD()
|
||
end
|
||
```
|
||
|
||
#### `ui.toggleInventory()`
|
||
Toggles inventory visibility.
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
ui.toggleInventory()
|
||
```
|
||
|
||
#### `ui.toggleCatLog()`
|
||
Toggles cat log visibility.
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
ui.toggleCatLog()
|
||
```
|
||
|
||
### Audio Module Functions
|
||
|
||
#### `audio.init()`
|
||
Initializes audio system.
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
audio.init()
|
||
```
|
||
|
||
#### `audio.createPurrSound()`
|
||
Creates purr sound source.
|
||
|
||
**Returns:** `table|nil` - Sound source or nil
|
||
|
||
**Example:**
|
||
```lua
|
||
local purr = audio.createPurrSound()
|
||
```
|
||
|
||
#### `audio.createMeowSound()`
|
||
Creates meow sound source.
|
||
|
||
**Returns:** `table|nil` - Sound source or nil
|
||
|
||
**Example:**
|
||
```lua
|
||
local meow = audio.createMeowSound()
|
||
```
|
||
|
||
#### `audio.createClickSound()`
|
||
Creates click sound source.
|
||
|
||
**Returns:** `table|nil` - Sound source or nil
|
||
|
||
**Example:**
|
||
```lua
|
||
local click = audio.createClickSound()
|
||
```
|
||
|
||
#### `audio.createAmbientSound()`
|
||
Creates ambient room sound.
|
||
|
||
**Returns:** `table|nil` - Sound source or nil
|
||
|
||
**Example:**
|
||
```lua
|
||
local ambient = audio.createAmbientSound()
|
||
```
|
||
|
||
#### `audio.playPurr(position)`
|
||
Plays purr at position.
|
||
|
||
**Parameters:**
|
||
- `position` (table): {x, y, z} position
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
audio.playPurr(cat.position)
|
||
```
|
||
|
||
#### `audio.playMeow(position)`
|
||
Plays meow at position.
|
||
|
||
**Parameters:**
|
||
- `position` (table): {x, y, z} position
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
audio.playMeow(cat.position)
|
||
```
|
||
|
||
#### `audio.playClick()`
|
||
Plays click sound.
|
||
|
||
**Returns:** `nil`
|
||
|
||
**Example:**
|
||
```lua
|
||
audio.playClick()
|
||
```
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
### Common Errors and Solutions
|
||
|
||
#### Error: "attempt to index global 'core' (a nil value)"
|
||
|
||
**Cause:** Module not loaded or initialization order issue
|
||
|
||
**Solution:**
|
||
```lua
|
||
-- Check module is loaded
|
||
if not core then
|
||
core = require('src.core')
|
||
core.init()
|
||
end
|
||
```
|
||
|
||
#### Error: "invalid key to 'next'"
|
||
|
||
**Cause:** Modifying table while iterating
|
||
|
||
**Solution:**
|
||
```lua
|
||
-- Iterate backwards for removal
|
||
for i = #array, 1, -1 do
|
||
if shouldRemove(array[i]) then
|
||
table.remove(array, i)
|
||
end
|
||
end
|
||
```
|
||
|
||
#### Error: "out of memory"
|
||
|
||
**Cause:** Texture or mesh allocation failure
|
||
|
||
**Solution:**
|
||
```lua
|
||
-- Check texture limits
|
||
local maxTextures = lovr.graphics.getStats().maxTextures
|
||
print('Max textures:', maxTextures)
|
||
|
||
-- Free unused textures
|
||
voxel.textures = nil
|
||
```
|
||
|
||
#### Error: "mesh has too many vertices"
|
||
|
||
**Cause:** Mesh exceeds GPU limits
|
||
|
||
**Solution:**
|
||
```lua
|
||
-- Use smaller meshes
|
||
local maxVertices = lovr.graphics.getStats().maxVertices
|
||
print('Max vertices:', maxVertices)
|
||
|
||
-- Split large meshes
|
||
```
|
||
|
||
#### Error: "file not found: savegame.json"
|
||
|
||
**Cause:** No save file exists
|
||
|
||
**Solution:**
|
||
```lua
|
||
-- Check before loading
|
||
local file = lovr.filesystem.newFile('savegame.json', 'r')
|
||
if file then
|
||
file:close()
|
||
core.loadGame()
|
||
else
|
||
print('No save file found')
|
||
end
|
||
```
|
||
|
||
### Debugging Tips
|
||
|
||
**1. Enable LOVR Debug Mode**
|
||
```bash
|
||
lovr . --verbose
|
||
```
|
||
|
||
**2. Add Debug Logging**
|
||
```lua
|
||
-- Debug module loading
|
||
print('Loading:', debug.info(2, 's'))
|
||
|
||
-- Debug function calls
|
||
print('Function:', debug.info(2, 'n'))
|
||
|
||
-- Debug variable values
|
||
print('Value:', type(value), json.encode(value))
|
||
```
|
||
|
||
**3. Use Test Helper**
|
||
```lua
|
||
local utils = test_helper.utils
|
||
|
||
-- Deep equality check
|
||
utils.assertDeepEqual(actual, expected, 'Debug message')
|
||
|
||
-- Mock pass for rendering tests
|
||
local pass = utils.createMockPass()
|
||
```
|
||
|
||
**4. Check LOVR Stats**
|
||
```lua
|
||
local stats = lovr.graphics.getStats()
|
||
print('Draw calls:', stats.drawCalls)
|
||
print('Textures:', stats.textures)
|
||
print('Meshes:', stats.meshes)
|
||
```
|
||
|
||
### Performance Optimization
|
||
|
||
**1. Reduce Draw Calls**
|
||
```lua
|
||
-- Batch similar objects
|
||
-- Use instanced rendering if available
|
||
-- Combine meshes when possible
|
||
```
|
||
|
||
**2. Optimize Texture Usage**
|
||
```lua
|
||
-- Use smaller textures
|
||
-- Reuse textures between objects
|
||
-- Compress textures if supported
|
||
```
|
||
|
||
**3. Limit Object Count**
|
||
```lua
|
||
-- Cap furniture count
|
||
-- Use object pooling for cats
|
||
-- Remove off-screen objects
|
||
```
|
||
|
||
**4. Profile Code**
|
||
```lua
|
||
-- Simple timing
|
||
local start = lovr.timer.getTime()
|
||
-- ... code ...
|
||
local elapsed = lovr.timer.getTime() - start
|
||
print(string.format('Took %.3fms', elapsed * 1000))
|
||
```
|
||
|
||
### Memory Management
|
||
|
||
**1. Monitor Memory Usage**
|
||
```lua
|
||
-- Check memory
|
||
local mem = collectgarbage('count')
|
||
print('Memory:', mem, 'KB')
|
||
```
|
||
|
||
**2. Clean Up Unused Data**
|
||
```lua
|
||
-- Clear texture cache
|
||
voxel.textures = {}
|
||
|
||
-- Reset state
|
||
core.state = {
|
||
cats = {},
|
||
furniture = {},
|
||
-- ... reset other fields
|
||
}
|
||
```
|
||
|
||
**3. Use Weak References**
|
||
```lua
|
||
-- For caches
|
||
local cache = setmetatable({}, {__mode = 'v'})
|
||
```
|
||
|
||
**4. Avoid Memory Leaks**
|
||
```lua
|
||
-- Always remove event listeners
|
||
-- Clear timers
|
||
-- Close file handles
|
||
```
|
||
|
||
---
|
||
|
||
## Additional Resources
|
||
|
||
### LOVR Documentation
|
||
- https://lovr.org/docs
|
||
- https://lovr.org/guide
|
||
|
||
### Lua Programming
|
||
- https://www.lua.org/manual/5.4/
|
||
- http://lua-users.org/wiki/
|
||
|
||
### Busted Testing
|
||
- https://lunarmodules.github.io/busted/
|
||
- https://github.com/Olivine-Labs/busted
|
||
|
||
### JSON Specification
|
||
- https://www.json.org/
|
||
- https://datatracker.ietf.org/doc/html/rfc8259
|
||
|
||
---
|
||
|
||
**Last Updated:** July 2026
|
||
**Version:** 1.0.0
|
||
**Maintainer:** Cat Haven Team
|