BITMAPWIN SDK
BitmapWin is not a game. It is the layer games, tools, cities and autonomous systems are built on. The SDK is the contract both humans and AI agents write against — deliberately readable by a machine, so an agent can build a working experience without guessing.
This family is how we group things, not an on-chain link. A piece is only a verified on-chain child when its parent inscription was spent in the reveal (Tag 3). Inscription ids and existing parents stay unchanged.
LAND
LIVE DATARead a BITMAP's identity, holder, club, parcels and neighbours. Ownership is first-is-first on chain; the SDK never invents it.
- > land.get(height)
- > land.owner(height)
- > land.neighbours(height, radius)
- > land.rights(height, address)
BUILD
LIVE DATAAttach models, pages and deployed extenders to land you control or were granted.
- > build.models(height)
- > build.deploy(extender, version, height)
- > build.undeploy(deploymentId)
PLAYER
LIVE DATAPosition, velocity, direction, movement and animation state, health and combat state for the local player and interpolated peers.
- > player.self()
- > player.peers()
- > player.onMove(cb)
- > player.onHit(cb)
WORLD
LIVE DATAThe shared 1,000 × 1,000 world: chunks, districts, cursors, chat channels and the chain tip.
- > world.tip()
- > world.chunk(x, z)
- > world.chat.send(text)
- > world.presence()
ECONOMY
LIVE DATAListings, offers, confirmed sales, leases and BIT balances. Settlement stays on Bitcoin.
- > economy.listings(height)
- > economy.trades(height)
- > economy.leases(height)
- > economy.bit.balance(address)
SOCIAL
LIVE DATAProfiles, builder identity, follows, watchlists and parties.
- > social.profile(address)
- > social.follow(address)
- > social.party()
AI
COMING SOONThe agent surface: describe an experience, receive a manifest, review it, then publish it as a normal extender version. Nothing is generated behind your back.
- > ai.plan(prompt)
- > ai.review(planId)
- > ai.publish(planId)
BWVXLOADER.JS — ONE DRAW CALL
Render any Land Forge payload (BWVX1 or BWVX2) in your own Three.js viewer as a single InstancedMesh. Need Blender or Unreal? Use DOWNLOAD .GLB in Land Forge.
// BWVXLoader.js — renders any BITMAP Land Forge payload (BWVX1 or BWVX2) in ONE draw call.
// Input: the base64 payload from an inscription (data-bwvx / metadata). MIT, no dependencies beyond three.
import * as THREE from "three";
export const BWVX_PALETTE = ["#5fa83a","#7a5634","#e3cf8f","#8a8a8a","#6d6d6d","#b8874a","#c79a5b","#a5462f","#f3c43b","#bfe8f5","#ffd27a"];
export function bwvxBoxes(b64) {
let raw;
try { raw = atob(b64); } catch { throw new Error("BWVX: payload is not valid base64"); }
const b = Uint8Array.from(raw, (c) => c.charCodeAt(0));
const v2 = b[0] === 66 && b[1] === 87 && b[2] === 50 && b[3] === 0; // "BW2\0": greedy boxes, 7 bytes each; else BWVX1, 4 bytes per voxel
const body = v2 ? b.length - 4 : b.length, stride = v2 ? 7 : 4;
if (body <= 0 || body % stride !== 0) throw new Error("BWVX: truncated payload");
const out = [];
for (let i = v2 ? 4 : 0; i < b.length; i += stride) {
const box = v2 ? [b[i], b[i+1], b[i+2], b[i+3], b[i+4], b[i+5], b[i+6]] : [b[i], b[i+1], b[i+2], b[i], b[i+1], b[i+2], b[i+3]];
if (box[3] < box[0] || box[4] < box[1] || box[5] < box[2]) throw new Error("BWVX: reversed box bounds at byte " + i);
if (box[6] >= BWVX_PALETTE.length) throw new Error("BWVX: invalid palette index " + box[6] + " at byte " + i);
out.push(box);
}
return out; // [x0, y0, z0, x1, y1, z1, kindIndex] inclusive, y is up
}
export function loadBWVX(b64, { blockSize = 1 } = {}) {
const boxes = bwvxBoxes(b64);
const mesh = new THREE.InstancedMesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ roughness: 0.75 }),
boxes.length,
);
const m = new THREE.Matrix4(), p = new THREE.Vector3(), q = new THREE.Quaternion(), s = new THREE.Vector3();
const colors = BWVX_PALETTE.map((h) => new THREE.Color(h));
boxes.forEach(([x0, y0, z0, x1, y1, z1, k], i) => {
const sx = x1 - x0 + 1, sy = y1 - y0 + 1, sz = z1 - z0 + 1;
p.set((x0 + sx / 2) * blockSize, (y0 + sy / 2) * blockSize, (z0 + sz / 2) * blockSize);
s.set(sx * blockSize, sy * blockSize, sz * blockSize);
mesh.setMatrixAt(i, m.compose(p, q, s));
mesh.setColorAt(i, colors[k]);
});
mesh.instanceMatrix.needsUpdate = true;
if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
mesh.computeBoundingSphere();
return mesh; // add to your scene: scene.add(loadBWVX(payload))
}
// Safe viewer swap: always removes the previous build first, so a bad payload never leaves stale geometry.
export function showBWVX(scene, b64, prev, opts) {
if (prev) { scene.remove(prev); prev.geometry.dispose(); prev.material.dispose(); }
try { const mesh = loadBWVX(b64, opts); scene.add(mesh); return { mesh, error: null }; }
catch (e) { return { mesh: null, error: String((e && e.message) || e) }; }
}
INTEROPERABILITY — USING EXPORTS ELSEWHERE
Land Forge and the Colony build proposals export the same build three ways. Each export stays one mesh, so it draws in one draw call.
- .BWVX · COORDINATES
- Raw BWVX2 bytes: a "BW2\0" header, then 7-byte boxes (x0 y0 z0 x1 y1 z1 kind), with inclusive bounds and y pointing up. One unit is one block on the 64-block canvas. Exported files keep their canvas positions. When a file is imported into Land Forge, it is shifted so its lowest corner sits at 0,0,0; the shape is unchanged and you choose where to place it. Base64 text of the same bytes is also accepted.
- .GLB · COLOURS
- glTF 2.0 binary file: one node, one mesh and one primitive with POSITION, NORMAL and COLOR_0. The colours are vertex colours in linear RGB, taken from each block's top colour in the Forge palette. There are no textures or UVs, and the material is standard with roughness 0.75. One unit is one block.
- THREE.JS LOADER · DEPENDENCIES
- Needs only
threeas an ES module (tested with r185) and the browser'satob. It returns one InstancedMesh with one instance per box. "Copy Three.js loader" in Colony adds a ready-madePAYLOADexport:scene.add(loadBWVX(PAYLOAD)). UseshowBWVXto swap builds safely. - OTHER ENGINES
- Blender: File → Import → glTF 2.0. To see the colours, set the material's Base Color to a Color Attribute node. Unreal: import the .glb with the Interchange importer, then multiply Vertex Color into Base Color. Godot: drag the .glb into the project and turn on Vertex Color → Use as Albedo. Unity: use glTFast, with a shader that reads vertex colour. Babylon.js / web: load the .glb with any glTF loader. Bitcoin: inscribe .bwvx data as the build payload. Land Forge and the Oasis read it directly.
RUNTIME API — CODE THAT ACTUALLY RUNS
LIVE DATA· EXECUTES IN A SANDBOX ON DEPLOYED LANDA published version can carry JavaScript. It runs in a sealed sandbox — no page, no network, no wallet — and can only describe objects, movement and quests, which every visitor standing on that BITMAP then sees.
world
LIVE DATADescribe what stands on the tile. Every call returns a handle you can move, recolour or remove later — the world renders the result for everyone standing nearby.
- > world.prop({ shape, x, z, y, w, h, d, color, rotationY, label, glow, interactive })Create a solid. Returns a handle.
- > world.model({ inscriptionId, x, z, y, w, rotationY })Place an on-chain GLB/GLTF inscription.
- > handle.set({ x, z, y, rotationY, color, label })Move or restyle a prop.
- > handle.remove()Delete the prop.
- > world.count()How many props are alive.
quest
LIVE DATAGive visitors something to do. Quests appear in the world HUD for anyone on the tile and can change state as players act.
- > quest.add({ id, title, detail, reward })Publish a quest.
- > quest.state(id, 'open' | 'active' | 'done')Advance it.
- > quest.remove(id)Withdraw it.
on
LIVE DATAReact to the simulation. Handlers are called by the runtime, never by the page, and a throwing handler stops the extender instead of breaking the world.
- > on('tick', ({ time, dt, player }) => {})~15Hz. player is tile-local strides, or null when nobody is on the tile.
- > on('interact', ({ propId, player }) => {})A visitor clicked an interactive prop.
- > on('enter', ({ player }) => {})A visitor stepped onto the tile.
- > on('leave', () => {})The tile is empty again.
state
LIVE DATAReal world state, saved to the land. Anything you put in state comes back next session for every visitor, so buildings, NPCs and progress persist instead of resetting. Quest states and props created with `persist: true` are saved for you automatically. Signed-out visitors still play — their session simply is not written back.
- > state.get(key, fallback)Read the shared record for this deployment.
- > state.set(key, value)Save JSON — objects, arrays, numbers, strings.
- > state.add(key, amount)Increment a saved counter and return it.
- > state.remove(key)Forget one key.
- > state.all()The whole shared record.
- > state.player.get / set / add / remove / allPrivate per-visitor record (progress, inventory).
- > world.prop({ id, persist: true, ... })Save this object's position, rotation, colour and label.
land
LIVE DATARead-only facts about the tile the code is deployed on. Ownership comes from the chain — the SDK never invents it.
- > land.heightBITMAP number.
- > land.clubClub this BITMAP belongs to.
- > land.ownerCurrent holder address, or null.
- > land.configThe JSON config chosen at deploy time.
util
LIVE DATADeterministic helpers. Randomness is seeded from the BITMAP number, so the same land always builds the same way for every visitor.
- > random()Seeded 0..1.
- > randomInt(min, max)Seeded integer.
- > log(...values)Writes to the extender console (last 40 lines).
economy / social
COMING SOONReading listings, trades, leases, BIT balances and profiles from inside extender code is next. Until it ships, those surfaces stay in the app UI so nothing here pretends to be wired.
- > economy.listings()Not available in v0.2.
- > social.profile(address)Not available in v0.2.
// BitmapWin SDK v0.2 — injected into every extender sandbox
declare const land: { height: number; club: string; owner: string | null; config: Record<string, unknown> };
declare function on(event: "tick" | "interact" | "enter" | "leave", handler: (event: any) => void): void;
declare function log(...values: unknown[]): void;
declare function random(): number;
declare function randomInt(min: number, max: number): number;
type Handle = {
set(patch: Partial<{ x: number; y: number; z: number; rotationY: number; color: string; label: string | null }>): void;
remove(): void;
readonly id: string;
};
declare const world: {
prop(spec: {
shape?: "box" | "cylinder" | "sphere" | "cone" | "plane";
id?: string;
x?: number; y?: number; z?: number;
w?: number; h?: number; d?: number;
color?: string; rotationY?: number;
label?: string; glow?: boolean; interactive?: boolean;
/** save this object's place, rotation, colour and label to the land */
persist?: boolean;
}): Handle;
model(spec: { inscriptionId: string; x?: number; y?: number; z?: number; w?: number; rotationY?: number; label?: string; persist?: boolean }): Handle;
count(): number;
};
declare const quest: {
add(spec: { id: string; title: string; detail?: string; reward?: string }): void;
state(id: string, state: "open" | "active" | "done"): void;
remove(id: string): void;
};
type StateBucket = {
get<T = unknown>(key: string, fallback?: T): T;
set(key: string, value: unknown): void;
add(key: string, amount: number): number;
remove(key: string): void;
all(): Record<string, unknown>;
};
/** Saved world state. Shared record for everyone, plus a private per-visitor record. */
declare const state: StateBucket & { player: StateBucket };Pier, harbour master, three sailing ships and two quests.
A meeting hall with a beacon and a greeting quest.
Pedestals that render on-chain models listed in deploy config.
Arrival gate, control hints and a saved guest counter. Good first build for any land.
Six floating checkpoints and a summit flag, with progress saved per visitor.
Five hidden caches per land, remembered for each visitor who digs them.
Seating circle, live fire and a shared tally of nights spent together.
Market stall showing a headline, note and contact set in deploy config.
- MAX 240 OBJECTS
- MAX 24 QUESTS
- MAX 24000 CODE BYTES
LAND PERMISSIONS
Every extender declares what it needs on a tile. An owner grants rights explicitly; a deployment is refused when the live chain says the caller has none.
- VIEW
- ENTER
- BUILD
- EDIT
- DEPLOY
- MANAGE
- LEASE
- MONETIZE
AGENT TOOLS — GUARDIANS & LAND FORGE
Machine-readable recipes an AI agent can follow. Agents prepare and preview; a human wallet always signs and pays.
- 01SUMMON
Pick a BITMAP. Its block data (/r/blockinfo/<height>) decides body, wings, palette and name — no randomness.
Exact payment, one Guardian per block[ OPEN ] - 02INSCRIBE
Inscribe a Tag 11 delegate to the V7 Guardian master. Any Ordinals explorer renders the same 3D Guardian.
1,000 sat tribute + miner fee[ OPEN ] - 03BREED
Pair two inscribed Guardians (sire + dam). Offspring genome is a pure crossover seeded by both inscription ids.
546 sat pet tribute + miner fee[ OPEN ] - 04VERIFY PEDIGREE
The pet page re-derives the genome from both parents and compares it with the inscribed metadata.
Free
- 01PICK A BITMAP
Enter or select a BITMAP you hold. The floor shows its real parcel layout from the block.
Free[ OPEN ] - 02COMPOSE
Place own blocks (up to 12,000) and drag up to 32 inscribed community pieces anywhere. M moves, R rotates.
Free to design[ OPEN ] - 03ENCODE
Choose Recursive (pieces referenced by id), Lean BWVX2 (greedy boxes) or standalone 3D HTML.
Smallest payload wins[ OPEN ] - 04REINSCRIBE ON THE SAT
Pay commit, sign the parent claim. The BITMAP sat goes in at input 0 and comes back at output 0; piece makers are paid in the reveal.
Miner fee + ≥546 sats per maker[ OPEN ]
Read the Guardian for a BITMAP: DNA, on-chain name, inscription id if inscribed.
{
"input": {
"height": "integer block height"
},
"output": "{ height, name, species, inscribed, inscriptionId | null }"
}Deterministically preview a pet from two inscribed Guardian inscription ids. Same inputs always give the same pet.
{
"input": {
"sireId": "inscription id",
"damId": "inscription id"
},
"output": "{ genome, traits, sire, dam }"
}Encode voxels [x,y,z,color] (color 0–10) as BWVX1 or BWVX2, returning the smaller.
{
"input": {
"height": "integer",
"voxels": "Array<[x,y,z,color]>"
},
"output": "{ f: 'BWVX1'|'BWVX2', h, d: base64 }"
}Build a BWVX_ASM recursive manifest from inscribed pieces + placements (max 32).
{
"input": {
"height": "integer",
"pieces": "Array<{ id, x, y, z, rot }>",
"own": "optional voxels"
},
"output": "{ f: 'BWVX_ASM', h, parts, d? }"
}Check on chain whether an address holds a Bitmap Emporium Resident portrait (image child of 717328.bitmap) or a valid 717328 parcel. Public, read-only.
{
"input": {
"address": "Bitcoin address"
},
"output": "{ address, height: 717328, resident, portraits: inscription ids, parcels: Array<{ index, inscriptionId }>, checkedAt }"
}List the reveal outputs owed to verified piece makers (≥546 sats each, max 8).
{
"input": {
"pieceIds": "inscription ids"
},
"output": "Array<{ address, sats }>"
}Collection: THE OTHERTHINGS (umbrella brand)
children: Guardians, Land (BITMAPs), Forge builds, Pets
note: brand grouping only. Verified Ordinals parentage = Tag 3 parent spent in reveal.
CBOR metadata (RFC 8949)
f "BWVX1" | "BWVX2" | "BWVX_ASM"
h block height (integer)
d base64 payload (legacy readers: meta.d ?? meta.b)
p optional parcel anchor
l optional lineage / tribute list
BWVX1 4 bytes per voxel [X, Y, Z, ColorIdx] palette 0–10
BWVX2 greedy boxes [X, Y, Z, W, H, D, ColorIdx]
ASM parts: [{ id, x, y, z, rot }] fetched live via /r/metadata/<id>