BINX FORGEALL YOU NEED IS AN IDEA.Support BINX ↗
PRACTICAL GUIDES & COMPARISONS · ORIGINAL SOURCE INCLUDED

Add Mobile Controls That Stop When Your Finger Lifts

Use pointer ownership, cancellation and keyboard parity to prevent stuck movement in browser games.

Actual Signal Sweep gameplay: a playable BINX reference
Signal Sweep · Actual gameplay · Related playable reference

Thumbnail: an actual related playable BINX demo. It is a gameplay reference, not evidence that a save, inventory, network or native reverse-engineering integration has been demonstrated.

Difficulty: beginner to intermediate. Prerequisites: basic variables, functions and a test copy of your project. Reference: Forge workshop 1.0, browser ES modules and Node 24 checks. Engine-specific translation is a separate integration.

What you will learn

Use pointer ownership, cancellation and keyboard parity to prevent stuck movement in browser games.

Download complete MIT workshop
  1. Extract into a new folder and run node check.mjs from that folder.
  2. For the browser games, run python3 -m http.server 8080 with Python 3 and open http://localhost:8080/index.html. Do not open modules with file://.
  3. Read the full setup instructions and the original code licence. Keep your existing game on a separate test branch.

The JavaScript behaviour checks and native C comparison can run independently of the browser. Ghidra UI, physical-phone and your engine integration are separate checks. This is a small reference, not a certification of your project.

Explore the related game idea · Start with Build a Game · Improve your existing game

Five practical steps

Add Mobile Controls That Stop When Your Finger Lifts: Map actions before devices → Track each finger independently → Handle every release path
Original implementation diagram · not gameplay or a tool screenshot
  1. Map actions before devices

    Define left, right, jump and pause as game actions. Map keyboard and touch to these actions so the movement system stays shared. Keep jump as a press event and movement as a held state; otherwise a held jump may retrigger on landing.

    Expected result: One action map used by both input methods.

    Check it: Run the same movement check with keys and with buttons; speeds should match.

    pointerInputObserve the inputs → check the state → compare the result
  2. Track each finger independently

    Read pointerInput. It maps pointer IDs to actions, rather than storing one global touched boolean. Capture the pointer on pointerdown so its release is delivered even after it leaves the button. Apply touch-action:none only to game controls.

    Expected result: Two fingers can hold different controls at once.

    Check it: Hold left with one pointer and fire with another; releasing fire must not release left.

  3. Handle every release path

    Release the specific owner on pointerup, pointercancel and lostpointercapture. Clear all keyboard and pointer state on blur, hidden tabs, restart and scene changes. Pause the game on focus loss; resuming must require a deliberate action.

    Expected result: A cancelled pointer cannot leave movement running.

    Check it: Hold a direction, switch tabs and return; the workshop remains paused with no held direction.

  4. Keep controls readable and reachable

    Use labelled buttons at least 44 CSS pixels high, space them apart and keep pause outside the thumb cluster. Preserve scrolling elsewhere on the guide page. Do not hijack arrow keys while focus is in a select, input, button or link.

    Expected result: Controls work with touch, keyboard and ordinary page navigation.

    Check it: At 320px width, verify no horizontal scroll and tab to every control without a focus trap.

  5. Test the real target

    The included recipe workshop implements this pattern. Open a recipe, Start, hold Right, Pause, release and Resume. Emulated widths are useful layout checks; physical phones still need multi-touch, rotation, browser chrome and interrupted-gesture testing.

    Expected result: A reproducible input checklist rather than a mobile-ready claim.

    Check it: Test pointer cancellation and simultaneous buttons on a real phone before shipping the target build.

Read and reuse the actual code

systems.mjs: complete source · Standalone behavioural checks · Shared browser runner and input. Original BINX Forge code, MIT; the package includes every required file and its notice.

