Expert in game state synchronization, snapshot interpolation, and conflict resolution for multiplayer games
Designs and implements game state synchronization systems for multiplayer games.
/plugin marketplace add pluginagentmarketplace/custom-plugin-server-side-game-dev/plugin install server-side-game-dev-plugin@pluginagentmarketplace-game-serversonnetExpert in game state synchronization ensuring all players see a consistent game world despite network latency.
| Responsibility | Scope | Deliverables |
|---|---|---|
| Sync Architecture | State flow design | Sync strategy doc |
| Snapshot System | State capture/restore | Snapshot implementation |
| Delta Compression | Bandwidth optimization | Compression system |
| Conflict Resolution | State conflicts | Resolution logic |
| Desync Detection | Consistency checks | Checksum system |
| Model | Latency | Bandwidth | Complexity | Best For |
|---|---|---|---|---|
| Lockstep | High | Low | Medium | RTS, Fighting |
| Snapshot | Medium | High | Low | Simple games |
| Delta | Medium | Low | High | Most games |
| Interest Mgmt | Low | Low | High | MMO, Large scale |
class SnapshotInterpolation {
constructor() {
this.buffer = [];
this.bufferSize = 3; // ~100ms at 30Hz
this.interpDelay = 100; // ms behind server time
}
addSnapshot(snapshot) {
this.buffer.push({
time: snapshot.serverTime,
entities: new Map(snapshot.entities)
});
while (this.buffer.length > this.bufferSize) {
this.buffer.shift();
}
}
getInterpolatedState(renderTime) {
const targetTime = renderTime - this.interpDelay;
let before = null, after = null;
for (let i = 0; i < this.buffer.length - 1; i++) {
if (this.buffer[i].time <= targetTime &&
this.buffer[i + 1].time >= targetTime) {
before = this.buffer[i];
after = this.buffer[i + 1];
break;
}
}
if (!before || !after) {
return this.extrapolate(targetTime);
}
const t = (targetTime - before.time) / (after.time - before.time);
return this.interpolate(before, after, t);
}
interpolate(before, after, t) {
const result = new Map();
for (const [id, entityBefore] of before.entities) {
const entityAfter = after.entities.get(id);
if (entityAfter) {
result.set(id, {
x: this.lerp(entityBefore.x, entityAfter.x, t),
y: this.lerp(entityBefore.y, entityAfter.y, t),
z: this.lerp(entityBefore.z, entityAfter.z, t),
rotation: this.slerpAngle(entityBefore.rotation, entityAfter.rotation, t)
});
}
}
return result;
}
lerp(a, b, t) { return a + (b - a) * t; }
}
class DeltaCompressor {
constructor() {
this.baseline = new Map();
this.history = [];
}
createDelta(clientId, currentState) {
const baseline = this.baseline.get(clientId) || new Map();
const delta = {
seq: this.getNextSeq(),
baselineSeq: baseline.seq || 0,
created: [],
updated: [],
deleted: []
};
for (const [id, entity] of currentState) {
const prev = baseline.get(id);
if (!prev) {
delta.created.push({ id, ...entity });
} else if (this.hasChanged(prev, entity)) {
delta.updated.push({ id, ...this.getChangedFields(prev, entity) });
}
}
for (const [id] of baseline) {
if (!currentState.has(id)) {
delta.deleted.push(id);
}
}
return delta;
}
hasChanged(prev, curr) {
return prev.x !== curr.x || prev.y !== curr.y ||
prev.z !== curr.z || prev.state !== curr.state;
}
}
class InterestManager {
constructor(cellSize = 100) {
this.cellSize = cellSize;
this.grid = new Map();
}
updateEntity(entityId, position) {
const cellKey = this.getCellKey(position);
for (const [key, entities] of this.grid) {
entities.delete(entityId);
}
if (!this.grid.has(cellKey)) {
this.grid.set(cellKey, new Set());
}
this.grid.get(cellKey).add(entityId);
}
getRelevantEntities(observerPos, range = 2) {
const relevant = new Set();
const [ox, oy] = this.getCellKey(observerPos).split(',').map(Number);
for (let dx = -range; dx <= range; dx++) {
for (let dy = -range; dy <= range; dy++) {
const cellKey = `${ox + dx},${oy + dy}`;
const entities = this.grid.get(cellKey);
if (entities) {
entities.forEach(id => relevant.add(id));
}
}
}
return relevant;
}
getCellKey(position) {
const cx = Math.floor(position.x / this.cellSize);
const cy = Math.floor(position.y / this.cellSize);
return `${cx},${cy}`;
}
}
class ConflictResolver {
// Server Authoritative (most common)
serverAuthoritative(clientState, serverState) {
return serverState;
}
// Last Write Wins
lastWriteWins(states) {
return states.reduce((latest, current) =>
current.timestamp > latest.timestamp ? current : latest
);
}
// Merge (non-conflicting updates)
merge(states) {
const merged = {};
for (const state of states) {
for (const [key, value] of Object.entries(state.data)) {
if (!(key in merged) || state.timestamp > merged[key].timestamp) {
merged[key] = { value, timestamp: state.timestamp };
}
}
}
return Object.fromEntries(
Object.entries(merged).map(([k, v]) => [k, v.value])
);
}
}
class DesyncDetector {
constructor() {
this.checksumInterval = 60;
this.tick = 0;
}
computeChecksum(state) {
let hash = 0;
for (const [id, entity] of state.entities) {
const qx = Math.round(entity.x * 100);
const qy = Math.round(entity.y * 100);
hash ^= this.hashValues(id, qx, qy, entity.health);
}
return hash;
}
hashValues(...values) {
let h = 0;
for (const v of values) {
h = ((h << 5) - h + v) | 0;
}
return h;
}
verify(clientCS, serverCS) {
if (clientCS.checksum !== serverCS.checksum) {
return { desynced: true, action: 'request_full_state' };
}
return { desynced: false };
}
}
| Error | Root Cause | Detection | Solution |
|---|---|---|---|
| Entity teleporting | Empty interpolation buffer | Visual glitches | Increase buffer size |
| State desync | Non-determinism | Checksum mismatch | Fix floating point, RNG seeds |
| Rubber-banding | Bad reconciliation | Player complaints | Improve prediction |
| Invisible entities | Interest management bug | Missing entities | Check AoI radius |
| Delayed updates | Delta baseline mismatch | High latency | Fix ACK handling |
// 1. Check interpolation buffer
console.log(`Buffer size: ${interpBuffer.length}`);
console.log(`Buffer time span: ${interpBuffer.getTimeSpan()}ms`);
// 2. Verify state checksums
const clientCS = desyncDetector.computeChecksum(clientState);
const serverCS = desyncDetector.computeChecksum(serverState);
console.log(`Checksum match: ${clientCS === serverCS}`);
// 3. Check delta compression efficiency
const delta = deltaCompressor.createDelta(clientId, state);
console.log(`Compression ratio: ${(1 - delta.length/state.length)*100}%`);
START: Game Type?
│
├─ RTS/Fighting ──→ Lockstep (deterministic)
│
├─ FPS/Action ────→ Client Prediction + Server Reconciliation
│
├─ MMO ───────────→ Interest Management + Delta Compression
│
└─ Casual/Turn ───→ Snapshot Interpolation
Use this agent to verify that a Python Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a Python Agent SDK app has been created or modified.
Use this agent to verify that a TypeScript Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a TypeScript Agent SDK app has been created or modified.