14 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Build Commands
Build variants
make- Make and install debug version. Usually all that's needed.make fast- Build optimized versionmake release- Build release version with LTO and optimizationsmake small- Build minimal size versionmake web- Build for web/emscripten platformmake crosswin- Cross-compile for Windows using mingw32
Testing
After install with 'make', just run 'cell' and point it at the actor you want to launch. "cell tests/toml" runs the actor "tests/toml.js"
Scripting language
This is called "cell", but it is is a variant of javascript and extremely similar.
Common development commands
meson setup build_<variant>- Configure build directorymeson compile -C build_<variant>- Compile in build directory./build_dbg/prosperon examples/<example>- Run example from build directory- Copy prosperon to game directory and run:
cp build_dbg/prosperon <game-dir>/ && cd <game-dir> && ./prosperon
Architecture Overview
Prosperon is an actor-based game engine inspired by Douglas Crockford's Misty system. Key architectural principles:
Actor Model
- Each actor runs on its own thread
- Communication only through message passing (no shared JavaScript objects)
- Hierarchical actor system with spawning/killing
- Actor lifecycle: awake, update, draw, garbage collection
JavaScript Style Guide
- Use
use()function for imports (Misty-style, not ES6 import/export) - Prefer closures and javascript objects and prototypes over ES6 style classes
- Follow existing JavaScript patterns in the codebase
- Functions as first-class citizens
- Do not use const or let; only var
Core Systems
-
Actor System (scripts/core/engine.js)
- Message passing via
send(),$_.receive() - Actor spawning/management
- Register-based component system (update, draw, gui, etc.)
- Message passing via
-
Module System
use()function for loading modules- Module paths:
scripts/modules/,scripts/modules/ext/ - Custom QuickJS build with embedded C modules
-
Build System
- Meson build configuration (Makefile is convenience wrapper)
- Multiple platform targets (Windows, macOS, Linux, Web)
- Custom QuickJS build in
subprojects/ - Uses SDL3 for cross-platform support
Engine Entry Points
source/prosperon.c- Main C entry pointscripts/core/engine.js- JavaScript engine initialization for systemscripts/core/base.jshas modifications to this Javascript runtime (for example, additions to the base Array, String, etc)
Subprojects
- C code has many subprojects, who's source and sometimes documentation can be found in subprojects. subprojects/quickjs/doc has documentation for quickjs
Resource System
- Scripts are bundled into
core.zipduring build - Runtime module loading via PhysFS
- Resource paths checked in order:
/,scripts/modules/,scripts/modules/ext/
Notable Dependencies
- QuickJS (custom build) - JavaScript runtime
- SDL3 - Platform abstraction
- Chipmunk2D - Physics
- ENet - Networking
- Soloud - Audio
- Tracy - Profiling (when enabled)
Development Tips
Running Games
# Build first
make debug
# Run example from build directory
./build_dbg/prosperon examples/chess
# Or copy to game directory
cp build_dbg/prosperon examples/chess/
cd examples/chess
./prosperon
Documentation
- Documentation is found in docs
- Documentation for the JS modules loaded with 'use' is docs/api/modules
- .md files directly in docs gives a high level overview
- docs/dull is what this specific Javascript system is (including alterations from quickjs/es6)
Shader Development
- Shaders are in
shaders/directory as HLSL - Compile script:
shaders/compile.sh - Outputs to platform-specific formats:
dxil/,msl/,spv/
Example Games
Located in examples/ directory:
chess- Chess implementation (has its own Makefile)pong- Classic pong gamesnake- Snake gametetris- Tetris clonebunnymark- Performance test
Testing
# Run all tests
meson test -C build_dbg
# Run specific test
./build_dbg/prosperon tests/spawn_actor.js
Debugging
- Use debug build:
make debug - Tracy profiler support when enabled
- Console logging available via
log.console(),log.error(), etc. - Log files written to
.prosperon/log.txt
Project Structure Notes
Core JavaScript Modules
- JavaScript modules are defined using the MISTUSE macro in jsffi.c
- The
js_os_funcs,js_io_funcs, etc. arrays define the available functions for each module - New functions are added with MIST_FUNC_DEF(module, function, args_count)
File I/O
io.slurp(path)- Reads a file as textio.slurpbytes(path)- Reads a file as an ArrayBufferio.slurpwrite(path, data)- Writes data (string or ArrayBuffer) to a fileio.exists(path)- Checks if a file exists
Script Loading
- The
use(path)function in engine.js loads JavaScript modules - Script loading happens in prosperon.c and the engine.js script
- jsffi.c contains the C hooks for the QuickJS JavaScript engine
- Added functionality for bytecode compilation and loading:
os.compile_bytecode(source, filename)- Compiles JS to bytecode, returns ArrayBufferos.eval_bytecode(bytecode)- Evaluates bytecode from an ArrayBuffercompile(scriptPath)- Compiles a JS file to a .jso bytecode file- Modified
use()to check for .jso files before loading .js files
QuickJS Bytecode API
JS_Evalwith JS_EVAL_FLAG_COMPILE_ONLY - Compiles without executingJS_WriteObjectwith JS_WRITE_OBJ_BYTECODE - Serializes to bytecodeJS_ReadObjectwith JS_READ_OBJ_BYTECODE - Deserializes and loads bytecode- Bytecode files use .jso extension alongside .js files
Available JavaScript APIs
Core APIs
actor- Base prototype for all actor objects$_- Special global for actor messagingprosperon- Global engine interfaceconsole- Logging and debugging interface
Framework APIs
moth- Higher-level game framework that simplifies Prosperon usage- Handles window creation, game loop, and event dispatching
- Provides simple configuration via config.js
- Auto-initializes systems like rendering and input
- Manages camera, resolution, and FPS automatically
Rendering
draw2d- 2D drawing primitivesrender- Low-level rendering operationsgraphics- Higher-level graphics utilitiescamera- Camera controls and transformationssprite- Sprite rendering and management
Physics and Math
math- Mathematical utilitiesgeometry- Geometric calculations and shapestransform- Object transformations
Input and Events
input- Mouse, keyboard, and touch handlingevent- Event management system
Networking
enet- Networking through ENet libraryhttp- HTTP client capabilities
Audio
sound- Audio playback using SoLoud
Utility Modules
time- Time management and delays- Must be imported with
use('time') - No
time.now()function - use:time.number()- Number representation of current timetime.record()- Struct representation of current timetime.text()- Text representation of current time
- Must be imported with
io- File I/O operationsjson- JSON parsing and serializationutil- General utilitiescolor- Color manipulationminiz- Compression utilitiesnota- Structured data formatwota- Serialization formatqr- QR code generation/readingtween- Animation tweeningspline- Spline calculationsimgui- Immediate mode GUI
Game Development Patterns
Project Structure
- Game config is typically in
config.js - Main entry point is
main.js - Resource loading through
resources.js
Actor Pattern Usage
- Create actors with
actor.spawn(script, config) - Start actors with
$_.start(callback, script)- the system automatically sends a greeting, callback receives {type: 'greet', actor: actor_ref}- No need to manually send greetings -
$_.starthandles this automatically
- No need to manually send greetings -
- Manage actor hierarchy with overlings and underlings
- Schedule actor tasks with
$_.delay()method - Clean up with
kill()andgarbage()
Actor Messaging with Callbacks
When sending a message with a callback, respond by sending to the message itself:
// Sender side:
send(actor, {type: 'status'}, response => {
log.console(response); // Handle the response
});
// Receiver side:
$_.receiver(msg => {
if (msg.type === 'status') {
send(msg, {status: 'ok'}); // Send response to the message itself
}
});
Critical Rules for Message Callbacks:
- A message can only be used ONCE as a send target - after sending a response to a message, it cannot be used again
- If you need to send multiple updates (like progress), only the download request message should be used for the final response
- Status requests should each get their own individual response
- Actor objects and message headers are completely opaque - never try to access internal properties
- Never access
msg.__HEADER__or similar - the actor system handles routing internally - Use
$_.delay()to schedule work and avoid blocking the message receiver
Game Loop Registration
- Register functions like
update,draw,gui, etc. - Set function.layer property to control execution order
- Use
Registersystem to manage callbacks
Program vs Module Pattern
- Programs are actor scripts that don't return values, they execute top-to-bottom
- Modules are files that return single values (usually objects) that get frozen
- Programs can spawn other programs as underlings
- Programs have lifecycle hooks: awake, update, draw, garbage, etc.
Technical Capabilities
Graphics Pipeline
- Supports multiple render backends (Direct3D, Metal, Vulkan via SDL3)
- Custom shader system with cross-platform compilation
- Sprite batching for efficient 2D rendering
- Camera systems for both 2D and 3D
Asset Support
- Images: PNG, JPG, QOI, etc.
- Audio: Various formats through SoLoud
- Models: Basic 3D model support
- Custom formats: Aseprite animations, etc.
Developer Tools
- Built-in documentation system with
cell.DOC - Tracy profiler integration for performance monitoring
- Imgui debugging tools
- Console logging with various severity levels
Misty Networking Patterns
Prosperon implements the Misty actor networking model. Understanding these patterns is critical for building distributed applications.
Portal Reply Pattern
Portals must reply with an actor object, not application data:
// CORRECT: Portal replies with actor
$_.portal(e => {
send(e, $_); // Reply with server actor
}, 5678);
// WRONG: Portal sends application data
$_.portal(e => {
send(e, {type: 'game_start'}); // This breaks the pattern
}, 5678);
Two-Phase Connection Protocol
Proper Misty networking follows a two-phase pattern:
Phase 1: Actor Connection
- Client contacts portal using
$_.contact() - Portal replies with an actor object
- This establishes the communication channel
Phase 2: Application Communication
- Client sends application messages to the received actor
- Normal bidirectional messaging begins
- Application logic handles game/service initialization
Message Handling Best Practices
Messages should be treated as opaque objects with your application data:
// CORRECT: Store actor references separately
var players = {};
$_.receiver(msg => {
if (msg.type === 'join_game' && msg.player_id) {
// Store the message for later response
players[msg.player_id] = msg;
// Later, respond to the stored message
send(players[msg.player_id], {type: 'game_start'});
}
});
// WRONG: Trying to access internal message properties
$_.receiver(msg => {
var sender = msg.__HEADER__.replycc; // Never do this!
});
Return ID Lifecycle
- Each reply callback gets a unique return ID
- Return IDs are consumed once and then deleted
- Reusing message objects with return headers causes "Could not find return function" errors
- Always create clean actor references for ongoing communication
Actor Object Transparency
Actor objects must be completely opaque black boxes that work identically regardless of transport:
// Actor objects work transparently for:
// - Same-process communication (fastest - uses mailbox)
// - Inter-process communication (uses mailbox)
// - Network communication (uses ENet)
// The actor shouldn't know or care about the transport mechanism
send(opponent, {type: 'move', from: [0,0], to: [1,1]});
Key Implementation Details:
actor_send()inscripts/core/engine.jshandles routing based on available actor data- Actor objects sent in message data automatically get address/port populated when received over network
- Three communication pathways:
os.mailbox_exist()check → mailbox send → network send - Actor objects must contain all necessary routing information for transparent messaging
Common Networking Bugs
- Portal sending application data: Portal should only establish actor connections
- Return ID collision: Reusing messages with return headers for multiple sends
- Mixed phases: Trying to do application logic during connection establishment
- Header pollution: Using received message objects as actor references
- Missing actor address info: Actor objects in message data need network address population (fixed in engine.js:746-766)
Example: Correct Chess Networking
// Server: Portal setup
$_.portal(e => {
send(e, $_); // Just reply with actor
}, 5678);
// Client: Two-phase connection
$_.contact((actor, reason) => {
if (actor) {
opponent = actor;
send(opponent, {type: 'join_game'}); // Phase 2: app messaging
}
}, {address: "localhost", port: 5678});
// Server: Handle application messages
$_.receiver(e => {
if (e.type === 'join_game') {
opponent = e.__HEADER__.replycc;
send(opponent, {type: 'game_start', your_color: 'black'});
}
});
Memory Management
- When working with a conversational AI system like Claude, it's important to maintain a clean and focused memory
- Regularly review and update memories to ensure they remain relevant and helpful
- Delete or modify memories that are no longer accurate or useful
- Prioritize information that can genuinely assist in future interactions