Inspect complete systems.mjs
// Original BINX Forge practice code. MIT; see LICENSE.txt.
export function readSave(raw) {
  const s = JSON.parse(raw);
  if (!s || s.version !== 1 || !Number.isInteger(s.coins) || s.coins < 0 || s.coins > 9999 || !['room-a','room-b'].includes(s.room)) throw Error('Invalid save');
  return {version:1, coins:s.coins, room:s.room};
}
export function transfer(from, to, id, count, capacity=10) {
  if (!Number.isInteger(count) || count < 1 || !Number.isInteger(from[id]) || from[id] < count || Object.values(to).reduce((a,b)=>a+b,0)+count > capacity) return false;
  from[id]-=count; to[id]=(to[id]||0)+count; return true;
}
export function pointerInput() {
  const owners=new Map();
  return {press:(id,action)=>owners.set(id,action),release:id=>owners.delete(id),clear:()=>owners.clear(),held:action=>[...owners.values()].includes(action)};
}
export function enemyMode(distance, hp, cooldown) {
  if (hp<=0) return 'dead';
  if (cooldown>0) return 'recover';
  if (distance<24) return 'attack';
  return distance<180?'chase':'idle';
}
export function frameSummary(samples) {
  const s=samples.filter(Number.isFinite).filter(x=>x>=0).sort((a,b)=>a-b);
  if (!s.length) throw Error('No samples');
  return {median:s[Math.floor((s.length-1)*.5)],p95:s[Math.ceil(s.length*.95)-1],count:s.length};
}
export function fixedStep(clock, elapsed, update) {
  clock.carry+=Math.max(0,Math.min(.1,elapsed));
  while(clock.carry>=1/60){update(1/60);clock.carry-=1/60;}
}
// Owned protocol specimen: version:u8, coins:u16 little-endian, room:u8.
export function decodeRecord(bytes) {
  if(bytes.length!==4)throw Error('Expected four bytes');
  const v=new DataView(bytes.buffer,bytes.byteOffset,bytes.byteLength);
  if(v.getUint8(0)!==1||v.getUint8(3)>1)throw Error('Unknown record');
  return {version:1,coins:v.getUint16(1,true),room:v.getUint8(3)};
}
export function applyInput(player, packet) {
  if(!packet||!Number.isInteger(packet.seq)||packet.seq<=player.seq||![-1,0,1].includes(packet.dx))return false;
  player.seq=packet.seq;player.x=Math.max(0,Math.min(100,player.x+packet.dx*2));return true;
}
export function jumpTrace(speed=300,gravity=900,dt=1/120) {
  let y=0,vy=-speed,t=0,peak=0;
  const rows=[{t,y,vy}];
  while(t<3){vy+=gravity*dt;y+=vy*dt;t+=dt;peak=Math.min(peak,y);rows.push({t,y,vy});if(y>=0)break;}
  return {rows,height:-peak,airtime:t};
}

Code rights: the included MIT notice permits use, modification and distribution, including commercial games and source products, with the copyright and licence notice. Added third-party files have their own terms. Read the official licence & usage terms.

Common failures and fixes

Movement sticks after a swipe

Add cancellation and focus-loss cleanup; pointerup alone misses interruptions.

Page will not scroll

Restrict touch-action:none to the controls instead of the whole document.

Two fingers cancel each other

Track pointer IDs; do not clear every action whenever one finger releases.

Make Your Game Better

Change one system after the baseline works. Keep the free reference and compare the same inputs before and after.

Optional building blocks from original creators

Inspect exact formats and editions on each resource page. Pay for a resource only when it fills an identified gap; the free code exercise needs no paid engine or pack.

Licensing summary, not legal advice. Before publishing or selling, check exact terms for finished-game use, reselling files and including files in a source/template product separately.

Learn at the original sources

Sources checked 11 October 2026. The numbered plan is original Forge instruction; linked documentation does not imply every engine or device has been tested.

Take this guide to your AI

Review and copy into your assistant. Nothing is sent automatically. These prompts include the complete focus source, full-package links, checks and compatibility limits.

Review a prompt or select its text manually.

Review build prompt
Review debug prompt
Review upgrade prompt

Read the complete guide and prompts as Markdown →

Keep learning

Browse every guide and recipe →