Common Issues
This guide covers common issues you might encounter when using martini-kit SDK and how to resolve them.
State Not Syncing
Symptom
Client state doesn’t update or lags behind the host.
Possible Causes
- Transport not connected
- Host not sending state updates
- Client not receiving messages
- Network connectivity issues
Solutions
1. Check Transport Connection
// Check connection state
const connectionState = transport.metrics?.getConnectionState();
console.log('Connection state:', connectionState);
// Should be 'connected'
if (connectionState !== 'connected') {
console.error('Transport not connected');
} 2. Verify Host Configuration
const runtime = new GameRuntime(game, transport, {
isHost: true, // Make sure this is true on host
playerIds: [transport.getPlayerId()],
syncRateMs: 50, // Check sync rate is reasonable
}); 3. Check Browser Console
- Open DevTools (F12)
- Look for errors in Console tab
- Check Network tab for failed requests
4. Test with LocalTransport First
// Simplify to test locally
const transport = new LocalTransport({
roomId: 'test-room',
isHost: true,
}); Actions Not Applying
Symptom
Calling submitAction doesn’t affect the game state.
Possible Causes
- Using
playerIdinstead oftargetId - Action handler has an error
- State structure mismatch
- Action not registered
Solutions
1. Use targetId Correctly
// CORRECT ✅
actions: {
takeDamage: {
apply(state, context, input: { amount: number }) {
const player = state.players[context.targetId]; // Use targetId!
if (player) {
player.health -= input.amount;
}
}
}
}
// WRONG ❌
actions: {
takeDamage: {
apply(state, context, input: { amount: number }) {
const player = state.players[context.playerId]; // Wrong!
if (player) {
player.health -= input.amount;
}
}
}
} 2. Add Logging to Action Handlers
actions: {
move: {
apply(state, context, input) {
console.log('Move action:', { context, input });
// Your action logic
}
}
} 3. Verify State Structure
// Make sure your state matches your interface
interface GameState {
players: Record<string, Player>; // Not Player[]
} 4. Check Action Registration
const game = defineGame({
actions: {
move: { apply: () => {} }, // Make sure action is here
jump: { apply: () => {} },
}
});
// Later...
runtime.submitAction('move', input); // Action name must match Sprites Not Appearing on Client
Symptom
Sprites visible on host but not on client.
Possible Causes
- Wrong sprite namespace
- Sprite not tracked on host
- Assets not preloaded on client
- Sprite created but not added to scene
Solutions
1. Verify Sprite Namespace
// In adapter config
const adapter = new PhaserAdapter(runtime, scene, {
spriteNamespace: '_sprites', // Must match state key
});
// In state
interface GameState {
_sprites: Record<string, SpriteData>; // Key must match namespace
} 2. Track Sprites on Host
// On host only
if (this.runtime.isHost) {
const sprite = this.add.sprite(x, y, 'player');
this.adapter.trackSprite(sprite, playerId);
} 3. Preload Assets on All Clients
preload() {
// Load on BOTH host and clients
this.load.image('player', '/assets/player.png');
this.load.image('enemy', '/assets/enemy.png');
} 4. Use SpriteManager
// Automatically creates sprites on clients
const playerManager = this.adapter.createSpriteManager({
onCreate: (key, data, scene) => {
return scene.add.sprite(data.x, data.y, 'player');
},
onUpdate: (sprite, data) => {
sprite.x = data.x;
sprite.y = data.y;
}
}); TypeScript Errors
“Type ‘any’ is not assignable…”
Cause: Strict type checking enabled.
Solution: Add proper typing
// Before
function handleData(data: any) {
// ...
}
// After
interface PlayerData {
x: number;
y: number;
}
function handleData(data: PlayerData) {
// ...
} “Property ‘x’ does not exist on type…”
Cause: State interface doesn’t match actual state.
Solution: Update interface
interface GameState {
players: Record<string, Player>;
score: number; // Add missing property
} “Cannot find module ‘@martini-kit/core’”
Cause: Package not installed or build not run.
Solution:
# Install dependencies
pnpm install
# Build packages
pnpm build Performance Issues
Symptom
Game lags, stutters, or has high CPU usage.
Possible Causes
- Sync rate too high
- Too many sprites/objects
- Inefficient action handlers
- Memory leaks
Solutions
1. Adjust Sync Rate
const runtime = new GameRuntime(game, transport, {
isHost: true,
playerIds: ['p1'],
syncRateMs: 100, // Increase for better performance (default: 50)
}); 2. Use Object Pooling
class ProjectilePool {
private pool: Projectile[] = [];
acquire(): Projectile {
return this.pool.pop() ?? this.createProjectile();
}
release(projectile: Projectile): void {
this.pool.push(projectile);
}
} 3. Optimize Action Handlers
// Bad - creates new object every frame
actions: {
tick: {
apply(state) {
state.players = { ...state.players }; // Don't do this!
}
}
}
// Good - mutate directly
actions: {
tick: {
apply(state) {
for (const player of Object.values(state.players)) {
player.x += player.vx;
}
}
}
} 4. Profile with DevTools
import { StateInspector } from '@martini-kit/devtools';
const inspector = new StateInspector({
maxSnapshots: 50,
actionFilter: {
exclude: ['tick'], // Exclude high-frequency actions
}
});
inspector.attach(runtime);
console.log(inspector.getStats()); Player Join/Leave Issues
Symptom
Players can’t join or game breaks when someone leaves.
Possible Causes
- Not handling
onPlayerJoin/onPlayerLeave - State not cleaned up on leave
- Race condition in join logic
Solutions
1. Implement Lifecycle Handlers
const game = defineGame({
onPlayerJoin(state, playerId) {
state.players[playerId] = {
x: 100,
y: 100,
health: 100,
};
},
onPlayerLeave(state, playerId) {
delete state.players[playerId];
}
}); 2. Use PlayerManager Helper
const playerManager = createPlayerManager({
factory: (playerId, index) => ({
x: 100 + index * 50,
y: 200,
}),
});
export const game = defineGame({
setup: ({ playerIds }) => ({
players: playerManager.initialize(playerIds),
}),
onPlayerJoin: (state, playerId) => {
playerManager.handleJoin(state.players, playerId);
},
onPlayerLeave: (state, playerId) => {
playerManager.handleLeave(state.players, playerId);
},
}); Determinism Issues
Symptom
Game state diverges between host and clients.
Cause
Using non-deterministic functions like Math.random() or Date.now().
Solutions
1. Use SeededRandom
// WRONG ❌
const angle = Math.random() * Math.PI * 2;
// CORRECT ✅
const angle = context.random.float(0, Math.PI * 2); 2. Avoid Date.now()
// WRONG ❌
const timestamp = Date.now();
// CORRECT ✅ - Track time in state
actions: {
tick: {
apply(state, context, input: { deltaTime: number }) {
state.gameTime += input.deltaTime;
}
}
} Build Errors
“Cannot find module” in production build
Cause: Missing dependencies or incorrect imports.
Solution:
# Clean and rebuild
pnpm clean
pnpm install
pnpm build Circular dependency warnings
Cause: Files importing each other.
Solution: Refactor to break the cycle
// Before (circular)
// a.ts
import { B } from './b';
// b.ts
import { A } from './a';
// After (fixed)
// types.ts
export interface A {}
export interface B {}
// a.ts
import type { B } from './types';
// b.ts
import type { A } from './types'; Still Having Issues?
If you can’t find a solution here:
- Check the Debugging Guide for detailed debugging steps
- Search GitHub Issues for similar problems
- Ask in GitHub Discussions
- Open a new issue with a minimal reproduction
When asking for help, include:
- martini-kit SDK version
- Browser and OS
- Minimal code reproduction
- Console errors
- Steps to reproduce