| # | player | phone | best | ledges | runs | last run |
|---|
Stickyfoot is built from three separable parts: an editor that writes segment files, a runtime that links those segments into an endless chain, and a local player store. Here is how each one works, and what the files look like.
The world is a singly linked list of LevelNode objects. Every node is exactly 1024 Γ 576 world pixels β the same frame the editor draws in, sized for a small tablet β so nodes butt together with no seams and no scaling maths.
head β [node 12] β [node 13] β [node 14] β tail
x=12288 x=13312 x=14336
Each frame the chain does two things:
tail.x + 1024 < camX + viewWidth + 1024, a new node is appended: the next segment is chosen from the pool, its art is baked once into an offscreen canvas, and its collision boxes are translated into world space.head.x + 1024 < camX, the head node has fully left the screen. It is unlinked, its baked canvas is sized to 0 Γ 0 to release the bitmap, its box array is emptied, and its next/prev pointers are nulled so the garbage collector can take the whole object.Only three or four nodes exist at any moment, no matter how long the run lasts. Open the node monitor on the play screen (F3) to watch spawned, live, and destroyed counts plus the live bitmap budget.
The editor exports a pair of files that share an id and a frame size. Collision never mentions art, art never mentions collision; the runtime joins them by id.
{
"format": "stickyfoot.collision",
"version": 1,
"id": "seg_meadow_01",
"width": 1024,
"height": 576,
"entry": { "x": 96, "y": 380 },
"boxes": [
{ "x": 0, "y": 448, "w": 256, "h": 128, "type": "platform" },
{ "x": 448, "y": 320, "w": 192, "h": 64, "type": "platform" },
{ "x": 704, "y": 256, "w": 64, "h": 192, "type": "cling" },
{ "x": 448, "y": 288, "w": 192, "h": 32, "type": "hazard" }
]
}
{
"format": "stickyfoot.art",
"version": 1,
"id": "seg_meadow_01",
"width": 1024,
"height": 576,
"layers": [
{ "name": "background", "z": 0, "pieces": [ { "sprite": "fern", "x": 320, "y": 384, "w": 64, "h": 64 } ] },
{ "name": "midground", "z": 1, "pieces": [ { "sprite": "dirt", "x": 0, "y": 448, "w": 256, "h": 128 } ] },
{ "name": "foreground", "z": 2, "pieces": [] }
]
}
entry is where the gecko stands when a segment is the first node of a run, and the camera centres on it, so put it over a ledge. The editor draws a ghost gecko there. If a run starts on a segment whose entry is not above anything solid, the runtime falls back to the leftmost ledge.
Both files are validated on load: mismatched width/height, a missing id, or an unknown box type is rejected rather than silently drawn wrong. A segment with art but no collision is playable but empty; a segment with collision but no art renders as untextured hitboxes, which is useful while blocking out a layout.
platform β solid. Landed on from above, blocks from the sides and below.cling β a gecko surface. Stick to any face of it, including walls and ceilings, then launch again.hazard β touching it ends the run.bounce β springy. Reverses vertical speed at 80% and keeps you airborne.The palette takes imported PNG, GIF, WebP or JPEG files. One thing decides whether a piece looks right: its size in pixels. Every art pixel is drawn as a 4 Γ 4 block, and a piece repeats across whatever rectangle you drag out, so:
The starter file button hands you a correctly sized PNG to paint over: a 16 Γ 16 shaded block that repeats cleanly, a 32 Γ 32 version of the same, or a transparent 32 Γ 32 guide with corner ticks and a centre cross for lining up a prop. Paint over it, keep the canvas size, save, and import.
Imported pieces live in this browser under stickyfoot.art.v1 and appear in the palette with a dashed border. Exported art files carry a copy of every custom piece they use, in a sprites block keyed by sprite name:
{
"format": "stickyfoot.art",
"id": "seg_meadow_01",
"sprites": {
"art_mossy_brick": { "label": "mossy", "w": 16, "h": 16, "src": "data:image/png;base64,β¦" }
},
"layers": [
{ "name": "midground", "z": 1,
"pieces": [ { "sprite": "art_mossy_brick", "x": 0, "y": 448, "w": 256, "h": 128 } ] }
]
}
So a segment stays portable: importing it on another machine also installs the art it needs. Saved maps skip the copy, since the library already has the pixels. If a piece ever goes missing, the game draws a magenta box where it should be rather than quietly leaving a hole.
The editor edits a map: an ordered list of slides, where one slide is one node. The strip under the canvas is that list, left to right, in the order the runtime will link them. Click a slide to edit it; the + between two slides inserts there, either empty or as a copy of the slide you are on.
Saving a map keeps it in this browser under stickyfoot.maps.v1 and makes it the active map, so the world switch on the play screen can run it. βPlay from this slideβ pins the slides from the current one onward to the head of the chain and lets the endless generator take over after the last one. Loading the example gives you a copy of the opening six nodes of the built-in world, editable without touching the original.
A whole map exports as one file, which is just the pairs collected in order:
{
"format": "stickyfoot.map",
"version": 1,
"id": "map_fern_hollow",
"name": "Fern Hollow",
"width": 1024, "height": 576,
"nodes": [
{ "collision": { β¦ }, "art": { β¦ } },
{ "collision": { β¦ }, "art": { β¦ } }
]
}
Import takes either a .map.json (replaces the whole map) or one .collision.json + .art.json pair (replaces the current slide), so the per-segment files stay the unit you can hand to someone else.
At rest, a drag vector d is measured from where the pointer went down to where it is now. Launch velocity is the reverse of that drag, clamped and scaled:
power = min(|d|, 190) / 190 // 0 β¦ 1
v = normalize(-d) Γ power Γ 23 // world px per tick
each tick: vy += 0.62 ; x += vx ; y += vy ; vx *= 0.9994
The simulation runs on a fixed 60 Hz accumulator so the arc is identical on any refresh rate. The dotted preview is the same integrator run forward, and it gets shorter as your score climbs β early ledges show you the whole arc, later ones make you read it.
Run game in the header launches the cabinet: fullscreen, no page chrome, no navigation. It is meant to be left running at an event, so it does not let go on its own β the browser back button is caught and pushed back, closing the tab asks for confirmation, and the only way out is the β» button, which asks before it shuts down. Fullscreen is the one thing a browser will always take back: if someone presses Escape the cabinet keeps running filled to the window and the βΆ button restores it.
The attract screen shows the top eight names and scores and offers one action: play. A cabinet run is anonymous while it is being played β there is no player to pick. When the run ends, the score is held aside and the player is asked for a name, an email and a phone number. All three are trimmed of surrounding and repeated whitespace, then checked: at least two characters of name, an address of the form name@host.tld, and 7 to 15 digits of phone. If any of them is missing or malformed the score is discarded β nothing is written. The same happens if the player presses discard, or walks away and the ninety-second timer runs out.
A score that passes is matched against the existing players by email or by phone digits, ignoring case and formatting, so a regular keeps one row on the board rather than collecting duplicates. Their best score is the highest they have ever posted, not the most recent.
Players, high scores and saved segments are kept in this browser's local storage under the stickyfoot.* keys. Nothing is sent anywhere, and clearing the browser's site data erases it. Exported segment files are plain JSON you can keep, edit by hand, or hand to someone else to import.