Sonic Unleashed Recompiled Explained: Build Your Own Speed Platformer
Meet the creators of the Sonic Unleashed PC port, understand static recompilation, then build an original speed platformer with free code, assets and AI.
What the creator made

The visible water-running, ring energy and HUD illustrate the speed-platforming design discussed here.
hedge-dev team — Skyth (creator/lead), Sajid (co-creator), with the credited contributors
Unleashed Recompiled is an unofficial Windows and Linux port of the Xbox 360 version of Sonic Unleashed. Its README credits Skyth as creator and lead developer, Sajid as co-creator, and a wider team handling rendering, menus, installation, artwork and mod support.
The project advertises higher resolutions, ultrawide presentation, high frame rates and mod support. Game assets are excluded; installation requires files from the user’s own legitimately acquired copy. The official README directs visitors to this repository rather than an independent official website.
The checked release is v1.0.3. Development and known-issue pages remain separate from release status: a published build does not mean every high-frame-rate issue is fixed.
Latest published release checked: v1.0.3, 3 April 2025 · checked 2026-10-11.
AI involvement is not established by the checked README and release. Recompilation is not evidence that ChatGPT, Claude or Codex made this project.
The original project and its presentation remain at the credited source. Our gallery below contains original concept art and real source-rendered tutorial frames, not creator gameplay screenshots.
How it works: static recompilation
The project uses XenonRecomp for PowerPC-to-C++ translation and XenosRecomp for shader-to-HLSL translation, with a PC runtime and renderer. This differs from rewriting gameplay from observation or emulating the whole console.
- Original executable instructions
- Platform-specific translation
- Native code + compatible runtime
- PC renderer and input
Recompilation translates instructions; decompilation aims to recover higher-level structure; a mechanics recreation writes a new implementation. An AI tool can help with inspection or coding, but those terms alone do not establish AI use.
GPL-3.0 project code; commercial game files have separate rights. Read the original licence ↗. Open-source tool licensing does not license the commercial game’s art, audio, characters or binaries. Forge supplies no ROMs or commercial game downloads.
Learn reverse engineering using permitted practice code → · Explore AI game mashups and creator examples →
What could your game look like?
Skycoil Sprint · 2D speed platformer
A human courier carries a wind beacon through mossy islands. Orange scarf, teal boots, hexagonal charge cells and copper turbines give the game its own visual language.

Reach the beacon on one connected course. Choose between safer steady running and energy-limited boosts; jump over red drones and recover at a checkpoint.
The complete MIT browser example has one course, run/jump/boost, energy cells, four stationary hazards, checkpoint recovery, lives, a timer, pause/restart and a validated local best. Enemy patrols, combat, extra levels and a boss are later milestones in the full AI prompt.



Build your own game: thirteen visual steps
Create your own original game inspired by these gameplay ideas. This tutorial does not reproduce the featured commercial game or imply affiliation with its creators.
Choose your free engine and scope
Run the included independent Canvas/JavaScript course first: no paid editor or external package is required. For an editor-based expansion, Godot’s free MIT engine is a suitable alternative; pin the exact installed version and use its matching documentation. The browser course is a 2D side view, not a Sonic port or a tested Godot project.

