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

Build an Inventory System: Stacks, Capacity and Safe Transfers

Make inventory transfers atomic so full bags, duplicate clicks and invalid quantities cannot create or destroy items.

Actual Paper Duel gameplay: a playable BINX reference
Paper Duel · 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

Make inventory transfers atomic so full bags, duplicate clicks and invalid quantities cannot create or destroy items.

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

Build an Inventory System: Stacks, Capacity and Safe Transfers: Separate items from their pictures → Validate the whole transaction → Move both sides together
Original implementation diagram · not gameplay or a tool screenshot
  1. Separate items from their pictures

    Use stable item IDs such as potion and key. The inventory stores counts; a separate catalogue supplies labels, icons and allowed uses. Start with two containers and a capacity measured in total item units, not visual slots.

    Expected result: A chest {potion:3} and empty bag {}.

    Check it: Rename an item label; its stored ID and count must remain unchanged.

    transferObserve the inputs → check the state → compare the result
  2. Validate the whole transaction

    Read transfer in systems.mjs. It checks a positive integer, stock and destination capacity before mutating either container. This example assumes trusted, nonnegative counts in the two containers; validate imported saves before using them.

    Expected result: A rejected transfer changes neither container.

    Check it: Try zero, negative, fractional and excessive counts, then a full bag; compare both inventories to their snapshots.

  3. Move both sides together

    Subtract from the source and add to the destination as one accepted operation. Route drag-and-drop, keyboard selection and button clicks through that same function. Do not decrement in one UI event and increment in a later animation callback.

    Expected result: Moving two potions leaves one in the chest and two in the bag.

    Check it: Repeat the same click: the second transfer fails if its requested stock is no longer present.

  4. Render the accepted state

    Refresh labels and icons from data only after the transfer returns true. A drag preview is temporary UI, not ownership. Announce the moved amount or the capacity failure, and provide keyboard buttons alongside dragging.

    Expected result: UI and game state agree after every accepted move.

    Check it: Cancel a drag, use keyboard-only controls and close/reopen the bag; total item count stays three.

  5. Add rules without hiding them

    This ten-unit model is deliberately simpler than weight, equipment slots or multiplayer trading. Choose one next rule, such as potion stacks of five, and define it before changing the UI. Networked inventories require server-authoritative transactions.

    Expected result: A small invariant: no negative count, capacity respected, total conserved.

    Check it: Run checks before connecting your engine UI; then test full inventory, save/reload and two rapid actions.

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

Items duplicate when dragged

One transaction owns the mutation; visual drop and pointer release must not both transfer.

Capacity feels inconsistent

Say whether capacity means units, weight or slots and use the same calculation everywhere.

A save imports negative stacks

Validate persistent data first; this transfer helper is not a full save validator.

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 →