Skip to content

Adapt the playable example ​

Use Gap Run to learn how game code fits into the SDK. Hold the left or right side of the screen to steer a ball through gaps. Passing a wall earns a point; consecutive near misses increase the bonus. A collision ends the run. The host owns the start and result UI, so the game can run inside a feed without its own menus.

First run the example. The source is examples/template/; the built game is dist/example/g/template/. Edit source files, rebuild with npm run build, then reload. npm run example serves the existing build and does not watch or rebuild source changes.

Follow one run through the source ​

Open examples/template/main.js. It imports only Three.js and the public portal-kit entry. The root SDK import registers the implementations used by createGame.

CodeRole in Gap Run
ID, BUILD, COLORSValues shared with game.json; the example tests check that they agree
load(ctx)Create the renderer, scenery, reusable walls, ball and input zones. Yield between batches, prepare graphics and apply the current quality tier
arm()Reset the waiting scene and counters before each run, without consuming seeded randomness
start(ctx, first)Generate the initial gaps with ctx.rng(), use the starting touch and begin music
update(dt, ctx)Read the already sampled steering axis, advance walls, detect collisions, score passes and recycle walls
render(alpha, ctx)Interpolate the ball and walls, apply visual effects and draw the scene; keep collision and scoring out of this callback
resize(view, ctx)Keep a fixed horizontal field of view when the viewport changes
quality(tier)Hide the decorative grid at low quality. Guard scene references because this callback can run during loading
dispose()Drop game references; the SDK releases the warmed scene, renderer and its own subsystems
test, autoplayExpose observable game state and a simple deterministic player for inspection and preview capture

The lifecycle defines callback order and resource ownership. See SDK API for arguments and synchronous callback requirements. For individual systems, use Input, HUD and visual feedback, Audio and Graphics.

Change the game deliberately ​

Start with one visible change, then play and restart several runs:

ChangeEdit and check
ColorsUpdate COLORS in main.js and colors in game.json. Rebuild; scenery and SDK accents should agree
Steering responseAdjust STEER, measured in world units per second. Check both held touches and a short starting tap
DifficultyAdjust SPEED, GAP, SPACING or RAMP. Gaps must remain wider than the ball and reachable at the current speed
RewardsChange pass() and crash(). Keep the HUD and reported score synchronized through ctx.score(); use ctx.over() once to finish
Music or effectsChange MUSIC and event-triggered ctx.audio.sfx calls. Verify mute, pause, restart and teardown with the audio guide
Scene detailChange quality(tier) and initial tier handling in load. Read the current tier after asynchronous loading steps

SPEED and GAP interpolate over RAMP simulation seconds. Increasing speed or narrowing gaps changes play difficulty, not only appearance; measure representative runs and update the manifest to describe the actual game. Do not copy the template's duration estimates as measurements for a new game.

Run npm run check after edits. It verifies source/manifest agreement, public packages and lifecycle behavior. Then use the host fixture to test start, pause, restart and disposal. The repository browser suite exercises this template; a separate game needs equivalent checks against its own URL.

Inspect and capture ​

Open the game with ?pptest=1. Besides the standard test hook, Gap Run exposes:

GetterResult
x()Ball position across the track, in world units
axis()Sampled steering input: -1, 0 or 1
speed()Forward speed in world units per second
gates()Walls successfully passed in this run
streak()Consecutive near-miss bonus count
nextGap(){ z, cx, gap } for the next wall, or null; values are world units

For reproducible captures, initialize through a host with a fixed seed, test: true and capture: true. Start, pause, then call __portalTest.autoplay(true) and __portalTest.step(n, dtMs) in the game frame. The test hook defines stepping behavior; preview media defines the required files. The SDK does not encode videos, capture posters or generate catalog acceptance data.

Understand the build output ​

npm run build creates two outputs:

  • packages/web-sdk/dist/portal-kit.js: the standalone ES module bundle, with no runtime imports.
  • dist/example/: a local example site with g/template/, kit/, the Three.js build under lib/three/, a landing page, notices, and host.html with message fixtures for integration testing.

The example imports no Three.js addons. Its import map reserves three/addons/, but the build leaves that directory empty. If your game uses addons, include every addon and transitive dependency it imports, along with applicable licenses. Do not copy the entire dependency tree merely to satisfy an unused import-map entry.

The build validates manifest structure. It does not create preview media, add a production host, deploy a site or certify performance. host.html is a developer test fixture. Keep it out of your production player flow. The runtime environment defines the files and URLs a host expects.

Start a separate project ​

Use local package installation when your game lives outside this repository. Install Three.js in that project if needed, then use your own bundler or a relative import map as shown in Getting started. The repository build stages examples/template/ specifically; copying it to a new folder does not add that folder to the build.

Choose your game's identity and keep its code, manifest and directory name aligned. Update the HTML title, source attribution and credits for your content, and preserve attribution for material you retain. Increase the game build number when changing distributed game files; SDK and protocol versions are independent. Before publishing, supply real preview assets and complete the shipping checks.

Wire protocol 2 · Manifest 2 · SDK runtime 0.2.1