Genuine existing Forge capture, 10 October 2026. Choose the genre; it does not show the creator’s game. Set up a complete project
Download the ZIP or all eight files below into one folder. With an installed Python 3, run python -m http.server 8080 there and open http://localhost:8080/index.html. Another static server is fine if .mjs gets a JavaScript MIME type. Do not open modules through file://. Open the console and resolve missing-file errors before changing gameplay.
Choose free resources that fit
Start with the drawn geometry so no art import blocks movement. Kenney’s New Platformer Pack supplies CC0 2D artwork; UI Pack supplies interface art, and Impact Sounds supplies audio. Download at the credited source, keep exact licence files, check frame sizes/formats, and import one sprite and one sound first. These packs do not supply your controller or enemy AI.
Create the player
fresh() creates one mutable player with independent position, velocity and life state. Start creates a running state; Restart replaces the run while the single frame loop remains. Keep world data outside DOM/UI code. In Godot, begin with a CharacterBody2D, visual child and collision shape; handle movement in physics updates using the engine collision API.
Build a connected environment
The public platforms array defines five connected height sections across one 2400-pixel course. Change one section at a time; retest horizontal collision and landings. The gold flag is the recovery point and the teal beacon is the finish. Drawn decoration stays separate from solid platform rectangles. In a Godot adaptation, author a small collision-backed tile scene before making a large world.
Tune running, jumping and the camera
world.mjs ramps velocity toward a target instead of teleporting. Run is the normal movement; Shift/Boost increases the cap while consuming energy. A short jump buffer and coyote window soften input timing, releasing Jump trims height, and the camera follows within world bounds. Test standing starts, direction reversal, early/late jump presses, steps and landings before raising speed.

