TipTap Games
← Real-time simulation

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.

  1. Attach your creator logic

    The simulation is off until your game carries a compiled logic artifact. Publish a logic.ts under Creator logic, attach it in Creator Studio, and the game finds TipTap.sim alongside TipTap.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.sim is available. From here you write no networking.

  2. Find a match

    The simulation rides a multiplayer room, so join one — that is what hands the sim its seat and its seed. quickMatch can 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.

  3. 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.

  4. 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.

  5. 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