Your first simulation game
A top-down arena, running your own compiled logic. Attach the artifact, find a match, and run one loop of input and raw view — the platform owns every hard networking problem underneath.
- Your logic, 60 Hz
- You write no netcode
- Input + raw view, that's the loop
The simulation tier is a different way to build a multiplayer game: instead of a message pipe you get a physics world that is already networked, and the physics are the ones you wrote. The rules live in a creator logic file, compiled server-side to WebAssembly and stepped identically on every client. This page is the other half — the drawing document that supplies intent each frame and paints the state it gets back.
Attach your creator logic
The simulation is off until your game carries a compiled logic artifact. Publish a
logic.tsunder Creator logic, attach it in Creator Studio, and the game findsTipTap.simalongsideTipTap.net.Attach a published artifact; sim rides the connection
// The simulation is your OWN compiled logic. Write and publish a logic.ts under // Creator logic, then attach the published artifact to this game — there is no // genre menu, the arena and the rules are the ones you wrote. // Creator Studio → Advanced → Creator logic → attach a published artifact if (!TipTap.sim) return; // not a creator-logic game, or no platform // TipTap.sim now runs your simulation, rolled back and reconciled for you. You // will not write a tick, a snapshot, a sequence number or a line of reconciliation.✓
TipTap.simis available. From here you write no networking.Find a match
The simulation rides a multiplayer room, so join one — that is what hands the sim its seat and its seed.
quickMatchcan be refused for a beat while a freshly-opened panel settles into the centre of the feed, so retry until it is the active panel.quickMatch, with a gentle retry while the panel settles
// The simulation rides a multiplayer room; joining one is what gives the sim its // seat and its seed, and two players meeting there is what makes it a match. // quickMatch can be refused for a beat while a freshly-opened panel settles into // the centre of the feed — retry until it is the active panel. const net = TipTap.net; (function match(attempt) { net.quickMatch({ minPlayers: 2, maxPlayers: 8 }) .then(() => net.ready && net.ready()) .catch(() => { if (attempt < 12) setTimeout(() => match(attempt + 1), 1500); }); })(0);✓ Two players are in a room. The sim now has a seat and a shared seed.
Define and start
define()takes no config for a creator game — the arena, the bodies and the constants are baked into the artifact you compiled, not passed in here. It resolves once the sim has its seat and seed;start()begins the loop.define({}) then start() — the world is yours already
// define() takes NO config for a creator game: the arena, the constants and the // bodies are baked into the artifact you compiled, not passed here. It resolves // once the sim has its seat and seed from the room; start() begins the loop. await TipTap.sim.define({}); await TipTap.sim.start(); // the 60 Hz loop begins✓ The world exists and is stepping at 60 Hz on every player's device.
Input and view — the whole loop
This is the entire per-frame contract: tell the simulation what this player wants, and draw what it hands back.
getView()returns your own arena bytes — already predicted for you and reconciled for everyone — and nothing decodes them, because the layout is the one you wrote. Read them at your offsets and paint.input() intent, getView() for your own raw state
// Your own state layout — the same offsets you wrote in logic.ts. const PLAYER_X = 0, PLAYER_Y = 32, PLAYER_HP = 128; function frame() { // 1. Record this player's INTENT. The SDK turns it into 60 inputs a second and // owns the prediction. The fields are the ones your logic.ts reads. TipTap.sim.input({ moveX: stick.x, moveY: stick.y, aim: aimTurns, fire: firing ? 1 : 0 }); // 2. getView() hands back your OWN arena bytes — already predicted for this // player and reconciled for everyone. Nothing decodes them; you read them // at your own offsets, because it is your state. const bytes = TipTap.sim.getView(); const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); for (let s = 0; s < seats; s++) { drawPlayer(dv.getFloat32(PLAYER_X + s * 4, true), dv.getFloat32(PLAYER_Y + s * 4, true), bytes[PLAYER_HP + s]); } requestAnimationFrame(frame); } frame();✓ A playable, in-sync arena. Design it gently — slow time-to-kill, generous hitboxes, a small map — or the netcode gets blamed for the design.
React to events
Use events for feel — a shot sound, a hit flash. They arrive about a round-trip behind state and re-fire on re-simulation, so read state for anything visual and keep events for audio.
onEvent for sound, not for state
// Events are for FEEL — a sound, a screen shake — and arrive ~1 RTT behind state, // and re-fire on re-simulation. Read state (getView) for anything visual; use // events for audio. TipTap.sim.onEvent((e) => { if (e.type === "fire") { if (TipTap.canPlayAudio()) playSound("shot"); } });✓ That's a real-time multiplayer game with none of the netcode written by you.
Where to go next
- Real-time simulation → the full SDK surface the drawing document uses.
- Creator logic → write and compile the simulation itself.