Actual original simulation and renderer after a jump; the scene uses geometric tutorial art. Add energy, pickups and the delivery goal
Each hexagonal cell has a stable index and can be collected once. Boost drains energy, ordinary movement restores it, and a cell adds a bounded refill. Crossing the beacon finishes only a running run. Status text outside Canvas reports the outcome; pause or an ended run cannot keep awarding cells. Avoid promising that the exact franchise movement or collection formulas have been recreated.
Handle hazards and later enemy AI
The supplied red drones are stationary collision hazards. Damage has a recovery window, consumes one life, and restores the checkpoint without duplicating cells. Three lost lives or an expired timer ends the run. Add a patrol/wind-up/recover enemy as a separate upgrade only after these rules work; reuse a compatible public engine system first.
Build menus and desktop/mobile controls
Start/Resume, Pause and Restart are real DOM buttons. Desktop A/D or arrows, Space and Shift feed the same world inputs as touch hold buttons. Track pointer IDs so one released finger cannot clear another held input; release on pointer cancellation and clear keys when focus changes. Leaving the window/tab pauses, and Resume is explicit. Test actual keyboard, touch, focus and status announcements on the published target.
Add animation and optional effects
render.mjs draws a scarf, running boots, layered islands and resource HUD without changing the simulation. Reduced motion removes decorative movement. Optional short tones start only after enabling sound and use bounded oscillator lifetimes. Replace shapes with licensed frames after checking the actual atlas; add readable landing/boost effects with capped counts rather than hiding hazards under particles.
Save only validated progress
This first reference saves the best completed time locally, with version 1, a finite 0–60-second value and 0–12 cells. Storage failures do not interrupt the game. Restart resets the run, not the saved best. Test missing, corrupt, future-version and unavailable storage. Full campaign progression/cloud saving are later systems; editable local records are not trusted competitive scores.
Test the real game
Run node check.mjs with Node 24.19.0 or compatible recent Node. The checks drive the full route at several simulated update rates, test rejected time values, boost limits, one-use pickups, checkpoint damage, pause/finish immutability, save validation and fresh independent runs. Native Canvas frames demonstrate rendering; browser input, real phone performance, clipboard and sound need separate target checks.
Publish and add one upgrade
Upload every example file to a static HTTPS host such as GitHub Pages, keeping relative module paths intact. Check the exact published index.html, every file MIME type, keyboard/phone controls, win/loss, tab pause, blocked storage and two restarts. Then use one upgrade prompt below. The larger multi-level game in COPY TO MAKE GAME is a development plan, not a claim that this small reference already includes it.
Complete original code and download
Download complete MIT source ZIP · Run, test and publish instructions · MIT licence
index.html — complete source
Open this file<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Skycoil Sprint — original speed-platformer example</title><link rel="stylesheet" href="game.css"><script type="module" src="main.mjs"></script></head><body><main><a href="/guides/sonic-unleashed-recompiled-build-speed-platformer">← Tutorial and complete source</a><h1>Skycoil Sprint</h1><p>Original 2D tutorial game. Run to the beacon; jump over red drones and collect teal energy cells. Concept artwork in the guide shows a possible future art direction.</p><canvas id="game" width="960" height="480" tabindex="0" aria-label="Skycoil Sprint game. A D or arrows move, Space jumps, Shift boosts."></canvas><p id="status" role="status" aria-live="polite">Ready. Press Start.</p><p id="hud">Lives 3 · Energy 100 · Cells 0 · Time 0</p><div class="menu"><button id="start">Start / Resume</button><button id="pause">Pause</button><button id="restart">Restart</button></div><div class="controls" aria-label="Touch controls"><button data-key="left" aria-label="Hold to move left">◀</button><button data-key="right" aria-label="Hold to move right">▶</button><button data-key="jump">JUMP</button><button data-key="boost">BOOST</button></div><label><input id="sound" type="checkbox"> Enable short sound effects</label><p>A/D or arrows · Space to jump · Shift to boost · P to pause. Leaving the tab pauses the game. Restart makes a fresh run; your local best is separate.</p><p id="best">No saved local best yet.</p><p><a href="README.md">Run, test and publish this example</a> · <a href="LICENSE.txt">MIT licence</a></p></main></body></html>
world.mjs — complete source
Open this file// Original, independently written public tutorial. MIT, 2026 BINX Forge.
export const WIDTH=960,HEIGHT=480,GOAL=2260;
export const platforms=[{x:0,y:404,w:620,h:76},{x:620,y:372,w:480,h:108},{x:1100,y:404,w:440,h:76},{x:1540,y:350,w:450,h:130},{x:1990,y:404,w:410,h:76}];
export const hazards=[{x:440,y:380,w:34,h:24},{x:930,y:348,w:34,h:24},{x:1430,y:380,w:34,h:24},{x:1810,y:326,w:34,h:24}];
const overlap=(a,b)=>a.x<b.x+b.w&&a.x+a.w>b.x&&a.y<b.y+b.h&&a.y+a.h>b.y;
export function fresh(){return {status:'ready',time:0,lives:3,energy:100,cells:0,player:{x:70,y:368,w:24,h:36,vx:0,vy:0,grounded:true},checkpoint:70,collected:[],camera:0,coyote:.1,buffer:0,jumpHeld:false,immune:0,message:'Reach the wind beacon. Jump over red drones; collect teal cells.'};}
export function start(s){if(s.status==='ready'||s.status==='paused')s.status='running';}
export function pause(s){if(s.status==='running'){s.status='paused';s.jumpHeld=false;s.buffer=0;}}
export function damage(s){if(s.status!=='running'||s.immune>0)return;s.lives--;if(s.lives<=0){s.status='lost';s.message='Out of lives. Restart the route.';return;}const p=s.player;p.x=s.checkpoint;p.y=s.checkpoint<1000?368:368;p.vx=p.vy=0;p.grounded=false;s.immune=1.4;s.energy=Math.max(s.energy,35);s.message='Checkpoint recovered. Your collected cells are safe.';}
function tick(s,input,dt){
const p=s.player;s.time+=dt;if(s.time>=60){s.time=60;s.status='lost';s.message='Time expired. Try a cleaner route.';return;}
s.immune=Math.max(0,s.immune-dt);s.buffer=Math.max(0,s.buffer-dt);s.coyote=p.grounded?.1:Math.max(0,s.coyote-dt);
const dir=Number(!!input.right)-Number(!!input.left),boost=!!input.boost&&dir!==0&&s.energy>0;
s.energy=Math.max(0,Math.min(100,s.energy+(boost?-38:16)*dt));
const target=dir*(boost?460:260),rate=dir?1400:1700;p.vx+=Math.max(-rate*dt,Math.min(rate*dt,target-p.vx));
if(s.buffer>0&&s.coyote>0){p.vy=-570;p.grounded=false;s.coyote=0;s.buffer=0;}
if(!input.jump&&p.vy<-260)p.vy=-260;
p.vy=Math.min(820,p.vy+1500*dt);const oldBottom=p.y+p.h;
p.x=Math.max(0,Math.min(2370,p.x+p.vx*dt));
for(const q of platforms){if(overlap(p,q)){if(p.vx>0)p.x=q.x-p.w;else if(p.vx<0)p.x=q.x+q.w;p.vx=0;}}
p.y+=p.vy*dt;p.grounded=false;
for(const q of platforms){if(overlap(p,q)){if(p.vy>=0&&oldBottom<=q.y+2){p.y=q.y-p.h;p.vy=0;p.grounded=true;}else if(p.vy<0){p.y=q.y+q.h;p.vy=0;}}}
for(let i=0;i<12;i++){const c={x:200+i*165,y:(i>=3&&i<=5?280:i>=9&&i<=10?260:316),w:20,h:20};if(!s.collected.includes(i)&&overlap(p,c)){s.collected.push(i);s.cells++;s.energy=Math.min(100,s.energy+24);}}
if(p.x>=1140&&p.grounded)s.checkpoint=1140;
if(p.y>HEIGHT+100||hazards.some(h=>overlap(p,h)))damage(s);
if(p.x>=GOAL&&s.status==='running'){s.status='won';s.message='Delivery complete! '+s.cells+' energy cells, '+s.time.toFixed(2)+' seconds.';}
s.camera=Math.max(0,Math.min(1440,p.x-280));
}
export function advance(s,input={},elapsed=0){if(s.status!=='running'||!Number.isFinite(elapsed)||elapsed<=0)return;const rising=!!input.jump&&!s.jumpHeld;s.jumpHeld=!!input.jump;if(rising)s.buffer=.13;let left=Math.min(elapsed,.1);for(let i=0;i<12&&left>1e-9&&s.status==='running';i++){const dt=Math.min(left,1/120);tick(s,input,dt);left-=dt;}}
export function readBest(raw){try{const s=JSON.parse(raw);return s?.version===1&&Number.isFinite(s.best)&&s.best>0&&s.best<=60&&Number.isInteger(s.cells)&&s.cells>=0&&s.cells<=12?{version:1,best:s.best,cells:s.cells}:null;}catch{return null;}}
render.mjs — complete source
Open this fileimport {WIDTH,HEIGHT,platforms,hazards,GOAL} from './world.mjs';
export function render(ctx,s,{reduced=false}={}){
ctx.clearRect(0,0,WIDTH,HEIGHT);const bg=ctx.createLinearGradient(0,0,0,HEIGHT);bg.addColorStop(0,'#123649');bg.addColorStop(1,'#377e80');ctx.fillStyle=bg;ctx.fillRect(0,0,WIDTH,HEIGHT);
for(let i=0;i<8;i++){const x=i*180-s.camera*.22;ctx.fillStyle=i%2?'#215364':'#296577';ctx.beginPath();ctx.moveTo(x,420);ctx.lineTo(x+90,170+(i%3)*38);ctx.lineTo(x+190,420);ctx.fill();}
ctx.save();ctx.translate(-s.camera,0);
for(const q of platforms){ctx.fillStyle='#173840';ctx.fillRect(q.x,q.y,q.w,q.h);ctx.fillStyle='#67b58c';ctx.fillRect(q.x,q.y,q.w,10);for(let i=0;i<q.w;i+=50){ctx.fillStyle='#244b51';ctx.fillRect(q.x+i+12,q.y+30,25,28);}}
for(let i=0;i<12;i++){if(s.collected.includes(i))continue;const x=210+i*165,y=(i>=3&&i<=5?290:i>=9&&i<=10?270:326);ctx.fillStyle='#6dfff0';ctx.beginPath();for(let j=0;j<6;j++){const a=j*Math.PI/3;ctx.lineTo(x+12*Math.cos(a),y+12*Math.sin(a));}ctx.closePath();ctx.fill();}
for(const h of hazards){ctx.fillStyle='#fd7977';ctx.fillRect(h.x,h.y,h.w,h.h);ctx.fillStyle='#271e35';ctx.fillRect(h.x+5,h.y+7,7,7);ctx.fillRect(h.x+22,h.y+7,7,7);}
ctx.fillStyle='#fbc86e';ctx.fillRect(1140,318,6,86);ctx.fillRect(1146,318,50,26);ctx.fillStyle='#83ffdf';ctx.fillRect(GOAL+20,280,8,124);ctx.beginPath();ctx.arc(GOAL+24,272,24,0,Math.PI*2);ctx.fill();
const p=s.player,phase=reduced?0:Math.sin(s.time*22)*(p.grounded?Math.min(1,Math.abs(p.vx)/260):.3);ctx.save();ctx.translate(p.x+p.w/2,p.y+p.h/2);if(s.immune>0&&!reduced&&Math.floor(s.time*12)%2)ctx.globalAlpha=.45;
ctx.fillStyle='#ffb958';ctx.fillRect(-9,-6,18,21);ctx.fillStyle='#f6d7b0';ctx.fillRect(-7,-19,14,14);ctx.fillStyle='#1d2933';ctx.fillRect(-8,-20,17,5);ctx.fillStyle='#ff754b';ctx.fillRect(-10,-4,25,5);ctx.fillRect(-22,-4+(reduced?0:phase*2),18,4);ctx.fillStyle='#6bffe8';ctx.fillRect(-9,14,7,7+phase*4);ctx.fillRect(3,14,7,7-phase*4);ctx.restore();ctx.restore();
ctx.fillStyle='rgba(5,20,29,.85)';ctx.fillRect(18,18,325,70);ctx.fillStyle='#e5fff7';ctx.font='bold 18px sans-serif';ctx.fillText('SKYCOIL SPRINT',32,44);ctx.font='14px sans-serif';ctx.fillText('Energy '+Math.round(s.energy)+' • Cells '+s.cells+' • Lives '+s.lives,32,72);
if(s.status!=='running'){ctx.fillStyle='rgba(3,15,24,.72)';ctx.fillRect(0,HEIGHT/2-42,WIDTH,84);ctx.fillStyle='#fff5dd';ctx.textAlign='center';ctx.font='bold 25px sans-serif';ctx.fillText(s.status==='ready'?'Ready for a wind-run?':s.status==='paused'?'Paused':s.status==='won'?'Delivery complete!':'Try again',WIDTH/2,HEIGHT/2+8);ctx.textAlign='left';}
}
main.mjs — complete source
Open this fileimport {fresh,start,pause,advance,readBest} from './world.mjs';
import {render} from './render.mjs';
const canvas=document.getElementById('game'),ctx=canvas.getContext('2d'),status=document.getElementById('status'),hud=document.getElementById('hud'),bestNode=document.getElementById('best'),sound=document.getElementById('sound'),motion=matchMedia('(prefers-reduced-motion: reduce)');
let state=fresh(),last=0,audio=null,saved=false;const keyboard=new Set(),pointers=new Map(),keyMap={ArrowLeft:'left',KeyA:'left',ArrowRight:'right',KeyD:'right',Space:'jump',ShiftLeft:'boost',ShiftRight:'boost'},saveKey='forge.skycoil.best.v1';
let best=null;try{best=readBest(localStorage.getItem(saveKey));}catch{}
function showBest(){bestNode.textContent=best?'Local best: '+best.best.toFixed(2)+'s · '+best.cells+' cells. Stored only in this browser.':'No saved local best yet.';}showBest();
function clear(){keyboard.clear();pointers.clear();state.jumpHeld=false;state.buffer=0;}
function message(){status.textContent=state.message;}
function tone(){if(!sound.checked)return;try{audio??=new AudioContext();if(audio.state==='suspended')return;const o=audio.createOscillator(),g=audio.createGain();o.frequency.value=680;g.gain.setValueAtTime(.035,audio.currentTime);g.gain.exponentialRampToValueAtTime(.0001,audio.currentTime+.07);o.connect(g).connect(audio.destination);o.start();o.stop(audio.currentTime+.08);o.onended=()=>{o.disconnect();g.disconnect();};}catch{sound.checked=false;}}
sound.addEventListener('change',async()=>{if(sound.checked){try{audio??=new AudioContext();await audio.resume();tone();}catch{sound.checked=false;}}});
document.getElementById('start').addEventListener('click',()=>{clear();start(state);state.message='Run to the beacon. Jump over red drones.';message();canvas.focus();});
document.getElementById('pause').addEventListener('click',()=>{pause(state);clear();state.message='Paused. Press Resume.';message();});
document.getElementById('restart').addEventListener('click',()=>{clear();state=fresh();start(state);saved=false;message();canvas.focus();});
canvas.addEventListener('keydown',e=>{if(keyMap[e.code]){e.preventDefault();keyboard.add(keyMap[e.code]);}if(e.code==='KeyP'&&!e.repeat){pause(state);clear();state.message='Paused. Press Resume.';message();}});
window.addEventListener('keyup',e=>{if(keyMap[e.code])keyboard.delete(keyMap[e.code]);});
canvas.addEventListener('blur',()=>{keyboard.clear();state.jumpHeld=false;state.buffer=0;});
for(const b of document.querySelectorAll('[data-key]')){b.addEventListener('pointerdown',e=>{e.preventDefault();b.setPointerCapture(e.pointerId);pointers.set(e.pointerId,b.dataset.key);});for(const name of ['pointerup','pointercancel','lostpointercapture'])b.addEventListener(name,e=>pointers.delete(e.pointerId));}
function hidden(){if(document.hidden){clear();pause(state);state.message='Paused while away. Press Resume.';message();}}
document.addEventListener('visibilitychange',hidden);window.addEventListener('blur',()=>{clear();pause(state);message();});window.addEventListener('pagehide',()=>{clear();pause(state);audio?.suspend();});
function frame(now){const dt=last?(now-last)/1000:0;last=now;const input=Object.fromEntries([...keyboard,...pointers.values()].map(k=>[k,true])),cells=state.cells,phase=state.status;advance(state,input,dt);if(state.cells>cells)tone();if(phase!==state.status)message();render(ctx,state,{reduced:motion.matches});hud.textContent='Lives '+state.lives+' · Energy '+Math.round(state.energy)+' · Cells '+state.cells+' · Time '+state.time.toFixed(1)+' / 60s';
if(state.status==='won'&&!saved){saved=true;if(!best||state.time<best.best){const record={version:1,best:state.time,cells:state.cells};try{localStorage.setItem(saveKey,JSON.stringify(record));best=record;showBest();}catch{bestNode.textContent='Run complete; local storage is unavailable.';}}}requestAnimationFrame(frame);}
message();requestAnimationFrame(frame);
game.css — complete source
Open this file:root{color-scheme:dark;font-family:system-ui,sans-serif;background:#071b25;color:#e0f8f3}body{margin:0}main{max-width:1000px;margin:auto;padding:20px}a{color:#82ffe2}canvas{width:100%;height:auto;display:block;border:1px solid #4b7780;border-radius:16px;outline-offset:4px}button{border:1px solid #77cab6;border-radius:100px;min-width:64px;min-height:52px;background:#163b43;color:#effff8;font:700 14px system-ui;padding:10px 16px;cursor:pointer}button:focus-visible,a:focus-visible{outline:3px solid #ffd578;outline-offset:4px}.menu,.controls{display:flex;gap:10px;flex-wrap:wrap;margin:15px 0}.controls button{touch-action:none;user-select:none}.controls{justify-content:center}.controls button[data-key=jump],.controls button[data-key=boost]{background:#37504a}p{line-height:1.6}#hud{font-variant-numeric:tabular-nums}@media(max-width:500px){main{padding:12px}h1{font-size:26px}.controls{gap:6px}button{padding:8px 12px;min-width:52px}}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto}}
README.md — complete source
Open this file# Skycoil Sprint
Original independent MIT browser tutorial. No private BINX Engine code, third-party game source or purchased assets.
Download all eight files (including this README and check.mjs), keeping them together. Serve this folder with any local static HTTP server supporting .mjs as JavaScript; for example, if Python 3 is installed, run `python -m http.server 8080`, then open http://localhost:8080/index.html. Opening index.html as file:// does not load modules consistently. Python is only an optional server; the game has no Python dependency.
Run the actual rules checks with Node 24.19.0 or compatible recent Node: `node check.mjs`.
Controls: A/D or arrows, Space jump, Shift boost. Touch hold buttons are provided. Desktop controls require focus on the canvas. Start/Resume restores focus. One 2400px connected course, jump buffering/coyote time, bounded acceleration, boost energy, pickups, four stationary hazard drones, checkpoint recovery, three lives, 60-second outcome, pause/restart, optional short generated tones and validated browser-local best-time record.
Publish the whole folder on a static HTTPS host with MIME support for .mjs. GitHub Pages instructions: https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site . Check the exact public URL, keyboard and real phone touch, sound opt-in, tab/focus changes, blocked storage, loss/win and two restarts. Simulation/source-rendered frames do not certify browser input, phone, audio or assistive acceptance.
The game renders its own geometric artwork. The guide's AI concept illustration is a proposed higher-fidelity direction, not this game's screenshot or a downloadable art pack. Stationary hazard drones are obstacles, not pathfinding enemy AI or combat. See the guide's separate ambitious build and upgrade prompts for those systems. No multiplayer, cloud save, 3D exporter or franchise recreation.
LICENSE.txt — complete source
Open this fileMIT License
Copyright (c) 2026 BINX Forge
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
check.mjs — complete source
Open this fileimport assert from 'node:assert/strict';
import {fresh,start,pause,advance,damage,readBest,GOAL} from './world.mjs';
let passed=0;function check(name,fn){fn();passed++;console.log('PASS '+name);}
function drive(rate){const s=fresh();start(s);let held=0;for(let i=0;i<rate*60+1&&s.status==='running';i++){const x=s.player.x,near=[440,620,930,1430,1540,1810].some(a=>a-x>0&&a-x<85);if(s.player.grounded&&near&&held<=0)held=.18;advance(s,{right:true,jump:held>0},1/rate);held-=1/rate;}return s;}
check('actual route wins at 15/30/60/120 simulated updates',()=>{for(const rate of [15,30,60,120]){const s=drive(rate);assert.equal(s.status,'won');assert.ok(s.player.x>=GOAL);assert.equal(s.lives,3);assert.ok(s.time<10);}});
check('invalid elapsed time cannot mutate a run; long stalls are capped',()=>{const s=fresh();start(s);const old=JSON.stringify(s);for(const t of [NaN,Infinity,-1,0])advance(s,{right:true},t);assert.equal(JSON.stringify(s),old);advance(s,{right:true},9);assert.ok(s.time<=.100001);assert.ok(s.player.x<90);});
check('boost drains bounded energy and raises movement speed',()=>{const a=fresh(),b=fresh();start(a);start(b);for(let i=0;i<30;i++){advance(a,{right:true},1/120);advance(b,{right:true,boost:true},1/120);}assert.ok(b.player.x>a.player.x);assert.ok(b.energy<a.energy);b.energy=0;advance(b,{right:true,boost:true},.1);assert.ok(b.energy>=0&&b.energy<=100);});
check('jump button hold does not repeatedly jump in midair',()=>{const s=fresh();start(s);advance(s,{jump:true},.05);const before=s.player.vy;advance(s,{jump:true},.05);assert.ok(s.player.vy>before);assert.ok(!s.player.grounded);});
check('pickups award once even after reentering the same cell',()=>{const s=fresh();start(s);s.player.x=200;s.player.y=310;advance(s,{},1/120);assert.equal(s.cells,1);s.player.x=200;s.player.y=310;advance(s,{},1/120);assert.equal(s.cells,1);});
check('damage recovers checkpoint once and preserves collected cells',()=>{const s=fresh();start(s);s.checkpoint=1140;s.cells=2;s.collected=[1,2];damage(s);assert.equal(s.lives,2);assert.equal(s.player.x,1140);assert.equal(s.cells,2);damage(s);assert.equal(s.lives,2);s.immune=0;damage(s);s.immune=0;damage(s);assert.equal(s.status,'lost');});
check('pause, win and loss freeze simulation and restart is independent',()=>{for(const mode of ['paused','won','lost']){const s=fresh();s.status=mode;const old=JSON.stringify(s);advance(s,{right:true,jump:true,boost:true},.1);assert.equal(JSON.stringify(s),old);}const a=drive(60),b=fresh();assert.equal(b.cells,0);assert.equal(b.lives,3);assert.notEqual(a.player,b.player);start(b);pause(b);assert.equal(b.status,'paused');});
check('timeout ends exactly at sixty simulated seconds',()=>{const s=fresh();start(s);s.time=59.99;advance(s,{},.1);assert.equal(s.time,60);assert.equal(s.status,'lost');});
check('local best accepts only versioned bounded values',()=>{assert.deepEqual(readBest('{"version":1,"best":8.8,"cells":9}'),{version:1,best:8.8,cells:9});for(const raw of ['',null,'{}','{"version":2,"best":8,"cells":2}','{"version":1,"best":999,"cells":1}','{"version":1,"best":8,"cells":13}','{"version":1,"best":0,"cells":0}'])assert.equal(readBest(raw),null);});
console.log(passed+' rules checks passed; browser/phone/audio/clipboard acceptance is separate.');
Free vs paid resources
| Free first | What it contributes | Licence / compatibility |
|---|---|---|
| Godot Godot Engine contributors | Free, open-source game editor for 2D and 3D projects, with scene, animation and interface tools. | MIT · Creator source ↗ Import one compatible file; art is separate from rules. |
| New Platformer Pack Kenney | Platformer tiles, character poses, enemies and backgrounds, with ten short sounds. | CC0-1.0 · Creator source ↗ Import one compatible file; art is separate from rules. |
| UI Pack Kenney | Interface artwork for buttons, panels and sliders. | CC0-1.0 · Creator source ↗ Import one compatible file; art is separate from rules. |
| Impact Sounds Kenney | Short impacts and footsteps for action feedback and surface changes. | CC0-1.0 · Creator source ↗ Import one compatible file; art is separate from rules. |
Free source code, artwork, engine licences and commercial game files are separate. Check permissions to sell a finished game, resell files and include them in source/template products. An AI opinion is not licence clearance.
COPY TO MAKE GAME
Paste the complete development brief into your AI. It includes the working source, verified resource facts, milestones and target tests. Review it and adapt the identity to your own idea.
Select and copy the text manually if clipboard access is unavailable.
Review the complete game-making prompt
MAKE YOUR GAME EVEN BETTER
Beginner: Replace geometric art with original animation
Review or select the upgrade text.
Intermediate: Patrol drones and a second route
Review or select the upgrade text.
Questions before you start
Is this an AI Sonic Unleashed remake or a commercial game download?
No. The original project is a separately credited PC port. Our example and AI prompt create Skycoil Sprint, an independent game with its own identity.
Can ChatGPT, Claude or Codex build the full game?
Use an AI tool that can edit and run your chosen project, work through the milestones and review its output. A copied prompt alone is not a tested finished game. Follow the source and licence checks even when the AI sounds confident.
Why free code before paid assets?
A working controller, outcome and restart reveal what the game actually needs. Buy an optional resource only when its files, compatibility and rights solve that need.
More games to build
Sources and creator credit
- Unleashed Recompiled official repository ↗
- Official release ↗
- Project licence ↗
- XenonRecomp ↗
- XenosRecomp ↗
- Godot player bodies ↗
- Godot licence ↗
- Kenney platformer pack ↗
- JavaScript Canvas ↗
- GitHub Pages publishing ↗
Project facts checked 2026-10-11. Resource editions carry their own checked dates. No measured search volume, ranking promise or independent AI-output acceptance is claimed. hedge-dev team — Skyth (creator/lead), Sajid (co-creator), with the credited contributors owns the featured project; no endorsement is implied. Corrections and review policy ↗