` is the container where Phaser will insert the game canvas.
+- Phaser is loaded first, then your game script.
+
+### Creating the Game Instance
+
+In `js/main.js`, create the Phaser game object and register a game state:
+
+```javascript
+// Create a Phaser game instance
+// Parameters: width, height, renderer, DOM element ID
+window.onload = function () {
+ let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game');
+
+ // Add and start the play state
+ game.state.add('play', PlayState);
+ game.state.start('play');
+};
+```
+
+- `960, 600` sets the game canvas dimensions in pixels.
+- `Phaser.AUTO` lets Phaser choose between WebGL and Canvas rendering automatically.
+- `'game'` is the ID of the DOM element that will contain the canvas.
+
+### The PlayState Object
+
+Define the game state as an object with lifecycle methods:
+
+```javascript
+PlayState = {};
+
+PlayState.init = function () {
+ // Called first when the state starts
+};
+
+PlayState.preload = function () {
+ // Load all assets here
+};
+
+PlayState.create = function () {
+ // Create game entities and set up the world
+};
+
+PlayState.update = function () {
+ // Called every frame (~60 times per second)
+ // Handle game logic, input, collisions here
+};
+```
+
+- `init` -- runs first; used for configuration and receiving parameters.
+- `preload` -- used to load all assets (images, audio, JSON) before the game starts.
+- `create` -- called once after assets are loaded; used to create sprites, groups, and game objects.
+- `update` -- called every frame at ~60fps; used for input handling, physics checks, and game logic.
+
+At this point you should see an empty black canvas rendered on the page.
+
+---
+
+## The Game Loop
+
+Phaser uses a game loop architecture. Every frame, Phaser calls `update()`, which is where you handle input, move sprites, and check collisions. Before the loop starts, `preload()` loads assets and `create()` sets up the initial game state.
+
+### Loading and Displaying the Background
+
+Start by loading and displaying a background image to verify the game loop is working:
+
+```javascript
+PlayState.preload = function () {
+ this.game.load.image('background', 'images/background.png');
+};
+
+PlayState.create = function () {
+ // Add the background image at position (0, 0)
+ this.game.add.image(0, 0, 'background');
+};
+```
+
+- `this.game.load.image(key, path)` loads an image and assigns it a key for later reference.
+- `this.game.add.image(x, y, key)` creates a static image at the given position.
+
+You should now see the background image rendered in the game canvas.
+
+### Understanding the Frame Cycle
+
+```
+preload() -> [assets loaded] -> create() -> update() -> update() -> update() -> ...
+```
+
+Each call to `update()` represents one frame. The game targets 60 frames per second. All movement, input reading, and collision detection happen inside `update()`.
+
+---
+
+## Creating Platforms
+
+Platforms are the surfaces the hero walks and jumps on. They are loaded from the level JSON data and created as physics-enabled sprites arranged in a group.
+
+### Loading Platform Assets
+
+Load the level JSON data and all platform tile images in `preload`:
+
+```javascript
+PlayState.preload = function () {
+ this.game.load.image('background', 'images/background.png');
+
+ // Load level data
+ this.game.load.json('level:1', 'data/level01.json');
+
+ // Load platform images
+ this.game.load.image('ground', 'images/ground.png');
+ this.game.load.image('grass:8x1', 'images/grass_8x1.png');
+ this.game.load.image('grass:6x1', 'images/grass_6x1.png');
+ this.game.load.image('grass:4x1', 'images/grass_4x1.png');
+ this.game.load.image('grass:2x1', 'images/grass_2x1.png');
+ this.game.load.image('grass:1x1', 'images/grass_1x1.png');
+};
+```
+
+### Spawning Platforms from Level Data
+
+Create a method to load the level and spawn each platform as a sprite inside a physics group:
+
+```javascript
+PlayState.create = function () {
+ // Add the background
+ this.game.add.image(0, 0, 'background');
+
+ // Load level data and spawn entities
+ this._loadLevel(this.game.cache.getJSON('level:1'));
+};
+
+PlayState._loadLevel = function (data) {
+ // Create a group for platforms
+ this.platforms = this.game.add.group();
+
+ // Spawn each platform from the level data
+ data.platforms.forEach(this._spawnPlatform, this);
+};
+
+PlayState._spawnPlatform = function (platform) {
+ // Add a sprite at the platform's position using the specified image
+ let sprite = this.platforms.create(platform.x, platform.y, platform.image);
+
+ // Enable physics on this platform
+ this.game.physics.enable(sprite);
+
+ // Make platform immovable so it doesn't get pushed by the hero
+ sprite.body.allowGravity = false;
+ sprite.body.immovable = true;
+};
+```
+
+- `this.game.add.group()` creates a Phaser group -- a container for related sprites that enables batch operations and collision detection.
+- `this.platforms.create(x, y, key)` creates a sprite inside the group.
+- `sprite.body.immovable = true` prevents the platform from being pushed by other physics bodies.
+- `sprite.body.allowGravity = false` prevents platforms from falling due to gravity.
+
+You should now see the ground and grass platform tiles rendered on the screen.
+
+---
+
+## The Main Character Sprite
+
+Now add the hero character that the player will control.
+
+### Loading the Hero Image
+
+Add the hero image to `preload`. Initially we use a single static image; we will switch to a spritesheet later for animations:
+
+```javascript
+// In PlayState.preload:
+this.game.load.image('hero', 'images/hero_stopped.png');
+```
+
+### Spawning the Hero
+
+Add the hero to `_loadLevel` and create a spawn method:
+
+```javascript
+PlayState._loadLevel = function (data) {
+ this.platforms = this.game.add.group();
+ data.platforms.forEach(this._spawnPlatform, this);
+
+ // Spawn the hero at the position defined in level data
+ this._spawnCharacters({ hero: data.hero });
+};
+
+PlayState._spawnCharacters = function (data) {
+ // Create the hero sprite
+ this.hero = this.game.add.sprite(data.hero.x, data.hero.y, 'hero');
+
+ // Set the anchor to the bottom-center for easier positioning
+ this.hero.anchor.set(0.5, 1);
+};
+```
+
+- `anchor.set(0.5, 1)` sets the sprite's origin point to the horizontal center and vertical bottom. This makes it easier to position the hero on top of platforms, since the `y` position refers to the hero's feet rather than the top-left corner.
+
+---
+
+## Keyboard Controls
+
+Capture keyboard input so the player can move the hero left, right, and jump.
+
+### Setting Up Input Keys
+
+In `init`, configure the keyboard controls:
+
+```javascript
+PlayState.init = function () {
+ // Force integer rendering for pixel-art crispness
+ this.game.renderer.renderSession.roundPixels = true;
+
+ // Capture arrow keys
+ this.keys = this.game.input.keyboard.addKeys({
+ left: Phaser.KeyCode.LEFT,
+ right: Phaser.KeyCode.RIGHT,
+ up: Phaser.KeyCode.UP
+ });
+};
+```
+
+- `addKeys()` captures the specified keys and returns an object with key state references.
+- `Phaser.KeyCode.LEFT`, `RIGHT`, `UP` correspond to the arrow keys.
+- `renderSession.roundPixels = true` prevents pixel-art sprites from appearing blurry due to sub-pixel rendering.
+
+### Reading Input in Update
+
+Handle the key states in `update`. For now, just log the direction; the next step adds physics-based movement:
+
+```javascript
+PlayState.update = function () {
+ this._handleInput();
+};
+
+PlayState._handleInput = function () {
+ if (this.keys.left.isDown) {
+ // Move hero left
+ } else if (this.keys.right.isDown) {
+ // Move hero right
+ } else {
+ // Stop (no key held)
+ }
+};
+```
+
+- `this.keys.left.isDown` returns `true` while the left arrow key is held down.
+- The `else` clause handles the case where neither left nor right is pressed (the hero should stop).
+
+---
+
+## Moving Sprites with Physics
+
+Enable Arcade Physics so the hero can move with velocity and interact with platforms through collisions.
+
+### Enabling the Physics Engine
+
+Enable Arcade Physics in `init`:
+
+```javascript
+PlayState.init = function () {
+ this.game.renderer.renderSession.roundPixels = true;
+
+ this.keys = this.game.input.keyboard.addKeys({
+ left: Phaser.KeyCode.LEFT,
+ right: Phaser.KeyCode.RIGHT,
+ up: Phaser.KeyCode.UP
+ });
+
+ // Enable Arcade Physics
+ this.game.physics.startSystem(Phaser.Physics.ARCADE);
+};
+```
+
+### Adding a Physics Body to the Hero
+
+Enable physics on the hero sprite in `_spawnCharacters`:
+
+```javascript
+PlayState._spawnCharacters = function (data) {
+ this.hero = this.game.add.sprite(data.hero.x, data.hero.y, 'hero');
+ this.hero.anchor.set(0.5, 1);
+
+ // Enable physics body on the hero
+ this.game.physics.enable(this.hero);
+};
+```
+
+### Moving with Velocity
+
+Now update `_handleInput` to set the hero's velocity based on key presses:
+
+```javascript
+const SPEED = 200; // pixels per second
+
+PlayState._handleInput = function () {
+ if (this.keys.left.isDown) {
+ this.hero.body.velocity.x = -SPEED;
+ } else if (this.keys.right.isDown) {
+ this.hero.body.velocity.x = SPEED;
+ } else {
+ this.hero.body.velocity.x = 0;
+ }
+};
+```
+
+- `body.velocity.x` sets the horizontal speed in pixels per second.
+- A negative value moves the sprite left; positive moves it right.
+- Setting velocity to `0` when no keys are pressed makes the hero stop immediately.
+
+The hero can now move left and right, but will fall through platforms and off the screen because there is no gravity or collision handling yet.
+
+---
+
+## Gravity
+
+Add gravity so the hero falls downward and collides with platforms.
+
+### Setting Global Gravity
+
+Enable gravity for the entire physics world in `init`:
+
+```javascript
+PlayState.init = function () {
+ this.game.renderer.renderSession.roundPixels = true;
+
+ this.keys = this.game.input.keyboard.addKeys({
+ left: Phaser.KeyCode.LEFT,
+ right: Phaser.KeyCode.RIGHT,
+ up: Phaser.KeyCode.UP
+ });
+
+ this.game.physics.startSystem(Phaser.Physics.ARCADE);
+
+ // Set global gravity
+ this.game.physics.arcade.gravity.y = 1200;
+};
+```
+
+- `gravity.y = 1200` applies a downward acceleration of 1200 pixels per second squared to all physics-enabled sprites (unless they opt out with `allowGravity = false`).
+
+### Collision Detection Between Hero and Platforms
+
+Add collision detection in `update` so the hero lands on platforms instead of falling through:
+
+```javascript
+PlayState.update = function () {
+ this._handleCollisions();
+ this._handleInput();
+};
+
+PlayState._handleCollisions = function () {
+ // Make the hero collide with the platform group
+ this.game.physics.arcade.collide(this.hero, this.platforms);
+};
+```
+
+- `arcade.collide(spriteA, groupB)` checks for physics collisions between the hero and every sprite in the platforms group. When the hero lands on a platform, the physics engine prevents it from passing through and resolves the overlap.
+- It is important to call `_handleCollisions()` before `_handleInput()` so collision data (like whether the hero is touching the ground) is up to date when we process input.
+
+The hero now falls due to gravity and lands on the platforms. You can walk left and right on the platforms.
+
+---
+
+## Jumps
+
+Allow the hero to jump when the up arrow key is pressed -- but only when standing on a platform (no mid-air jumps).
+
+### Implementing the Jump Mechanic
+
+Add a jump constant and update `_handleInput`:
+
+```javascript
+const SPEED = 200;
+const JUMP_SPEED = 600;
+
+PlayState._handleInput = function () {
+ if (this.keys.left.isDown) {
+ this.hero.body.velocity.x = -SPEED;
+ } else if (this.keys.right.isDown) {
+ this.hero.body.velocity.x = SPEED;
+ } else {
+ this.hero.body.velocity.x = 0;
+ }
+
+ // Handle jumping
+ if (this.keys.up.isDown) {
+ this._jump();
+ }
+};
+
+PlayState._jump = function () {
+ let canJump = this.hero.body.touching.down;
+
+ if (canJump) {
+ this.hero.body.velocity.y = -JUMP_SPEED;
+ }
+
+ return canJump;
+};
+```
+
+- `this.hero.body.touching.down` is `true` when the hero's physics body is touching another body on its underside -- meaning the hero is standing on something.
+- Setting `velocity.y` to a negative value launches the hero upward (the y-axis points downward in screen coordinates).
+- The `canJump` check prevents the hero from jumping while already in the air, enforcing single-jump behavior.
+- The method returns whether the jump was performed, which is useful later for playing sound effects.
+
+### Adding a Jump Sound Effect
+
+Load a jump sound and play it on successful jumps:
+
+```javascript
+// In PlayState.preload:
+this.game.load.audio('sfx:jump', 'audio/sfx/jump.wav');
+
+// In PlayState.create:
+this.sfx = {
+ jump: this.game.add.audio('sfx:jump')
+};
+
+// In PlayState._jump, after setting velocity:
+PlayState._jump = function () {
+ let canJump = this.hero.body.touching.down;
+
+ if (canJump) {
+ this.hero.body.velocity.y = -JUMP_SPEED;
+ this.sfx.jump.play();
+ }
+
+ return canJump;
+};
+```
+
+---
+
+## Pickable Coins
+
+Add collectible coins that the player can pick up to increase their score.
+
+### Loading Coin Assets
+
+Load the coin spritesheet and coin sound effect in `preload`:
+
+```javascript
+// In PlayState.preload:
+this.game.load.spritesheet('coin', 'images/coin_animated.png', 22, 22);
+this.game.load.audio('sfx:coin', 'audio/sfx/coin.wav');
+```
+
+- `load.spritesheet(key, path, frameWidth, frameHeight)` loads a spritesheet and slices it into individual frames of 22x22 pixels for animation.
+
+### Spawning Coins from Level Data
+
+Update `_loadLevel` to create a coins group and spawn each coin:
+
+```javascript
+PlayState._loadLevel = function (data) {
+ this.platforms = this.game.add.group();
+ this.coins = this.game.add.group();
+
+ data.platforms.forEach(this._spawnPlatform, this);
+ data.coins.forEach(this._spawnCoin, this);
+
+ this._spawnCharacters({ hero: data.hero });
+};
+
+PlayState._spawnCoin = function (coin) {
+ let sprite = this.coins.create(coin.x, coin.y, 'coin');
+ sprite.anchor.set(0.5, 0.5);
+
+ // Add a tween animation to make the coin bob up and down
+ this.game.physics.enable(sprite);
+ sprite.body.allowGravity = false;
+
+ // Coin bobbing animation with a tween
+ sprite.animations.add('rotate', [0, 1, 2, 1], 6, true); // 6fps, looping
+ sprite.animations.play('rotate');
+};
+```
+
+- Each coin is created inside the `coins` group for easy collision detection.
+- `allowGravity = false` prevents coins from falling.
+- The `animations.add` creates a frame animation using the spritesheet frames 0, 1, 2, 1 at 6fps, looping continuously.
+
+### Collecting Coins
+
+Add the coin sound to the sfx object and detect overlap between the hero and coins:
+
+```javascript
+// In PlayState.create, add to the sfx object:
+this.sfx = {
+ jump: this.game.add.audio('sfx:jump'),
+ coin: this.game.add.audio('sfx:coin')
+};
+
+// In PlayState._handleCollisions:
+PlayState._handleCollisions = function () {
+ this.game.physics.arcade.collide(this.hero, this.platforms);
+
+ // Detect overlap between hero and coins (no physical collision, just overlap)
+ this.game.physics.arcade.overlap(
+ this.hero, this.coins, this._onHeroVsCoin, null, this
+ );
+};
+
+PlayState._onHeroVsCoin = function (hero, coin) {
+ this.sfx.coin.play();
+ coin.kill(); // Remove the coin from the game
+ this.coinPickupCount++;
+};
+```
+
+- `arcade.overlap()` checks if two sprites/groups overlap without resolving collisions physically. When an overlap is detected, it calls the callback function (`_onHeroVsCoin`).
+- `coin.kill()` removes the coin sprite from the game world.
+- `this.coinPickupCount` tracks the number of coins collected (initialize it in `_loadLevel`).
+
+### Initializing the Coin Counter
+
+```javascript
+PlayState._loadLevel = function (data) {
+ this.platforms = this.game.add.group();
+ this.coins = this.game.add.group();
+
+ data.platforms.forEach(this._spawnPlatform, this);
+ data.coins.forEach(this._spawnCoin, this);
+
+ this._spawnCharacters({ hero: data.hero });
+
+ // Initialize coin counter
+ this.coinPickupCount = 0;
+};
+```
+
+---
+
+## Walking Enemies
+
+Add spider enemies that walk back and forth on platforms. The hero can stomp on them from above but dies if touching them from the side.
+
+### Loading Enemy Assets
+
+```javascript
+// In PlayState.preload:
+this.game.load.spritesheet('spider', 'images/spider.png', 42, 32);
+this.game.load.image('invisible-wall', 'images/invisible_wall.png');
+this.game.load.audio('sfx:stomp', 'audio/sfx/stomp.wav');
+```
+
+- The spider spritsheet has frames for a crawling animation.
+- Invisible walls are placed at platform edges to keep spiders from walking off -- they are not rendered visually but have physics bodies.
+
+### Spawning Enemies
+
+Update `_loadLevel` and add a spawn method for spiders:
+
+```javascript
+PlayState._loadLevel = function (data) {
+ this.platforms = this.game.add.group();
+ this.coins = this.game.add.group();
+ this.spiders = this.game.add.group();
+ this.enemyWalls = this.game.add.group();
+
+ data.platforms.forEach(this._spawnPlatform, this);
+ data.coins.forEach(this._spawnCoin, this);
+ data.spiders.forEach(this._spawnSpider, this);
+
+ this._spawnCharacters({ hero: data.hero });
+
+ // Make enemy walls invisible
+ this.enemyWalls.visible = false;
+
+ this.coinPickupCount = 0;
+};
+```
+
+### Creating Invisible Walls on Platforms
+
+Modify `_spawnPlatform` to add invisible walls at both edges of each platform:
+
+```javascript
+PlayState._spawnPlatform = function (platform) {
+ let sprite = this.platforms.create(platform.x, platform.y, platform.image);
+ this.game.physics.enable(sprite);
+ sprite.body.allowGravity = false;
+ sprite.body.immovable = true;
+
+ // Spawn invisible walls at the left and right edges of this platform
+ this._spawnEnemyWall(platform.x, platform.y, 'left');
+ this._spawnEnemyWall(platform.x + sprite.width, platform.y, 'right');
+};
+
+PlayState._spawnEnemyWall = function (x, y, side) {
+ let sprite = this.enemyWalls.create(x, y, 'invisible-wall');
+
+ // Anchor to the bottom of the wall and adjust position based on side
+ sprite.anchor.set(side === 'left' ? 1 : 0, 1);
+
+ this.game.physics.enable(sprite);
+ sprite.body.immovable = true;
+ sprite.body.allowGravity = false;
+};
+```
+
+- Each platform gets two invisible walls, one at each edge.
+- The walls act as barriers that prevent spiders from walking off the edge.
+- The anchor is set so the wall aligns to the correct side of the platform.
+
+### Spawning and Animating Spiders
+
+```javascript
+PlayState._spawnSpider = function (spider) {
+ let sprite = this.spiders.create(spider.x, spider.y, 'spider');
+ sprite.anchor.set(0.5, 1);
+
+ // Add the crawl animation
+ sprite.animations.add('crawl', [0, 1, 2], 8, true);
+ sprite.animations.add('die', [0, 4, 0, 4, 0, 4, 3, 3, 3, 3, 3, 3], 12);
+ sprite.animations.play('crawl');
+
+ // Enable physics
+ this.game.physics.enable(sprite);
+
+ // Set initial movement speed
+ sprite.body.velocity.x = Spider.SPEED;
+};
+
+// Spider speed constant
+const Spider = { SPEED: 100 };
+```
+
+- Spiders have two animations: `crawl` (looping) and `die` (played once on death).
+- `velocity.x = 100` starts the spider moving to the right at 100 pixels per second.
+
+### Making Spiders Bounce Off Walls
+
+Add collision handling so spiders reverse direction when hitting invisible walls or platform edges:
+
+```javascript
+// In PlayState._handleCollisions:
+PlayState._handleCollisions = function () {
+ this.game.physics.arcade.collide(this.hero, this.platforms);
+ this.game.physics.arcade.collide(this.spiders, this.platforms);
+ this.game.physics.arcade.collide(this.spiders, this.enemyWalls);
+
+ this.game.physics.arcade.overlap(
+ this.hero, this.coins, this._onHeroVsCoin, null, this
+ );
+ this.game.physics.arcade.overlap(
+ this.hero, this.spiders, this._onHeroVsEnemy, null, this
+ );
+};
+```
+
+To make spiders reverse direction when colliding with walls, check their velocity each frame and flip them:
+
+```javascript
+// In PlayState.update, after collision handling, update spider directions:
+PlayState.update = function () {
+ this._handleCollisions();
+ this._handleInput();
+
+ // Update spider facing direction based on velocity
+ this.spiders.forEach(function (spider) {
+ if (spider.body.touching.right || spider.body.blocked.right) {
+ spider.body.velocity.x = -Spider.SPEED; // Turn left
+ } else if (spider.body.touching.left || spider.body.blocked.left) {
+ spider.body.velocity.x = Spider.SPEED; // Turn right
+ }
+ }, this);
+};
+```
+
+- When a spider touches a wall on its right side, it reverses to move left, and vice versa.
+- `body.touching` is set by Phaser after collision resolution.
+
+---
+
+## Death
+
+Implement hero death when touching enemies and the stomp mechanic for killing enemies.
+
+### Hero vs Enemy: Stomp or Die
+
+When the hero overlaps with a spider, check if the hero is falling (stomping) or not:
+
+```javascript
+PlayState._onHeroVsEnemy = function (hero, enemy) {
+ if (hero.body.velocity.y > 0) {
+ // Hero is falling -> stomp the enemy
+ enemy.body.velocity.x = 0; // Stop enemy movement
+ enemy.body.enable = false; // Disable enemy physics
+
+ // Play die animation then remove the enemy
+ enemy.animations.play('die');
+ enemy.events.onAnimationComplete.addOnce(function () {
+ enemy.kill();
+ });
+
+ // Bounce the hero up after stomping
+ hero.body.velocity.y = -JUMP_SPEED / 2;
+
+ this.sfx.stomp.play();
+ } else {
+ // Hero touched enemy from side or below -> die
+ this._killHero();
+ }
+};
+
+PlayState._killHero = function () {
+ this.hero.kill();
+ // Restart the level after a short delay
+ this.game.time.events.add(500, function () {
+ this.game.state.restart(true, false, { level: this.level });
+ }, this);
+};
+```
+
+- If `hero.body.velocity.y > 0`, the hero is moving downward (falling), indicating a stomp.
+- On stomp: the enemy stops, plays its death animation, and is removed. The hero bounces up.
+- If the hero is not falling, the hero dies. `this.hero.kill()` removes the hero from the game.
+- After 500ms, the entire state is restarted, effectively reloading the level.
+
+### Add Stomp Sound
+
+```javascript
+// In PlayState.create, add to sfx:
+this.sfx = {
+ jump: this.game.add.audio('sfx:jump'),
+ coin: this.game.add.audio('sfx:coin'),
+ stomp: this.game.add.audio('sfx:stomp')
+};
+```
+
+### Adding a Death Animation for the Hero
+
+Make the hero flash and fall off the screen when dying:
+
+```javascript
+PlayState._killHero = function () {
+ this.hero.alive = false;
+
+ // Play a "dying" visual: the hero jumps up and falls off screen
+ this.hero.body.velocity.y = -JUMP_SPEED / 2;
+ this.hero.body.velocity.x = 0;
+ this.hero.body.allowGravity = true;
+
+ // Disable collisions so the hero falls through platforms
+ this.hero.body.collideWorldBounds = false;
+
+ // Restart after a delay
+ this.game.time.events.add(1000, function () {
+ this.game.state.restart(true, false, { level: this.level });
+ }, this);
+};
+```
+
+### Guarding Input When Dead
+
+Prevent input from controlling the hero after death:
+
+```javascript
+PlayState._handleInput = function () {
+ if (!this.hero.alive) { return; }
+
+ if (this.keys.left.isDown) {
+ this.hero.body.velocity.x = -SPEED;
+ } else if (this.keys.right.isDown) {
+ this.hero.body.velocity.x = SPEED;
+ } else {
+ this.hero.body.velocity.x = 0;
+ }
+
+ if (this.keys.up.isDown) {
+ this._jump();
+ }
+};
+```
+
+- `this.hero.alive` is set to `false` in `_killHero`, so input is ignored after death and the hero falls off screen naturally.
+
+---
+
+## Scoreboard
+
+Display the number of collected coins on screen using a bitmap font.
+
+### Loading the Bitmap Font
+
+```javascript
+// In PlayState.preload:
+this.game.load.image('font:numbers', 'images/numbers.png');
+this.game.load.image('icon:coin', 'images/coin_icon.png');
+```
+
+### Creating the HUD
+
+Create a fixed HUD (heads-up display) that shows the coin icon and count:
+
+```javascript
+PlayState._createHud = function () {
+ let coinIcon = this.game.make.image(0, 0, 'icon:coin');
+
+ // Create a dynamic text label for the coin count
+ this.hud = this.game.add.group();
+
+ // Use a retroFont or a regular text object for the score
+ let scoreStyle = {
+ font: '30px monospace',
+ fill: '#fff'
+ };
+ this.coinFont = this.game.add.text(
+ coinIcon.width + 7, 0, 'x0', scoreStyle
+ );
+
+ this.hud.add(coinIcon);
+ this.hud.add(this.coinFont);
+
+ this.hud.position.set(10, 10);
+ this.hud.fixedToCamera = true;
+};
+```
+
+Alternatively, using Phaser's `RetroFont` for pixel-art number rendering:
+
+```javascript
+PlayState._createHud = function () {
+ // Bitmap-based number rendering using RetroFont
+ this.coinFont = this.game.add.retroFont(
+ 'font:numbers', 20, 26,
+ '0123456789X ', 6
+ );
+
+ let coinIcon = this.game.make.image(0, 0, 'icon:coin');
+
+ let coinScoreImg = this.game.make.image(
+ coinIcon.x + coinIcon.width + 7, 0, this.coinFont
+ );
+
+ this.hud = this.game.add.group();
+ this.hud.add(coinIcon);
+ this.hud.add(coinScoreImg);
+ this.hud.position.set(10, 10);
+ this.hud.fixedToCamera = true;
+};
+```
+
+- `retroFont` creates a bitmap font from a spritesheet containing character glyphs.
+- Parameters: image key, character width, character height, character set string, number of characters per row.
+
+### Calling createHud in create
+
+```javascript
+PlayState.create = function () {
+ this.game.add.image(0, 0, 'background');
+
+ this._loadLevel(this.game.cache.getJSON('level:1'));
+
+ // Create the HUD
+ this._createHud();
+};
+```
+
+### Updating the Score Display
+
+Update the score text whenever a coin is collected:
+
+```javascript
+PlayState._onHeroVsCoin = function (hero, coin) {
+ this.sfx.coin.play();
+ coin.kill();
+ this.coinPickupCount++;
+
+ // Update the HUD
+ this.coinFont.text = 'x' + this.coinPickupCount;
+};
+```
+
+---
+
+## Animations for the Main Character
+
+Replace the static hero image with a spritesheet and add animations for different states: idle (stopped), running, jumping, and falling.
+
+### Loading the Hero Spritesheet
+
+Replace the single image load with a spritesheet in `preload`:
+
+```javascript
+// Replace: this.game.load.image('hero', 'images/hero_stopped.png');
+// With:
+this.game.load.spritesheet('hero', 'images/hero.png', 36, 42);
+```
+
+- The hero spritesheet is 36 pixels wide and 42 pixels tall per frame.
+- Frames include idle, walk cycle, jump, and fall poses.
+
+### Defining Animations
+
+In `_spawnCharacters`, add animation definitions after creating the hero sprite:
+
+```javascript
+PlayState._spawnCharacters = function (data) {
+ this.hero = this.game.add.sprite(data.hero.x, data.hero.y, 'hero');
+ this.hero.anchor.set(0.5, 1);
+ this.game.physics.enable(this.hero);
+
+ // Define animations
+ this.hero.animations.add('stop', [0]); // Single frame: idle
+ this.hero.animations.add('run', [1, 2], 8, true); // 2 frames at 8fps, looping
+ this.hero.animations.add('jump', [3]); // Single frame: jumping up
+ this.hero.animations.add('fall', [4]); // Single frame: falling down
+};
+```
+
+- `animations.add(name, frames, fps, loop)` registers an animation with the given name.
+- Single-frame animations like `stop`, `jump`, and `fall` effectively set a static pose.
+- The `run` animation alternates between frames 1 and 2 at 8fps.
+
+### Playing the Correct Animation
+
+Add a method to determine and play the right animation based on the hero's current state:
+
+```javascript
+PlayState._getAnimationName = function () {
+ let name = 'stop'; // Default: standing still
+
+ if (!this.hero.alive) {
+ name = 'stop'; // Use idle frame when dead
+ } else if (this.hero.body.velocity.y < 0) {
+ name = 'jump'; // Moving upward
+ } else if (this.hero.body.velocity.y > 0 && !this.hero.body.touching.down) {
+ name = 'fall'; // Moving downward and not on ground
+ } else if (this.hero.body.velocity.x !== 0 && this.hero.body.touching.down) {
+ name = 'run'; // Moving horizontally on the ground
+ }
+
+ return name;
+};
+```
+
+### Flipping the Sprite Based on Direction
+
+Update the hero's facing direction and play the animation in `update`:
+
+```javascript
+PlayState.update = function () {
+ this._handleCollisions();
+ this._handleInput();
+
+ // Flip sprite based on movement direction
+ if (this.hero.body.velocity.x < 0) {
+ this.hero.scale.x = -1; // Face left
+ } else if (this.hero.body.velocity.x > 0) {
+ this.hero.scale.x = 1; // Face right
+ }
+
+ // Play the appropriate animation
+ this.hero.animations.play(this._getAnimationName());
+
+ // Update spider directions
+ this.spiders.forEach(function (spider) {
+ if (spider.body.touching.right || spider.body.blocked.right) {
+ spider.body.velocity.x = -Spider.SPEED;
+ } else if (spider.body.touching.left || spider.body.blocked.left) {
+ spider.body.velocity.x = Spider.SPEED;
+ }
+ }, this);
+};
+```
+
+- `this.hero.scale.x = -1` flips the sprite horizontally to face left. Setting it to `1` faces right. Because the anchor is at `(0.5, 1)`, the flip looks natural.
+- `animations.play()` only restarts the animation if the name changes, so calling it every frame is safe and efficient.
+
+---
+
+## Win Condition
+
+Add a door and key mechanic: the hero must collect a key, then reach the door to complete the level.
+
+### Loading Door and Key Assets
+
+```javascript
+// In PlayState.preload:
+this.game.load.spritesheet('door', 'images/door.png', 42, 66);
+this.game.load.spritesheet('key', 'images/key.png', 20, 22); // Key bobbing animation
+this.game.load.image('icon:key', 'images/key_icon.png');
+
+this.game.load.audio('sfx:key', 'audio/sfx/key.wav');
+this.game.load.audio('sfx:door', 'audio/sfx/door.wav');
+```
+
+### Spawning the Door and Key
+
+Update `_loadLevel` and `_spawnCharacters`:
+
+```javascript
+PlayState._loadLevel = function (data) {
+ this.platforms = this.game.add.group();
+ this.coins = this.game.add.group();
+ this.spiders = this.game.add.group();
+ this.enemyWalls = this.game.add.group();
+ this.bgDecoration = this.game.add.group();
+
+ // Must spawn decorations first (background layer)
+ // Spawn door before hero so it renders behind the hero
+ data.platforms.forEach(this._spawnPlatform, this);
+ data.coins.forEach(this._spawnCoin, this);
+ data.spiders.forEach(this._spawnSpider, this);
+
+ this._spawnDoor(data.door.x, data.door.y);
+ this._spawnKey(data.key.x, data.key.y);
+ this._spawnCharacters({ hero: data.hero });
+
+ this.enemyWalls.visible = false;
+
+ this.coinPickupCount = 0;
+ this.hasKey = false;
+};
+
+PlayState._spawnDoor = function (x, y) {
+ this.door = this.bgDecoration.create(x, y, 'door');
+ this.door.anchor.setTo(0.5, 1);
+
+ this.game.physics.enable(this.door);
+ this.door.body.allowGravity = false;
+};
+
+PlayState._spawnKey = function (x, y) {
+ this.key = this.bgDecoration.create(x, y, 'key');
+ this.key.anchor.set(0.5, 0.5);
+
+ this.game.physics.enable(this.key);
+ this.key.body.allowGravity = false;
+
+ // Add a bobbing up-and-down tween to the key
+ this.key.y -= 3;
+ this.game.add.tween(this.key)
+ .to({ y: this.key.y + 6 }, 800, Phaser.Easing.Sinusoidal.InOut)
+ .yoyo(true)
+ .loop()
+ .start();
+};
+```
+
+- The door is placed in a background decoration group so it renders behind the hero.
+- The key has a sinusoidal bobbing tween that moves it 6 pixels up and down over 800ms, looping forever.
+
+### Collecting the Key and Opening the Door
+
+Add key and door sound effects to the sfx object:
+
+```javascript
+// In PlayState.create sfx:
+this.sfx = {
+ jump: this.game.add.audio('sfx:jump'),
+ coin: this.game.add.audio('sfx:coin'),
+ stomp: this.game.add.audio('sfx:stomp'),
+ key: this.game.add.audio('sfx:key'),
+ door: this.game.add.audio('sfx:door')
+};
+```
+
+Add overlap detection for the key and door in `_handleCollisions`:
+
+```javascript
+PlayState._handleCollisions = function () {
+ this.game.physics.arcade.collide(this.hero, this.platforms);
+ this.game.physics.arcade.collide(this.spiders, this.platforms);
+ this.game.physics.arcade.collide(this.spiders, this.enemyWalls);
+
+ this.game.physics.arcade.overlap(
+ this.hero, this.coins, this._onHeroVsCoin, null, this
+ );
+ this.game.physics.arcade.overlap(
+ this.hero, this.spiders, this._onHeroVsEnemy, null, this
+ );
+ this.game.physics.arcade.overlap(
+ this.hero, this.key, this._onHeroVsKey, null, this
+ );
+ this.game.physics.arcade.overlap(
+ this.hero, this.door, this._onHeroVsDoor,
+ // Only trigger if the hero has the key
+ function (hero, door) {
+ return this.hasKey && hero.body.touching.down;
+ }, this
+ );
+};
+```
+
+- The door overlap has a **process callback** (the fourth argument) that only triggers the overlap callback when `this.hasKey` is true and the hero is standing on something. This prevents the hero from entering the door while falling or without the key.
+
+### Key and Door Callbacks
+
+```javascript
+PlayState._onHeroVsKey = function (hero, key) {
+ this.sfx.key.play();
+ key.kill();
+ this.hasKey = true;
+};
+
+PlayState._onHeroVsDoor = function (hero, door) {
+ this.sfx.door.play();
+
+ // Freeze the hero and play the door opening animation
+ hero.body.velocity.x = 0;
+ hero.body.velocity.y = 0;
+ hero.body.enable = false;
+
+ // Play door open animation (transition from closed to open frame)
+ door.frame = 1; // Switch to "open" frame
+
+ // Advance to the next level after a short delay
+ this.game.time.events.add(500, this._goToNextLevel, this);
+};
+
+PlayState._goToNextLevel = function () {
+ this.camera.fade('#000');
+ this.camera.onFadeComplete.addOnce(function () {
+ this.game.state.restart(true, false, {
+ level: this.level + 1
+ });
+ }, this);
+};
+```
+
+- When the hero touches the key, the key is removed and `hasKey` is set to `true`.
+- When the hero reaches the door (with the key), the hero freezes, the door opens, and after a delay the game transitions to the next level.
+- `camera.fade()` creates a fade-to-black transition for a polished level switch.
+
+### Showing the Key Icon in the HUD
+
+Update `_createHud` to show whether the hero has collected the key:
+
+```javascript
+PlayState._createHud = function () {
+ this.keyIcon = this.game.make.image(0, 19, 'icon:key');
+ this.keyIcon.anchor.set(0, 0.5);
+
+ // ... existing coin HUD code ...
+
+ this.hud.add(this.keyIcon);
+ this.hud.add(coinIcon);
+ this.hud.add(coinScoreImg);
+ this.hud.position.set(10, 10);
+ this.hud.fixedToCamera = true;
+};
+```
+
+Update the key icon appearance each frame in `update`:
+
+```javascript
+// In PlayState.update, add:
+this.keyIcon.frame = this.hasKey ? 1 : 0;
+```
+
+- Frame 0 shows a grayed-out key icon; frame 1 shows the collected key icon.
+
+---
+
+## Switching Levels
+
+Support multiple levels by loading different JSON files based on a level index.
+
+### Passing Level Number Through init
+
+Modify `init` to accept a level parameter:
+
+```javascript
+PlayState.init = function (data) {
+ this.game.renderer.renderSession.roundPixels = true;
+
+ this.keys = this.game.input.keyboard.addKeys({
+ left: Phaser.KeyCode.LEFT,
+ right: Phaser.KeyCode.RIGHT,
+ up: Phaser.KeyCode.UP
+ });
+
+ this.game.physics.startSystem(Phaser.Physics.ARCADE);
+ this.game.physics.arcade.gravity.y = 1200;
+
+ // Store the current level number (default to 0)
+ this.level = (data.level || 0) % LEVEL_COUNT;
+};
+
+const LEVEL_COUNT = 2; // Total number of levels
+```
+
+- `data` is an object passed from `game.state.start()` or `game.state.restart()`.
+- The modulo operation (`% LEVEL_COUNT`) wraps around to level 0 after the last level, creating an infinite loop of levels.
+
+### Loading Level Data Dynamically
+
+Update `preload` to load the correct level based on `this.level`:
+
+```javascript
+PlayState.preload = function () {
+ this.game.load.image('background', 'images/background.png');
+
+ // Load the current level's JSON data
+ this.game.load.json('level:0', 'data/level00.json');
+ this.game.load.json('level:1', 'data/level01.json');
+
+ // ... load all other assets ...
+};
+```
+
+Update `create` to use the correct level data:
+
+```javascript
+PlayState.create = function () {
+ this.sfx = {
+ jump: this.game.add.audio('sfx:jump'),
+ coin: this.game.add.audio('sfx:coin'),
+ stomp: this.game.add.audio('sfx:stomp'),
+ key: this.game.add.audio('sfx:key'),
+ door: this.game.add.audio('sfx:door')
+ };
+
+ this.game.add.image(0, 0, 'background');
+
+ // Load level data based on current level number
+ this._loadLevel(this.game.cache.getJSON('level:' + this.level));
+
+ this._createHud();
+};
+```
+
+### Starting the Game at Level 0
+
+Update the initial state start to pass level 0:
+
+```javascript
+window.onload = function () {
+ let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game');
+ game.state.add('play', PlayState);
+ game.state.start('play', true, false, { level: 0 });
+};
+```
+
+- The third and fourth `start` arguments control world/cache clearing. `true, false` keeps the cache between restarts (so assets do not need to be reloaded) but clears the world.
+- `{ level: 0 }` is passed to `init` as the `data` parameter.
+
+### Level Transition Flow
+
+The complete level flow is:
+
+1. Hero collects key -> `hasKey = true`
+2. Hero reaches door -> `_onHeroVsDoor` fires
+3. Camera fades to black -> `_goToNextLevel` fires
+4. State restarts with `{ level: this.level + 1 }`
+5. `init` receives the new level number
+6. The correct level JSON is loaded and the game continues
+
+---
+
+## Moving Forward
+
+Congratulations -- you have built a complete 2D platformer. Here are ideas for extending the game further:
+
+### Suggested Improvements
+
+- **Mobile / touch controls:** Add on-screen buttons or swipe gestures using `game.input.onDown` for touch-enabled devices.
+- **More levels:** Create additional JSON level files with new platform layouts, coin placements, and enemy configurations.
+- **Menu screen:** Add a `MenuState` with a title screen and start button before entering `PlayState`.
+- **Game over screen:** Instead of instantly restarting, show a "Game Over" screen with the score.
+- **Lives system:** Give the hero multiple lives instead of instant restart.
+- **Power-ups:** Add items like speed boosts, double jump, or invincibility.
+- **Moving platforms:** Create platforms that travel along a path using tweens.
+- **Different enemy types:** Add flying enemies, enemies that shoot projectiles, or enemies with different movement patterns.
+- **Parallax scrolling:** Add multiple background layers that scroll at different speeds for depth.
+- **Camera scrolling:** For levels wider than the screen, use `game.camera.follow(this.hero)` to scroll with the hero.
+- **Sound and music:** Add background music and additional sound effects for a more polished experience.
+- **Particle effects:** Use Phaser's particle emitter for coin collection sparkles, enemy death effects, or dust when landing.
+
+### Full Game Source Reference
+
+Below is the complete `main.js` file combining all steps for reference. This represents the final state of the game with all features:
+
+```javascript
+// =============================================================================
+// Constants
+// =============================================================================
+
+const SPEED = 200;
+const JUMP_SPEED = 600;
+const LEVEL_COUNT = 2;
+const Spider = { SPEED: 100 };
+
+// =============================================================================
+// Game State: PlayState
+// =============================================================================
+
+PlayState = {};
+
+// -----------------------------------------------------------------------------
+// init
+// -----------------------------------------------------------------------------
+
+PlayState.init = function (data) {
+ this.game.renderer.renderSession.roundPixels = true;
+
+ this.keys = this.game.input.keyboard.addKeys({
+ left: Phaser.KeyCode.LEFT,
+ right: Phaser.KeyCode.RIGHT,
+ up: Phaser.KeyCode.UP
+ });
+
+ this.game.physics.startSystem(Phaser.Physics.ARCADE);
+ this.game.physics.arcade.gravity.y = 1200;
+
+ this.level = (data.level || 0) % LEVEL_COUNT;
+};
+
+// -----------------------------------------------------------------------------
+// preload
+// -----------------------------------------------------------------------------
+
+PlayState.preload = function () {
+ // Background
+ this.game.load.image('background', 'images/background.png');
+
+ // Level data
+ this.game.load.json('level:0', 'data/level00.json');
+ this.game.load.json('level:1', 'data/level01.json');
+
+ // Platform tiles
+ this.game.load.image('ground', 'images/ground.png');
+ this.game.load.image('grass:8x1', 'images/grass_8x1.png');
+ this.game.load.image('grass:6x1', 'images/grass_6x1.png');
+ this.game.load.image('grass:4x1', 'images/grass_4x1.png');
+ this.game.load.image('grass:2x1', 'images/grass_2x1.png');
+ this.game.load.image('grass:1x1', 'images/grass_1x1.png');
+
+ // Characters
+ this.game.load.spritesheet('hero', 'images/hero.png', 36, 42);
+ this.game.load.spritesheet('spider', 'images/spider.png', 42, 32);
+ this.game.load.image('invisible-wall', 'images/invisible_wall.png');
+
+ // Collectibles
+ this.game.load.spritesheet('coin', 'images/coin_animated.png', 22, 22);
+ this.game.load.spritesheet('key', 'images/key.png', 20, 22);
+ this.game.load.spritesheet('door', 'images/door.png', 42, 66);
+
+ // HUD
+ this.game.load.image('icon:coin', 'images/coin_icon.png');
+ this.game.load.image('icon:key', 'images/key_icon.png');
+ this.game.load.image('font:numbers', 'images/numbers.png');
+
+ // Audio
+ this.game.load.audio('sfx:jump', 'audio/sfx/jump.wav');
+ this.game.load.audio('sfx:coin', 'audio/sfx/coin.wav');
+ this.game.load.audio('sfx:stomp', 'audio/sfx/stomp.wav');
+ this.game.load.audio('sfx:key', 'audio/sfx/key.wav');
+ this.game.load.audio('sfx:door', 'audio/sfx/door.wav');
+};
+
+// -----------------------------------------------------------------------------
+// create
+// -----------------------------------------------------------------------------
+
+PlayState.create = function () {
+ // Sound effects
+ this.sfx = {
+ jump: this.game.add.audio('sfx:jump'),
+ coin: this.game.add.audio('sfx:coin'),
+ stomp: this.game.add.audio('sfx:stomp'),
+ key: this.game.add.audio('sfx:key'),
+ door: this.game.add.audio('sfx:door')
+ };
+
+ // Background
+ this.game.add.image(0, 0, 'background');
+
+ // Load level
+ this._loadLevel(this.game.cache.getJSON('level:' + this.level));
+
+ // HUD
+ this._createHud();
+};
+
+// -----------------------------------------------------------------------------
+// update
+// -----------------------------------------------------------------------------
+
+PlayState.update = function () {
+ this._handleCollisions();
+ this._handleInput();
+
+ // Update hero sprite direction and animation
+ if (this.hero.body.velocity.x < 0) {
+ this.hero.scale.x = -1;
+ } else if (this.hero.body.velocity.x > 0) {
+ this.hero.scale.x = 1;
+ }
+ this.hero.animations.play(this._getAnimationName());
+
+ // Update spider directions when hitting walls
+ this.spiders.forEach(function (spider) {
+ if (spider.body.touching.right || spider.body.blocked.right) {
+ spider.body.velocity.x = -Spider.SPEED;
+ } else if (spider.body.touching.left || spider.body.blocked.left) {
+ spider.body.velocity.x = Spider.SPEED;
+ }
+ }, this);
+
+ // Update key icon in HUD
+ this.keyIcon.frame = this.hasKey ? 1 : 0;
+};
+
+// -----------------------------------------------------------------------------
+// Level Loading
+// -----------------------------------------------------------------------------
+
+PlayState._loadLevel = function (data) {
+ // Create groups (order matters for rendering layers)
+ this.bgDecoration = this.game.add.group();
+ this.platforms = this.game.add.group();
+ this.coins = this.game.add.group();
+ this.spiders = this.game.add.group();
+ this.enemyWalls = this.game.add.group();
+
+ // Spawn entities from level data
+ data.platforms.forEach(this._spawnPlatform, this);
+ data.coins.forEach(this._spawnCoin, this);
+ data.spiders.forEach(this._spawnSpider, this);
+
+ this._spawnDoor(data.door.x, data.door.y);
+ this._spawnKey(data.key.x, data.key.y);
+ this._spawnCharacters({ hero: data.hero });
+
+ // Hide invisible walls
+ this.enemyWalls.visible = false;
+
+ // Initialize game state
+ this.coinPickupCount = 0;
+ this.hasKey = false;
+};
+
+// -----------------------------------------------------------------------------
+// Spawn Methods
+// -----------------------------------------------------------------------------
+
+PlayState._spawnPlatform = function (platform) {
+ let sprite = this.platforms.create(platform.x, platform.y, platform.image);
+ this.game.physics.enable(sprite);
+ sprite.body.allowGravity = false;
+ sprite.body.immovable = true;
+
+ // Add invisible walls at both edges for enemy AI
+ this._spawnEnemyWall(platform.x, platform.y, 'left');
+ this._spawnEnemyWall(platform.x + sprite.width, platform.y, 'right');
+};
+
+PlayState._spawnEnemyWall = function (x, y, side) {
+ let sprite = this.enemyWalls.create(x, y, 'invisible-wall');
+ sprite.anchor.set(side === 'left' ? 1 : 0, 1);
+ this.game.physics.enable(sprite);
+ sprite.body.immovable = true;
+ sprite.body.allowGravity = false;
+};
+
+PlayState._spawnCharacters = function (data) {
+ this.hero = this.game.add.sprite(data.hero.x, data.hero.y, 'hero');
+ this.hero.anchor.set(0.5, 1);
+ this.game.physics.enable(this.hero);
+ this.hero.body.collideWorldBounds = true;
+
+ // Hero animations
+ this.hero.animations.add('stop', [0]);
+ this.hero.animations.add('run', [1, 2], 8, true);
+ this.hero.animations.add('jump', [3]);
+ this.hero.animations.add('fall', [4]);
+};
+
+PlayState._spawnCoin = function (coin) {
+ let sprite = this.coins.create(coin.x, coin.y, 'coin');
+ sprite.anchor.set(0.5, 0.5);
+ this.game.physics.enable(sprite);
+ sprite.body.allowGravity = false;
+
+ sprite.animations.add('rotate', [0, 1, 2, 1], 6, true);
+ sprite.animations.play('rotate');
+};
+
+PlayState._spawnSpider = function (spider) {
+ let sprite = this.spiders.create(spider.x, spider.y, 'spider');
+ sprite.anchor.set(0.5, 1);
+ this.game.physics.enable(sprite);
+
+ sprite.animations.add('crawl', [0, 1, 2], 8, true);
+ sprite.animations.add('die', [0, 4, 0, 4, 0, 4, 3, 3, 3, 3, 3, 3], 12);
+ sprite.animations.play('crawl');
+
+ sprite.body.velocity.x = Spider.SPEED;
+};
+
+PlayState._spawnDoor = function (x, y) {
+ this.door = this.bgDecoration.create(x, y, 'door');
+ this.door.anchor.setTo(0.5, 1);
+ this.game.physics.enable(this.door);
+ this.door.body.allowGravity = false;
+};
+
+PlayState._spawnKey = function (x, y) {
+ this.key = this.bgDecoration.create(x, y, 'key');
+ this.key.anchor.set(0.5, 0.5);
+ this.game.physics.enable(this.key);
+ this.key.body.allowGravity = false;
+
+ // Bobbing tween
+ this.key.y -= 3;
+ this.game.add.tween(this.key)
+ .to({ y: this.key.y + 6 }, 800, Phaser.Easing.Sinusoidal.InOut)
+ .yoyo(true)
+ .loop()
+ .start();
+};
+
+// -----------------------------------------------------------------------------
+// Input
+// -----------------------------------------------------------------------------
+
+PlayState._handleInput = function () {
+ if (!this.hero.alive) { return; }
+
+ if (this.keys.left.isDown) {
+ this.hero.body.velocity.x = -SPEED;
+ } else if (this.keys.right.isDown) {
+ this.hero.body.velocity.x = SPEED;
+ } else {
+ this.hero.body.velocity.x = 0;
+ }
+
+ if (this.keys.up.isDown) {
+ this._jump();
+ }
+};
+
+PlayState._jump = function () {
+ let canJump = this.hero.body.touching.down;
+ if (canJump) {
+ this.hero.body.velocity.y = -JUMP_SPEED;
+ this.sfx.jump.play();
+ }
+ return canJump;
+};
+
+// -----------------------------------------------------------------------------
+// Collisions
+// -----------------------------------------------------------------------------
+
+PlayState._handleCollisions = function () {
+ // Physical collisions
+ this.game.physics.arcade.collide(this.hero, this.platforms);
+ this.game.physics.arcade.collide(this.spiders, this.platforms);
+ this.game.physics.arcade.collide(this.spiders, this.enemyWalls);
+
+ // Overlap detection (no physical push)
+ this.game.physics.arcade.overlap(
+ this.hero, this.coins, this._onHeroVsCoin, null, this
+ );
+ this.game.physics.arcade.overlap(
+ this.hero, this.spiders, this._onHeroVsEnemy, null, this
+ );
+ this.game.physics.arcade.overlap(
+ this.hero, this.key, this._onHeroVsKey, null, this
+ );
+ this.game.physics.arcade.overlap(
+ this.hero, this.door, this._onHeroVsDoor,
+ function (hero, door) {
+ return this.hasKey && hero.body.touching.down;
+ }, this
+ );
+};
+
+// -----------------------------------------------------------------------------
+// Collision Callbacks
+// -----------------------------------------------------------------------------
+
+PlayState._onHeroVsCoin = function (hero, coin) {
+ this.sfx.coin.play();
+ coin.kill();
+ this.coinPickupCount++;
+ this.coinFont.text = 'x' + this.coinPickupCount;
+};
+
+PlayState._onHeroVsEnemy = function (hero, enemy) {
+ if (hero.body.velocity.y > 0) {
+ // Stomp: hero is falling onto the enemy
+ enemy.body.velocity.x = 0;
+ enemy.body.enable = false;
+ enemy.animations.play('die');
+ enemy.events.onAnimationComplete.addOnce(function () {
+ enemy.kill();
+ });
+ hero.body.velocity.y = -JUMP_SPEED / 2;
+ this.sfx.stomp.play();
+ } else {
+ // Hero dies
+ this._killHero();
+ }
+};
+
+PlayState._onHeroVsKey = function (hero, key) {
+ this.sfx.key.play();
+ key.kill();
+ this.hasKey = true;
+};
+
+PlayState._onHeroVsDoor = function (hero, door) {
+ this.sfx.door.play();
+ hero.body.velocity.x = 0;
+ hero.body.velocity.y = 0;
+ hero.body.enable = false;
+
+ door.frame = 1; // Open door
+
+ this.game.time.events.add(500, this._goToNextLevel, this);
+};
+
+// -----------------------------------------------------------------------------
+// Death and Level Transitions
+// -----------------------------------------------------------------------------
+
+PlayState._killHero = function () {
+ this.hero.alive = false;
+ this.hero.body.velocity.y = -JUMP_SPEED / 2;
+ this.hero.body.velocity.x = 0;
+ this.hero.body.allowGravity = true;
+ this.hero.body.collideWorldBounds = false;
+
+ this.game.time.events.add(1000, function () {
+ this.game.state.restart(true, false, { level: this.level });
+ }, this);
+};
+
+PlayState._goToNextLevel = function () {
+ this.camera.fade('#000');
+ this.camera.onFadeComplete.addOnce(function () {
+ this.game.state.restart(true, false, {
+ level: this.level + 1
+ });
+ }, this);
+};
+
+// -----------------------------------------------------------------------------
+// Animations
+// -----------------------------------------------------------------------------
+
+PlayState._getAnimationName = function () {
+ let name = 'stop';
+
+ if (!this.hero.alive) {
+ name = 'stop';
+ } else if (this.hero.body.velocity.y < 0) {
+ name = 'jump';
+ } else if (this.hero.body.velocity.y > 0 && !this.hero.body.touching.down) {
+ name = 'fall';
+ } else if (this.hero.body.velocity.x !== 0 && this.hero.body.touching.down) {
+ name = 'run';
+ }
+
+ return name;
+};
+
+// -----------------------------------------------------------------------------
+// HUD
+// -----------------------------------------------------------------------------
+
+PlayState._createHud = function () {
+ this.keyIcon = this.game.make.image(0, 19, 'icon:key');
+ this.keyIcon.anchor.set(0, 0.5);
+
+ let coinIcon = this.game.make.image(
+ this.keyIcon.width + 7, 0, 'icon:coin'
+ );
+
+ let scoreStyle = { font: '24px monospace', fill: '#fff' };
+ this.coinFont = this.game.add.text(
+ coinIcon.x + coinIcon.width + 7, 0, 'x0', scoreStyle
+ );
+
+ this.hud = this.game.add.group();
+ this.hud.add(this.keyIcon);
+ this.hud.add(coinIcon);
+ this.hud.add(this.coinFont);
+ this.hud.position.set(10, 10);
+ this.hud.fixedToCamera = true;
+};
+
+// =============================================================================
+// Entry Point
+// =============================================================================
+
+window.onload = function () {
+ let game = new Phaser.Game(960, 600, Phaser.AUTO, 'game');
+ game.state.add('play', PlayState);
+ game.state.start('play', true, false, { level: 0 });
+};
+```
+
+### Key Concepts Summary
+
+| Concept | Phaser API | Purpose |
+|---------|-----------|---------|
+| Game instance | `new Phaser.Game(w, h, renderer, container)` | Creates the game canvas and engine |
+| Game states | `game.state.add()` / `game.state.start()` | Organizes code into init/preload/create/update lifecycle |
+| Loading images | `game.load.image(key, path)` | Loads a static image asset |
+| Loading spritesheets | `game.load.spritesheet(key, path, fw, fh)` | Loads an animated spritesheet |
+| Loading JSON | `game.load.json(key, path)` | Loads JSON data (level definitions) |
+| Loading audio | `game.load.audio(key, path)` | Loads a sound effect |
+| Sprite groups | `game.add.group()` | Container for related sprites; enables batch collision detection |
+| Physics bodies | `game.physics.enable(sprite)` | Adds an Arcade Physics body to a sprite |
+| Gravity | `game.physics.arcade.gravity.y` | Global downward acceleration |
+| Collision | `arcade.collide(a, b)` | Physical collision resolution (sprites push each other) |
+| Overlap | `arcade.overlap(a, b, callback)` | Detection without physical push (for pickups) |
+| Velocity | `sprite.body.velocity.x/y` | Movement speed in pixels per second |
+| Immovable | `sprite.body.immovable = true` | Prevents sprite from being pushed by collisions |
+| Animations | `sprite.animations.add(name, frames, fps, loop)` | Defines a frame animation |
+| Tweens | `game.add.tween(target).to(props, duration, easing)` | Smooth property animation |
+| Keyboard input | `game.input.keyboard.addKeys({...})` | Captures specific keyboard keys |
+| Camera | `this.camera.fade()` | Screen transition effects |
+| Anchor | `sprite.anchor.set(x, y)` | Sets the origin point for positioning and rotation |
+| Sprite flipping | `sprite.scale.x = -1` | Horizontally mirrors the sprite |
diff --git a/skills/game-engine/assets/gameBase-template-repo.md b/skills/game-engine/assets/gameBase-template-repo.md
new file mode 100644
index 000000000..795ac0257
--- /dev/null
+++ b/skills/game-engine/assets/gameBase-template-repo.md
@@ -0,0 +1,310 @@
+# GameBase Template Repository
+
+A feature-rich, opinionated starter template for 2D game projects built with **Haxe** and the **Heaps** game engine. Created and maintained by **Sebastien Benard** (deepnight), the lead developer behind *Dead Cells*. GameBase provides a production-tested foundation with entity management, level integration via LDtk, rendering pipeline, and a game loop architecture -- all designed to let developers skip boilerplate and jump straight into game-specific logic.
+
+**Repository:** [github.com/deepnight/gameBase](https://github.com/deepnight/gameBase)
+**Author:** [Sebastien Benard / deepnight](https://deepnight.net)
+**Technology:** Haxe + Heaps (HashLink or JS targets)
+**Level editor integration:** [LDtk](https://ldtk.io)
+
+---
+
+## Purpose
+
+GameBase exists to solve the "blank project" problem. Instead of setting up rendering, entity systems, camera controls, debug overlays, and level loading from scratch, developers clone this repository and begin implementing game-specific mechanics immediately. It reflects patterns refined through commercial game development, particularly from the development of *Dead Cells*.
+
+Key benefits:
+- Pre-built entity system with grid-based positioning and sub-pixel precision
+- LDtk level editor integration for visual level design
+- Built-in debug tools and overlays
+- Frame-rate independent game loop with fixed-step updates
+- Camera system with follow, shake, zoom, and clamp
+- Configurable Controller/input management
+- Scalable rendering pipeline with Heaps
+
+---
+
+## Repository Structure
+
+```
+gameBase/
+ src/
+ game/
+ App.hx -- Application entry point and initialization
+ Game.hx -- Main game process, holds level and entities
+ Entity.hx -- Base entity class with grid coords, velocity, animation
+ Level.hx -- Level loading and collision map from LDtk
+ Camera.hx -- Camera follow, shake, zoom, clamping
+ Fx.hx -- Visual effects (particles, flashes, etc.)
+ Types.hx -- Enums, typedefs, and constants
+ en/
+ Hero.hx -- Player entity (example implementation)
+ Mob.hx -- Enemy entity (example implementation)
+ import.hx -- Global imports (available everywhere)
+ res/
+ atlas/ -- Sprite sheets and texture atlases
+ levels/ -- LDtk level project files
+ fonts/ -- Bitmap fonts
+ .ldtk -- LDtk project file (root)
+ build.hxml -- Haxe compiler configuration
+ Makefile -- Build/run shortcuts
+ README.md
+```
+
+---
+
+## Key Files and Their Roles
+
+### `src/game/App.hx` -- Application Entry Point
+
+The main application class that extends `dn.Process`. Handles:
+- Window/display initialization
+- Scene management (root scene graph)
+- Global input controller setup
+- Debug toggle and console
+
+```haxe
+class App extends dn.Process {
+ public static var ME : App;
+
+ override function init() {
+ ME = this;
+ // Initialize rendering, controller, assets
+ new Game();
+ }
+}
+```
+
+### `src/game/Game.hx` -- Game Process
+
+Manages the active game session:
+- Holds reference to the current `Level`
+- Manages all active `Entity` instances (via a global linked list)
+- Handles pause, game-over, and restart logic
+- Coordinates camera and effects
+
+```haxe
+class Game extends dn.Process {
+ public var level : Level;
+ public var hero : en.Hero;
+ public var fx : Fx;
+ public var camera : Camera;
+
+ public function new() {
+ super(App.ME);
+ level = new Level();
+ fx = new Fx();
+ camera = new Camera();
+ hero = new en.Hero();
+ }
+}
+```
+
+### `src/game/Entity.hx` -- Base Entity
+
+The core entity class featuring:
+- **Grid-based positioning:** `cx`, `cy` (integer cell coordinates) plus `xr`, `yr` (sub-cell ratio 0.0 to 1.0) for smooth sub-pixel movement
+- **Velocity and friction:** `dx`, `dy` (velocity) with configurable `frictX`, `frictY`
+- **Gravity:** Optional per-entity gravity
+- **Sprite management:** Animated sprite via Heaps `h2d.Anim` or `dn.heaps.HSprite`
+- **Lifecycle:** `update()`, `fixedUpdate()`, `postUpdate()`, `dispose()`
+- **Collision helpers:** `hasCollision(cx, cy)` check against the level collision map
+
+```haxe
+class Entity {
+ // Grid position
+ public var cx : Int = 0; // Cell X
+ public var cy : Int = 0; // Cell Y
+ public var xr : Float = 0.5; // X ratio within cell (0..1)
+ public var yr : Float = 1.0; // Y ratio within cell (0..1)
+
+ // Velocity
+ public var dx : Float = 0;
+ public var dy : Float = 0;
+
+ // Pixel position (computed)
+ public var attachX(get,never) : Float;
+ inline function get_attachX() return (cx + xr) * Const.GRID;
+ public var attachY(get,never) : Float;
+ inline function get_attachY() return (cy + yr) * Const.GRID;
+
+ // Physics step
+ public function fixedUpdate() {
+ xr += dx;
+ dx *= frictX;
+
+ // X collision
+ if (xr > 1) { cx++; xr--; }
+ if (xr < 0) { cx--; xr++; }
+
+ yr += dy;
+ dy *= frictY;
+
+ // Y collision
+ if (yr > 1) { cy++; yr--; }
+ if (yr < 0) { cy--; yr++; }
+ }
+}
+```
+
+### `src/game/Level.hx` -- Level Management
+
+Loads and manages level data from LDtk project files:
+- Parses tile layers, entity layers, and int grid layers
+- Builds a collision grid (`hasCollision(cx, cy)`)
+- Provides helper methods to query the level structure
+
+```haxe
+class Level {
+ var data : ldtk.Level;
+ var collisions : Map
;
+
+ public function new(ldtkLevel) {
+ data = ldtkLevel;
+ // Parse IntGrid layer for collision marks
+ for (cy in 0...data.l_Collisions.cHei)
+ for (cx in 0...data.l_Collisions.cWid)
+ if (data.l_Collisions.getInt(cx, cy) == 1)
+ collisions.set(coordId(cx, cy), true);
+ }
+
+ public inline function hasCollision(cx:Int, cy:Int) : Bool {
+ return collisions.exists(coordId(cx, cy));
+ }
+}
+```
+
+### `src/game/Camera.hx` -- Camera System
+
+Provides:
+- **Target tracking:** Follow an entity smoothly with configurable dead zones
+- **Shake:** Screen shake with decay
+- **Zoom:** Dynamic zoom in/out
+- **Clamping:** Keep the camera within level bounds
+
+### `src/game/Fx.hx` -- Effects System
+
+Particle and visual effect management:
+- Particle pools
+- Screen flash
+- Slow-motion helpers
+- Color overlay effects
+
+---
+
+## Technology Stack
+
+### Haxe
+
+A cross-platform, high-level programming language that compiles to multiple targets:
+- **HashLink (HL):** Native bytecode VM for desktop (primary dev target)
+- **JavaScript (JS):** Browser/web target
+- **C/C++:** Via HXCPP for native builds
+
+### Heaps (Heaps.io)
+
+A high-performance, cross-platform 2D/3D game engine:
+- GPU-accelerated rendering via OpenGL/DirectX/WebGL
+- Scene graph architecture with `h2d.Object` hierarchy
+- Sprite batching and texture atlases
+- Bitmap font rendering
+- Input abstraction
+
+### LDtk
+
+A modern, open-source 2D level editor created by Sebastien Benard:
+- Visual, tile-based level design
+- IntGrid layers for collision and metadata
+- Entity layers for game object placement
+- Auto-tiling rules
+- Haxe API auto-generated from the project file
+
+---
+
+## Setup Instructions
+
+### Prerequisites
+
+1. **Install Haxe** (4.0+): [haxe.org](https://haxe.org/download/)
+2. **Install HashLink** (for desktop target): [hashlink.haxe.org](https://hashlink.haxe.org/)
+3. **Install LDtk** (for level editing): [ldtk.io](https://ldtk.io/)
+
+### Getting Started
+
+```bash
+# Clone the repository
+git clone https://github.com/deepnight/gameBase.git my-game
+cd my-game
+
+# Install Haxe dependencies
+haxelib install heaps
+haxelib install deepnightLibs
+haxelib install ldtk-haxe-api
+
+# Build and run (HashLink target)
+haxe build.hxml
+hl bin/client.hl
+
+# Or use the Makefile (if available)
+make run
+```
+
+### Using as a Starting Point
+
+1. **Clone or use the template** -- Do not fork; clone into a new directory with your game's name.
+2. **Rename the package** -- Update `src/game/` package declarations and project references to match your game.
+3. **Edit `build.hxml`** -- Adjust the main class, output path, and target as needed.
+4. **Design levels in LDtk** -- Open the `.ldtk` file, define your layers and entities, and export.
+5. **Implement entities** -- Create new entity classes in `src/game/en/` extending `Entity`.
+6. **Iterate** -- Use the debug console (toggle in-game) for live inspection and tuning.
+
+---
+
+## Build Targets
+
+| Target | Command | Output | Use Case |
+|--------|---------|--------|----------|
+| HashLink | `haxe build.hxml` | `bin/client.hl` | Development, desktop release |
+| JavaScript | `haxe build.js.hxml` | `bin/client.js` | Web/browser builds |
+| DirectX/OpenGL | Via HL native | Native executable | Production desktop release |
+
+---
+
+## Debug Features
+
+GameBase includes built-in debug tooling:
+- **Debug overlay:** Toggle with a key to show entity bounds, grid, velocities, collision map
+- **Console:** In-game command console for toggling flags, teleporting, spawning entities
+- **FPS counter:** Visible frame-rate and update-rate monitor
+- **Process inspector:** View active processes and their hierarchy
+
+---
+
+## Game Loop Architecture
+
+GameBase uses a fixed-timestep game loop pattern:
+
+```
+Each frame:
+ 1. preUpdate() -- Input polling, pre-frame logic
+ 2. fixedUpdate() -- Physics, movement, collisions (fixed timestep)
+ - May run 0-N times per frame to catch up
+ 3. update() -- General per-frame logic
+ 4. postUpdate() -- Sprite position sync, camera update, rendering prep
+```
+
+This ensures physics behavior is consistent regardless of frame rate, while rendering and visual updates remain smooth.
+
+---
+
+## Entity Lifecycle
+
+```
+Constructor --> init() --> [game loop: fixedUpdate/update/postUpdate] --> dispose()
+```
+
+- **Constructor:** Set initial position, create sprite, register in global entity list
+- **fixedUpdate():** Physics step (velocity, friction, gravity, collision)
+- **update():** AI, state machine, animation triggers
+- **postUpdate():** Sync sprite position to grid coordinates, apply visual effects
+- **dispose():** Remove from entity list, destroy sprite, clean up references
diff --git a/skills/game-engine/assets/paddle-game-template.md b/skills/game-engine/assets/paddle-game-template.md
new file mode 100644
index 000000000..2222e997b
--- /dev/null
+++ b/skills/game-engine/assets/paddle-game-template.md
@@ -0,0 +1,1528 @@
+# Paddle Game Template (2D Breakout)
+
+A complete step-by-step guide for building a 2D Breakout game with pure JavaScript and the HTML5 Canvas API. This template walks through every stage of development, from setting up the canvas to implementing a lives system and polished game loop.
+
+**What you will build:** A classic breakout/paddle game where the player controls a paddle to bounce a ball and destroy a field of bricks, with score tracking, win/lose conditions, keyboard and mouse controls, and a lives system.
+
+**Prerequisites:** Basic to intermediate JavaScript knowledge and familiarity with HTML.
+
+**Source:** Based on the [MDN 2D Breakout Game Tutorial](https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript).
+
+---
+
+## Step 1: Create the Canvas and Draw on It
+
+The first step is setting up the HTML document with a `` element and learning to draw basic shapes using the 2D rendering context.
+
+### HTML Structure
+
+Create your base HTML file with an embedded canvas element:
+
+```html
+
+
+
+
+ Gamedev Canvas Workshop
+
+
+
+
+
+
+
+
+```
+
+### Getting the Canvas Reference and 2D Context
+
+The canvas element provides a drawing surface. You access it through a 2D rendering context:
+
+```javascript
+const canvas = document.getElementById("myCanvas");
+const ctx = canvas.getContext("2d");
+```
+
+- `canvas` is a reference to the HTML `` element.
+- `ctx` is the 2D rendering context object, which provides all drawing methods.
+
+### Drawing a Filled Rectangle
+
+Use `rect()` to define a rectangle and `fill()` to render it:
+
+```javascript
+ctx.beginPath();
+ctx.rect(20, 40, 50, 50);
+ctx.fillStyle = "red";
+ctx.fill();
+ctx.closePath();
+```
+
+- The first two parameters (`20, 40`) set the top-left corner coordinates.
+- The second two parameters (`50, 50`) set the width and height.
+- `fillStyle` sets the fill color.
+- `fill()` renders the shape as a solid fill.
+
+### Drawing a Circle
+
+Use `arc()` to define a circle:
+
+```javascript
+ctx.beginPath();
+ctx.arc(240, 160, 20, 0, Math.PI * 2, false);
+ctx.fillStyle = "green";
+ctx.fill();
+ctx.closePath();
+```
+
+- `240, 160` -- center x, y coordinates.
+- `20` -- radius.
+- `0` -- start angle (radians).
+- `Math.PI * 2` -- end angle (full circle).
+- `false` -- draw clockwise.
+
+### Drawing a Stroked Rectangle (Outline Only)
+
+Use `stroke()` instead of `fill()` for outlines, and `strokeStyle` for outline color:
+
+```javascript
+ctx.beginPath();
+ctx.rect(160, 10, 100, 40);
+ctx.strokeStyle = "rgb(0 0 255 / 50%)";
+ctx.stroke();
+ctx.closePath();
+```
+
+- Uses an RGB color with 50% alpha transparency.
+- `stroke()` draws only the outline, not a solid fill.
+
+### Key Methods Reference
+
+| Method | Purpose |
+|--------|---------|
+| `beginPath()` | Start a new drawing path |
+| `closePath()` | Close the current path |
+| `rect(x, y, width, height)` | Define a rectangle |
+| `arc(x, y, radius, startAngle, endAngle, counterclockwise)` | Define a circle or arc |
+| `fillStyle` | Set the fill color |
+| `fill()` | Fill the shape with the fill color |
+| `strokeStyle` | Set the stroke (outline) color |
+| `stroke()` | Draw an outline of the shape |
+
+### Complete Code for Step 1
+
+```html
+
+
+
+
+
+```
+
+---
+
+## Step 2: Move the Ball
+
+Now we animate the ball by creating a game loop that redraws the canvas on each frame and updates the ball position using velocity variables.
+
+### Creating the Draw Loop
+
+Define a `draw()` function that executes repeatedly using `setInterval`:
+
+```javascript
+function draw() {
+ // drawing code
+}
+setInterval(draw, 10);
+```
+
+`setInterval(draw, 10)` calls the `draw` function every 10 milliseconds, creating approximately 100 frames per second.
+
+### Drawing the Ball
+
+Inside the `draw()` function, draw a ball (circle) at a fixed position:
+
+```javascript
+ctx.beginPath();
+ctx.arc(50, 50, 10, 0, Math.PI * 2);
+ctx.fillStyle = "#0095DD";
+ctx.fill();
+ctx.closePath();
+```
+
+### Adding Position Variables
+
+Instead of hardcoded positions, use variables so we can update them each frame. Place these above the `draw()` function:
+
+```javascript
+let x = canvas.width / 2;
+let y = canvas.height - 30;
+```
+
+This starts the ball at the horizontal center, near the bottom of the canvas.
+
+### Adding Velocity Variables
+
+Define speed and direction for horizontal (`dx`) and vertical (`dy`) movement:
+
+```javascript
+let dx = 2;
+let dy = -2;
+```
+
+- `dx = 2` moves the ball 2 pixels right per frame.
+- `dy = -2` moves the ball 2 pixels up per frame (negative y is upward on canvas).
+
+### Updating Position Each Frame
+
+Add position updates at the end of the `draw()` function:
+
+```javascript
+x += dx;
+y += dy;
+```
+
+### Clearing the Canvas
+
+Without clearing, the ball leaves a trail. Add `clearRect()` at the start of each frame:
+
+```javascript
+ctx.clearRect(0, 0, canvas.width, canvas.height);
+```
+
+### Refactoring Into a Separate drawBall() Function
+
+For clean, maintainable code, separate the ball-drawing logic:
+
+```javascript
+function drawBall() {
+ ctx.beginPath();
+ ctx.arc(x, y, 10, 0, Math.PI * 2);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+```
+
+### Complete Code for Step 2
+
+```javascript
+const canvas = document.getElementById("myCanvas");
+const ctx = canvas.getContext("2d");
+
+let x = canvas.width / 2;
+let y = canvas.height - 30;
+let dx = 2;
+let dy = -2;
+
+function drawBall() {
+ ctx.beginPath();
+ ctx.arc(x, y, 10, 0, Math.PI * 2);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawBall();
+ x += dx;
+ y += dy;
+}
+
+setInterval(draw, 10);
+```
+
+**Key concepts:**
+- **Animation loop**: `setInterval(draw, 10)` continuously redraws the scene.
+- **Position variables**: `x` and `y` track the ball's current location.
+- **Velocity variables**: `dx` and `dy` determine movement per frame.
+- **Canvas clearing**: `clearRect()` removes the previous frame before drawing the new one.
+
+---
+
+## Step 3: Bounce Off the Walls
+
+We add collision detection so the ball bounces off the canvas edges instead of disappearing.
+
+### Defining the Ball Radius
+
+Extract the ball radius into a named constant for reuse in collision calculations:
+
+```javascript
+const ballRadius = 10;
+```
+
+Update `drawBall()` to use this variable:
+
+```javascript
+function drawBall() {
+ ctx.beginPath();
+ ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+```
+
+### Basic Wall Collision (Without Radius Adjustment)
+
+The simplest approach checks if the next ball position goes beyond the canvas boundaries:
+
+```javascript
+// Left and right walls
+if (x + dx > canvas.width || x + dx < 0) {
+ dx = -dx;
+}
+
+// Top and bottom walls
+if (y + dy > canvas.height || y + dy < 0) {
+ dy = -dy;
+}
+```
+
+Reversing `dx` or `dy` (multiplying by -1) changes the ball's direction.
+
+### Improved Collision (Accounting for Ball Radius)
+
+The basic version lets the ball sink halfway into the wall before bouncing. To fix this, account for the ball's radius:
+
+```javascript
+// Left and right walls
+if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
+ dx = -dx;
+}
+
+// Top and bottom walls
+if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
+ dy = -dy;
+}
+```
+
+### Collision Detection Conditions
+
+| Wall | Condition | Action |
+|------|-----------|--------|
+| **Left** | `x + dx < ballRadius` | `dx = -dx` |
+| **Right** | `x + dx > canvas.width - ballRadius` | `dx = -dx` |
+| **Top** | `y + dy < ballRadius` | `dy = -dy` |
+| **Bottom** | `y + dy > canvas.height - ballRadius` | `dy = -dy` |
+
+### Complete Code for Step 3
+
+```javascript
+const canvas = document.getElementById("myCanvas");
+const ctx = canvas.getContext("2d");
+const ballRadius = 10;
+
+let x = canvas.width / 2;
+let y = canvas.height - 30;
+let dx = 2;
+let dy = -2;
+
+function drawBall() {
+ ctx.beginPath();
+ ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawBall();
+
+ // Collision detection - left and right walls
+ if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
+ dx = -dx;
+ }
+
+ // Collision detection - top and bottom walls
+ if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
+ dy = -dy;
+ }
+
+ x += dx;
+ y += dy;
+}
+
+setInterval(draw, 10);
+```
+
+---
+
+## Step 4: Paddle and Keyboard Controls
+
+Now we add a player-controlled paddle at the bottom of the screen and wire up keyboard input (left/right arrow keys).
+
+### Defining Paddle Variables
+
+```javascript
+const paddleHeight = 10;
+const paddleWidth = 75;
+let paddleX = (canvas.width - paddleWidth) / 2;
+```
+
+- `paddleHeight` and `paddleWidth` define the paddle dimensions.
+- `paddleX` starts the paddle centered horizontally. It is a `let` because it will change as the player moves it.
+
+### Drawing the Paddle
+
+Create a `drawPaddle()` function. The paddle sits at the very bottom of the canvas:
+
+```javascript
+function drawPaddle() {
+ ctx.beginPath();
+ ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+```
+
+- The y-position is `canvas.height - paddleHeight`, placing it flush with the bottom edge.
+
+### Keyboard State Variables
+
+Track whether arrow keys are currently pressed:
+
+```javascript
+let rightPressed = false;
+let leftPressed = false;
+```
+
+### Event Listeners for Key Presses
+
+Register handlers for `keydown` (key pressed) and `keyup` (key released):
+
+```javascript
+document.addEventListener("keydown", keyDownHandler);
+document.addEventListener("keyup", keyUpHandler);
+```
+
+### Key Handler Functions
+
+Set the boolean flags based on which key is pressed or released:
+
+```javascript
+function keyDownHandler(e) {
+ if (e.key === "Right" || e.key === "ArrowRight") {
+ rightPressed = true;
+ } else if (e.key === "Left" || e.key === "ArrowLeft") {
+ leftPressed = true;
+ }
+}
+
+function keyUpHandler(e) {
+ if (e.key === "Right" || e.key === "ArrowRight") {
+ rightPressed = false;
+ } else if (e.key === "Left" || e.key === "ArrowLeft") {
+ leftPressed = false;
+ }
+}
+```
+
+Both `"ArrowRight"` (modern browsers) and `"Right"` (legacy IE/Edge) are checked for compatibility.
+
+### Paddle Movement Logic (With Boundary Checking)
+
+Add this inside the `draw()` function to move the paddle based on key state, while keeping it within canvas bounds:
+
+```javascript
+if (rightPressed) {
+ paddleX = Math.min(paddleX + 7, canvas.width - paddleWidth);
+} else if (leftPressed) {
+ paddleX = Math.max(paddleX - 7, 0);
+}
+```
+
+- The paddle moves 7 pixels per frame.
+- `Math.min` prevents the paddle from going past the right edge.
+- `Math.max` prevents it from going past the left edge.
+
+### Complete Code for Step 4
+
+```javascript
+const canvas = document.getElementById("myCanvas");
+const ctx = canvas.getContext("2d");
+const ballRadius = 10;
+
+let x = canvas.width / 2;
+let y = canvas.height - 30;
+let dx = 2;
+let dy = -2;
+
+const paddleHeight = 10;
+const paddleWidth = 75;
+let paddleX = (canvas.width - paddleWidth) / 2;
+
+let rightPressed = false;
+let leftPressed = false;
+
+document.addEventListener("keydown", keyDownHandler);
+document.addEventListener("keyup", keyUpHandler);
+
+function keyDownHandler(e) {
+ if (e.key === "Right" || e.key === "ArrowRight") {
+ rightPressed = true;
+ } else if (e.key === "Left" || e.key === "ArrowLeft") {
+ leftPressed = true;
+ }
+}
+
+function keyUpHandler(e) {
+ if (e.key === "Right" || e.key === "ArrowRight") {
+ rightPressed = false;
+ } else if (e.key === "Left" || e.key === "ArrowLeft") {
+ leftPressed = false;
+ }
+}
+
+function drawBall() {
+ ctx.beginPath();
+ ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+
+function drawPaddle() {
+ ctx.beginPath();
+ ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawBall();
+ drawPaddle();
+
+ if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
+ dx = -dx;
+ }
+ if (y + dy > canvas.height - ballRadius || y + dy < ballRadius) {
+ dy = -dy;
+ }
+
+ if (rightPressed) {
+ paddleX = Math.min(paddleX + 7, canvas.width - paddleWidth);
+ } else if (leftPressed) {
+ paddleX = Math.max(paddleX - 7, 0);
+ }
+
+ x += dx;
+ y += dy;
+}
+
+setInterval(draw, 10);
+```
+
+---
+
+## Step 5: Game Over
+
+We replace the bottom-wall bounce with actual game logic: the ball should bounce off the paddle, but if it misses, it is game over.
+
+### Storing the Interval Reference
+
+To stop the game loop on game over, store the interval ID:
+
+```javascript
+let interval = 0;
+```
+
+Then assign the return value of `setInterval`:
+
+```javascript
+interval = setInterval(draw, 10);
+```
+
+### Implementing Game Over and Paddle Collision
+
+Replace the bottom-wall collision check. Instead of bouncing off the bottom edge, we now check whether the ball hits the paddle or misses it:
+
+```javascript
+if (y + dy < ballRadius) {
+ // Ball hits top wall -- bounce
+ dy = -dy;
+} else if (y + dy > canvas.height - ballRadius) {
+ // Ball reaches bottom edge
+ if (x > paddleX && x < paddleX + paddleWidth) {
+ // Ball hits paddle -- bounce
+ dy = -dy;
+ } else {
+ // Ball missed the paddle -- game over
+ alert("GAME OVER");
+ document.location.reload();
+ clearInterval(interval);
+ }
+}
+```
+
+**How paddle collision works:**
+- `x > paddleX` -- the ball is past the paddle's left edge.
+- `x < paddleX + paddleWidth` -- the ball is before the paddle's right edge.
+- If both are true, the ball is above the paddle, so it bounces.
+- If the ball reaches the bottom without hitting the paddle, the game ends.
+
+### Complete Code for Step 5
+
+```javascript
+const canvas = document.getElementById("myCanvas");
+const ctx = canvas.getContext("2d");
+const ballRadius = 10;
+
+let x = canvas.width / 2;
+let y = canvas.height - 30;
+let dx = 2;
+let dy = -2;
+
+const paddleHeight = 10;
+const paddleWidth = 75;
+let paddleX = (canvas.width - paddleWidth) / 2;
+
+let rightPressed = false;
+let leftPressed = false;
+let interval = 0;
+
+document.addEventListener("keydown", keyDownHandler);
+document.addEventListener("keyup", keyUpHandler);
+
+function keyDownHandler(e) {
+ if (e.key === "Right" || e.key === "ArrowRight") {
+ rightPressed = true;
+ } else if (e.key === "Left" || e.key === "ArrowLeft") {
+ leftPressed = true;
+ }
+}
+
+function keyUpHandler(e) {
+ if (e.key === "Right" || e.key === "ArrowRight") {
+ rightPressed = false;
+ } else if (e.key === "Left" || e.key === "ArrowLeft") {
+ leftPressed = false;
+ }
+}
+
+function drawBall() {
+ ctx.beginPath();
+ ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+
+function drawPaddle() {
+ ctx.beginPath();
+ ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawBall();
+ drawPaddle();
+
+ // Left and right wall collision
+ if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
+ dx = -dx;
+ }
+
+ // Top wall collision
+ if (y + dy < ballRadius) {
+ dy = -dy;
+ } else if (y + dy > canvas.height - ballRadius) {
+ // Bottom edge: paddle collision or game over
+ if (x > paddleX && x < paddleX + paddleWidth) {
+ dy = -dy;
+ } else {
+ alert("GAME OVER");
+ document.location.reload();
+ clearInterval(interval);
+ }
+ }
+
+ // Paddle movement
+ if (rightPressed) {
+ paddleX = Math.min(paddleX + 7, canvas.width - paddleWidth);
+ } else if (leftPressed) {
+ paddleX = Math.max(paddleX - 7, 0);
+ }
+
+ x += dx;
+ y += dy;
+}
+
+interval = setInterval(draw, 10);
+```
+
+---
+
+## Step 6: Build the Brick Field
+
+Now we create the grid of bricks that the ball will destroy. The bricks are stored in a 2D array and drawn in rows and columns.
+
+### Brick Configuration Variables
+
+Define constants that control the layout of the brick field:
+
+```javascript
+const brickRowCount = 3;
+const brickColumnCount = 5;
+const brickWidth = 75;
+const brickHeight = 20;
+const brickPadding = 10;
+const brickOffsetTop = 30;
+const brickOffsetLeft = 30;
+```
+
+- `brickRowCount` / `brickColumnCount` -- how many rows and columns of bricks.
+- `brickWidth` / `brickHeight` -- dimensions of each individual brick.
+- `brickPadding` -- space between bricks.
+- `brickOffsetTop` / `brickOffsetLeft` -- distance from the top and left canvas edges to the first brick.
+
+### Creating the Bricks 2D Array
+
+Use nested loops to create a 2D array. Each brick stores its `x` and `y` position (initially `0`, calculated during drawing):
+
+```javascript
+const bricks = [];
+for (let c = 0; c < brickColumnCount; c++) {
+ bricks[c] = [];
+ for (let r = 0; r < brickRowCount; r++) {
+ bricks[c][r] = { x: 0, y: 0 };
+ }
+}
+```
+
+### The drawBricks() Function
+
+Loop through every brick, calculate its position, store it, and draw it:
+
+```javascript
+function drawBricks() {
+ for (let c = 0; c < brickColumnCount; c++) {
+ for (let r = 0; r < brickRowCount; r++) {
+ const brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
+ const brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
+ bricks[c][r].x = brickX;
+ bricks[c][r].y = brickY;
+ ctx.beginPath();
+ ctx.rect(brickX, brickY, brickWidth, brickHeight);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+ }
+ }
+}
+```
+
+**Position calculation formula:**
+- `brickX = column * (brickWidth + brickPadding) + brickOffsetLeft`
+- `brickY = row * (brickHeight + brickPadding) + brickOffsetTop`
+
+This creates an evenly-spaced grid with consistent padding and margins.
+
+### Calling drawBricks() in the Game Loop
+
+Add the call at the beginning of your `draw()` function, after clearing the canvas:
+
+```javascript
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawBricks();
+ drawBall();
+ drawPaddle();
+ // ... rest of draw function
+}
+```
+
+### Complete Code for Step 6
+
+```javascript
+const canvas = document.getElementById("myCanvas");
+const ctx = canvas.getContext("2d");
+const ballRadius = 10;
+
+let x = canvas.width / 2;
+let y = canvas.height - 30;
+let dx = 2;
+let dy = -2;
+
+const paddleHeight = 10;
+const paddleWidth = 75;
+let paddleX = (canvas.width - paddleWidth) / 2;
+
+let rightPressed = false;
+let leftPressed = false;
+let interval = 0;
+
+const brickRowCount = 3;
+const brickColumnCount = 5;
+const brickWidth = 75;
+const brickHeight = 20;
+const brickPadding = 10;
+const brickOffsetTop = 30;
+const brickOffsetLeft = 30;
+
+const bricks = [];
+for (let c = 0; c < brickColumnCount; c++) {
+ bricks[c] = [];
+ for (let r = 0; r < brickRowCount; r++) {
+ bricks[c][r] = { x: 0, y: 0 };
+ }
+}
+
+document.addEventListener("keydown", keyDownHandler);
+document.addEventListener("keyup", keyUpHandler);
+
+function keyDownHandler(e) {
+ if (e.key === "Right" || e.key === "ArrowRight") {
+ rightPressed = true;
+ } else if (e.key === "Left" || e.key === "ArrowLeft") {
+ leftPressed = true;
+ }
+}
+
+function keyUpHandler(e) {
+ if (e.key === "Right" || e.key === "ArrowRight") {
+ rightPressed = false;
+ } else if (e.key === "Left" || e.key === "ArrowLeft") {
+ leftPressed = false;
+ }
+}
+
+function drawBall() {
+ ctx.beginPath();
+ ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+
+function drawPaddle() {
+ ctx.beginPath();
+ ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+}
+
+function drawBricks() {
+ for (let c = 0; c < brickColumnCount; c++) {
+ for (let r = 0; r < brickRowCount; r++) {
+ const brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
+ const brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
+ bricks[c][r].x = brickX;
+ bricks[c][r].y = brickY;
+ ctx.beginPath();
+ ctx.rect(brickX, brickY, brickWidth, brickHeight);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+ }
+ }
+}
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawBricks();
+ drawBall();
+ drawPaddle();
+
+ if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
+ dx = -dx;
+ }
+ if (y + dy < ballRadius) {
+ dy = -dy;
+ } else if (y + dy > canvas.height - ballRadius) {
+ if (x > paddleX && x < paddleX + paddleWidth) {
+ dy = -dy;
+ } else {
+ alert("GAME OVER");
+ document.location.reload();
+ clearInterval(interval);
+ }
+ }
+
+ if (rightPressed) {
+ paddleX = Math.min(paddleX + 7, canvas.width - paddleWidth);
+ } else if (leftPressed) {
+ paddleX = Math.max(paddleX - 7, 0);
+ }
+
+ x += dx;
+ y += dy;
+}
+
+interval = setInterval(draw, 10);
+```
+
+---
+
+## Step 7: Collision Detection
+
+With bricks on screen, we need to detect when the ball hits one and make it disappear. Each brick gets a `status` property: `1` means visible, `0` means destroyed.
+
+### Adding the Status Property to Bricks
+
+Update the brick initialization to include a `status` flag:
+
+```javascript
+const bricks = [];
+for (let c = 0; c < brickColumnCount; c++) {
+ bricks[c] = [];
+ for (let r = 0; r < brickRowCount; r++) {
+ bricks[c][r] = { x: 0, y: 0, status: 1 };
+ }
+}
+```
+
+### The collisionDetection() Function
+
+Loop through every brick and check if the ball's center is within the brick's bounding box:
+
+```javascript
+function collisionDetection() {
+ for (let c = 0; c < brickColumnCount; c++) {
+ for (let r = 0; r < brickRowCount; r++) {
+ const b = bricks[c][r];
+ if (b.status === 1) {
+ if (
+ x > b.x &&
+ x < b.x + brickWidth &&
+ y > b.y &&
+ y < b.y + brickHeight
+ ) {
+ dy = -dy;
+ b.status = 0;
+ }
+ }
+ }
+ }
+}
+```
+
+**Collision conditions (all four must be true simultaneously):**
+- `x > b.x` -- ball center is to the right of the brick's left edge.
+- `x < b.x + brickWidth` -- ball center is to the left of the brick's right edge.
+- `y > b.y` -- ball center is below the brick's top edge.
+- `y < b.y + brickHeight` -- ball center is above the brick's bottom edge.
+
+When a collision is detected:
+- `dy = -dy` reverses the ball's vertical direction (bounce).
+- `b.status = 0` marks the brick as destroyed.
+
+### Updating drawBricks() to Respect Status
+
+Only draw bricks that are still active (`status === 1`):
+
+```javascript
+function drawBricks() {
+ for (let c = 0; c < brickColumnCount; c++) {
+ for (let r = 0; r < brickRowCount; r++) {
+ if (bricks[c][r].status === 1) {
+ const brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
+ const brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
+ bricks[c][r].x = brickX;
+ bricks[c][r].y = brickY;
+ ctx.beginPath();
+ ctx.rect(brickX, brickY, brickWidth, brickHeight);
+ ctx.fillStyle = "#0095DD";
+ ctx.fill();
+ ctx.closePath();
+ }
+ }
+ }
+}
+```
+
+### Calling collisionDetection() in the Game Loop
+
+Add the call in your `draw()` function, after drawing all elements:
+
+```javascript
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawBricks();
+ drawBall();
+ drawPaddle();
+ collisionDetection();
+ // ... rest of draw function
+}
+```
+
+---
+
+## Step 8: Track the Score and Win
+
+We add a score counter that increments each time a brick is destroyed, and a win condition that triggers when all bricks are gone.
+
+### Initializing the Score
+
+```javascript
+let score = 0;
+```
+
+### The drawScore() Function
+
+Display the current score on the canvas using text rendering:
+
+```javascript
+function drawScore() {
+ ctx.font = "16px Arial";
+ ctx.fillStyle = "#0095DD";
+ ctx.fillText(`Score: ${score}`, 8, 20);
+}
+```
+
+- `ctx.font` sets the font size and family (like CSS).
+- `ctx.fillText(text, x, y)` renders text at the given coordinates.
+- Position `(8, 20)` places the score in the top-left corner.
+
+### Incrementing the Score
+
+In the `collisionDetection()` function, increment the score when a brick is hit:
+
+```javascript
+dy = -dy;
+b.status = 0;
+score++;
+```
+
+### Adding the Win Condition
+
+After incrementing the score, check if the player has destroyed all bricks:
+
+```javascript
+score++;
+if (score === brickRowCount * brickColumnCount) {
+ alert("YOU WIN, CONGRATULATIONS!");
+ document.location.reload();
+ clearInterval(interval);
+}
+```
+
+The total number of bricks is `brickRowCount * brickColumnCount`. When the score reaches that number, every brick has been destroyed.
+
+### Complete collisionDetection() with Score and Win
+
+```javascript
+function collisionDetection() {
+ for (let c = 0; c < brickColumnCount; c++) {
+ for (let r = 0; r < brickRowCount; r++) {
+ const b = bricks[c][r];
+ if (b.status === 1) {
+ if (
+ x > b.x &&
+ x < b.x + brickWidth &&
+ y > b.y &&
+ y < b.y + brickHeight
+ ) {
+ dy = -dy;
+ b.status = 0;
+ score++;
+ if (score === brickRowCount * brickColumnCount) {
+ alert("YOU WIN, CONGRATULATIONS!");
+ document.location.reload();
+ clearInterval(interval);
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+### Calling drawScore() in the Game Loop
+
+Add the call in your `draw()` function:
+
+```javascript
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawBricks();
+ drawBall();
+ drawPaddle();
+ drawScore();
+ collisionDetection();
+ // ... rest of draw function
+}
+```
+
+### Canvas Text Methods Reference
+
+| Method/Property | Purpose |
+|-----------------|---------|
+| `ctx.font` | Set font size and family |
+| `ctx.fillStyle` | Set text color |
+| `ctx.fillText(text, x, y)` | Draw filled text at coordinates |
+
+---
+
+## Step 9: Mouse Controls
+
+In addition to keyboard controls, we add mouse support so the player can move the paddle by moving the mouse.
+
+### Adding the mousemove Event Listener
+
+Register the handler alongside the existing keyboard listeners:
+
+```javascript
+document.addEventListener("mousemove", mouseMoveHandler);
+```
+
+### The mouseMoveHandler Function
+
+Calculate the mouse's horizontal position relative to the canvas and update the paddle position:
+
+```javascript
+function mouseMoveHandler(e) {
+ const relativeX = e.clientX - canvas.offsetLeft;
+ if (relativeX > 0 && relativeX < canvas.width) {
+ paddleX = relativeX - paddleWidth / 2;
+ }
+}
+```
+
+**How it works:**
+- `e.clientX` -- the mouse's horizontal position in the browser viewport.
+- `canvas.offsetLeft` -- the distance from the canvas's left edge to the viewport's left edge.
+- `relativeX` -- the mouse position relative to the canvas (not the viewport).
+- The boundary check (`relativeX > 0 && relativeX < canvas.width`) ensures the paddle only moves when the mouse is over the canvas.
+- `paddleX = relativeX - paddleWidth / 2` centers the paddle under the mouse cursor by subtracting half the paddle width.
+
+### Complete Event Listener Setup (Keyboard + Mouse)
+
+```javascript
+document.addEventListener("keydown", keyDownHandler);
+document.addEventListener("keyup", keyUpHandler);
+document.addEventListener("mousemove", mouseMoveHandler);
+```
+
+Both control methods work simultaneously. The player can use arrow keys or mouse -- or switch between them at any time.
+
+---
+
+## Step 10: Finishing Up
+
+The final step adds a lives system (so the player gets multiple chances) and upgrades the game loop from `setInterval` to `requestAnimationFrame` for smoother rendering.
+
+### Adding the Lives Variable
+
+```javascript
+let lives = 3;
+```
+
+### The drawLives() Function
+
+Display the remaining lives in the top-right corner:
+
+```javascript
+function drawLives() {
+ ctx.font = "16px Arial";
+ ctx.fillStyle = "#0095DD";
+ ctx.fillText(`Lives: ${lives}`, canvas.width - 65, 20);
+}
+```
+
+### Implementing the Lives System
+
+Replace the immediate game-over logic with a lives-based system. When the ball misses the paddle:
+
+```javascript
+if (y + dy < ballRadius) {
+ dy = -dy;
+} else if (y + dy > canvas.height - ballRadius) {
+ if (x > paddleX && x < paddleX + paddleWidth) {
+ dy = -dy;
+ } else {
+ lives--;
+ if (!lives) {
+ alert("GAME OVER");
+ document.location.reload();
+ } else {
+ // Reset ball and paddle positions
+ x = canvas.width / 2;
+ y = canvas.height - 30;
+ dx = 2;
+ dy = -2;
+ paddleX = (canvas.width - paddleWidth) / 2;
+ }
+ }
+}
+```
+
+**What happens when a life is lost:**
+- `lives--` decrements the lives counter.
+- If `lives` reaches `0`, the game ends with an alert and page reload.
+- Otherwise, the ball resets to center-bottom, velocity resets, and the paddle resets to center.
+
+### Upgrading to requestAnimationFrame
+
+Replace `setInterval` with `requestAnimationFrame` for a smoother, browser-optimized game loop:
+
+**Old approach (remove):**
+```javascript
+interval = setInterval(draw, 10);
+```
+
+**New approach:**
+Add `requestAnimationFrame(draw)` at the end of the `draw()` function:
+
+```javascript
+function draw() {
+ // ... all drawing and logic ...
+ requestAnimationFrame(draw);
+}
+
+// Start the game by calling draw() once:
+draw();
+```
+
+`requestAnimationFrame` lets the browser schedule rendering at the optimal frame rate (typically 60fps), which is more efficient than a fixed 10ms interval.
+
+### Calling drawLives() in the Game Loop
+
+```javascript
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawBricks();
+ drawBall();
+ drawPaddle();
+ drawScore();
+ drawLives();
+ collisionDetection();
+ // ... rest of logic ...
+ requestAnimationFrame(draw);
+}
+```
+
+---
+
+## Complete Final Game Code
+
+Below is the entire game in a single, self-contained HTML file. This is the final product of all 10 steps combined.
+
+```html
+
+
+
+
+ 2D Breakout Game
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Quick Reference: All Game Variables
+
+| Variable | Type | Purpose |
+|----------|------|---------|
+| `canvas` | const | Reference to the HTML canvas element |
+| `ctx` | const | 2D rendering context |
+| `ballRadius` | const | Radius of the ball (10) |
+| `x`, `y` | let | Current ball position |
+| `dx`, `dy` | let | Ball velocity (pixels per frame) |
+| `paddleHeight` | const | Height of the paddle (10) |
+| `paddleWidth` | const | Width of the paddle (75) |
+| `paddleX` | let | Current horizontal position of the paddle |
+| `rightPressed` | let | Whether the right arrow key is held down |
+| `leftPressed` | let | Whether the left arrow key is held down |
+| `brickRowCount` | const | Number of brick rows (3) |
+| `brickColumnCount` | const | Number of brick columns (5) |
+| `brickWidth` | const | Width of each brick (75) |
+| `brickHeight` | const | Height of each brick (20) |
+| `brickPadding` | const | Space between bricks (10) |
+| `brickOffsetTop` | const | Distance from top of canvas to first brick row (30) |
+| `brickOffsetLeft` | const | Distance from left of canvas to first brick column (30) |
+| `bricks` | const | 2D array holding all brick objects |
+| `score` | let | Current player score |
+| `lives` | let | Remaining lives (starts at 3) |
+
+## Quick Reference: All Functions
+
+| Function | Purpose |
+|----------|---------|
+| `keyDownHandler(e)` | Sets `rightPressed` or `leftPressed` to `true` on key press |
+| `keyUpHandler(e)` | Sets `rightPressed` or `leftPressed` to `false` on key release |
+| `mouseMoveHandler(e)` | Moves paddle to follow mouse horizontal position |
+| `collisionDetection()` | Checks ball against all active bricks; destroys hit bricks, increments score, checks win |
+| `drawBall()` | Renders the ball at current `(x, y)` position |
+| `drawPaddle()` | Renders the paddle at current `paddleX` position |
+| `drawBricks()` | Renders all bricks with `status === 1` |
+| `drawScore()` | Renders the score text in the top-left corner |
+| `drawLives()` | Renders the lives text in the top-right corner |
+| `draw()` | Main game loop: clears canvas, draws everything, handles collisions, updates positions |
diff --git a/skills/game-engine/assets/simple-2d-engine.md b/skills/game-engine/assets/simple-2d-engine.md
new file mode 100644
index 000000000..b8e79fa26
--- /dev/null
+++ b/skills/game-engine/assets/simple-2d-engine.md
@@ -0,0 +1,507 @@
+# Simple 2D Platformer Engine Template
+
+A grid-based 2D platformer engine tutorial by **Sebastien Benard** (deepnight), the lead developer behind *Dead Cells*. This template covers the fundamental architecture for a performant platformer: a dual-coordinate positioning system that blends integer grid cells with sub-pixel precision, velocity and friction mechanics, gravity, and a robust collision detection and response system. The approach is language-agnostic but examples use Haxe.
+
+**Source references:**
+- [Part 1 - Basics](https://deepnight.net/tutorial/a-simple-platformer-engine-part-1-basics/)
+- [Part 2 - Collisions](https://deepnight.net/tutorial/a-simple-platformer-engine-part-2-collisions/)
+
+**Author:** [Sebastien Benard / deepnight](https://deepnight.net)
+
+---
+
+## Engine Architecture Overview
+
+The engine is built around a grid-based world where each cell has a fixed pixel size (e.g., 16x16). Entities exist within this grid using a **dual-coordinate system**: integer cell coordinates for coarse position and floating-point ratios for sub-pixel precision within each cell. This design enables pixel-perfect collision detection against the grid while maintaining smooth, fluid movement.
+
+### Core Principles
+
+1. **Grid is truth:** The world is a 2D grid of cells. Collision data lives in the grid.
+2. **Entities straddle cells:** An entity's position is defined by which cell it occupies (`cx`, `cy`) plus how far into that cell it is (`xr`, `yr`).
+3. **Velocity is in grid-ratio units:** Movement deltas (`dx`, `dy`) represent fractions of a cell per step, not raw pixels.
+4. **Collisions are grid lookups:** Instead of testing sprite bounds against geometry, the engine checks the grid cells an entity is about to enter.
+
+---
+
+## Part 1: Basics
+
+### The Grid
+
+The level is a 2D array where each cell is either empty or solid. A constant defines the cell size in pixels:
+
+```haxe
+static inline var GRID = 16;
+```
+
+Collision data is stored as a simple 2D boolean or integer map:
+
+```haxe
+// Check if a grid cell is solid
+function hasCollision(cx:Int, cy:Int):Bool {
+ // Look up cell value in the level data
+ return level.getCollision(cx, cy) != 0;
+}
+```
+
+### Entity Positioning: Dual Coordinates
+
+Every entity tracks its position using four values:
+
+| Variable | Type | Description |
+|----------|------|-------------|
+| `cx` | Int | Cell X coordinate (which column the entity is in) |
+| `cy` | Int | Cell Y coordinate (which row the entity is in) |
+| `xr` | Float | X ratio within the cell, range 0.0 to 1.0 |
+| `yr` | Float | Y ratio within the cell, range 0.0 to 1.0 |
+
+An entity at `cx=5, cy=3, xr=0.5, yr=1.0` is horizontally centered in cell (5,3) and sitting on the bottom edge.
+
+### Converting to Pixel Coordinates
+
+To render the entity, convert grid coordinates to pixel positions:
+
+```haxe
+// Pixel position for rendering
+var pixelX : Float = (cx + xr) * GRID;
+var pixelY : Float = (cy + yr) * GRID;
+```
+
+This produces smooth, sub-pixel-precise positions for rendering even though the collision system operates on discrete grid cells.
+
+### Velocity and Movement
+
+Velocity is expressed in **cell-ratio units per fixed-step** (not pixels per frame):
+
+```haxe
+var dx : Float = 0; // Horizontal velocity (cells per step)
+var dy : Float = 0; // Vertical velocity (cells per step)
+```
+
+Each fixed-step update, velocity is added to the ratio:
+
+```haxe
+// Apply horizontal movement
+xr += dx;
+
+// Apply vertical movement
+yr += dy;
+```
+
+### Cell Overflow
+
+When the ratio exceeds the 0..1 range, the entity has moved into an adjacent cell:
+
+```haxe
+// X overflow
+while (xr > 1) { xr--; cx++; }
+while (xr < 0) { xr++; cx--; }
+
+// Y overflow
+while (yr > 1) { yr--; cy++; }
+while (yr < 0) { yr++; cy--; }
+```
+
+### Friction
+
+Friction is applied as a multiplier each step, decaying velocity toward zero:
+
+```haxe
+var frictX : Float = 0.82; // Horizontal friction (0 = instant stop, 1 = no friction)
+var frictY : Float = 0.82; // Vertical friction
+
+// Applied each step after movement
+dx *= frictX;
+dy *= frictY;
+
+// Clamp very small values to zero
+if (Math.abs(dx) < 0.0005) dx = 0;
+if (Math.abs(dy) < 0.0005) dy = 0;
+```
+
+Typical friction values:
+- `0.82` -- Standard ground friction (responsive, quick stop)
+- `0.94` -- Ice or slippery surface (slow deceleration)
+- `0.96` -- Air friction (very slow horizontal deceleration)
+
+### Gravity
+
+Gravity is a constant added to `dy` each step:
+
+```haxe
+static inline var GRAVITY = 0.05; // In cell-ratio units per step^2
+
+// In fixedUpdate:
+dy += GRAVITY;
+```
+
+Since `dy` accumulates and friction is applied, the entity reaches a natural terminal velocity.
+
+### Rendering / Sprite Sync
+
+After the physics step, the sprite is placed at the computed pixel position:
+
+```haxe
+// In postUpdate, after physics is done:
+sprite.x = (cx + xr) * GRID;
+sprite.y = (cy + yr) * GRID;
+```
+
+For a platformer character, the anchor point is typically at the bottom-center of the sprite. With `yr = 1.0` representing the bottom of the current cell, the sprite's feet align with the floor.
+
+### Basic Entity Template
+
+```haxe
+class Entity {
+ // Grid coordinates
+ var cx : Int = 0;
+ var cy : Int = 0;
+ var xr : Float = 0.5;
+ var yr : Float = 1.0;
+
+ // Velocity
+ var dx : Float = 0;
+ var dy : Float = 0;
+
+ // Friction
+ var frictX : Float = 0.82;
+ var frictY : Float = 0.82;
+
+ // Gravity
+ static inline var GRAVITY = 0.05;
+
+ // Grid size
+ static inline var GRID = 16;
+
+ // Pixel position (computed)
+ public var attachX(get, never) : Float;
+ inline function get_attachX() return (cx + xr) * GRID;
+
+ public var attachY(get, never) : Float;
+ inline function get_attachY() return (cy + yr) * GRID;
+
+ public function fixedUpdate() {
+ // Gravity
+ dy += GRAVITY;
+
+ // Apply velocity
+ xr += dx;
+ yr += dy;
+
+ // Apply friction
+ dx *= frictX;
+ dy *= frictY;
+
+ // Clamp small values
+ if (Math.abs(dx) < 0.0005) dx = 0;
+ if (Math.abs(dy) < 0.0005) dy = 0;
+
+ // Cell overflow
+ while (xr > 1) { xr--; cx++; }
+ while (xr < 0) { xr++; cx--; }
+ while (yr > 1) { yr--; cy++; }
+ while (yr < 0) { yr++; cy--; }
+ }
+
+ public function postUpdate() {
+ sprite.x = attachX;
+ sprite.y = attachY;
+ }
+}
+```
+
+---
+
+## Part 2: Collisions
+
+### Collision Philosophy
+
+Instead of using bounding-box-to-bounding-box collision detection (which becomes complex with slopes, one-way platforms, and edge cases), this engine checks grid cells directly. Since the entity's position is already expressed in grid terms, collision detection becomes a series of simple integer lookups.
+
+### The Core Idea
+
+Before allowing the entity to move into a neighboring cell, check if that cell is solid. If it is, clamp the entity's ratio and zero out its velocity on that axis.
+
+### Axis Separation
+
+Collisions are handled **per axis** -- first X, then Y (or vice versa). This simplifies the logic and avoids corner-case tunneling issues.
+
+### X-Axis Collision
+
+After applying `dx` to `xr`, before doing the cell-overflow step, check for collisions:
+
+```haxe
+// Apply X movement
+xr += dx;
+
+// Check collision to the RIGHT
+if (dx > 0 && hasCollision(cx + 1, cy) && xr >= 0.7) {
+ xr = 0.7; // Clamp: stop before entering the solid cell
+ dx = 0; // Kill horizontal velocity
+}
+
+// Check collision to the LEFT
+if (dx < 0 && hasCollision(cx - 1, cy) && xr <= 0.3) {
+ xr = 0.3; // Clamp: stop before entering the solid cell
+ dx = 0; // Kill horizontal velocity
+}
+
+// Cell overflow (after collision check)
+while (xr > 1) { xr--; cx++; }
+while (xr < 0) { xr++; cx--; }
+```
+
+**Why 0.7 and 0.3?** These thresholds represent the entity's collision radius within a cell. An entity centered at `xr = 0.5` with a half-width of 0.3 cells would collide at `xr = 0.7` on the right side and `xr = 0.3` on the left side. Adjust these values based on entity width.
+
+### Y-Axis Collision
+
+Similarly, after applying `dy` to `yr`:
+
+```haxe
+// Apply Y movement
+yr += dy;
+
+// Check collision BELOW (floor)
+if (dy > 0 && hasCollision(cx, cy + 1) && yr >= 1.0) {
+ yr = 1.0; // Clamp: land on top of the solid cell
+ dy = 0; // Kill vertical velocity
+}
+
+// Check collision ABOVE (ceiling)
+if (dy < 0 && hasCollision(cx, cy - 1) && yr <= 0.3) {
+ yr = 0.3; // Clamp: stop before entering ceiling cell
+ dy = 0; // Kill vertical velocity
+}
+
+// Cell overflow
+while (yr > 1) { yr--; cy++; }
+while (yr < 0) { yr++; cy--; }
+```
+
+For floor collisions, `yr = 1.0` means the entity sits exactly on the bottom edge of its current cell, which is the top edge of the cell below it. This is the natural "standing on ground" position.
+
+### On-Ground Detection
+
+To determine if the entity is standing on solid ground (for jump logic, animations, etc.):
+
+```haxe
+function isOnGround() : Bool {
+ return hasCollision(cx, cy + 1) && yr >= 0.98;
+}
+```
+
+The threshold `0.98` instead of `1.0` allows for minor floating-point imprecision.
+
+### Complete Entity with Collisions
+
+```haxe
+class Entity {
+ var cx : Int = 0;
+ var cy : Int = 0;
+ var xr : Float = 0.5;
+ var yr : Float = 1.0;
+ var dx : Float = 0;
+ var dy : Float = 0;
+ var frictX : Float = 0.82;
+ var frictY : Float = 0.82;
+
+ static inline var GRID = 16;
+ static inline var GRAVITY = 0.05;
+
+ // Collision radius (half-width in cell-ratio units)
+ var collRadius : Float = 0.3;
+
+ function hasCollision(testCx:Int, testCy:Int):Bool {
+ return level.isCollision(testCx, testCy);
+ }
+
+ function isOnGround():Bool {
+ return hasCollision(cx, cy + 1) && yr >= 0.98;
+ }
+
+ public function fixedUpdate() {
+ // --- Gravity ---
+ dy += GRAVITY;
+
+ // --- X Axis ---
+ xr += dx;
+
+ // Right collision
+ if (dx > 0 && hasCollision(cx + 1, cy) && xr >= 1.0 - collRadius) {
+ xr = 1.0 - collRadius;
+ dx = 0;
+ }
+
+ // Left collision
+ if (dx < 0 && hasCollision(cx - 1, cy) && xr <= collRadius) {
+ xr = collRadius;
+ dx = 0;
+ }
+
+ // X cell overflow
+ while (xr > 1) { xr--; cx++; }
+ while (xr < 0) { xr++; cx--; }
+
+ // --- Y Axis ---
+ yr += dy;
+
+ // Floor collision
+ if (dy > 0 && hasCollision(cx, cy + 1) && yr >= 1.0) {
+ yr = 1.0;
+ dy = 0;
+ }
+
+ // Ceiling collision
+ if (dy < 0 && hasCollision(cx, cy - 1) && yr <= collRadius) {
+ yr = collRadius;
+ dy = 0;
+ }
+
+ // Y cell overflow
+ while (yr > 1) { yr--; cy++; }
+ while (yr < 0) { yr++; cy--; }
+
+ // --- Friction ---
+ dx *= frictX;
+ dy *= frictY;
+
+ if (Math.abs(dx) < 0.0005) dx = 0;
+ if (Math.abs(dy) < 0.0005) dy = 0;
+ }
+
+ public function postUpdate() {
+ sprite.x = (cx + xr) * GRID;
+ sprite.y = (cy + yr) * GRID;
+ }
+}
+```
+
+---
+
+## Collision Edge Cases and Solutions
+
+### Diagonal Movement / Corner Clipping
+
+Because collisions are checked per-axis in sequence, an entity moving diagonally into a corner naturally resolves against one axis first. This prevents the entity from getting stuck in corners and eliminates the need for complex diagonal collision logic.
+
+### High-Speed Tunneling
+
+If `dx` or `dy` is large enough to skip an entire cell in one step, the entity could "tunnel" through walls. Solutions:
+
+1. **Cap velocity:** Clamp `dx` and `dy` to a maximum of 0.5 (half a cell per step)
+2. **Subdivide steps:** If velocity exceeds the threshold, run the collision check in smaller increments
+3. **Ray-march the grid:** Check every cell along the movement path
+
+```haxe
+// Simple velocity cap
+if (dx > 0.5) dx = 0.5;
+if (dx < -0.5) dx = -0.5;
+if (dy > 0.5) dy = 0.5;
+if (dy < -0.5) dy = -0.5;
+```
+
+### One-Way Platforms
+
+Platforms the entity can jump up through but land on from above:
+
+```haxe
+// In Y collision, check for one-way platform
+if (dy > 0 && isOneWayPlatform(cx, cy + 1) && yr >= 1.0 && prevYr < 1.0) {
+ yr = 1.0;
+ dy = 0;
+}
+```
+
+Key: Only collide when the entity is moving downward (`dy > 0`) and was previously above the platform (`prevYr < 1.0`).
+
+### Slopes
+
+For basic slope support, instead of a binary collision check, query the slope height at the entity's x-position within the cell:
+
+```haxe
+// Pseudocode for slope collision
+var slopeHeight = getSlopeHeight(cx, cy + 1, xr);
+if (yr >= slopeHeight) {
+ yr = slopeHeight;
+ dy = 0;
+}
+```
+
+---
+
+## Jumping
+
+Jumping is simply a negative `dy` impulse:
+
+```haxe
+function jump() {
+ if (isOnGround()) {
+ dy = -0.5; // Jump impulse (in cell-ratio units)
+ }
+}
+```
+
+Gravity naturally decelerates the upward motion, creating a parabolic arc. To allow variable-height jumps (holding the button longer = higher jump):
+
+```haxe
+// On jump button release, reduce upward velocity
+function onJumpRelease() {
+ if (dy < 0) {
+ dy *= 0.5; // Cut remaining upward velocity
+ }
+}
+```
+
+---
+
+## Coordinate System Diagram
+
+```
+ Cell (cx, cy) Next Cell (cx+1, cy)
+ +-------------------+ +-------------------+
+ | | | |
+ | xr=0.0 xr=1.0 --> | xr=0.0 |
+ | | | |
+ | * | | |
+ | (xr=0.5, | | |
+ | yr=0.5) | | |
+ | | | |
+ +-------------------+ +-------------------+
+ yr=0.0 yr=1.0 = top of cell below
+
+ Pixel position = (cx + xr) * GRID, (cy + yr) * GRID
+```
+
+---
+
+## Update Order Summary
+
+```
+fixedUpdate():
+ 1. Apply gravity dy += GRAVITY
+ 2. Apply X velocity xr += dx
+ 3. Check X collisions Clamp xr, zero dx if colliding
+ 4. Handle X cell overflow cx/xr normalization
+ 5. Apply Y velocity yr += dy
+ 6. Check Y collisions Clamp yr, zero dy if colliding
+ 7. Handle Y cell overflow cy/yr normalization
+ 8. Apply friction dx *= frictX, dy *= frictY
+ 9. Zero out tiny values Threshold check
+
+postUpdate():
+ 1. Sync sprite position sprite.x/y = pixel coords
+ 2. Update animation Based on state/velocity
+ 3. Camera follow Track entity
+```
+
+---
+
+## Design Advantages
+
+| Feature | Benefit |
+|---------|---------|
+| Grid-based collision | O(1) lookup per check, no broad-phase needed |
+| Dual coordinates | Sub-pixel smooth rendering with integer collision |
+| Per-axis collision | Simple logic, naturally handles corners |
+| Ratio-based velocity | Resolution-independent movement |
+| Friction multiplier | Tunable feel per surface type |
+| Cell overflow while-loops | Handles multi-cell movement safely |
diff --git a/skills/game-engine/references/3d-web-games.md b/skills/game-engine/references/3d-web-games.md
new file mode 100644
index 000000000..374eaf773
--- /dev/null
+++ b/skills/game-engine/references/3d-web-games.md
@@ -0,0 +1,754 @@
+# 3D Web Games
+
+A comprehensive reference for building 3D games on the web, covering foundational theory, major frameworks, shader programming, collision detection, and immersive WebXR experiences.
+
+Sources: [MDN Web Docs -- Games Techniques: 3D on the web](https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web)
+
+---
+
+## 3D Theory and Fundamentals
+
+Understanding the core concepts behind 3D rendering is essential before working with any framework.
+
+### Coordinate System
+
+WebGL uses the **right-hand coordinate system**:
+
+- **X-axis** -- points to the right
+- **Y-axis** -- points up
+- **Z-axis** -- points out of the screen toward the viewer
+
+All 3D objects are positioned relative to this coordinate system.
+
+### Vertices, Edges, Faces, and Meshes
+
+- **Vertex** -- a point in 3D space defined by `(x, y, z)` with additional attributes: color (RGBA, values 0.0-1.0), normal (direction the vertex faces, used for lighting), and texture coordinates.
+- **Edge** -- a line connecting two vertices.
+- **Face** -- a flat surface bounded by edges (e.g., a triangle connecting three vertices).
+- **Geometry** -- the structural shape built from vertices, edges, and faces.
+- **Material** -- the surface appearance, combining color, texture, roughness, metalness, etc.
+- **Mesh** -- geometry combined with a material to produce a renderable 3D object.
+
+### The Rendering Pipeline
+
+The pipeline transforms 3D objects into 2D pixels on screen, in four major stages:
+
+**1. Vertex Processing**
+
+Combines individual vertex data into primitives (triangles, lines, points) and applies transformations:
+
+- **Model transformation** -- positions and orients objects in world space.
+- **View transformation** -- positions and orients the virtual camera.
+- **Projection transformation** -- defines the camera's field of view (FOV), aspect ratio, near plane, and far plane.
+- **Viewport transformation** -- maps the result to the screen viewport.
+
+**2. Rasterization**
+
+Converts 3D primitives into 2D fragments aligned to the pixel grid.
+
+**3. Fragment Processing**
+
+Determines the final color of each fragment using textures and lighting:
+
+- **Textures**: 2D images mapped onto 3D surfaces. Individual texture elements are called *texels*. Texture wrapping repeats images around geometry; texture filtering handles minification and magnification when displayed resolution differs from texture resolution.
+- **Lighting (Phong model)**: Four types of light interaction -- **diffuse** (distant directional light like the sun), **specular** (point source highlights like a flashlight), **ambient** (constant global illumination), and **emissive** (light emitted by the object itself).
+
+**4. Output Merging**
+
+Converts 3D fragments into the final 2D pixel grid. Off-screen and occluded objects are culled for efficiency.
+
+### Camera
+
+The camera defines what is visible:
+
+- **Position** -- location in 3D space.
+- **Direction** -- where the camera points.
+- **Orientation** -- rotation around the viewing axis.
+
+### Practical Tips
+
+- Size and position values in WebGL are unitless; you decide whether they represent millimeters, meters, feet, or anything else.
+- Understand the pipeline conceptually before diving into code; the vertex and fragment processing stages are programmable via shaders.
+- Every framework (Three.js, Babylon.js, A-Frame, PlayCanvas) abstracts this pipeline, but the fundamentals remain the same.
+
+---
+
+## Frameworks
+
+### Three.js
+
+Three.js is one of the most popular 3D engines for the web. It provides a high-level API over WebGL with a large ecosystem of plugins, examples, and community support.
+
+#### Setup
+
+```html
+
+
+
+
+ Three.js Demo
+
+
+
+
+
+
+
+```
+
+Or install via npm:
+
+```bash
+npm install --save three
+npm install --save-dev vite
+npx vite
+```
+
+#### Core Components
+
+**Renderer** -- displays the scene in the browser:
+
+```javascript
+const renderer = new THREE.WebGLRenderer({ antialias: true });
+renderer.setSize(WIDTH, HEIGHT);
+renderer.setClearColor(0xdddddd, 1);
+document.body.appendChild(renderer.domElement);
+```
+
+**Scene** -- container for all 3D objects, lights, and the camera:
+
+```javascript
+const scene = new THREE.Scene();
+```
+
+**Camera** -- defines the viewpoint (PerspectiveCamera is most common):
+
+```javascript
+const camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT);
+camera.position.z = 50;
+scene.add(camera);
+```
+
+Parameters: field of view (degrees), aspect ratio. Other camera types include Orthographic and Cube.
+
+#### Geometry, Material, and Mesh
+
+```javascript
+// Geometry defines the shape
+const boxGeometry = new THREE.BoxGeometry(10, 10, 10);
+const torusGeometry = new THREE.TorusGeometry(7, 1, 16, 32);
+const dodecahedronGeometry = new THREE.DodecahedronGeometry(7);
+
+// Material defines the surface appearance
+const basicMaterial = new THREE.MeshBasicMaterial({ color: 0x0095dd }); // No lighting
+const phongMaterial = new THREE.MeshPhongMaterial({ color: 0xff9500 }); // Glossy
+const lambertMaterial = new THREE.MeshLambertMaterial({ color: 0xeaeff2 }); // Matte
+
+// Mesh combines geometry + material
+const cube = new THREE.Mesh(boxGeometry, basicMaterial);
+cube.position.set(-25, 0, 0);
+cube.rotation.set(0.4, 0.2, 0);
+scene.add(cube);
+```
+
+#### Lighting
+
+```javascript
+const light = new THREE.PointLight(0xffffff);
+light.position.set(-10, 15, 50);
+scene.add(light);
+```
+
+Other light types: Ambient, Directional, Hemisphere, Spot.
+
+Note: `MeshBasicMaterial` does not respond to lighting. Use `MeshPhongMaterial` or `MeshLambertMaterial` for lit surfaces.
+
+#### Animation Loop
+
+```javascript
+let t = 0;
+function render() {
+ t += 0.01;
+ requestAnimationFrame(render);
+
+ cube.rotation.y += 0.01; // continuous rotation
+ torus.scale.y = Math.abs(Math.sin(t)); // pulsing scale
+ dodecahedron.position.y = -7 * Math.sin(t * 2); // bobbing position
+
+ renderer.render(scene, camera);
+}
+render();
+```
+
+#### Practical Tips
+
+- Use `Math.abs()` when animating scale with `Math.sin()` to avoid negative scale values.
+- The render loop uses `requestAnimationFrame` for smooth, browser-optimized frame updates.
+- Consult [Three.js documentation](https://threejs.org/docs/) for the full API.
+
+---
+
+### Babylon.js
+
+Babylon.js is a full-featured 3D engine with a built-in math library, physics support, and extensive documentation.
+
+#### Setup
+
+```html
+
+
+```
+
+#### Engine, Scene, and Render Loop
+
+```javascript
+const canvas = document.getElementById("render-canvas");
+const engine = new BABYLON.Engine(canvas);
+
+const scene = new BABYLON.Scene(engine);
+scene.clearColor = new BABYLON.Color3(0.8, 0.8, 0.8);
+
+function renderLoop() {
+ scene.render();
+}
+engine.runRenderLoop(renderLoop);
+```
+
+#### Camera and Lighting
+
+```javascript
+const camera = new BABYLON.FreeCamera("camera", new BABYLON.Vector3(0, 0, -10), scene);
+const light = new BABYLON.PointLight("light", new BABYLON.Vector3(10, 10, 0), scene);
+```
+
+#### Creating Meshes
+
+```javascript
+const box = BABYLON.Mesh.CreateBox("box", 2, scene); // name, size, scene
+const torus = BABYLON.Mesh.CreateTorus("torus", 2, 0.5, 15, scene); // name, diameter, thickness, tessellation, scene
+const cylinder = BABYLON.Mesh.CreateCylinder("cylinder", 2, 2, 2, 12, 1, scene);
+// name, height, topDiameter, bottomDiameter, tessellation, heightSubdivisions, scene
+```
+
+#### Materials
+
+```javascript
+const boxMaterial = new BABYLON.StandardMaterial("material", scene);
+boxMaterial.emissiveColor = new BABYLON.Color3(0, 0.58, 0.86);
+box.material = boxMaterial;
+```
+
+#### Transforms and Animation
+
+```javascript
+box.position.x = 5;
+box.rotation.x = -0.2;
+box.scaling.x = 1.5;
+
+// Animation inside render loop
+let t = 0;
+function renderLoop() {
+ scene.render();
+ t -= 0.01;
+ box.rotation.y = t * 2;
+ torus.scaling.z = Math.abs(Math.sin(t * 2)) + 0.5;
+ cylinder.position.y = Math.sin(t * 3);
+}
+engine.runRenderLoop(renderLoop);
+```
+
+#### Practical Tips
+
+- The `BABYLON` global object contains all framework functions.
+- `BABYLON.Vector3` and `BABYLON.Color3` are used extensively for positioning and coloring.
+- Babylon.js includes a built-in math library for vectors, colors, and matrices.
+- Consult [Babylon.js documentation](https://doc.babylonjs.com/) for advanced features like physics, particles, and post-processing.
+
+---
+
+### A-Frame
+
+A-Frame is Mozilla's declarative, HTML-based framework for building VR/AR experiences on the web. It uses an entity-component system and runs on WebGL under the hood.
+
+#### Setup
+
+```html
+
+
+
+
+ A-Frame Demo
+
+
+
+
+
+
+
+
+
+```
+
+The `` element is the root container. A-Frame auto-includes a default camera, lighting, and input controls.
+
+#### Primitives and Entities
+
+```html
+
+
+
+
+
+
+
+```
+
+#### Creating Entities with JavaScript
+
+```javascript
+const scene = document.querySelector("a-scene");
+const cylinder = document.createElement("a-cylinder");
+cylinder.setAttribute("color", "#FF9500");
+cylinder.setAttribute("height", "2");
+cylinder.setAttribute("radius", "0.75");
+cylinder.setAttribute("position", "3 1 0");
+scene.appendChild(cylinder);
+```
+
+#### Camera and Lighting
+
+```html
+
+
+
+
+
+```
+
+Default controls: WASD keys for movement, mouse for looking around. A VR mode button appears in the bottom-right corner.
+
+#### Animation
+
+Declarative animation via HTML attributes:
+
+```html
+
+
+```
+
+Animation properties: `property` (attribute to animate), `from`/`to` (start/end values), `dir` (alternate or normal), `loop` (boolean), `dur` (milliseconds), `easing` (easing function).
+
+Dynamic animation via JavaScript:
+
+```javascript
+let t = 0;
+function render() {
+ t += 0.01;
+ requestAnimationFrame(render);
+ cylinder.setAttribute("position", `3 ${Math.sin(t * 2) + 1} 0`);
+}
+render();
+```
+
+#### Practical Tips
+
+- A-Frame is ideal for rapid VR/AR prototyping using familiar HTML syntax.
+- The entity-component architecture makes it extensible; community plugins add physics, gamepad controls, and more.
+- Use `` for background colors or 360-degree images.
+- A-Frame supports desktop, mobile (iOS/Android), and VR headsets (Meta Quest, HTC Vive).
+
+---
+
+### PlayCanvas
+
+PlayCanvas is a WebGL game engine with two workflow options:
+
+1. **Engine approach** -- include the PlayCanvas JavaScript library directly in HTML and code from scratch.
+2. **Editor approach** -- use the online drag-and-drop visual editor for scene composition.
+
+#### Key Features
+
+- Entity-component system architecture
+- Built-in physics engine powered by [ammo.js](https://github.com/kripken/ammo.js/)
+- Collision detection
+- Audio support
+- Input handling (keyboard, mouse, touch, gamepads)
+- Resource/asset management
+
+#### Practical Tips
+
+- PlayCanvas excels for team-based game development thanks to its online editor with real-time collaboration.
+- The engine-only approach is lightweight and can be embedded in any web page.
+- Consult the [PlayCanvas developer documentation](https://developer.playcanvas.com/) for tutorials on entities, components, cameras, lights, materials, and animations.
+
+---
+
+## GLSL Shaders
+
+GLSL (OpenGL Shading Language) is a C-like language that runs directly on the GPU, enabling custom control over the rendering pipeline's vertex and fragment processing stages.
+
+### What Shaders Are
+
+Shaders are small programs that execute on the GPU instead of the CPU. They are strongly typed and rely heavily on vector and matrix mathematics. There are two types relevant to WebGL:
+
+- **Vertex shader** -- runs once per vertex, transforms 3D positions into screen coordinates.
+- **Fragment shader** (pixel shader) -- runs once per pixel, determines the final RGBA color.
+
+### Vertex Shader
+
+The vertex shader's job is to set `gl_Position`, a built-in GLSL variable storing the vertex's transformed position:
+
+```glsl
+void main() {
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x, position.y, position.z, 1.0);
+}
+```
+
+- `projectionMatrix` -- handles perspective or orthographic projection (provided by Three.js).
+- `modelViewMatrix` -- combines model and view transformations (provided by Three.js).
+- `vec4(x, y, z, w)` -- a 4-component vector; `w` defaults to 1.0 for positional vertices.
+
+You can manipulate vertices directly:
+
+```glsl
+void main() {
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x + 10.0, position.y, position.z + 5.0, 1.0);
+}
+```
+
+### Fragment Shader
+
+The fragment shader's job is to set `gl_FragColor`, a built-in GLSL variable holding the RGBA color:
+
+```glsl
+void main() {
+ gl_FragColor = vec4(0.0, 0.58, 0.86, 1.0);
+}
+```
+
+RGBA components are floats from 0.0 to 1.0. Alpha 0.0 is fully transparent; 1.0 is fully opaque.
+
+### Using Shaders in HTML and Three.js
+
+Embed shader source in script tags with custom type attributes:
+
+```html
+
+
+
+```
+
+Apply them with `ShaderMaterial`:
+
+```javascript
+const shaderMaterial = new THREE.ShaderMaterial({
+ vertexShader: document.getElementById("vertexShader").textContent,
+ fragmentShader: document.getElementById("fragmentShader").textContent,
+});
+
+const cube = new THREE.Mesh(boxGeometry, shaderMaterial);
+```
+
+### The Shader Pipeline
+
+1. **Vertex shader** processes each vertex and outputs `gl_Position`.
+2. **Rasterization** maps 3D coordinates to 2D screen pixels.
+3. **Fragment shader** processes each pixel and outputs `gl_FragColor`.
+
+### Key Concepts
+
+- **Uniforms** -- values passed from JavaScript to the shader, constant across all vertices/fragments in a single draw call (e.g., light position, time).
+- **Attributes** -- per-vertex data passed to the vertex shader (e.g., position, normal, UV coordinates).
+- **Varyings** -- values passed from the vertex shader to the fragment shader, interpolated across the surface.
+
+### Practical Tips
+
+- Shaders run on the GPU and offload computation from the CPU, which is critical for real-time performance.
+- Three.js, Babylon.js, and other frameworks abstract much of the shader setup; pure WebGL requires significantly more boilerplate.
+- [ShaderToy](https://www.shadertoy.com/) is an excellent resource for shader examples and inspiration.
+- GLSL requires explicit type declarations; always use `1.0` instead of `1` for floats.
+
+---
+
+## Collision Detection
+
+Collision detection determines when 3D objects intersect, which is fundamental for game physics, interaction, and gameplay logic.
+
+### Axis-Aligned Bounding Boxes (AABB)
+
+An AABB wraps an object in a non-rotated rectangular box aligned to the coordinate axes. It is the fastest common collision test because it uses only logical comparisons (no trigonometry).
+
+**Limitation**: AABBs do not rotate with the object. For rotating entities, either resize the bounding box each frame or use bounding spheres instead.
+
+#### Point vs. AABB
+
+Check whether a point lies inside a box by testing all three axes:
+
+```javascript
+function isPointInsideAABB(point, box) {
+ return (
+ point.x >= box.minX &&
+ point.x <= box.maxX &&
+ point.y >= box.minY &&
+ point.y <= box.maxY &&
+ point.z >= box.minZ &&
+ point.z <= box.maxZ
+ );
+}
+```
+
+#### AABB vs. AABB
+
+Check whether two boxes overlap on all three axes:
+
+```javascript
+function intersect(a, b) {
+ return (
+ a.minX <= b.maxX &&
+ a.maxX >= b.minX &&
+ a.minY <= b.maxY &&
+ a.maxY >= b.minY &&
+ a.minZ <= b.maxZ &&
+ a.maxZ >= b.minZ
+ );
+}
+```
+
+### Bounding Spheres
+
+Bounding spheres are invariant to rotation (the sphere stays the same regardless of how the object spins), which makes them ideal for rotating entities. However, they fit poorly on non-spherical shapes and cause more false positives.
+
+#### Point vs. Sphere
+
+Check whether the distance from the point to the sphere center is less than the radius:
+
+```javascript
+function isPointInsideSphere(point, sphere) {
+ const distance = Math.sqrt(
+ (point.x - sphere.x) ** 2 +
+ (point.y - sphere.y) ** 2 +
+ (point.z - sphere.z) ** 2
+ );
+ return distance < sphere.radius;
+}
+```
+
+**Performance optimization**: avoid the square root by comparing squared distances:
+
+```javascript
+const distanceSqr =
+ (point.x - sphere.x) ** 2 +
+ (point.y - sphere.y) ** 2 +
+ (point.z - sphere.z) ** 2;
+return distanceSqr < sphere.radius * sphere.radius;
+```
+
+#### Sphere vs. Sphere
+
+Check whether the distance between centers is less than the sum of radii:
+
+```javascript
+function intersect(sphere, other) {
+ const distance = Math.sqrt(
+ (sphere.x - other.x) ** 2 +
+ (sphere.y - other.y) ** 2 +
+ (sphere.z - other.z) ** 2
+ );
+ return distance < sphere.radius + other.radius;
+}
+```
+
+#### Sphere vs. AABB
+
+Find the point on the AABB closest to the sphere center by clamping, then check the distance:
+
+```javascript
+function intersect(sphere, box) {
+ const x = Math.max(box.minX, Math.min(sphere.x, box.maxX));
+ const y = Math.max(box.minY, Math.min(sphere.y, box.maxY));
+ const z = Math.max(box.minZ, Math.min(sphere.z, box.maxZ));
+
+ const distance = Math.sqrt(
+ (x - sphere.x) ** 2 +
+ (y - sphere.y) ** 2 +
+ (z - sphere.z) ** 2
+ );
+
+ return distance < sphere.radius;
+}
+```
+
+### Collision Detection with Three.js
+
+Three.js provides built-in `Box3` and `Sphere` objects plus visual helpers for bounding volume collision detection.
+
+#### Creating Bounding Volumes
+
+```javascript
+// Box3 from an object (recommended -- accounts for transforms and children)
+const knotBBox = new THREE.Box3(new THREE.Vector3(), new THREE.Vector3());
+knotBBox.setFromObject(knot);
+
+// Sphere from geometry
+const knotBSphere = new THREE.Sphere(
+ knot.position,
+ knot.geometry.boundingSphere.radius
+);
+```
+
+**Important**: `setFromObject()` accounts for position, rotation, scale, and child meshes. The geometry's `boundingBox` property does not.
+
+#### Intersection Tests
+
+```javascript
+// Point inside box or sphere
+knotBBox.containsPoint(point);
+knotBSphere.containsPoint(point);
+
+// Box vs. box
+knotBBox.intersectsBox(otherBox);
+
+// Sphere vs. sphere
+knotBSphere.intersectsSphere(otherSphere);
+```
+
+Note: `containsBox()` checks if one box fully encloses another, which is different from `intersectsBox()`.
+
+#### Sphere vs. Box3 (Custom Patch)
+
+Three.js does not natively provide sphere-vs-box testing. Add it manually:
+
+```javascript
+THREE.Sphere.__closest = new THREE.Vector3();
+THREE.Sphere.prototype.intersectsBox = function (box) {
+ THREE.Sphere.__closest.set(this.center.x, this.center.y, this.center.z);
+ THREE.Sphere.__closest.clamp(box.min, box.max);
+ const distance = this.center.distanceToSquared(THREE.Sphere.__closest);
+ return distance < this.radius * this.radius;
+};
+```
+
+#### BoxHelper for Visual Debugging
+
+`BoxHelper` creates a visible wireframe bounding box around any mesh and simplifies updates:
+
+```javascript
+const knotBoxHelper = new THREE.BoxHelper(knot, 0x00ff00);
+scene.add(knotBoxHelper);
+
+// After moving or rotating the mesh, update the helper
+knot.position.set(-3, 2, 1);
+knot.rotation.x = -Math.PI / 4;
+knotBoxHelper.update();
+
+// Convert to Box3 for intersection tests
+const box3 = new THREE.Box3();
+box3.setFromObject(knotBoxHelper);
+box3.intersectsBox(otherBox3);
+```
+
+Advantages of BoxHelper: auto-resizes with `update()`, includes child meshes, provides visual debugging. Limitation: box volumes only (no sphere helpers).
+
+### Physics Engines
+
+For more sophisticated collision detection and response, use a physics engine:
+
+- **Cannon.js** -- open-source 3D physics engine for JavaScript.
+- **ammo.js** -- JavaScript port of the Bullet physics library (used by PlayCanvas).
+
+Physics engines create a *physical body* attached to the visual mesh, with properties like velocity, position, rotation, and torque. A *physical shape* (box, sphere, convex hull) is used for collision calculations.
+
+### Practical Tips
+
+- Use AABBs for axis-aligned, non-rotating objects -- they are the fastest option.
+- Use bounding spheres for rotating objects -- the sphere is invariant to rotation.
+- For complex shapes, consider compound bounding volumes (multiple primitives combined).
+- Avoid `Math.sqrt()` in tight loops; compare squared distances instead.
+- For production games, integrate a physics engine rather than writing collision detection from scratch.
+
+---
+
+## WebXR
+
+WebXR is the modern web API for building virtual reality (VR) and augmented reality (AR) experiences in the browser. It replaces the deprecated WebVR API.
+
+### What WebXR Is
+
+The WebXR Device API provides access to XR hardware (headsets, controllers) and enables stereoscopic rendering. It captures real-time data including:
+
+- Headset position and orientation
+- Controller position, orientation, velocity, and acceleration
+- Input events from XR controllers
+
+### Supported Devices
+
+- Meta Quest
+- Valve Index
+- PlayStation VR (PSVR2)
+- Any device with a WebXR-compatible browser
+
+### Core Concepts
+
+Every WebXR experience requires two things:
+
+1. **Real-time positional data** -- the application continuously receives headset and controller positions in 3D space.
+2. **Real-time stereoscopic rendering** -- the application renders two slightly offset views (one for each eye) to the headset's display.
+
+### Framework Support
+
+All major 3D web frameworks support WebXR:
+
+- **A-Frame** -- built-in VR mode button; declarative HTML-based scenes automatically work in VR.
+- **Three.js** -- provides WebXR integration via `renderer.xr`. See [Three.js VR documentation](https://threejs.org/docs/#manual/en/introduction/How-to-create-VR-content).
+- **Babylon.js** -- built-in WebXR support via the XR Experience Helper.
+
+### Related APIs
+
+- **Gamepad API** -- for non-XR controller inputs (gamepads, joysticks).
+- **Device Orientation API** -- for detecting device rotation on mobile devices.
+
+### Design Principles
+
+- Prioritize **immersion** over raw graphics quality or gameplay complexity.
+- Users must feel like they are *part of the experience*.
+- Basic shapes rendered at high, stable frame rates can be more compelling in VR than detailed graphics at unstable frame rates.
+- Experimentation is essential; test frequently on actual hardware.
+
+### Practical Tips
+
+- Start with A-Frame for rapid VR prototyping -- its declarative HTML approach gets you to a working VR scene in minutes.
+- Use Three.js or Babylon.js when you need more control over rendering and performance.
+- Always test on real headsets; the experience is vastly different from desktop preview.
+- Maintain a stable, high frame rate (72-90+ FPS) to prevent motion sickness.
+- Consult [MDN WebXR Device API](https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API) for the full API reference.
diff --git a/skills/game-engine/references/algorithms.md b/skills/game-engine/references/algorithms.md
new file mode 100644
index 000000000..60b4716cf
--- /dev/null
+++ b/skills/game-engine/references/algorithms.md
@@ -0,0 +1,843 @@
+# Game Development Algorithms
+
+A comprehensive reference covering essential algorithms for game development, including
+line drawing, raycasting, collision detection, physics simulation, and vector mathematics.
+
+---
+
+## Bresenham's Line Algorithm -- Raycasting, Line of Sight, and Pathfinding
+
+> Source: https://deepnight.net/tutorial/bresenham-magic-raycasting-line-of-sight-pathfinding/
+
+### What It Is
+
+Bresenham's line algorithm is an efficient method for determining which cells in a grid
+lie along a straight line between two points. Originally developed for plotting pixels on
+raster displays, it has become a foundational tool in game development for raycasting,
+line-of-sight checks, and grid-based pathfinding. The algorithm uses only integer
+arithmetic (additions, subtractions, and bit shifts), making it extremely fast.
+
+### Mathematical / Algorithmic Concepts
+
+The core idea is to walk along the major axis (the axis with the greater distance) one
+cell at a time, accumulating an error term that tracks how far the true line deviates
+from the current minor-axis position. When the error exceeds a threshold, the minor-axis
+coordinate is incremented.
+
+Key properties:
+- **Integer-only arithmetic**: No floating-point division or multiplication required.
+- **Incremental error accumulation**: The fractional slope is tracked via an integer error
+ term, avoiding drift.
+- **Symmetry**: The algorithm works identically regardless of line direction by adjusting
+ step signs.
+
+Given two grid points `(x0, y0)` and `(x1, y1)`:
+
+```
+dx = abs(x1 - x0)
+dy = abs(y1 - y0)
+```
+
+The error term is initialized and updated each step. When it crosses zero, the secondary
+axis is stepped.
+
+### Pseudocode
+
+```
+function bresenham(x0, y0, x1, y1):
+ dx = abs(x1 - x0)
+ dy = abs(y1 - y0)
+ sx = sign(x1 - x0) // -1 or +1
+ sy = sign(y1 - y0) // -1 or +1
+ err = dx - dy
+
+ while true:
+ visit(x0, y0) // process or record this cell
+
+ if x0 == x1 AND y0 == y1:
+ break
+
+ e2 = 2 * err
+
+ if e2 > -dy:
+ err = err - dy
+ x0 = x0 + sx
+
+ if e2 < dx:
+ err = err + dx
+ y0 = y0 + sy
+```
+
+### Haxe Implementation (from source)
+
+```haxe
+public function hasLineOfSight(x0:Int, y0:Int, x1:Int, y1:Int):Bool {
+ var dx = hxd.Math.iabs(x1 - x0);
+ var dy = hxd.Math.iabs(y1 - y0);
+ var sx = (x0 < x1) ? 1 : -1;
+ var sy = (y0 < y1) ? 1 : -1;
+ var err = dx - dy;
+
+ while (true) {
+ if (isBlocking(x0, y0))
+ return false;
+
+ if (x0 == x1 && y0 == y1)
+ return true;
+
+ var e2 = 2 * err;
+ if (e2 > -dy) {
+ err -= dy;
+ x0 += sx;
+ }
+ if (e2 < dx) {
+ err += dx;
+ y0 += sy;
+ }
+ }
+}
+```
+
+### Practical Game Development Applications
+
+- **Line of Sight (LOS)**: Walk the Bresenham line from an entity to a target; if any
+ cell along the path is a wall or obstacle, line of sight is blocked.
+- **Raycasting on grids**: Cast rays from a source in multiple directions to compute
+ visibility maps or field-of-view cones.
+- **Grid-based pathfinding validation**: After computing a path (e.g., via A*), verify
+ that straight-line shortcuts between waypoints are unobstructed using Bresenham checks.
+- **Projectile tracing**: Determine which tiles a bullet or projectile passes through in
+ a tile-based game.
+- **Lighting and shadow casting**: Trace rays from a light source to compute lit vs
+ shadowed cells on a 2D grid.
+
+---
+
+## Collision Detection and Response Systems
+
+> Source: https://medium.com/@erikkubiak/dev-log-1-custom-engine-writing-my-collision-system-2a97856f9a93
+
+### What It Is
+
+A collision system is responsible for detecting when game objects overlap or intersect
+and then resolving those overlaps so that objects respond physically (bouncing, stopping,
+sliding). Building a custom collision system involves choosing appropriate bounding
+shapes, implementing overlap tests, and designing a resolution strategy.
+
+### Mathematical / Algorithmic Concepts
+
+#### Bounding Shapes
+
+- **AABB (Axis-Aligned Bounding Box)**: A rectangle whose sides are aligned with the
+ coordinate axes. Defined by a position (center or top-left corner) and half-widths.
+ Fast overlap tests but imprecise for rotated or irregular shapes.
+- **Circle / Sphere colliders**: Defined by center and radius. Overlap test is a simple
+ distance comparison.
+- **OBB (Oriented Bounding Box)**: A rotated rectangle. Uses the Separating Axis Theorem
+ for overlap tests.
+
+#### AABB vs AABB Overlap Test
+
+Two axis-aligned bounding boxes overlap if and only if they overlap on every axis:
+
+```
+overlapX = (a.x - a.halfW < b.x + b.halfW) AND (a.x + a.halfW > b.x - b.halfW)
+overlapY = (a.y - a.halfH < b.y + b.halfH) AND (a.y + a.halfH > b.y - b.halfH)
+collision = overlapX AND overlapY
+```
+
+#### Circle vs Circle Overlap Test
+
+```
+dx = a.x - b.x
+dy = a.y - b.y
+distSquared = dx * dx + dy * dy
+collision = distSquared < (a.radius + b.radius) ^ 2
+```
+
+Comparing squared distances avoids a costly square root operation.
+
+#### Separating Axis Theorem (SAT)
+
+Two convex shapes do NOT collide if there exists at least one axis along which their
+projections do not overlap. For rectangles, test the edge normals of both rectangles.
+If all projections overlap, the shapes are colliding.
+
+#### Sweep and Prune (Broad Phase)
+
+Rather than testing every pair of objects (O(n^2)), sort objects along one axis by
+their minimum extent. Objects that do not overlap on that axis cannot collide and are
+pruned from detailed checks.
+
+### Pseudocode -- Collision Detection and Resolution
+
+```
+// Broad phase: spatial hash or sweep-and-prune
+candidates = broadPhase(allObjects)
+
+for each pair (a, b) in candidates:
+ overlap = narrowPhaseTest(a, b)
+
+ if overlap:
+ // Compute penetration vector
+ penetration = computePenetration(a, b)
+
+ // Resolve: push objects apart along the minimum penetration axis
+ if a.isStatic:
+ b.position += penetration
+ else if b.isStatic:
+ a.position -= penetration
+ else:
+ a.position -= penetration * 0.5
+ b.position += penetration * 0.5
+
+ // Optional: apply impulse for velocity response
+ relativeVelocity = a.velocity - b.velocity
+ impulse = computeImpulse(relativeVelocity, penetration.normal, a.mass, b.mass)
+ a.velocity -= impulse / a.mass
+ b.velocity += impulse / b.mass
+```
+
+#### Minimum Penetration Vector (for AABBs)
+
+```
+function computePenetration(a, b):
+ overlapX_left = (a.x + a.halfW) - (b.x - b.halfW)
+ overlapX_right = (b.x + b.halfW) - (a.x - a.halfW)
+ overlapY_top = (a.y + a.halfH) - (b.y - b.halfH)
+ overlapY_bot = (b.y + b.halfH) - (a.y - a.halfH)
+
+ minOverlapX = min(overlapX_left, overlapX_right)
+ minOverlapY = min(overlapY_top, overlapY_bot)
+
+ if minOverlapX < minOverlapY:
+ return Vector(sign * minOverlapX, 0)
+ else:
+ return Vector(0, sign * minOverlapY)
+```
+
+### Spatial Partitioning Strategies
+
+| Strategy | Best For | Description |
+|---|---|---|
+| **Uniform Grid** | Evenly distributed objects | Divide world into fixed cells; objects register in their cell(s). |
+| **Quadtree** | Non-uniform distribution | Recursively subdivide space into 4 quadrants. Efficient for sparse scenes. |
+| **Spatial Hash** | Dynamic scenes | Hash object positions to buckets. O(1) lookup for neighbors. |
+| **Sweep and Prune** | Many moving objects | Sort by axis; only test overlapping intervals. |
+
+### Practical Game Development Applications
+
+- **Platformer physics**: Resolve player-vs-terrain collisions so the character lands on
+ platforms and cannot walk through walls.
+- **Projectile hit detection**: Determine when a projectile (often a small AABB or circle)
+ contacts an enemy or obstacle.
+- **Trigger zones**: Detect when a player enters a region (overlap test without physical
+ resolution) to trigger events.
+- **Entity stacking**: Handle objects piled on top of each other using iterative
+ resolution with multiple passes.
+
+---
+
+## Velocity and Speed
+
+> Source: https://www.gamedev.net/tutorials/programming/math-and-physics/a-quick-lesson-in-velocity-and-speed-r6109/
+
+### What It Is
+
+Velocity and speed are fundamental concepts for moving objects in games. **Speed** is a
+scalar (magnitude only), while **velocity** is a vector (magnitude and direction).
+Understanding the distinction is critical for implementing correct movement, physics,
+and AI steering behaviors.
+
+### Mathematical / Algorithmic Concepts
+
+#### Definitions
+
+- **Speed**: A scalar quantity representing how fast an object moves, regardless of
+ direction.
+ ```
+ speed = |velocity| = sqrt(vx^2 + vy^2)
+ ```
+
+- **Velocity**: A vector quantity representing both speed and direction.
+ ```
+ velocity = (vx, vy)
+ ```
+
+- **Acceleration**: The rate of change of velocity over time.
+ ```
+ acceleration = (ax, ay)
+ velocity += acceleration * deltaTime
+ ```
+
+#### Updating Position with Velocity
+
+Each frame, an object's position is updated by its velocity, scaled by the time step:
+
+```
+position.x += velocity.x * deltaTime
+position.y += velocity.y * deltaTime
+```
+
+This is **Euler integration**, the simplest (first-order) integration method.
+
+#### Normalizing Direction
+
+To move at a fixed speed in a given direction, normalize the direction vector and
+multiply by the desired speed:
+
+```
+direction = target - current
+length = sqrt(direction.x^2 + direction.y^2)
+if length > 0:
+ direction.x /= length
+ direction.y /= length
+velocity = direction * speed
+```
+
+This prevents the "diagonal movement problem" where moving diagonally at full speed
+on both axes results in ~1.414x the intended speed.
+
+#### Frame-Rate Independence
+
+Without `deltaTime`, movement speed depends on the frame rate:
+
+```
+// WRONG: frame-rate dependent
+position += velocity
+
+// CORRECT: frame-rate independent
+position += velocity * deltaTime
+```
+
+`deltaTime` is the elapsed time (in seconds) since the last frame update.
+
+### Pseudocode -- Complete Movement Update
+
+```
+function update(entity, deltaTime):
+ // Apply acceleration (gravity, thrust, friction, etc.)
+ entity.velocity.x += entity.acceleration.x * deltaTime
+ entity.velocity.y += entity.acceleration.y * deltaTime
+
+ // Clamp speed to a maximum
+ currentSpeed = magnitude(entity.velocity)
+ if currentSpeed > entity.maxSpeed:
+ entity.velocity = normalize(entity.velocity) * entity.maxSpeed
+
+ // Apply friction / drag
+ entity.velocity.x *= (1 - entity.friction * deltaTime)
+ entity.velocity.y *= (1 - entity.friction * deltaTime)
+
+ // Update position
+ entity.position.x += entity.velocity.x * deltaTime
+ entity.position.y += entity.velocity.y * deltaTime
+```
+
+### Practical Game Development Applications
+
+- **Character movement**: Apply velocity each frame to move the player smoothly,
+ clamping to a max speed for consistent feel.
+- **Projectiles**: Give bullets or arrows an initial velocity vector; update position
+ each frame.
+- **Gravity**: Apply a constant downward acceleration to velocity each frame to simulate
+ falling.
+- **Friction and drag**: Reduce velocity over time by multiplying by a damping factor
+ to simulate surface friction or air resistance.
+- **AI steering**: Compute a desired velocity toward a target, then smoothly adjust the
+ current velocity toward it (seek, flee, arrive behaviors).
+
+---
+
+## Physics Engine Fundamentals
+
+> Source: https://winter.dev/articles/physics-engine
+
+### What It Is
+
+A physics engine simulates real-world physical behaviors -- gravity, collisions, rigid
+body dynamics -- so that game objects move and interact realistically. The core loop of a
+physics engine consists of: applying forces, integrating motion, detecting collisions,
+and resolving collisions.
+
+### Mathematical / Algorithmic Concepts
+
+#### The Physics Loop
+
+A physics engine runs a fixed-timestep update loop:
+
+```
+accumulator = 0
+fixedDeltaTime = 1 / 60 // 60 Hz physics
+
+function physicsUpdate(frameDeltaTime):
+ accumulator += frameDeltaTime
+
+ while accumulator >= fixedDeltaTime:
+ step(fixedDeltaTime)
+ accumulator -= fixedDeltaTime
+```
+
+Using a fixed timestep ensures deterministic, stable simulation regardless of rendering
+frame rate.
+
+#### Integration Methods
+
+**Semi-Implicit Euler** (symplectic Euler) -- the standard for game physics:
+
+```
+velocity += acceleration * dt
+position += velocity * dt
+```
+
+This is more stable than explicit Euler (which updates position first) because velocity
+is updated before being used to update position.
+
+**Verlet Integration** -- an alternative that does not store velocity explicitly:
+
+```
+newPosition = 2 * position - oldPosition + acceleration * dt * dt
+oldPosition = position
+position = newPosition
+```
+
+Verlet is particularly useful for constraints (cloth, ragdoll) because positions can
+be directly manipulated while preserving momentum.
+
+#### Rigid Body Properties
+
+Each rigid body has:
+
+| Property | Description |
+|---|---|
+| `position` | Center of mass in world space |
+| `velocity` | Linear velocity vector |
+| `acceleration` | Sum of all forces / mass |
+| `mass` | Resistance to linear acceleration |
+| `inverseMass` | `1 / mass` (0 for static objects) |
+| `angle` | Rotation angle |
+| `angularVelocity` | Rate of rotation |
+| `inertia` | Resistance to angular acceleration |
+| `restitution` | Bounciness (0 = no bounce, 1 = perfectly elastic) |
+| `friction` | Surface friction coefficient |
+
+#### Force Accumulation
+
+Forces are accumulated each frame, then converted to acceleration:
+
+```
+function applyForce(body, force):
+ body.forceAccumulator += force
+
+function integrate(body, dt):
+ body.acceleration = body.forceAccumulator * body.inverseMass
+ body.velocity += body.acceleration * dt
+ body.position += body.velocity * dt
+ body.forceAccumulator = (0, 0) // reset
+```
+
+#### Collision Detection Pipeline
+
+The detection phase is split into two stages:
+
+1. **Broad Phase**: Quickly eliminate pairs that cannot possibly collide using bounding
+ volumes (AABBs) and spatial structures (grids, BVH trees, sweep-and-prune).
+
+2. **Narrow Phase**: For candidate pairs, perform precise shape-vs-shape tests to
+ determine if they actually overlap and compute contact information (collision normal,
+ penetration depth, contact points).
+
+#### Collision Resolution with Impulses
+
+When two bodies collide, an impulse is applied along the collision normal to separate
+them and adjust their velocities:
+
+```
+function resolveCollision(a, b, normal, penetration):
+ // Relative velocity at the contact point
+ relVel = b.velocity - a.velocity
+ velAlongNormal = dot(relVel, normal)
+
+ // Do not resolve if objects are separating
+ if velAlongNormal > 0:
+ return
+
+ // Coefficient of restitution (take minimum)
+ e = min(a.restitution, b.restitution)
+
+ // Impulse magnitude
+ j = -(1 + e) * velAlongNormal
+ j /= a.inverseMass + b.inverseMass
+
+ // Apply impulse
+ impulse = j * normal
+ a.velocity -= impulse * a.inverseMass
+ b.velocity += impulse * b.inverseMass
+
+ // Positional correction (prevent sinking)
+ correction = max(penetration - slop, 0) / (a.inverseMass + b.inverseMass) * percent
+ a.position -= correction * a.inverseMass * normal
+ b.position += correction * b.inverseMass * normal
+```
+
+Key constants:
+- `slop`: A small tolerance (e.g., 0.01) to prevent jitter from micro-penetrations.
+- `percent`: Typically 0.2 to 0.8; controls how aggressively positional correction is
+ applied.
+
+#### Rotational Dynamics
+
+For 2D rotation, torque is the rotational equivalent of force:
+
+```
+torque = cross(contactPoint - centerOfMass, impulse)
+angularAcceleration = torque * inverseInertia
+angularVelocity += angularAcceleration * dt
+angle += angularVelocity * dt
+```
+
+The moment of inertia depends on the shape:
+- **Circle**: `I = 0.5 * m * r^2`
+- **Rectangle**: `I = (1/12) * m * (w^2 + h^2)`
+
+### Pseudocode -- Complete Physics Step
+
+```
+function step(dt):
+ // 1. Apply external forces (gravity, player input, etc.)
+ for each body in world.bodies:
+ if not body.isStatic:
+ body.applyForce(gravity * body.mass)
+
+ // 2. Integrate velocities and positions
+ for each body in world.bodies:
+ if not body.isStatic:
+ body.velocity += (body.forceAccumulator * body.inverseMass) * dt
+ body.position += body.velocity * dt
+ body.angularVelocity += body.torque * body.inverseInertia * dt
+ body.angle += body.angularVelocity * dt
+ body.forceAccumulator = (0, 0)
+ body.torque = 0
+
+ // 3. Broad-phase collision detection
+ pairs = broadPhase(world.bodies)
+
+ // 4. Narrow-phase collision detection
+ contacts = []
+ for each (a, b) in pairs:
+ contact = narrowPhase(a, b)
+ if contact:
+ contacts.append(contact)
+
+ // 5. Resolve collisions (iterative solver)
+ for i in range(solverIterations): // typically 4-10 iterations
+ for each contact in contacts:
+ resolveCollision(contact.a, contact.b,
+ contact.normal, contact.penetration)
+```
+
+### Practical Game Development Applications
+
+- **Platformers**: Gravity, ground contact, jumping arcs, and moving platforms.
+- **Top-down games**: Sliding along walls, knockback from attacks.
+- **Ragdoll physics**: Chain of rigid bodies connected by constraints.
+- **Vehicle simulation**: Suspension springs, tire friction, engine force.
+- **Destruction**: Breaking objects into debris with individual physics bodies.
+
+---
+
+## Vector Mathematics for Game Development
+
+> Source: https://www.gamedev.net/tutorials/programming/math-and-physics/vector-maths-for-game-dev-beginners-r5442/
+
+### What It Is
+
+Vectors are the mathematical building blocks of game development. A vector represents
+a quantity with both magnitude and direction. In 2D games, vectors are pairs `(x, y)`;
+in 3D, triples `(x, y, z)`. Nearly every game system -- movement, physics, rendering,
+AI -- relies on vector operations.
+
+### Mathematical / Algorithmic Concepts
+
+#### Vector Representation
+
+A 2D vector:
+```
+v = (x, y)
+```
+
+A 3D vector:
+```
+v = (x, y, z)
+```
+
+Vectors can represent positions, directions, velocities, forces, or any quantity with
+magnitude and direction.
+
+#### Vector Addition
+
+Component-wise addition. Used to apply velocity to position, combine forces, etc.
+
+```
+a + b = (a.x + b.x, a.y + b.y)
+```
+
+**Example**: Moving a character by its velocity:
+```
+position = position + velocity * deltaTime
+```
+
+#### Vector Subtraction
+
+Component-wise subtraction. Used to find the direction and distance from one point to
+another.
+
+```
+a - b = (a.x - b.x, a.y - b.y)
+```
+
+**Example**: Direction from enemy to player:
+```
+directionToPlayer = player.position - enemy.position
+```
+
+#### Scalar Multiplication
+
+Scales a vector's magnitude without changing its direction:
+
+```
+s * v = (s * v.x, s * v.y)
+```
+
+**Example**: Setting movement speed:
+```
+velocity = normalizedDirection * speed
+```
+
+#### Magnitude (Length)
+
+The length of a vector, computed via the Pythagorean theorem:
+
+```
+|v| = sqrt(v.x^2 + v.y^2)
+```
+
+In 3D:
+```
+|v| = sqrt(v.x^2 + v.y^2 + v.z^2)
+```
+
+**Optimization**: When only comparing distances (not needing the actual value), use
+squared magnitude to avoid the expensive square root:
+
+```
+|v|^2 = v.x^2 + v.y^2
+```
+
+#### Normalization
+
+Produces a unit vector (length 1) pointing in the same direction:
+
+```
+normalize(v) = v / |v| = (v.x / |v|, v.y / |v|)
+```
+
+A normalized vector represents pure direction. Always check that `|v| > 0` before
+dividing to avoid division by zero.
+
+**Example**: Get the direction an entity is facing:
+```
+facing = normalize(target - self.position)
+```
+
+#### Dot Product
+
+A scalar result that encodes the angular relationship between two vectors:
+
+```
+a . b = a.x * b.x + a.y * b.y
+```
+
+In 3D:
+```
+a . b = a.x * b.x + a.y * b.y + a.z * b.z
+```
+
+Geometric interpretation:
+```
+a . b = |a| * |b| * cos(theta)
+```
+
+Where `theta` is the angle between the vectors. For unit vectors:
+```
+a . b = cos(theta)
+```
+
+Key properties:
+- `a . b > 0`: Vectors point in roughly the same direction (angle < 90 degrees).
+- `a . b == 0`: Vectors are perpendicular (angle = 90 degrees).
+- `a . b < 0`: Vectors point in roughly opposite directions (angle > 90 degrees).
+
+**Game dev uses**:
+- Field-of-view checks: Is the player in front of the enemy?
+- Lighting: Compute diffuse light intensity (`max(0, dot(normal, lightDir))`).
+- Projection: Project one vector onto another.
+
+#### Cross Product (3D)
+
+Produces a vector perpendicular to both input vectors:
+
+```
+a x b = (
+ a.y * b.z - a.z * b.y,
+ a.z * b.x - a.x * b.z,
+ a.x * b.y - a.y * b.x
+)
+```
+
+The magnitude of the cross product equals:
+```
+|a x b| = |a| * |b| * sin(theta)
+```
+
+In 2D, the "cross product" is a scalar (the z-component of the 3D cross product):
+```
+a x b = a.x * b.y - a.y * b.x
+```
+
+**Game dev uses**:
+- Determine winding order (clockwise vs counter-clockwise).
+- Compute surface normals for lighting.
+- Determine if a point is to the left or right of a line.
+
+#### Perpendicular Vector (2D)
+
+To get a vector perpendicular to `(x, y)`:
+```
+perp = (-y, x) // 90 degrees counter-clockwise
+perp = (y, -x) // 90 degrees clockwise
+```
+
+Useful for computing normals of 2D edges and walls.
+
+#### Projection
+
+Project vector `a` onto vector `b`:
+
+```
+proj_b(a) = (a . b / b . b) * b
+```
+
+If `b` is already a unit vector:
+```
+proj_b(a) = (a . b) * b
+```
+
+**Game dev uses**:
+- Determine velocity component along a surface normal (for bounce/reflection).
+- Sliding along a wall: Subtract the normal component from velocity.
+
+#### Reflection
+
+Reflect vector `v` across a surface with normal `n` (where `n` is a unit vector):
+
+```
+reflected = v - 2 * (v . n) * n
+```
+
+**Game dev uses**:
+- Ball bouncing off a wall.
+- Light reflection calculations.
+- Ricochet trajectories.
+
+### Pseudocode -- Vector2D Class
+
+```
+class Vector2D:
+ x, y
+
+ function add(other):
+ return Vector2D(x + other.x, y + other.y)
+
+ function subtract(other):
+ return Vector2D(x - other.x, y - other.y)
+
+ function scale(scalar):
+ return Vector2D(x * scalar, y * scalar)
+
+ function magnitude():
+ return sqrt(x * x + y * y)
+
+ function magnitudeSquared():
+ return x * x + y * y
+
+ function normalize():
+ mag = magnitude()
+ if mag > 0:
+ return Vector2D(x / mag, y / mag)
+ return Vector2D(0, 0)
+
+ function dot(other):
+ return x * other.x + y * other.y
+
+ function cross(other):
+ return x * other.y - y * other.x
+
+ function perpendicular():
+ return Vector2D(-y, x)
+
+ function reflect(normal):
+ d = dot(normal)
+ return Vector2D(x - 2 * d * normal.x, y - 2 * d * normal.y)
+
+ function angleTo(other):
+ return acos(normalize().dot(other.normalize()))
+
+ function distanceTo(other):
+ return subtract(other).magnitude()
+
+ function lerp(other, t):
+ return Vector2D(
+ x + (other.x - x) * t,
+ y + (other.y - y) * t
+ )
+```
+
+### Practical Game Development Applications
+
+- **Movement and steering**: Add velocity vectors to position; normalize direction
+ vectors and multiply by speed for consistent movement.
+- **Distance checks**: Use squared magnitude for performance-friendly radius checks
+ (e.g., "is this enemy within range?").
+- **Field-of-view**: Use the dot product between an entity's forward vector and the
+ direction to a target to determine if the target is within a vision cone.
+- **Wall sliding**: Project the velocity onto the wall's tangent (perpendicular to the
+ normal) to allow smooth sliding along surfaces.
+- **Reflections and bouncing**: Use the reflection formula when a projectile or ball
+ hits a surface.
+- **Interpolation**: Use `lerp` (linear interpolation) between two vectors for smooth
+ movement, camera tracking, and animations.
+- **Rotation**: Rotate a vector by an angle using trigonometry:
+ ```
+ rotated.x = v.x * cos(angle) - v.y * sin(angle)
+ rotated.y = v.x * sin(angle) + v.y * cos(angle)
+ ```
+
+---
+
+## Quick Reference Table
+
+| Algorithm / Concept | Primary Use Case | Complexity |
+|---|---|---|
+| Bresenham's Line | Grid raycasting, line of sight | O(max(dx, dy)) per ray |
+| AABB Overlap | Fast collision detection | O(1) per pair |
+| Circle Overlap | Round collider detection | O(1) per pair |
+| Separating Axis Theorem | Convex polygon collision | O(n) per pair (n = edges) |
+| Spatial Hashing | Broad-phase collision culling | O(1) average lookup |
+| Euler Integration | Simple physics stepping | O(1) per body per step |
+| Verlet Integration | Constraint-based physics | O(1) per body per step |
+| Impulse Resolution | Collision response | O(iterations * contacts) |
+| Vector Normalization | Direction extraction | O(1) |
+| Dot Product | Angle/projection queries | O(1) |
+| Cross Product | Perpendicularity / winding | O(1) |
+| Reflection | Bounce / ricochet | O(1) |
diff --git a/skills/game-engine/references/basics.md b/skills/game-engine/references/basics.md
new file mode 100644
index 000000000..f281e961f
--- /dev/null
+++ b/skills/game-engine/references/basics.md
@@ -0,0 +1,343 @@
+# Game Development Basics
+
+A comprehensive reference covering web game development technologies, game architecture, and the anatomy of a game loop.
+
+Sources:
+- https://developer.mozilla.org/en-US/docs/Games/Introduction
+- https://developer.mozilla.org/en-US/docs/Games/Anatomy
+
+---
+
+## Web Technologies for Game Development
+
+### Graphics and Rendering
+
+- **WebGL** -- Hardware-accelerated 2D and 3D graphics based on OpenGL ES 2.0. Provides direct GPU access for high-performance rendering.
+- **Canvas API** -- 2D drawing surface via the `` element. Suitable for 2D games, sprite rendering, and pixel manipulation.
+- **SVG** -- Scalable Vector Graphics for resolution-independent visuals. Useful for UI elements and simple vector-based games.
+- **HTML/CSS** -- Standard web technologies for building game UI, menus, HUDs, and overlays.
+
+### Audio
+
+- **Web Audio API** -- Advanced audio engine supporting real-time playback, synthesis, spatial audio, effects processing, and dynamic mixing.
+- **HTML Audio Element** -- Simple sound playback for background music and basic sound effects.
+
+### Input and Controls
+
+- **Gamepad API** -- Support for game controllers and gamepads, including button mapping and analog stick input.
+- **Touch Events API** -- Multi-touch input handling for mobile devices.
+- **Pointer Lock API** -- Locks the mouse cursor within the game area and provides raw coordinate deltas for precise camera/aiming control.
+- **Device Sensors** -- Accelerometer and gyroscope access for motion-based input.
+- **Full Screen API** -- Enables immersive full-screen game experiences.
+
+### Networking and Multiplayer
+
+- **WebSockets API** -- Persistent, bidirectional communication channel for real-time multiplayer, chat, and live updates.
+- **WebRTC API** -- Peer-to-peer connections for low-latency multiplayer, voice chat, and data channels.
+- **Fetch API** -- HTTP requests for downloading game assets, loading level data, and transmitting non-real-time game state.
+
+### Data Storage and Performance
+
+- **IndexedDB API** -- Client-side structured storage for save games, cached assets, and offline play support.
+- **Typed Arrays** -- Direct access to raw binary data buffers for GL textures, audio samples, and compact game data.
+- **Web Workers API** -- Background thread execution for offloading heavy computations (physics, pathfinding, AI) without blocking the main thread.
+
+### Languages and Compilation
+
+- **JavaScript** -- The primary language for web game development.
+- **C/C++ via Emscripten** -- Compile existing native game code to JavaScript or WebAssembly for web deployment.
+- **WebAssembly (Wasm)** -- Near-native execution speed for performance-critical game code.
+
+---
+
+## Types of Games You Can Build
+
+The modern web platform supports a full range of game types:
+
+- 3D action games and shooters
+- Role-playing games (RPGs)
+- 2D platformers and side-scrollers
+- Puzzle and strategy games
+- Card and board games
+- Casual and mobile-friendly games
+- Multiplayer experiences with real-time networking
+
+---
+
+## Advantages of Web-Based Game Development
+
+1. **Universal reach** -- Games run on smartphones, tablets, PCs, and Smart TVs through the browser.
+2. **No app store dependency** -- Deploy directly on the web without store approval processes.
+3. **Full revenue control** -- No mandatory revenue share; use any payment processing system.
+4. **Instant updates** -- Push updates immediately without waiting for store review.
+5. **Own your analytics** -- Collect your own data or choose any analytics provider.
+6. **Direct player relationships** -- Engage players without intermediaries.
+7. **Inherent shareability** -- Games are linkable and discoverable via standard web mechanisms.
+
+---
+
+## Anatomy of a Game Loop
+
+Every game operates through a continuous cycle of steps:
+
+1. **Present** -- Display the current game state to the player.
+2. **Accept** -- Receive user input (keyboard, mouse, gamepad, touch).
+3. **Interpret** -- Process raw input into meaningful game actions.
+4. **Calculate** -- Update the game state based on actions, physics, AI, and time.
+5. **Repeat** -- Loop back to present the updated state.
+
+Games may be **event-driven** (turn-based, waiting for player action) or **per-frame** (continuously updating via a main loop).
+
+---
+
+## Building a Game Loop with requestAnimationFrame
+
+### Basic Main Loop
+
+```javascript
+window.main = () => {
+ window.requestAnimationFrame(main);
+
+ // Your game logic here: update state, render frame
+};
+
+main(); // Start the cycle
+```
+
+Key points:
+- `requestAnimationFrame()` synchronizes callbacks to the browser's repaint schedule (typically 60 Hz).
+- Schedule the next frame **before** performing loop work to maximize available computation time.
+
+### Self-Contained Main Loop (IIFE)
+
+```javascript
+;(() => {
+ function main() {
+ window.requestAnimationFrame(main);
+
+ // Game logic here
+ }
+
+ main();
+})();
+```
+
+### Stoppable Main Loop
+
+```javascript
+;(() => {
+ function main() {
+ MyGame.stopMain = window.requestAnimationFrame(main);
+
+ // Game logic here
+ }
+
+ main();
+})();
+
+// To stop the loop:
+window.cancelAnimationFrame(MyGame.stopMain);
+```
+
+---
+
+## Timing and Frame Rate
+
+### DOMHighResTimeStamp
+
+`requestAnimationFrame` passes a `DOMHighResTimeStamp` to your callback, providing timing precision to 1/1000th of a millisecond.
+
+```javascript
+;(() => {
+ function main(tFrame) {
+ MyGame.stopMain = window.requestAnimationFrame(main);
+
+ // tFrame is a high-resolution timestamp in milliseconds
+ // Use it for delta-time calculations
+ }
+
+ main();
+})();
+```
+
+### Frame Time Budget
+
+At 60 Hz, each frame has approximately **16.67ms** of available processing time. The browser's frame cycle is:
+
+1. Start new frame (previous frame displayed to screen)
+2. Execute `requestAnimationFrame` callbacks
+3. Perform garbage collection and per-frame browser tasks
+4. Sleep until VSync, then repeat
+
+---
+
+## Simple Update and Render Pattern
+
+The simplest approach when your game can sustain the target frame rate:
+
+```javascript
+;(() => {
+ function main(tFrame) {
+ MyGame.stopMain = window.requestAnimationFrame(main);
+
+ update(tFrame); // Process game logic
+ render(); // Draw the frame
+ }
+
+ main();
+})();
+```
+
+Assumptions:
+- Each frame can process input and update state within the time budget.
+- The simulation runs at the same rate as the display refresh (typically ~60 FPS).
+- No frame interpolation is needed.
+
+---
+
+## Decoupled Update and Render with Fixed Timestep
+
+For robust handling of variable refresh rates and consistent simulation behavior:
+
+```javascript
+;(() => {
+ function main(tFrame) {
+ MyGame.stopMain = window.requestAnimationFrame(main);
+ const nextTick = MyGame.lastTick + MyGame.tickLength;
+ let numTicks = 0;
+
+ // Calculate how many simulation updates are needed
+ if (tFrame > nextTick) {
+ const timeSinceTick = tFrame - MyGame.lastTick;
+ numTicks = Math.floor(timeSinceTick / MyGame.tickLength);
+ }
+
+ queueUpdates(numTicks);
+ render(tFrame);
+ MyGame.lastRender = tFrame;
+ }
+
+ function queueUpdates(numTicks) {
+ for (let i = 0; i < numTicks; i++) {
+ MyGame.lastTick += MyGame.tickLength;
+ update(MyGame.lastTick);
+ }
+ }
+
+ MyGame.lastTick = performance.now();
+ MyGame.lastRender = MyGame.lastTick;
+ MyGame.tickLength = 50; // 20 Hz simulation rate (50ms per tick)
+
+ setInitialState();
+ main(performance.now());
+})();
+```
+
+Benefits:
+- **Deterministic simulation** -- Game logic runs at a fixed frequency regardless of display refresh rate.
+- **Smooth rendering** -- Rendering can interpolate between simulation states for visual smoothness.
+- **Portable behavior** -- Game behaves the same on 60 Hz, 120 Hz, and 144 Hz displays.
+
+---
+
+## Alternative Architecture Patterns
+
+### Separate setInterval for Updates
+
+```javascript
+// Game logic updates at a fixed rate
+setInterval(() => {
+ update();
+}, 50); // 20 Hz
+
+// Rendering synchronized to display
+requestAnimationFrame(function render(tFrame) {
+ requestAnimationFrame(render);
+ draw();
+});
+```
+
+Drawback: `setInterval` continues running even when the tab is not visible, wasting resources.
+
+### Web Worker for Updates
+
+```javascript
+// Heavy game logic runs in a background thread
+const updateWorker = new Worker('game-update-worker.js');
+
+requestAnimationFrame(function render(tFrame) {
+ requestAnimationFrame(render);
+ updateWorker.postMessage({ ticks: numTicksNeeded });
+ draw();
+});
+```
+
+Benefits: Does not block the main thread. Ideal for physics-heavy or AI-intensive games.
+Drawback: Communication overhead between worker and main thread.
+
+### requestAnimationFrame Driving a Web Worker
+
+```javascript
+;(() => {
+ function main(tFrame) {
+ MyGame.stopMain = window.requestAnimationFrame(main);
+
+ // Signal worker to compute updates
+ updateWorker.postMessage({
+ lastTick: MyGame.lastTick,
+ numTicks: calculatedNumTicks
+ });
+
+ render(tFrame);
+ }
+
+ main();
+})();
+```
+
+Benefits: No reliance on legacy timers. Worker performs computation in parallel.
+
+---
+
+## Handling Tab Focus Loss
+
+When a browser tab loses focus, `requestAnimationFrame` slows down or stops entirely. Strategies:
+
+| Strategy | Description | Best For |
+|---|---|---|
+| Treat gap as pause | Skip elapsed time; do not update | Single-player games |
+| Simulate the gap | Run all missed updates on regain | Simple simulations |
+| Sync from server/peer | Fetch authoritative state | Multiplayer games |
+
+Monitor the `numTicks` value after a focus-regain event. A very large value indicates the game was suspended and may need special handling rather than trying to simulate all missed frames.
+
+---
+
+## Comparison of Timing Approaches
+
+| Approach | Pros | Cons |
+|---|---|---|
+| Simple update/render per frame | Easy to implement, responsive | Breaks on slow/fast hardware |
+| Fixed timestep + interpolation | Consistent simulation, smooth visuals | More complex to implement |
+| Quality scaling | Maintains frame rate dynamically | Requires adaptive quality systems |
+
+---
+
+## Performance Best Practices
+
+- **Detach non-frame-critical code** from the main loop. Use events and callbacks for UI, network responses, and other asynchronous operations.
+- **Use Web Workers** for computationally expensive tasks like physics, pathfinding, and AI.
+- **Leverage GPU acceleration** through WebGL for rendering.
+- **Stay within the frame budget** -- monitor your update + render time to keep it under 16.67ms for 60 FPS.
+- **Throttle garbage collection pressure** by reusing objects and avoiding per-frame allocations.
+- **Plan your timing strategy early** -- changing the game loop architecture mid-development is difficult and error-prone.
+
+---
+
+## Popular 3D Frameworks and Libraries
+
+- **Three.js** -- General-purpose 3D library with a large ecosystem.
+- **Babylon.js** -- Full-featured 3D game engine with physics, audio, and scene management.
+- **A-Frame** -- Declarative 3D/VR framework built on Three.js.
+- **PlayCanvas** -- Cloud-hosted 3D game engine with a visual editor.
+- **Phaser** -- Popular 2D game framework with physics and input handling.
diff --git a/skills/game-engine/references/game-control-mechanisms.md b/skills/game-engine/references/game-control-mechanisms.md
new file mode 100644
index 000000000..029150475
--- /dev/null
+++ b/skills/game-engine/references/game-control-mechanisms.md
@@ -0,0 +1,617 @@
+# Game Control Mechanisms
+
+This reference covers the primary control mechanisms available for web-based games, including mobile touch, desktop keyboard and mouse, gamepad controllers, and unconventional input methods.
+
+## Mobile Touch Controls
+
+Mobile touch controls are essential for web-based games targeting mobile devices. A mobile-first approach ensures games are accessible on the most widely used platform for HTML5 games.
+
+### Key Events and APIs
+
+The core touch events available in the browser are:
+
+| Event | Description |
+|-------|-------------|
+| `touchstart` | Fired when the user places a finger on the screen |
+| `touchmove` | Fired when the user moves a finger while touching the screen |
+| `touchend` | Fired when the user lifts a finger from the screen |
+| `touchcancel` | Fired when a touch is cancelled or interrupted (e.g., finger moves off-screen) |
+
+**Registering touch event listeners:**
+
+```javascript
+const canvas = document.querySelector("canvas");
+canvas.addEventListener("touchstart", handleStart);
+canvas.addEventListener("touchmove", handleMove);
+canvas.addEventListener("touchend", handleEnd);
+canvas.addEventListener("touchcancel", handleCancel);
+```
+
+**Touch event properties:**
+
+- `e.touches[0]` -- Access the first touch point (zero-indexed for multitouch).
+- `e.touches[0].pageX` / `e.touches[0].pageY` -- Touch coordinates relative to the page.
+- Always subtract canvas offset to get position relative to the canvas element.
+
+### Code Examples
+
+**Pure JavaScript touch handler:**
+
+```javascript
+document.addEventListener("touchstart", touchHandler);
+document.addEventListener("touchmove", touchHandler);
+
+function touchHandler(e) {
+ if (e.touches) {
+ playerX = e.touches[0].pageX - canvas.offsetLeft - playerWidth / 2;
+ playerY = e.touches[0].pageY - canvas.offsetTop - playerHeight / 2;
+ e.preventDefault();
+ }
+}
+```
+
+**Phaser framework pointer system:**
+
+Phaser manages touch input through "pointers" representing individual fingers:
+
+```javascript
+// Access pointers
+this.game.input.activePointer; // Most recently active pointer
+this.game.input.pointer1; // First pointer
+this.game.input.pointer2; // Second pointer
+
+// Add more pointers (up to 10 total)
+this.game.input.addPointer();
+
+// Global input events
+this.game.input.onDown.add(itemTouched, this);
+this.game.input.onUp.add(itemReleased, this);
+this.game.input.onTap.add(itemTapped, this);
+this.game.input.onHold.add(itemHeld, this);
+```
+
+**Draggable sprite for ship movement:**
+
+```javascript
+const player = this.game.add.sprite(30, 30, "ship");
+player.inputEnabled = true;
+player.input.enableDrag();
+player.events.onDragStart.add(onDragStart, this);
+player.events.onDragStop.add(onDragStop, this);
+
+function onDragStart(sprite, pointer) {
+ console.log(`Dragging at: ${pointer.x}, ${pointer.y}`);
+}
+```
+
+**Invisible touch area for shooting (right half of screen):**
+
+```javascript
+this.buttonShoot = this.add.button(
+ this.world.width * 0.5, 0,
+ "button-alpha", // transparent image
+ null,
+ this
+);
+this.buttonShoot.onInputDown.add(this.goShootPressed, this);
+this.buttonShoot.onInputUp.add(this.goShootReleased, this);
+```
+
+**Virtual gamepad plugin:**
+
+```javascript
+this.gamepad = this.game.plugins.add(Phaser.Plugin.VirtualGamepad);
+this.joystick = this.gamepad.addJoystick(100, 420, 1.2, "gamepad");
+this.button = this.gamepad.addButton(400, 420, 1.0, "gamepad");
+```
+
+### Best Practices
+
+- Always call `preventDefault()` on touch events to avoid unwanted scrolling and default browser behavior.
+- Use invisible button areas rather than visible buttons to avoid covering gameplay.
+- Leverage natural touch gestures like dragging, which are more intuitive than on-screen buttons.
+- Subtract canvas offset and account for object dimensions when calculating positions.
+- Make touchable areas large enough for comfortable interaction.
+- Plan for multitouch support. Phaser supports up to 10 simultaneous pointers.
+- Use a framework like Phaser for automatic desktop and mobile compatibility.
+- Consider virtual gamepad/joystick plugins for advanced touch control UI.
+
+## Desktop with Mouse and Keyboard
+
+Desktop keyboard and mouse controls provide precise input for web games and are the default control scheme for desktop browsers.
+
+### Key Events and APIs
+
+**Keyboard events:**
+
+```javascript
+document.addEventListener("keydown", keyDownHandler);
+document.addEventListener("keyup", keyUpHandler);
+```
+
+- `event.code` returns readable key identifiers such as `"ArrowLeft"`, `"ArrowRight"`, `"ArrowUp"`, `"ArrowDown"`.
+- Use `requestAnimationFrame()` for continuous frame updates.
+
+**Phaser keyboard API:**
+
+```javascript
+this.cursors = this.input.keyboard.createCursorKeys(); // Arrow key objects
+this.keyLeft = this.input.keyboard.addKey(Phaser.KeyCode.A); // Custom key binding
+// Check key state with .isDown property
+// Listen for press events with .onDown.add()
+```
+
+**Phaser mouse API:**
+
+```javascript
+this.game.input.mousePointer; // Mouse position and state
+this.game.input.mousePointer.isDown; // Is any mouse button pressed
+this.game.input.mousePointer.x; // Mouse X coordinate
+this.game.input.mousePointer.y; // Mouse Y coordinate
+this.game.input.mousePointer.leftButton.isDown; // Left mouse button
+this.game.input.mousePointer.rightButton.isDown; // Right mouse button
+this.game.input.activePointer; // Platform-independent (mouse + touch)
+```
+
+### Code Examples
+
+**Pure JavaScript keyboard state tracking:**
+
+```javascript
+let rightPressed = false;
+let leftPressed = false;
+let upPressed = false;
+let downPressed = false;
+
+function keyDownHandler(event) {
+ if (event.code === "ArrowRight") rightPressed = true;
+ else if (event.code === "ArrowLeft") leftPressed = true;
+ if (event.code === "ArrowDown") downPressed = true;
+ else if (event.code === "ArrowUp") upPressed = true;
+}
+
+function keyUpHandler(event) {
+ if (event.code === "ArrowRight") rightPressed = false;
+ else if (event.code === "ArrowLeft") leftPressed = false;
+ if (event.code === "ArrowDown") downPressed = false;
+ else if (event.code === "ArrowUp") upPressed = false;
+}
+```
+
+**Game loop with input handling:**
+
+```javascript
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ if (rightPressed) playerX += 5;
+ else if (leftPressed) playerX -= 5;
+ if (downPressed) playerY += 5;
+ else if (upPressed) playerY -= 5;
+
+ ctx.drawImage(img, playerX, playerY);
+ requestAnimationFrame(draw);
+}
+```
+
+**Dual control support (Arrow keys + WASD) in Phaser:**
+
+```javascript
+this.cursors = this.input.keyboard.createCursorKeys();
+this.keyLeft = this.input.keyboard.addKey(Phaser.KeyCode.A);
+this.keyRight = this.input.keyboard.addKey(Phaser.KeyCode.D);
+this.keyUp = this.input.keyboard.addKey(Phaser.KeyCode.W);
+this.keyDown = this.input.keyboard.addKey(Phaser.KeyCode.S);
+
+// In update:
+if (this.cursors.left.isDown || this.keyLeft.isDown) {
+ // move left
+} else if (this.cursors.right.isDown || this.keyRight.isDown) {
+ // move right
+}
+if (this.cursors.up.isDown || this.keyUp.isDown) {
+ // move up
+} else if (this.cursors.down.isDown || this.keyDown.isDown) {
+ // move down
+}
+```
+
+**Multiple fire buttons:**
+
+```javascript
+this.keyFire1 = this.input.keyboard.addKey(Phaser.KeyCode.X);
+this.keyFire2 = this.input.keyboard.addKey(Phaser.KeyCode.SPACEBAR);
+
+if (this.keyFire1.isDown || this.keyFire2.isDown) {
+ // fire the weapon
+}
+```
+
+**Device-specific instructions:**
+
+```javascript
+if (this.game.device.desktop) {
+ moveText = "Arrow keys or WASD to move";
+ shootText = "X or Space to shoot";
+} else {
+ moveText = "Tap and hold to move";
+ shootText = "Tap to shoot";
+}
+```
+
+### Best Practices
+
+- Support multiple input methods: provide both arrow keys and WASD for movement, and multiple fire buttons (e.g., X and Space).
+- Use `activePointer` instead of `mousePointer` to support both mouse and touch input seamlessly.
+- Detect device type and display appropriate control instructions to the player.
+- Use `requestAnimationFrame()` for smooth animation and check key states in the game loop rather than reacting to individual key presses.
+- Allow keyboard shortcuts to skip non-gameplay screens (e.g., Enter to start, any key to skip intro).
+- Use Phaser or a similar framework for cross-browser compatibility, as they handle edge cases and browser differences automatically.
+
+## Desktop with Gamepad
+
+The Gamepad API enables web games to detect and respond to gamepad and controller input, bringing console-like experiences to the browser.
+
+### Key Events and APIs
+
+**Core events:**
+
+```javascript
+window.addEventListener("gamepadconnected", gamepadHandler);
+window.addEventListener("gamepaddisconnected", gamepadHandler);
+```
+
+**Gamepad object properties:**
+
+- `controller.id` -- Device identifier string.
+- `controller.buttons[]` -- Array of button objects, each with a `.pressed` boolean property.
+- `controller.axes[]` -- Array of analog stick values ranging from -1 to 1.
+
+**Standard button/axes mapping (Xbox 360 layout):**
+
+| Input | Index | Type |
+|-------|-------|------|
+| A Button | 0 | Button |
+| B Button | 1 | Button |
+| X Button | 2 | Button |
+| Y Button | 3 | Button |
+| D-Pad Up | 12 | Button |
+| D-Pad Down | 13 | Button |
+| D-Pad Left | 14 | Button |
+| D-Pad Right | 15 | Button |
+| Left Stick X | axes[0] | Axis |
+| Left Stick Y | axes[1] | Axis |
+| Right Stick X | axes[2] | Axis |
+| Right Stick Y | axes[3] | Axis |
+
+### Code Examples
+
+**Pure JavaScript connection handler:**
+
+```javascript
+let controller = {};
+let buttonsPressed = [];
+
+function gamepadHandler(e) {
+ controller = e.gamepad;
+ console.log(`Gamepad: ${controller.id}`);
+}
+
+window.addEventListener("gamepadconnected", gamepadHandler);
+```
+
+**Polling button states each frame:**
+
+```javascript
+function gamepadUpdateHandler() {
+ buttonsPressed = [];
+ if (controller.buttons) {
+ for (const [i, button] of controller.buttons.entries()) {
+ if (button.pressed) {
+ buttonsPressed.push(i);
+ }
+ }
+ }
+}
+
+function gamepadButtonPressedHandler(button) {
+ return buttonsPressed.includes(button);
+}
+```
+
+**Game loop integration:**
+
+```javascript
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ gamepadUpdateHandler();
+
+ if (gamepadButtonPressedHandler(12)) playerY -= 5; // D-Pad Up
+ else if (gamepadButtonPressedHandler(13)) playerY += 5; // D-Pad Down
+ if (gamepadButtonPressedHandler(14)) playerX -= 5; // D-Pad Left
+ else if (gamepadButtonPressedHandler(15)) playerX += 5; // D-Pad Right
+ if (gamepadButtonPressedHandler(0)) alert("BOOM!"); // A Button
+
+ ctx.drawImage(img, playerX, playerY);
+ requestAnimationFrame(draw);
+}
+```
+
+**Reusable GamepadAPI library with hold vs press detection:**
+
+```javascript
+const GamepadAPI = {
+ active: false,
+ controller: {},
+
+ connect(event) {
+ GamepadAPI.controller = event.gamepad;
+ GamepadAPI.active = true;
+ },
+
+ disconnect(event) {
+ delete GamepadAPI.controller;
+ GamepadAPI.active = false;
+ },
+
+ update() {
+ GamepadAPI.buttons.cache = [...GamepadAPI.buttons.status];
+ GamepadAPI.buttons.status = [];
+
+ const c = GamepadAPI.controller || {};
+ const pressed = [];
+
+ if (c.buttons) {
+ for (let b = 0; b < c.buttons.length; b++) {
+ if (c.buttons[b].pressed) {
+ pressed.push(GamepadAPI.buttons.layout[b]);
+ }
+ }
+ }
+
+ const axes = [];
+ if (c.axes) {
+ for (const ax of c.axes) {
+ axes.push(ax.toFixed(2));
+ }
+ }
+
+ GamepadAPI.axes.status = axes;
+ GamepadAPI.buttons.status = pressed;
+ return pressed;
+ },
+
+ buttons: {
+ layout: ["A", "B", "X", "Y", "LB", "RB", "LT", "RT",
+ "Back", "Start", "LS", "RS",
+ "DPad-Up", "DPad-Down", "DPad-Left", "DPad-Right"],
+ cache: [],
+ status: [],
+ pressed(button, hold) {
+ let newPress = false;
+ if (GamepadAPI.buttons.status.includes(button)) {
+ newPress = true;
+ }
+ if (!hold && GamepadAPI.buttons.cache.includes(button)) {
+ newPress = false;
+ }
+ return newPress;
+ }
+ },
+
+ axes: {
+ status: []
+ }
+};
+
+window.addEventListener("gamepadconnected", GamepadAPI.connect);
+window.addEventListener("gamepaddisconnected", GamepadAPI.disconnect);
+```
+
+**Analog stick movement with deadzone threshold:**
+
+```javascript
+if (GamepadAPI.axes.status) {
+ if (GamepadAPI.axes.status[0] > 0.5) playerX += 5; // Right
+ else if (GamepadAPI.axes.status[0] < -0.5) playerX -= 5; // Left
+ if (GamepadAPI.axes.status[1] > 0.5) playerY += 5; // Down
+ else if (GamepadAPI.axes.status[1] < -0.5) playerY -= 5; // Up
+}
+```
+
+**Context-aware control display:**
+
+```javascript
+if (this.game.device.desktop) {
+ if (GamepadAPI.active) {
+ moveText = "DPad or left Stick to move";
+ shootText = "A to shoot, Y for controls";
+ } else {
+ moveText = "Arrow keys or WASD to move";
+ shootText = "X or Space to shoot";
+ }
+} else {
+ moveText = "Tap and hold to move";
+ shootText = "Tap to shoot";
+}
+```
+
+### Best Practices
+
+- Always check `GamepadAPI.active` before processing gamepad input.
+- Differentiate between "hold" (continuous) and "press" (single new press) by caching previous frame button states.
+- Apply a deadzone threshold (e.g., 0.5) for analog stick values to avoid unintentional drift input.
+- Create a button mapping system because different devices may have different button layouts.
+- Poll gamepad state every frame by calling the update function inside `requestAnimationFrame`.
+- Display an on-screen indicator when a gamepad is connected, along with appropriate control instructions.
+- Browser support is approximately 63% globally; always provide fallback keyboard/mouse controls.
+
+## Other Control Mechanisms
+
+Unconventional control mechanisms can provide unique gameplay experiences and leverage emerging hardware beyond traditional input devices.
+
+### TV Remote Controls
+
+**Description:** Smart TV remotes emit standard keyboard events, allowing web games to run on TV screens without modification.
+
+**Key Events and APIs:**
+
+- Remote directional buttons map to standard arrow key codes.
+- Custom remote buttons have manufacturer-specific key codes.
+
+**Code Example:**
+
+```javascript
+// Standard arrow key controls work automatically with TV remotes
+this.cursors = this.input.keyboard.createCursorKeys();
+if (this.cursors.right.isDown) {
+ // move player right
+}
+
+// Discover manufacturer-specific remote key codes
+window.addEventListener("keydown", (event) => {
+ console.log(event.keyCode);
+});
+
+// Handle custom remote buttons (codes vary by manufacturer)
+window.addEventListener("keydown", (event) => {
+ switch (event.keyCode) {
+ case 8: // Pause (Panasonic example)
+ break;
+ case 588: // Custom action
+ break;
+ }
+});
+```
+
+**Best Practices:**
+
+- Log key codes to the console during development to discover remote button mappings.
+- Reuse existing keyboard control implementations since remotes emit keyboard events.
+- Refer to manufacturer documentation or cheat sheets for key code mappings.
+
+### Leap Motion (Hand Gesture Recognition)
+
+**Description:** Detects hand position, rotation, and grip strength for gesture-based control without physical contact using the Leap Motion sensor.
+
+**Key Events and APIs:**
+
+- `Leap.loop()` -- Frame-based hand tracking callback.
+- `hand.roll()` -- Horizontal rotation in radians.
+- `hand.pitch()` -- Vertical rotation in radians.
+- `hand.grabStrength` -- Grip strength as a float from 0 (open hand) to 1 (closed fist).
+
+**Code Example:**
+
+```html
+
+```
+
+```javascript
+const toDegrees = 1 / (Math.PI / 180);
+let horizontalDegree = 0;
+let verticalDegree = 0;
+const degreeThreshold = 30;
+let grabStrength = 0;
+
+Leap.loop({
+ hand(hand) {
+ horizontalDegree = Math.round(hand.roll() * toDegrees);
+ verticalDegree = Math.round(hand.pitch() * toDegrees);
+ grabStrength = hand.grabStrength;
+ },
+});
+
+function draw() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ if (horizontalDegree > degreeThreshold) playerX -= 5;
+ else if (horizontalDegree < -degreeThreshold) playerX += 5;
+
+ if (verticalDegree > degreeThreshold) playerY += 5;
+ else if (verticalDegree < -degreeThreshold) playerY -= 5;
+
+ if (grabStrength === 1) fireWeapon();
+
+ ctx.drawImage(img, playerX, playerY);
+ requestAnimationFrame(draw);
+}
+```
+
+**Best Practices:**
+
+- Use a degree threshold (e.g., 30 degrees) to filter out minor hand movements and noise.
+- Output diagnostic data during development to calibrate sensitivity.
+- Limit to simple actions like steering and shooting rather than complex multi-input schemes.
+- Requires Leap Motion drivers to be installed.
+
+### Doppler Effect (Microphone-Based Gesture Detection)
+
+**Description:** Detects hand movement direction and magnitude by analyzing frequency shifts in sound waves picked up by the device microphone. An emitted tone bounces off the user's hand, and the frequency difference indicates movement direction.
+
+**Key Events and APIs:**
+
+- Uses a Doppler effect detection library.
+- `bandwidth.left` and `bandwidth.right` provide frequency analysis values.
+
+**Code Example:**
+
+```javascript
+doppler.init((bandwidth) => {
+ const diff = bandwidth.left - bandwidth.right;
+ // Positive diff = movement in one direction
+ // Negative diff = movement in the other direction
+});
+```
+
+**Best Practices:**
+
+- Best suited for simple one-axis controls such as scrolling or up/down movement.
+- Less precise than Leap Motion or gamepad input.
+- Provides directional information through left/right frequency difference comparison.
+
+### Makey Makey (Physical Object Controllers)
+
+**Description:** Connects conductive objects (bananas, clay, drawn circuits, water, etc.) to a board that emulates keyboard and mouse input, enabling creative physical interfaces for games.
+
+**Key Events and APIs (via Cylon.js for custom hardware):**
+
+- `makey-button` driver for custom setups with Arduino or Raspberry Pi.
+- `"push"` event listener for button activation.
+- The Makey Makey board itself works over USB and emits standard keyboard events without requiring custom code.
+
+**Code Example (custom setup with Cylon.js):**
+
+```javascript
+const Cylon = require("cylon");
+
+Cylon.robot({
+ connections: {
+ arduino: { adaptor: "firmata", port: "/dev/ttyACM0" },
+ },
+ devices: {
+ makey: { driver: "makey-button", pin: 2 },
+ },
+ work(my) {
+ my.makey.on("push", () => {
+ console.log("Button pushed!");
+ // Trigger game action
+ });
+ },
+}).start();
+```
+
+**Best Practices:**
+
+- The Makey Makey board connects via USB and emits standard keyboard events, so existing keyboard controls work out of the box.
+- Use a 10 MOhm resistor for GPIO connections on custom setups.
+- Enables creative physical gaming experiences that are particularly good for exhibitions and installations.
+
+### General Recommendations for Unconventional Controls
+
+- Implement multiple control mechanisms to reach the broadest possible audience.
+- Build on a keyboard and gamepad foundation since most unconventional controllers emulate or complement standard input.
+- Use threshold values to filter noise and accidental inputs from imprecise hardware.
+- Provide visual diagnostics during development with console output and on-screen values.
+- Match control complexity to the game's needs. Not all mechanisms suit all games.
+- Test hardware setup thoroughly before implementing game logic on top of it.
diff --git a/skills/game-engine/references/game-engine-core-principles.md b/skills/game-engine/references/game-engine-core-principles.md
new file mode 100644
index 000000000..e69852786
--- /dev/null
+++ b/skills/game-engine/references/game-engine-core-principles.md
@@ -0,0 +1,695 @@
+# Game Engine Core Design Principles
+
+A comprehensive reference on the fundamental architecture and design principles behind building a game engine. Covers modularity, separation of concerns, core subsystems, and practical implementation guidance.
+
+Source: https://www.gamedev.net/articles/programming/general-and-gameplay-programming/making-a-game-engine-core-design-principles-r3210/
+
+---
+
+## Why Build a Game Engine
+
+A game engine is a reusable software framework that abstracts the common systems needed to build games. Rather than writing rendering, physics, input, and audio code from scratch for every project, a well-designed engine provides these as modular, configurable subsystems.
+
+Key motivations:
+- **Reusability** -- Use the same codebase across multiple game projects.
+- **Separation of engine code from game code** -- Engine developers and game designers can work independently.
+- **Maintainability** -- Well-structured code is easier to debug, extend, and optimize.
+- **Scalability** -- Add new features or platforms without rewriting existing systems.
+
+---
+
+## Core Design Principles
+
+### Modularity
+
+Every major system in the engine should be an independent module with a well-defined interface. Modules should communicate through clean APIs rather than reaching into each other's internals.
+
+**Why it matters:**
+- Swap implementations without affecting other systems (e.g., replace OpenGL renderer with Vulkan).
+- Test individual systems in isolation.
+- Allow teams to work on different modules in parallel.
+
+**Example structure:**
+
+```
+engine/
+ core/ -- Memory, logging, math, utilities
+ platform/ -- OS abstraction, windowing, file I/O
+ renderer/ -- Graphics API, shaders, materials
+ physics/ -- Collision, rigid body dynamics
+ audio/ -- Sound playback, mixing, spatial audio
+ input/ -- Keyboard, mouse, gamepad, touch
+ scripting/ -- Scripting language bindings
+ scene/ -- Scene graph, entity management
+ resources/ -- Asset loading, caching, streaming
+```
+
+### Separation of Concerns
+
+Each system should have a single, clearly defined responsibility. Avoid mixing rendering logic with physics, or input handling with game state management.
+
+**Practical guidelines:**
+- The renderer should not know about game mechanics.
+- The physics engine should not know how entities are rendered.
+- Input processing should translate raw device events into abstract actions that game code can consume.
+- The game logic layer sits on top of the engine and uses engine services without modifying them.
+
+### Data-Driven Design
+
+Wherever possible, behavior should be controlled by data rather than hard-coded logic. This allows designers and artists to modify game behavior without recompiling code.
+
+**Examples of data-driven approaches:**
+- Level layouts defined in data files (JSON, XML, binary) rather than code.
+- Entity properties and behaviors configured through component data.
+- Shader parameters exposed as material properties editable in tools.
+- Animation state machines defined in configuration rather than imperative code.
+
+### Minimize Dependencies
+
+Each module should depend on as few other modules as possible. The dependency graph should be a clean hierarchy, not a tangled web.
+
+```
+Game Code
+ |
+ v
+Engine High-Level Systems (Scene, Entity, Scripting)
+ |
+ v
+Engine Low-Level Systems (Renderer, Physics, Audio, Input)
+ |
+ v
+Engine Core (Memory, Math, Logging, Platform Abstraction)
+ |
+ v
+Operating System / Hardware
+```
+
+Circular dependencies between modules are a sign of poor architecture and should be eliminated.
+
+---
+
+## The Entity-Component-System (ECS) Pattern
+
+ECS is a widely adopted architectural pattern in modern game engines that favors composition over inheritance.
+
+### Core Concepts
+
+- **Entity** -- A unique identifier (often just an integer ID) that represents a game object. An entity has no behavior or data of its own.
+- **Component** -- A plain data container attached to an entity. Each component type stores one aspect of an entity's state (position, velocity, sprite, health, etc.).
+- **System** -- A function or object that processes all entities with a specific set of components. Systems contain the logic; components contain the data.
+
+### Why ECS Over Inheritance
+
+Traditional object-oriented inheritance creates rigid, deep hierarchies:
+
+```
+GameObject
+ -> MovableObject
+ -> Character
+ -> Player
+ -> Enemy
+ -> FlyingEnemy
+ -> GroundEnemy
+```
+
+Problems with this approach:
+- Adding a new entity type that combines traits from multiple branches requires restructuring the hierarchy or using multiple inheritance.
+- Deep hierarchies are fragile; changes to base classes ripple through all descendants.
+- Classes accumulate unused behavior over time.
+
+ECS solves these problems through composition:
+
+```javascript
+// An entity is just an ID
+const player = world.createEntity();
+
+// Attach components to define what it is
+world.addComponent(player, new Position(100, 200));
+world.addComponent(player, new Velocity(0, 0));
+world.addComponent(player, new Sprite("player.png"));
+world.addComponent(player, new Health(100));
+world.addComponent(player, new PlayerInput());
+
+// A "flying enemy" is just a different combination of components
+const flyingEnemy = world.createEntity();
+world.addComponent(flyingEnemy, new Position(400, 50));
+world.addComponent(flyingEnemy, new Velocity(0, 0));
+world.addComponent(flyingEnemy, new Sprite("bat.png"));
+world.addComponent(flyingEnemy, new Health(30));
+world.addComponent(flyingEnemy, new AIBehavior("patrol_fly"));
+world.addComponent(flyingEnemy, new Flying());
+```
+
+### Systems Process Components
+
+```javascript
+// Movement system: processes all entities with Position + Velocity
+function movementSystem(world, deltaTime) {
+ for (const [entity, pos, vel] of world.query(Position, Velocity)) {
+ pos.x += vel.x * deltaTime;
+ pos.y += vel.y * deltaTime;
+ }
+}
+
+// Render system: processes all entities with Position + Sprite
+function renderSystem(world, context) {
+ for (const [entity, pos, sprite] of world.query(Position, Sprite)) {
+ context.drawImage(sprite.image, pos.x, pos.y);
+ }
+}
+
+// Gravity system: only affects entities with Velocity but NOT Flying
+function gravitySystem(world, deltaTime) {
+ for (const [entity, vel] of world.query(Velocity).without(Flying)) {
+ vel.y += 9.8 * deltaTime;
+ }
+}
+```
+
+### Benefits of ECS
+
+- **Flexible composition** -- Create any entity type by mixing components without modifying code.
+- **Cache-friendly data layout** -- Storing components contiguously in memory improves CPU cache performance.
+- **Parallelism** -- Systems that operate on different component sets can run in parallel.
+- **Easy serialization** -- Components are plain data, making save/load straightforward.
+
+---
+
+## Core Engine Subsystems
+
+### Memory Management
+
+Custom memory management is critical for game engine performance. The default allocator (malloc/new) is general-purpose and not optimized for game workloads.
+
+**Common allocation strategies:**
+
+- **Stack Allocator** -- Fast LIFO allocations for temporary, frame-scoped data. Reset the stack pointer at the end of each frame.
+- **Pool Allocator** -- Fixed-size block allocation for objects of the same type (entities, components, particles). Zero fragmentation.
+- **Frame Allocator** -- A linear allocator that resets every frame. Ideal for per-frame temporary data.
+- **Double-Buffered Allocator** -- Two frame allocators that alternate each frame, allowing data from the previous frame to persist.
+
+```cpp
+// Conceptual frame allocator
+class FrameAllocator {
+ char* buffer;
+ size_t offset;
+ size_t capacity;
+
+public:
+ void* allocate(size_t size) {
+ void* ptr = buffer + offset;
+ offset += size;
+ return ptr;
+ }
+
+ void reset() {
+ offset = 0; // All allocations freed instantly
+ }
+};
+```
+
+### Resource Management
+
+The resource manager handles loading, caching, and lifetime management of game assets.
+
+**Key responsibilities:**
+- **Asynchronous loading** -- Load assets in background threads to avoid stalling the game loop.
+- **Reference counting** -- Track how many systems use an asset; unload when no longer referenced.
+- **Caching** -- Keep recently used assets in memory to avoid redundant disk reads.
+- **Hot reloading** -- Detect asset changes on disk and reload them at runtime during development.
+- **Resource handles** -- Use handles (IDs or smart pointers) rather than raw pointers to reference assets.
+
+```javascript
+class ResourceManager {
+ constructor() {
+ this.cache = new Map();
+ this.loading = new Map();
+ }
+
+ async load(path) {
+ // Return cached resource if available
+ if (this.cache.has(path)) {
+ return this.cache.get(path);
+ }
+
+ // Avoid duplicate loads
+ if (this.loading.has(path)) {
+ return this.loading.get(path);
+ }
+
+ // Start async load
+ const promise = this._loadFromDisk(path).then(resource => {
+ this.cache.set(path, resource);
+ this.loading.delete(path);
+ return resource;
+ });
+
+ this.loading.set(path, promise);
+ return promise;
+ }
+
+ unload(path) {
+ this.cache.delete(path);
+ }
+}
+```
+
+### Rendering Pipeline
+
+The rendering subsystem translates the game's visual state into pixels on screen.
+
+**Typical rendering pipeline stages:**
+
+1. **Scene traversal** -- Walk the scene graph or query ECS for renderable entities.
+2. **Frustum culling** -- Discard objects outside the camera's view.
+3. **Occlusion culling** -- Discard objects hidden behind other geometry.
+4. **Sorting** -- Order objects by material, depth, or transparency requirements.
+5. **Batching** -- Group objects with the same material to minimize draw calls and state changes.
+6. **Vertex processing** -- Transform vertices from model space to screen space (vertex shader).
+7. **Rasterization** -- Convert triangles to fragments (pixels).
+8. **Fragment processing** -- Compute final pixel color using lighting, textures, and effects (fragment shader).
+9. **Post-processing** -- Apply screen-space effects like bloom, tone mapping, and anti-aliasing.
+
+**Render command pattern:**
+
+Rather than making draw calls directly, build a list of render commands that can be sorted and batched before submission:
+
+```javascript
+class RenderCommand {
+ constructor(mesh, material, transform, sortKey) {
+ this.mesh = mesh;
+ this.material = material;
+ this.transform = transform;
+ this.sortKey = sortKey;
+ }
+}
+
+class Renderer {
+ constructor() {
+ this.commandQueue = [];
+ }
+
+ submit(command) {
+ this.commandQueue.push(command);
+ }
+
+ flush(context) {
+ // Sort by material to minimize state changes
+ this.commandQueue.sort((a, b) => a.sortKey - b.sortKey);
+
+ for (const cmd of this.commandQueue) {
+ this._bindMaterial(cmd.material);
+ this._setTransform(cmd.transform);
+ this._drawMesh(cmd.mesh, context);
+ }
+
+ this.commandQueue.length = 0;
+ }
+}
+```
+
+### Physics Integration
+
+The physics subsystem simulates physical behavior and detects collisions.
+
+**Key design considerations:**
+
+- **Fixed timestep** -- Physics should update at a fixed rate (e.g., 50 Hz) independent of the rendering frame rate. This ensures deterministic simulation behavior.
+- **Collision phases** -- Use a broad phase (spatial partitioning, bounding volume hierarchies) to quickly eliminate non-colliding pairs, followed by a narrow phase for precise intersection testing.
+- **Physics world separation** -- The physics world should maintain its own representation of objects (physics bodies) separate from game entities. A synchronization step maps between them.
+
+```javascript
+class PhysicsWorld {
+ constructor(fixedTimestep = 1 / 50) {
+ this.fixedTimestep = fixedTimestep;
+ this.accumulator = 0;
+ this.bodies = [];
+ }
+
+ update(deltaTime) {
+ this.accumulator += deltaTime;
+
+ while (this.accumulator >= this.fixedTimestep) {
+ this.step(this.fixedTimestep);
+ this.accumulator -= this.fixedTimestep;
+ }
+ }
+
+ step(dt) {
+ // Integrate velocities
+ for (const body of this.bodies) {
+ body.velocity.y += body.gravity * dt;
+ body.position.x += body.velocity.x * dt;
+ body.position.y += body.velocity.y * dt;
+ }
+
+ // Detect and resolve collisions
+ this.broadPhase();
+ this.narrowPhase();
+ this.resolveCollisions();
+ }
+}
+```
+
+### Input System
+
+The input system translates raw hardware events into game-meaningful actions.
+
+**Layered design:**
+
+1. **Hardware Layer** -- Receives raw events from the OS (key pressed, mouse moved, button down).
+2. **Mapping Layer** -- Translates raw inputs into named actions via configurable bindings (e.g., "Space" maps to "Jump", "W" maps to "MoveForward").
+3. **Action Layer** -- Exposes abstract actions that game code queries, completely decoupled from specific hardware inputs.
+
+```javascript
+class InputManager {
+ constructor() {
+ this.bindings = new Map();
+ this.actionStates = new Map();
+ }
+
+ bind(action, key) {
+ this.bindings.set(key, action);
+ }
+
+ handleKeyDown(event) {
+ const action = this.bindings.get(event.code);
+ if (action) {
+ this.actionStates.set(action, true);
+ }
+ }
+
+ handleKeyUp(event) {
+ const action = this.bindings.get(event.code);
+ if (action) {
+ this.actionStates.set(action, false);
+ }
+ }
+
+ isActionActive(action) {
+ return this.actionStates.get(action) || false;
+ }
+}
+
+// Usage
+const input = new InputManager();
+input.bind("Jump", "Space");
+input.bind("MoveLeft", "KeyA");
+input.bind("MoveRight", "KeyD");
+
+// In game update:
+if (input.isActionActive("Jump")) {
+ player.jump();
+}
+```
+
+### Event System
+
+An event system enables decoupled communication between engine subsystems and game code without direct references.
+
+**Publish-subscribe pattern:**
+
+```javascript
+class EventBus {
+ constructor() {
+ this.listeners = new Map();
+ }
+
+ on(eventType, callback) {
+ if (!this.listeners.has(eventType)) {
+ this.listeners.set(eventType, []);
+ }
+ this.listeners.get(eventType).push(callback);
+ }
+
+ off(eventType, callback) {
+ const callbacks = this.listeners.get(eventType);
+ if (callbacks) {
+ const index = callbacks.indexOf(callback);
+ if (index !== -1) callbacks.splice(index, 1);
+ }
+ }
+
+ emit(eventType, data) {
+ const callbacks = this.listeners.get(eventType);
+ if (callbacks) {
+ for (const callback of callbacks) {
+ callback(data);
+ }
+ }
+ }
+}
+
+// Usage
+const events = new EventBus();
+
+events.on("collision", (data) => {
+ console.log(`${data.entityA} collided with ${data.entityB}`);
+});
+
+events.on("entityDestroyed", (data) => {
+ spawnExplosion(data.position);
+ addScore(data.points);
+});
+
+// Emit from physics system
+events.emit("collision", { entityA: player, entityB: wall });
+```
+
+**Deferred events:**
+
+For performance and determinism, events can be queued during a frame and dispatched at a specific point in the update cycle:
+
+```javascript
+class DeferredEventBus extends EventBus {
+ constructor() {
+ super();
+ this.eventQueue = [];
+ }
+
+ queue(eventType, data) {
+ this.eventQueue.push({ type: eventType, data });
+ }
+
+ dispatchQueued() {
+ for (const event of this.eventQueue) {
+ this.emit(event.type, event.data);
+ }
+ this.eventQueue.length = 0;
+ }
+}
+```
+
+### Scene Management
+
+The scene manager organizes game content into logical groups and manages transitions between different game states.
+
+**Common patterns:**
+
+- **Scene graph** -- A hierarchical tree of nodes where child transforms are relative to parent transforms. Moving a parent moves all children.
+- **Scene stack** -- Scenes can be pushed and popped. A pause menu pushes on top of gameplay; dismissing it pops back to gameplay.
+- **Scene loading** -- Scenes define which assets and entities to load. The scene manager coordinates loading, initialization, and cleanup.
+
+```javascript
+class SceneManager {
+ constructor() {
+ this.scenes = new Map();
+ this.activeScene = null;
+ }
+
+ register(name, scene) {
+ this.scenes.set(name, scene);
+ }
+
+ async switchTo(name) {
+ if (this.activeScene) {
+ this.activeScene.onExit();
+ this.activeScene.unloadResources();
+ }
+
+ this.activeScene = this.scenes.get(name);
+ await this.activeScene.loadResources();
+ this.activeScene.onEnter();
+ }
+
+ update(deltaTime) {
+ if (this.activeScene) {
+ this.activeScene.update(deltaTime);
+ }
+ }
+
+ render(context) {
+ if (this.activeScene) {
+ this.activeScene.render(context);
+ }
+ }
+}
+```
+
+---
+
+## Platform Abstraction
+
+A well-designed engine abstracts platform-specific code behind a uniform interface. This enables the engine to run on multiple operating systems, graphics APIs, and hardware configurations.
+
+**Areas requiring abstraction:**
+
+| Concern | Examples |
+|---|---|
+| Windowing | Win32, X11, Cocoa, SDL, GLFW |
+| Graphics API | OpenGL, Vulkan, DirectX, Metal, WebGL |
+| File I/O | POSIX, Win32, virtual file systems |
+| Threading | pthreads, Win32 threads, Web Workers |
+| Audio output | WASAPI, CoreAudio, ALSA, Web Audio |
+| Input devices | DirectInput, XInput, evdev, Gamepad API |
+
+```javascript
+// Abstract file system interface
+class FileSystem {
+ async readFile(path) { throw new Error("Not implemented"); }
+ async writeFile(path, data) { throw new Error("Not implemented"); }
+ async exists(path) { throw new Error("Not implemented"); }
+}
+
+// Web implementation
+class WebFileSystem extends FileSystem {
+ async readFile(path) {
+ const response = await fetch(path);
+ return response.arrayBuffer();
+ }
+}
+
+// Node.js implementation
+class NodeFileSystem extends FileSystem {
+ async readFile(path) {
+ const fs = require("fs").promises;
+ return fs.readFile(path);
+ }
+}
+```
+
+---
+
+## Initialization and Shutdown Order
+
+Engine subsystems must be initialized in dependency order and shut down in reverse order.
+
+**Typical initialization sequence:**
+
+1. Core systems (logging, memory, configuration)
+2. Platform layer (window creation, input devices)
+3. Rendering system (graphics context, default resources)
+4. Audio system
+5. Physics system
+6. Resource manager (load default/shared assets)
+7. Scene manager
+8. Scripting system
+9. Game-specific initialization
+
+**Shutdown reverses this order** to ensure systems are cleaned up before the systems they depend on.
+
+```javascript
+class Engine {
+ async initialize() {
+ this.logger = new Logger();
+ this.config = new Config("engine.json");
+ this.platform = new Platform();
+ await this.platform.createWindow(this.config.window);
+
+ this.renderer = new Renderer(this.platform.canvas);
+ this.audio = new AudioSystem();
+ this.physics = new PhysicsWorld();
+ this.resources = new ResourceManager();
+ this.input = new InputManager(this.platform.window);
+ this.events = new EventBus();
+ this.scenes = new SceneManager();
+
+ this.logger.info("Engine initialized");
+ }
+
+ shutdown() {
+ this.scenes.cleanup();
+ this.resources.unloadAll();
+ this.input.cleanup();
+ this.physics.cleanup();
+ this.audio.cleanup();
+ this.renderer.cleanup();
+ this.platform.cleanup();
+ this.logger.info("Engine shutdown complete");
+ }
+
+ run() {
+ let lastTime = performance.now();
+
+ const loop = (currentTime) => {
+ const deltaTime = (currentTime - lastTime) / 1000;
+ lastTime = currentTime;
+
+ this.input.poll();
+ this.physics.update(deltaTime);
+ this.scenes.update(deltaTime);
+ this.events.dispatchQueued();
+ this.scenes.render(this.renderer);
+ this.renderer.present();
+
+ requestAnimationFrame(loop);
+ };
+
+ requestAnimationFrame(loop);
+ }
+}
+```
+
+---
+
+## Performance Principles
+
+### Avoid Premature Abstraction
+
+While modularity is important, over-engineering interfaces before understanding real requirements leads to unnecessary complexity. Start with simple, concrete implementations and refactor toward abstraction when actual use cases demand it.
+
+### Profile Before Optimizing
+
+Measure actual performance bottlenecks using profiling tools before spending time on optimization. Intuition about where time is spent is frequently wrong.
+
+### Data-Oriented Design
+
+Organize data by how it is accessed rather than by object-oriented abstractions. Storing components of the same type contiguously in memory (Structure of Arrays rather than Array of Structures) dramatically improves CPU cache hit rates.
+
+```javascript
+// Array of Structures (cache-unfriendly for position-only iteration)
+const entities = [
+ { position: {x: 0, y: 0}, sprite: "hero.png", health: 100 },
+ { position: {x: 5, y: 3}, sprite: "bat.png", health: 30 },
+];
+
+// Structure of Arrays (cache-friendly for position-only iteration)
+const positions = { x: [0, 5], y: [0, 3] };
+const sprites = ["hero.png", "bat.png"];
+const healths = [100, 30];
+```
+
+### Minimize Allocations in Hot Paths
+
+Avoid creating new objects or allocating memory during per-frame updates. Pre-allocate buffers, use object pools, and reuse temporary objects.
+
+### Batch Operations
+
+Group similar operations together to reduce overhead from context switching, draw call setup, and cache misses. Process all entities of a given type before moving to the next type.
+
+---
+
+## Summary of Key Principles
+
+| Principle | Description |
+|---|---|
+| Modularity | Independent subsystems with clean interfaces |
+| Separation of concerns | Each system has a single responsibility |
+| Data-driven design | Behavior controlled by data, not hard-coded logic |
+| Composition over inheritance | ECS pattern for flexible entity construction |
+| Minimal dependencies | Clean, hierarchical dependency graph |
+| Platform abstraction | Uniform interfaces over platform-specific code |
+| Fixed timestep physics | Deterministic simulation independent of frame rate |
+| Event-driven communication | Decoupled interaction through publish-subscribe |
+| Data-oriented performance | Optimize memory layout for access patterns |
+| Measure before optimizing | Profile to identify actual bottlenecks |
diff --git a/skills/game-engine/references/game-publishing.md b/skills/game-engine/references/game-publishing.md
new file mode 100644
index 000000000..a0799007f
--- /dev/null
+++ b/skills/game-engine/references/game-publishing.md
@@ -0,0 +1,352 @@
+# Game Publishing
+
+This reference covers the three pillars of publishing web-based games: distribution channels and platforms, promotion strategies, and monetization models.
+
+## Game Distribution
+
+Game distribution encompasses the channels and platforms through which players discover and access your game. Choosing the right distribution strategy depends on your target audience, game type, and business goals.
+
+### Self-Hosting
+
+Self-hosting gives you maximum control over your game and the ability to push instant updates without waiting for app store approval.
+
+- Upload the game to a remote server with a catchy, memorable domain name.
+- Concatenate and minify source code to reduce payload size.
+- Uglify code to make reverse engineering harder and protect intellectual property.
+- Provide an online demo if you plan to package the game for closed stores like iTunes or Steam.
+- Consider hosting on GitHub Pages for free hosting, version control, and potential community contributions.
+
+### Publishers and Portals
+
+Independent game portals offer natural promotion from high-traffic sites and potential monetization through ads or revenue sharing.
+
+**Popular independent portals:**
+
+- HTML5Games.com
+- GameArter.com
+- MarketJS.com
+- GameFlare
+- GameDistribution.com
+- GameSaturn.com
+- Playmox.com
+- Poki (developers.poki.com)
+- CrazyGames (developer.crazygames.com)
+
+**Licensing options:**
+
+- Exclusive licensing: Restrict distribution to a single buyer for higher per-deal revenue.
+- Non-exclusive licensing: Distribute widely across multiple portals for broader reach.
+
+### Web Stores
+
+**Chrome Web Store:**
+
+- Requires a manifest file and a zipped package containing game resources.
+- Minimal game modifications needed.
+- Simple online submission form.
+
+### Native Mobile Stores
+
+**iOS App Store:**
+
+- Strict requirements with a 1-2 week approval wait period.
+- Extremely competitive with hundreds of thousands of apps.
+- Generally favors paid games.
+- Most prominent mobile store but hardest to stand out.
+
+**Google Play (Android):**
+
+- Less strict requirements than iOS.
+- High volume of daily submissions.
+- Freemium model preferred (free download with in-app purchases or ads).
+- Most paid iOS games appear as free-to-play on Android.
+
+**Other mobile platforms (Windows Phone, BlackBerry, etc.):**
+
+- Less competition and easier to gain visibility.
+- Smaller market share but less crowded.
+
+### Native Desktop
+
+**Steam:**
+
+- Largest desktop game distribution platform.
+- Access via the Steam Direct program for indie developers.
+- Requires support for multiple platforms (Windows, macOS, Linux) with separate uploads.
+- Must handle cross-platform compatibility issues.
+
+**Humble Bundle:**
+
+- Primarily an exposure and promotional opportunity.
+- Bundle pricing model at low prices.
+- More focused on gaining visibility than generating direct revenue.
+
+### Packaging Tools
+
+Tools for distributing HTML5 games to closed ecosystems:
+
+| Tool | Platforms |
+|------|-----------|
+| Ejecta | iOS (ImpactJS-specific) |
+| NW.js | Windows, Mac, Linux |
+| Electron | Windows, Mac, Linux |
+| Intel XDK | Multiple platforms |
+| Manifold.js | iOS, Android, Windows |
+
+### Platform Strategy
+
+- **Mobile first:** Mobile devices account for the vast majority of HTML5 game traffic. Design games playable with one or two fingers while holding the device.
+- **Desktop for development:** Build and test on desktop first before debugging on mobile.
+- **Multi-platform:** Support desktop even if targeting mobile primarily. HTML5 games have the advantage of write-once, deploy-everywhere.
+- **Diversify:** Do not rely on a single store. Spread across multiple platforms to reduce risk.
+- **Instant updates:** One of the key advantages of web distribution is the ability to push quick bug fixes without waiting for app store approval.
+
+## Game Promotion
+
+Game promotion requires a sustained, multi-channel strategy. Most promotional methods are free, making them accessible to indie developers with limited budgets. Visibility is as important as game quality -- even excellent games fail without promotion.
+
+### Website and Blog
+
+**Essential website components:**
+
+- Screenshots and game trailers.
+- Detailed descriptions and downloadable press kits (use tools like Presskit()).
+- System requirements and available platforms.
+- Support and contact information.
+- A playable demo, at least browser-based.
+- SEO optimization for discoverability.
+
+**Blogging strategy:**
+
+- Document the development process, bugs encountered, and lessons learned.
+- Publish monthly progress reports.
+- Continual content creation improves SEO rankings over time.
+- Builds credibility and community reputation.
+
+### Social Media
+
+- Use the `#gamedev` hashtag for community engagement on platforms like Twitter/X.
+- Be authentic and avoid pushy advertisements or dry press releases.
+- Share development tips, industry insights, and behind-the-scenes content.
+- Monitor YouTube and Twitch streamers who might cover your game.
+- Participate in forums such as HTML5GameDevs.com.
+- Engage genuinely with the community. Answer questions, be supportive, and avoid constant self-promotion.
+- Offer discounts and contest prizes to build goodwill.
+
+### Press Outreach
+
+- Research press outlets that specifically cover your game's genre and platform.
+- Be humble, polite, and patient when contacting journalists and reviewers.
+- Avoid mass, irrelevant submissions. Target your outreach carefully.
+- A quality game paired with an honest approach yields the best success rates.
+- Reference guides like "How To Contact Press" from Pixel Prospector for detailed strategies.
+
+### Competitions
+
+- Participate in game development competitions (game jams) to network and gain community exposure.
+- Mandatory themes spark creative ideas and force innovation.
+- Winning brings automatic promotion from organizers and community attention.
+- Great for launching early demos and building reputation.
+
+### Tutorials and Educational Content
+
+- Document and teach what you have implemented in your game.
+- Use your game as a practical case study in articles and tutorials.
+- Publish on platforms like Tuts+ Game Development, which often pay for content.
+- Focus on a single aspect in detail and provide genuine value to readers.
+- Dual benefit: promotes your game while establishing you as a knowledgeable developer.
+
+### Events
+
+**Conferences:**
+
+- Give technical talks about challenges you overcame during development.
+- Demonstrate API implementations with your game as a real example.
+- Focus on knowledge-sharing over marketing. Developers are particularly sensitive to heavy-handed sales pitches.
+
+**Fairs and expos:**
+
+- Secure a booth among other developers for direct fan interaction.
+- Stand out with unique, original presentations.
+- Provides real-world user testing and immediate feedback.
+- Helps uncover bugs and issues that players find organically.
+
+### Promo Codes
+
+- Create the ability to distribute free or limited-access promo codes.
+- Distribute to press, media, YouTube and Twitch personalities, competition winners, and community influencers.
+- Reaching the right people with free access can generate free advertising to thousands of potential players.
+
+### Community Building
+
+- Send weekly newsletters with regular updates to your audience.
+- Organize online competitions related to your game or game development in general.
+- Host local meetups for in-person developer gatherings.
+- Demonstrates passion and builds trust and reliability.
+- Your community becomes your advocates when you need support or buzz for a launch.
+
+### Key Promotion Principles
+
+| Factor | Importance |
+|--------|-----------|
+| Consistency | Regular content and engagement across all channels |
+| Authenticity | Genuine community interaction, not transactional |
+| Patience | Building relationships and reputation takes time |
+| Value-first | Provide content worth consuming before asking for anything |
+| Multiple channels | Never rely on a single promotional strategy |
+
+## Game Monetization
+
+Monetization strategy should align with your game type, target audience, and distribution platforms. Diversifying income streams provides better business stability.
+
+### Paid Games
+
+**Model:** Fixed, up-front price charged before the player gains access.
+
+- Requires significant marketing investment to drive purchases.
+- Pricing varies by market and quality: arcade iOS titles around $0.99, desktop RPGs on Steam around $20.
+- Success depends on game quality, market research, and marketing effectiveness.
+- Study market trends and learn from failures quickly.
+
+### In-App Purchases (IAPs)
+
+**Model:** Free game acquisition with paid optional content and features.
+
+**Types of purchasable content:**
+
+- Bonus levels
+- Better weapons or spells
+- Energy refills
+- In-game currency
+- Premium features and virtual goods
+
+**Key metrics and considerations:**
+
+- Requires thousands of downloads to generate meaningful revenue.
+- Only approximately 1 in 1,000 players typically makes a purchase.
+- Earnings depend heavily on promotional activities and player volume.
+- Player volume is the critical success factor.
+
+### Freemium
+
+**Model:** Free game with optional premium features and paid benefits.
+
+- Add value to the game rather than restricting core content behind a paywall.
+- Avoid "pay-to-win" mechanics that players dislike and that damage retention and reputation.
+- Do not paywall game progression.
+- Focus on delivering enjoyable free experiences first, then offer premium enhancements.
+
+**Add-ons and DLCs:**
+
+- New level sets with new characters, weapons, and story content.
+- Requires an established base game with proven popularity.
+- Provides additional value for existing, engaged players.
+
+### Advertisements
+
+**Model:** Passive income through ad display with revenue sharing between developer and ad network.
+
+**Ad networks:**
+
+- **Google AdSense:** Most effective but not game-optimized. Can be risky for game-related accounts.
+- **LeadBolt:** Game-focused alternative with easier implementation.
+- **Video ads:** Pre-roll format shown during loading screens is gaining popularity.
+
+**Placement strategy:**
+
+- Show ads between game sessions or on game-over screens.
+- Balance ad visibility with player experience.
+- Keep ads subtle to avoid annoying players and hurting retention.
+- Revenue is typically very modest for low-traffic games.
+
+**Revenue sharing:** Usually 70/30 or 50/50 splits with publishers.
+
+### Licensing
+
+**Model:** One-time payment for distribution rights. The publisher handles monetization.
+
+**Exclusive licenses:**
+
+- Sold to a single publisher only.
+- Cannot be sold again in any form after the deal.
+- Price range: $2,000 to $5,000 USD.
+- Only pursue if the deal is profitable enough to justify exclusivity. Stop promoting the game after the sale.
+
+**Non-exclusive licenses:**
+
+- Can be sold to multiple publishers simultaneously.
+- Publisher can only distribute on their own portal (site-locked).
+- Price range: approximately $500 USD per publisher.
+- Most popular licensing approach. Works well with multiple publishers continuously.
+
+**Subscription model:**
+
+- Monthly passive revenue per game.
+- Price range: $20 to $50 USD per month per game.
+- Flexible payment options: lump sum or monthly.
+- Risk: can be cancelled at any time by the publisher.
+
+**Ad revenue share:**
+
+- Publisher drives traffic and earnings are split.
+- Split: 70/30 or 50/50 deals, collected monthly.
+- Warning: new or low-quality publishers may offer as little as $2 USD total.
+
+**Important licensing notes:**
+
+- Publishers may require custom API implementation (factor the development cost into your pricing).
+- Better to accept a lower license fee from an established, reputable publisher than risk fraud with unknown buyers.
+- Contact publishers through their websites or HTML5 Gamedevs forums.
+
+### Branding and Custom Work
+
+**Non-exclusive licensing with branding:**
+
+- Client buys code rights and implements their own graphics.
+- Example: swapping game food items for client-branded products.
+
+**Freelance branding:**
+
+- Developer reuses existing game code and adds client-provided graphics.
+- Client directs implementation details.
+- Price varies greatly based on brand, client expectations, and scope of work.
+
+### Other Monetization Strategies
+
+**Selling digital assets:**
+
+- Sell game graphics and art assets on platforms like Envato Market and ThemeForest.
+- Best for graphic designers who can create reusable assets.
+- Provides passive, modest but consistent income.
+
+**Writing articles and tutorials:**
+
+- Publish game development articles on platforms like Tuts+ Game Development, which pay for content.
+- Dual benefit: promotes your game while generating direct income.
+- Focus on genuine knowledge-sharing with your games as practical examples.
+
+**Merchandise:**
+
+- T-shirts, stickers, and branded gadgets.
+- Most profitable for highly popular, visually recognizable games (e.g., Angry Birds).
+- Some developers earn more from merchandise than from the games themselves.
+- Best as a diversified secondary revenue stream.
+
+**Community donations:**
+
+- Add donate buttons on game pages.
+- Effectiveness depends on the strength of your community relationship.
+- Works best when players know you personally and understand how donations help continued development.
+
+### Monetization Summary
+
+| Model | Revenue Type | Best For | Risk Level |
+|-------|-------------|----------|------------|
+| Paid Games | One-time | High-quality games with strong marketing | High |
+| In-App Purchases | Per transaction | Popular games with high download volume | Medium |
+| Advertisements | Passive/CPM | Casual, addictive games | Low-Medium |
+| Non-Exclusive Licensing | One-time (~$500) | All game types | Low |
+| Exclusive Licensing | One-time ($2K-$5K) | Proven, quality games | Medium |
+| Subscriptions | Monthly passive | Games with established track records | Medium |
+| Merchandise | Per sale | Popular franchises with visual identity | High |
+| Articles/Tutorials | Per publication | Developers with niche expertise | Low |
diff --git a/skills/game-engine/references/techniques.md b/skills/game-engine/references/techniques.md
new file mode 100644
index 000000000..7536a691b
--- /dev/null
+++ b/skills/game-engine/references/techniques.md
@@ -0,0 +1,894 @@
+# Game Development Techniques
+
+A comprehensive reference covering essential techniques for building web-based games, compiled from MDN Web Docs.
+
+---
+
+## Async Scripts
+
+**Source:** [MDN - Async Scripts for asm.js](https://developer.mozilla.org/en-US/docs/Games/Techniques/Async_scripts)
+
+### What It Is
+
+Async compilation allows JavaScript engines to compile asm.js code off the main thread during game loading and cache the generated machine code. This prevents recompilation on subsequent loads and gives the browser maximum flexibility to optimize the compilation process.
+
+### How It Works
+
+When a script is loaded asynchronously, the browser can compile it on a background thread while the main thread continues handling rendering and user interaction. The compiled code is cached so future visits skip recompilation entirely.
+
+### When to Use It
+
+- Medium or large games that compile asm.js code.
+- Any game where startup performance matters (which is virtually all games).
+- When you want the browser to cache compiled machine code across sessions.
+
+### Code Examples
+
+**HTML attribute approach:**
+
+```html
+
+```
+
+**JavaScript dynamic creation (defaults to async):**
+
+```javascript
+const script = document.createElement("script");
+script.src = "file.js";
+document.body.appendChild(script);
+```
+
+**Important:** Inline scripts are never async, even with the `async` attribute. They compile and run immediately:
+
+```html
+
+
+```
+
+**Using Blob URLs for async compilation of string-based code:**
+
+```javascript
+const blob = new Blob([codeString]);
+const script = document.createElement("script");
+const url = URL.createObjectURL(blob);
+script.onload = script.onerror = () => URL.revokeObjectURL(url);
+script.src = url;
+document.body.appendChild(script);
+```
+
+The key insight is that setting `src` (rather than `innerHTML` or `textContent`) triggers async compilation.
+
+---
+
+## Optimizing Startup Performance
+
+**Source:** [MDN - Optimizing Startup Performance](https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Optimizing_startup_performance)
+
+### What It Is
+
+A collection of strategies for improving how quickly web applications and games start up and become responsive, preventing the app, browser, or device from appearing frozen to users.
+
+### How It Works
+
+The core principle is avoiding blocking the main thread during startup. Work is offloaded to background threads (Web Workers), startup code is broken into small micro-tasks, and the main thread is kept free for user events and rendering. The event loop must keep cycling continuously.
+
+### When to Use It
+
+- Always -- this is a universal concern for all web applications and games.
+- Critical for new apps since it is easier to build asynchronously from the start.
+- Essential when porting native apps that expect synchronous loading and need refactoring.
+
+### Key Techniques
+
+**1. Script Loading with `defer` and `async`**
+
+Prevent blocking HTML parsing:
+
+```html
+
+
+```
+
+**2. Web Workers for Heavy Processing**
+
+Move data fetching, decoding, and calculations to workers. This frees the main thread for UI and user events.
+
+**3. Data Processing**
+
+- Use browser-provided decoders (image, video) instead of custom implementations.
+- Process data in parallel whenever possible, not sequentially.
+- Offload asset decoding (e.g., JPEG to raw texture data) to workers.
+
+**4. Resource Loading**
+
+- Do not include scripts or stylesheets outside the critical rendering path in the startup HTML -- load them only when needed.
+- Use resource hints: `preconnect`, `preload`.
+
+**5. Code Size and Compression**
+
+- Minify JavaScript files.
+- Use Gzip or Brotli compression.
+- Optimize and compress data files.
+
+**6. Perceived Performance**
+
+- Display splash screens to keep users engaged.
+- Show progress indicators for heavy sites.
+- Make time feel faster even if absolute duration stays the same.
+
+**7. Emscripten Main Loop Blockers (for ported apps)**
+
+```javascript
+emscripten_push_main_loop_blocker();
+// Establish functions to execute before main thread continues
+// Create queue of functions called in sequence
+```
+
+### Performance Targets
+
+| Metric | Target |
+|---|---|
+| Initial content appearance | 1-2 seconds |
+| User-perceptible delay | 50ms or less |
+| Sluggish threshold | Greater than 200ms |
+
+Users on older or slower devices experience longer delays than developers -- always optimize accordingly.
+
+---
+
+## WebRTC Data Channels
+
+**Source:** [MDN - WebRTC Data Channels](https://developer.mozilla.org/en-US/docs/Games/Techniques/WebRTC_data_channels)
+
+### What It Is
+
+WebRTC data channels let you send text or binary data over an active connection to a peer. In the context of games, this enables players to send data to each other for text chat or game state synchronization, without routing through a central server.
+
+### How It Works
+
+WebRTC establishes a peer-to-peer connection between two browsers. Once established, a data channel can be opened on that connection. Data channels come in two flavors:
+
+**Reliable Channels:**
+- Guarantee that messages arrive at the peer.
+- Maintain message order -- messages arrive in the same sequence they were sent.
+- Analogous to TCP sockets.
+
+**Unreliable Channels:**
+- Make no guarantees about message delivery.
+- Messages may not arrive in any particular order.
+- Messages may not arrive at all.
+- Analogous to UDP sockets.
+
+### When to Use It
+
+- **Reliable channels:** Turn-based games, chat, or any scenario where every message must arrive in order.
+- **Unreliable channels:** Real-time action games where low latency matters more than guaranteed delivery (e.g., position updates where stale data is worse than missing data).
+
+### Use Cases in Games
+
+- Player-to-player text chat communication.
+- Game status information exchange between players.
+- Real-time game state synchronization.
+- Peer-to-peer multiplayer without a dedicated game server.
+
+### Implementation Notes
+
+- The WebRTC API is primarily known for audio and video communication but includes robust peer-to-peer data channel capabilities.
+- Libraries are recommended to simplify implementation and work around browser differences.
+- Full WebRTC documentation is available at [MDN WebRTC API](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API).
+
+---
+
+## Audio for Web Games
+
+**Source:** [MDN - Audio for Web Games](https://developer.mozilla.org/en-US/docs/Games/Techniques/Audio_for_Web_Games)
+
+### What It Is
+
+Audio provides feedback and atmosphere in web games. This technique covers implementing audio across desktop and mobile platforms, addressing browser differences and optimization strategies.
+
+### How It Works
+
+Two primary APIs are available:
+
+1. **HTMLMediaElement** -- The standard `` element for basic audio playback.
+2. **Web Audio API** -- An advanced API for dynamic audio manipulation, positioning, and precise timing.
+
+### When to Use It
+
+- Use `` elements for simple, linear playback (background music without complex control).
+- Use the Web Audio API for dynamic music, 3D spatial audio, precise timing, and real-time manipulation.
+- Use audio sprites when targeting mobile or when you have many short sound effects.
+
+### Key Challenges on Mobile
+
+- **Autoplay policy:** Browsers restrict autoplay with sound. Playback must be user-initiated via click or tap.
+- **Volume control:** Mobile browsers may disable programmatic volume control to preserve OS-level user control.
+- **Buffering/preloading:** Mobile browsers often disable buffering before playback initiation to reduce data usage.
+
+### Technique 1: Audio Sprites
+
+Combines multiple audio clips into a single file, playing specific sections by timestamp, borrowed from the CSS sprites concept.
+
+**HTML:**
+
+```html
+
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+```
+
+**JavaScript:**
+
+```javascript
+const myAudio = document.getElementById("myAudio");
+const buttons = document.getElementsByTagName("button");
+let stopTime = 0;
+
+for (const button of buttons) {
+ button.addEventListener("click", () => {
+ myAudio.currentTime = button.dataset.start;
+ stopTime = Number(button.dataset.stop);
+ myAudio.play();
+ });
+}
+
+myAudio.addEventListener("timeupdate", () => {
+ if (myAudio.currentTime > stopTime) {
+ myAudio.pause();
+ }
+});
+```
+
+**Priming audio for mobile (trigger on first user interaction):**
+
+```javascript
+const myAudio = document.createElement("audio");
+myAudio.src = "my-sprite.mp3";
+myAudio.play();
+myAudio.pause();
+```
+
+### Technique 2: Web Audio API Multi-Track Music
+
+Load and synchronize separate audio tracks with precise timing.
+
+**Create audio context and load files:**
+
+```javascript
+const audioCtx = new AudioContext();
+
+async function getFile(filepath) {
+ const response = await fetch(filepath);
+ const arrayBuffer = await response.arrayBuffer();
+ const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
+ return audioBuffer;
+}
+```
+
+**Track playback with synchronization:**
+
+```javascript
+let offset = 0;
+
+function playTrack(audioBuffer) {
+ const trackSource = audioCtx.createBufferSource();
+ trackSource.buffer = audioBuffer;
+ trackSource.connect(audioCtx.destination);
+
+ if (offset === 0) {
+ trackSource.start();
+ offset = audioCtx.currentTime;
+ } else {
+ trackSource.start(0, audioCtx.currentTime - offset);
+ }
+
+ return trackSource;
+}
+```
+
+**Handle autoplay policy in playback handlers:**
+
+```javascript
+playButton.addEventListener("click", () => {
+ if (audioCtx.state === "suspended") {
+ audioCtx.resume();
+ }
+
+ playTrack(track);
+ playButton.dataset.playing = true;
+});
+```
+
+### Technique 3: Beat-Synchronized Track Playback
+
+For seamless transitions, sync new tracks to beat boundaries:
+
+```javascript
+const tempo = 3.074074076; // Time in seconds of your beat/bar
+
+if (offset === 0) {
+ source.start();
+ offset = context.currentTime;
+} else {
+ const relativeTime = context.currentTime - offset;
+ const beats = relativeTime / tempo;
+ const remainder = beats - Math.floor(beats);
+ const delay = tempo - remainder * tempo;
+ source.start(context.currentTime + delay, relativeTime + delay);
+}
+```
+
+### Technique 4: Positional Audio (3D Spatialization)
+
+Use the `PannerNode` to position audio in 3D space:
+
+- Position objects in game world space.
+- Set direction and movement of audio sources.
+- Apply environmental effects (cave reverb, underwater muffling, etc.).
+
+Particularly useful for WebGL 3D games to tie audio to visual objects and the player's viewpoint.
+
+### Decision Matrix
+
+| Technique | Use When | Pros | Cons |
+|---|---|---|---|
+| Audio Sprites | Many short sounds, mobile | Reduces HTTP requests, mobile-friendly | Seeking accuracy reduced at low bitrates |
+| Basic `` | Simple linear playback | Broad support | Limited control, autoplay restrictions |
+| Web Audio API | Dynamic music, 3D positioning, precise timing | Full control, real-time manipulation, sync | More complex code |
+| Positional Audio | 3D immersive games | Realism, player immersion | Requires WebGL context awareness |
+
+---
+
+## 2D Collision Detection
+
+**Source:** [MDN - 2D Collision Detection](https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection)
+
+### What It Is
+
+2D collision detection algorithms determine when game entities overlap or intersect based on their shape types (rectangle-to-rectangle, rectangle-to-circle, circle-to-circle, etc.). Rather than pixel-perfect detection, games typically use simple generic shapes called "hitboxes" that cover entities, balancing visual accuracy with performance.
+
+### How It Works
+
+Each algorithm checks the geometric relationship between two shapes. If any overlap is detected, a collision is reported. The approach varies by shape type.
+
+### When to Use It
+
+- Use AABB for simple rectangular entities without rotation.
+- Use circle collision for round entities or when you need fast, simple checks.
+- Use the Separating Axis Theorem (SAT) for complex convex polygons.
+- Use broad-phase narrowing (quad trees, spatial hashmaps) when you have many entities.
+
+### Algorithm 1: Axis-Aligned Bounding Box (AABB)
+
+Collision detection between two axis-aligned rectangles (no rotation). Detects collision by ensuring there is no gap between any of the 4 sides of the rectangles.
+
+```javascript
+class BoxEntity extends BaseEntity {
+ width = 20;
+ height = 20;
+
+ isCollidingWith(other) {
+ return (
+ this.position.x < other.position.x + other.width &&
+ this.position.x + this.width > other.position.x &&
+ this.position.y < other.position.y + other.height &&
+ this.position.y + this.height > other.position.y
+ );
+ }
+}
+```
+
+### Algorithm 2: Circle Collision
+
+Collision detection between two circles. Takes the center points of two circles and checks whether the distance between them is less than the sum of their radii.
+
+```javascript
+class CircleEntity extends BaseEntity {
+ radius = 10;
+
+ isCollidingWith(other) {
+ const dx =
+ this.position.x + this.radius - (other.position.x + other.radius);
+ const dy =
+ this.position.y + this.radius - (other.position.y + other.radius);
+ const distance = Math.sqrt(dx * dx + dy * dy);
+ return distance < this.radius + other.radius;
+ }
+}
+```
+
+Note: The circle's `x` and `y` coordinates refer to their top-left corner, so you must add the radius to compare their actual centers.
+
+### Algorithm 3: Separating Axis Theorem (SAT)
+
+A collision algorithm that detects collisions between any two convex polygons. It works by projecting each polygon onto every possible axis and checking for overlap. If any axis shows a gap, the polygons are not colliding.
+
+SAT is more complex to implement but handles arbitrary convex polygon shapes.
+
+### Collision Performance: Broad Phase and Narrow Phase
+
+Testing every entity against every other entity is computationally expensive (O(n^2)). Games split collision detection into two phases:
+
+**Broad Phase** -- Uses spatial data structures to quickly identify which entities could be colliding:
+- Quad Trees
+- R-Trees
+- Spatial Hashmaps
+
+**Narrow Phase** -- Applies precise collision algorithms (AABB, Circle, SAT) only to the small list of candidates from the broad phase.
+
+### Base Engine Code
+
+**CSS for collision visualization:**
+
+```css
+.entity {
+ display: inline-block;
+ position: absolute;
+ height: 20px;
+ width: 20px;
+ background-color: blue;
+}
+
+.movable {
+ left: 50px;
+ top: 50px;
+ background-color: red;
+}
+
+.collision-state {
+ background-color: green !important;
+}
+```
+
+**JavaScript collision checker and entity system:**
+
+```javascript
+const collider = {
+ moveableEntity: null,
+ staticEntities: [],
+ checkCollision() {
+ const isColliding = this.staticEntities.some((staticEntity) =>
+ this.moveableEntity.isCollidingWith(staticEntity),
+ );
+ this.moveableEntity.setCollisionState(isColliding);
+ },
+};
+
+const container = document.getElementById("container");
+
+class BaseEntity {
+ ref;
+ position;
+ constructor(position) {
+ this.position = position;
+ this.ref = document.createElement("div");
+ this.ref.classList.add("entity");
+ this.ref.style.left = `${this.position.x}px`;
+ this.ref.style.top = `${this.position.y}px`;
+ container.appendChild(this.ref);
+ }
+ shiftPosition(dx, dy) {
+ this.position.x += dx;
+ this.position.y += dy;
+ this.redraw();
+ }
+ redraw() {
+ this.ref.style.left = `${this.position.x}px`;
+ this.ref.style.top = `${this.position.y}px`;
+ }
+ setCollisionState(isColliding) {
+ if (isColliding && !this.ref.classList.contains("collision-state")) {
+ this.ref.classList.add("collision-state");
+ } else if (!isColliding) {
+ this.ref.classList.remove("collision-state");
+ }
+ }
+ isCollidingWith(other) {
+ throw new Error("isCollidingWith must be implemented in subclasses");
+ }
+}
+
+document.addEventListener("keydown", (e) => {
+ e.preventDefault();
+ switch (e.key) {
+ case "ArrowLeft":
+ collider.moveableEntity.shiftPosition(-5, 0);
+ break;
+ case "ArrowUp":
+ collider.moveableEntity.shiftPosition(0, -5);
+ break;
+ case "ArrowRight":
+ collider.moveableEntity.shiftPosition(5, 0);
+ break;
+ case "ArrowDown":
+ collider.moveableEntity.shiftPosition(0, 5);
+ break;
+ }
+ collider.checkCollision();
+});
+```
+
+---
+
+## Tilemaps
+
+**Source:** [MDN - Tilemaps](https://developer.mozilla.org/en-US/docs/Games/Techniques/Tilemaps)
+
+### What It Is
+
+Tilemaps are a fundamental technique in 2D game development that constructs game worlds using small, regular-shaped images called tiles. Instead of storing large monolithic level images, the game world is assembled from a grid of reusable tile graphics, providing significant performance and memory benefits.
+
+### How It Works
+
+**Core structure:**
+
+1. **Tile Atlas (Spritesheet):** All tile images stored in a single atlas file. Each tile is assigned an index used as its identifier.
+2. **Tilemap Data Object:** Contains tile size (pixel dimensions), image atlas reference, map dimensions (in tiles or pixels), a visual grid (array of tile indices), and an optional logic grid (collision, pathfinding, spawn data).
+
+Special values (negative numbers, 0, or null) represent empty tiles.
+
+### When to Use It
+
+- Building 2D game worlds of any kind (platformers, RPGs, strategy games, puzzle games).
+- Games inspired by classics like Super Mario Bros, Pacman, Zelda, Starcraft, or Sim City.
+- Any scenario where a grid-based world offers logical advantages for pathfinding, collision, or level editing.
+
+### Rendering Static Tilemaps
+
+For maps fitting entirely on screen:
+
+```javascript
+for (let column = 0; column < map.columns; column++) {
+ for (let row = 0; row < map.rows; row++) {
+ const tile = map.getTile(column, row);
+ const x = column * map.tileSize;
+ const y = row * map.tileSize;
+ drawTile(tile, x, y);
+ }
+}
+```
+
+### Scrolling Tilemaps with Camera
+
+Convert between world coordinates (level position) and screen coordinates (rendered position):
+
+```javascript
+// These functions assume camera points to top-left corner
+
+function worldToScreen(x, y) {
+ return { x: x - camera.x, y: y - camera.y };
+}
+
+function screenToWorld(x, y) {
+ return { x: x + camera.x, y: y + camera.y };
+}
+```
+
+Key principle: Only render visible tiles to optimize performance. Apply the camera offset transformation during rendering.
+
+### Tilemap Types
+
+**Square Tiles (most common):**
+- Top-down view for RPGs and strategy games (Warcraft 2, Final Fantasy).
+- Side view for platformers (Super Mario Bros).
+
+**Isometric Tilemaps:**
+- Creates the illusion of a 3D environment.
+- Popular in simulation and strategy games (SimCity 2000, Pharaoh, Final Fantasy Tactics).
+
+### Layers
+
+Multiple visual layers enable:
+- Reusing tiles across different background types.
+- Characters appearing behind or in front of terrain (walking behind trees).
+- Richer worlds with fewer tile variations.
+
+Example: A rock tile rendered on a separate layer over grass, sand, or brick backgrounds.
+
+### Logic Grid
+
+A separate grid for non-visual game logic:
+- **Collision detection:** Mark walkable vs. blocked tiles.
+- **Character spawning:** Define spawn point locations.
+- **Pathfinding:** Create navigation graphs.
+- **Tile combinations:** Detect valid patterns (Tetris, Bejeweled).
+
+### Performance Optimization
+
+1. **Only render visible tiles** -- Skip off-screen tiles entirely.
+2. **Pre-render to canvas** -- Render the map to an off-screen canvas element and blit as a single operation.
+3. **Offcanvas buffering** -- Draw a section larger than the visible area (2x2 tiles bigger) to reduce redraws during scrolling.
+4. **Chunking** -- Divide large tilemaps into sections (e.g., 10x10 tile chunks), pre-render each as a "big tile."
+
+---
+
+## Controls: Gamepad API
+
+**Source:** [MDN - Controls Gamepad API](https://developer.mozilla.org/en-US/docs/Games/Techniques/Controls_Gamepad_API)
+
+### What It Is
+
+The Gamepad API provides an interface for detecting and using gamepad controllers in web browsers without plugins. It exposes button presses and axis changes through JavaScript, allowing console-like control of browser-based games.
+
+### How It Works
+
+Two fundamental events handle the controller lifecycle:
+
+- `gamepadconnected` -- fired when a gamepad is connected.
+- `gamepaddisconnected` -- fired when disconnected (physically or due to inactivity).
+
+Security note: User interaction with the controller is required while the page is visible for the event to fire (prevents fingerprinting).
+
+**Gamepad object properties:**
+
+| Property | Description |
+|---|---|
+| `id` | String containing controller information |
+| `index` | Unique identifier for the connected device |
+| `connected` | Boolean indicating connection status |
+| `mapping` | Layout type ("standard" is the common option) |
+| `axes` | Array of floats (-1 to 1) representing analog stick positions |
+| `buttons` | Array of GamepadButton objects with `pressed` and `value` properties |
+
+### When to Use It
+
+- When building games that should work with console controllers.
+- When supporting Xbox 360, Xbox One, PS3, or PS4 controllers on Windows and macOS.
+- When you want dual input support (keyboard + gamepad).
+
+### Code Examples
+
+**Basic setup structure:**
+
+```javascript
+const gamepadAPI = {
+ controller: {},
+ turbo: false,
+ connect() {},
+ disconnect() {},
+ update() {},
+ buttonPressed() {},
+ buttons: [],
+ buttonsCache: [],
+ buttonsStatus: [],
+ axesStatus: [],
+};
+```
+
+**Button layout (Xbox 360):**
+
+```javascript
+const gamepadAPI = {
+ buttons: [
+ "DPad-Up", "DPad-Down", "DPad-Left", "DPad-Right",
+ "Start", "Back", "Axis-Left", "Axis-Right",
+ "LB", "RB", "Power", "A", "B", "X", "Y",
+ ],
+};
+```
+
+**Event listeners:**
+
+```javascript
+window.addEventListener("gamepadconnected", gamepadAPI.connect);
+window.addEventListener("gamepaddisconnected", gamepadAPI.disconnect);
+```
+
+**Connection and disconnection handlers:**
+
+```javascript
+connect(evt) {
+ gamepadAPI.controller = evt.gamepad;
+ gamepadAPI.turbo = true;
+ console.log("Gamepad connected.");
+},
+
+disconnect(evt) {
+ gamepadAPI.turbo = false;
+ delete gamepadAPI.controller;
+ console.log("Gamepad disconnected.");
+},
+```
+
+**Update method (called every frame):**
+
+```javascript
+update() {
+ // Clear the buttons cache
+ gamepadAPI.buttonsCache = [];
+
+ // Move the buttons status from the previous frame to the cache
+ for (let k = 0; k < gamepadAPI.buttonsStatus.length; k++) {
+ gamepadAPI.buttonsCache[k] = gamepadAPI.buttonsStatus[k];
+ }
+
+ // Clear the buttons status
+ gamepadAPI.buttonsStatus = [];
+
+ // Get the gamepad object
+ const c = gamepadAPI.controller || {};
+
+ // Loop through buttons and push the pressed ones to the array
+ const pressed = [];
+ if (c.buttons) {
+ for (let b = 0; b < c.buttons.length; b++) {
+ if (c.buttons[b].pressed) {
+ pressed.push(gamepadAPI.buttons[b]);
+ }
+ }
+ }
+
+ // Loop through axes and push their values to the array
+ const axes = [];
+ if (c.axes) {
+ for (const ax of c.axes) {
+ axes.push(ax.toFixed(2));
+ }
+ }
+
+ // Assign received values
+ gamepadAPI.axesStatus = axes;
+ gamepadAPI.buttonsStatus = pressed;
+
+ return pressed;
+},
+```
+
+**Button detection with hold support:**
+
+```javascript
+buttonPressed(button, hold) {
+ let newPress = false;
+ if (gamepadAPI.buttonsStatus.includes(button)) {
+ newPress = true;
+ }
+ if (!hold && gamepadAPI.buttonsCache.includes(button)) {
+ newPress = false;
+ }
+ return newPress;
+},
+```
+
+Parameters:
+- `button` -- the button name to listen for.
+- `hold` -- if true, holding the button counts as continuous action; if false, only new presses register.
+
+**Usage in a game loop:**
+
+```javascript
+if (gamepadAPI.turbo) {
+ if (gamepadAPI.buttonPressed("A", "hold")) {
+ this.turbo_fire();
+ }
+ if (gamepadAPI.buttonPressed("B")) {
+ this.managePause();
+ }
+}
+```
+
+**Analog stick input with threshold (prevent stick drift):**
+
+```javascript
+if (gamepadAPI.axesStatus[0].x > 0.5) {
+ this.player.angle += 3;
+ this.turret.angle += 3;
+}
+```
+
+**Getting all connected gamepads:**
+
+```javascript
+const gamepads = navigator.getGamepads();
+// Returns an array where unavailable/disconnected slots contain null
+// Example with one device at index 1: [null, [object Gamepad]]
+```
+
+---
+
+## Crisp Pixel Art Look
+
+**Source:** [MDN - Crisp Pixel Art Look](https://developer.mozilla.org/en-US/docs/Games/Techniques/Crisp_pixel_art_look)
+
+### What It Is
+
+A technique for rendering pixel art without blurriness on high-resolution displays by mapping individual image pixels to blocks of screen pixels without smoothing interpolation. Retro pixel art requires preserving hard edges during scaling, but modern browsers default to smoothing algorithms that blend colors and create blur.
+
+### How It Works
+
+The CSS `image-rendering` property controls how browsers scale images. Setting it to `pixelated` enforces nearest-neighbor scaling, which preserves the crisp, blocky look of pixel art instead of applying bilinear or bicubic smoothing.
+
+**Key CSS values:**
+- `pixelated` -- preserves crisp edges for pixel art.
+- `crisp-edges` -- alternative supported on some browsers.
+
+### When to Use It
+
+- Retro-style games with pixel art assets.
+- Any game where you want a deliberately blocky, pixelated visual style.
+- When scaling small sprite images to larger display sizes.
+
+### Technique 1: Scaling ` ` Elements with CSS
+
+```html
+
+```
+
+```css
+img {
+ width: 48px;
+ height: 136px;
+ image-rendering: pixelated;
+}
+```
+
+### Technique 2: Crisp Pixel Art in Canvas
+
+Set the canvas `width`/`height` attributes to the original pixel art resolution, then use CSS `width`/`height` for scaling (e.g., 4x scale: 128 pixels to 512px CSS width).
+
+```html
+A cat
+```
+
+```css
+canvas {
+ width: 512px;
+ height: 512px;
+ image-rendering: pixelated;
+}
+```
+
+```javascript
+const ctx = document.getElementById("game").getContext("2d");
+
+const image = new Image();
+image.onload = () => {
+ ctx.drawImage(image, 0, 0);
+};
+image.src = "cat.png";
+```
+
+### Technique 3: Arbitrary Canvas Scaling with Correction
+
+For non-integer scale factors, image pixels must align to canvas pixels at integer multiples:
+
+```javascript
+const ctx = document.getElementById("game").getContext("2d");
+ctx.scale(0.8, 0.8);
+
+const image = new Image();
+image.onload = () => {
+ // Correct formula: dWidth = sWidth / xScale * n (where n is an integer)
+ ctx.drawImage(image, 0, 0, 128, 128, 0, 0, 128 / 0.8, 128 / 0.8);
+};
+image.src = "cat.png";
+```
+
+When using `drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)`:
+- `dWidth` must equal `sWidth / xScale * n`
+- `dHeight` must equal `sHeight / yScale * m`
+- Where `n` and `m` are positive integers (1, 2, 3, etc.)
+
+### Known Limitations
+
+**devicePixelRatio misalignment:** When `devicePixelRatio` is not an integer (e.g., at 110% browser zoom), pixels may render unevenly because CSS pixels cannot perfectly map to device pixels. This creates a non-uniform appearance without an easy solution.
+
+### Best Practices
+
+1. Use integer scale factors (2x, 3x, 4x) whenever possible.
+2. Preserve the aspect ratio -- scale width and height equally.
+3. Test across different browser zoom levels.
+4. Avoid fractional canvas scale factors or drawImage dimensions.
+5. Include descriptive `aria-label` attributes on canvas elements for accessibility.
diff --git a/skills/game-engine/references/terminology.md b/skills/game-engine/references/terminology.md
new file mode 100644
index 000000000..268a1a064
--- /dev/null
+++ b/skills/game-engine/references/terminology.md
@@ -0,0 +1,354 @@
+# Game Development Terminology
+
+A comprehensive glossary of video game development terms and concepts, organized by category for quick reference.
+
+Source: https://www.gameindustrycareerguide.com/video-game-development-terms-glossary/
+
+---
+
+## General Development Terms
+
+### AAA (Triple-A)
+A classification for games produced by large studios with significant budgets, large teams, and high production values. Examples include franchises like Call of Duty, Assassin's Creed, and The Last of Us.
+
+### Indie
+Short for "independent." Refers to games developed by small teams or individuals without the financial backing of a major publisher. Indie games often emphasize creativity and innovation over production scale.
+
+### Alpha
+An early phase of game development where the core features are implemented but the game is incomplete, unpolished, and likely contains significant bugs. Alpha builds are used for internal testing and feature validation.
+
+### Beta
+A later development phase where the game is feature-complete but still undergoing testing and bug fixing. Beta versions may be released to a limited audience for external testing (closed beta) or to the public (open beta).
+
+### Gold / Gone Gold
+The final version of a game that has been approved for manufacturing and distribution. "Going gold" means the game is complete and ready for release.
+
+### Build
+A compiled, runnable version of the game at a specific point in development. Teams produce regular builds for testing and milestone reviews.
+
+### Milestone
+A scheduled checkpoint in the development timeline where specific deliverables must be completed. Milestones are used to track progress and are often tied to publisher funding.
+
+### Crunch
+An extended period of overtime work, often occurring near the end of a project's development cycle to meet deadlines. Crunch is a well-known and often criticized aspect of the game industry.
+
+### Post-Mortem
+A retrospective analysis conducted after a project is completed, examining what went well, what went wrong, and lessons learned for future projects.
+
+### Vertical Slice
+A polished, playable section of the game that demonstrates the final quality target across all disciplines (art, design, programming, audio). Used to validate the game's vision and secure funding.
+
+---
+
+## Game Design Terms
+
+### Game Design Document (GDD)
+A comprehensive written document that describes the game's concept, mechanics, story, art direction, technical requirements, and all other aspects of the design. Serves as the blueprint for the entire team.
+
+### Game Mechanic
+A rule or system that defines how the game operates and how players interact with it. Examples include jumping, shooting, inventory management, crafting, and combat systems.
+
+### Gameplay Loop (Core Loop)
+The fundamental cycle of actions that a player repeats throughout the game. A well-designed core loop is engaging and rewarding. For example: explore, fight enemies, collect loot, upgrade character, repeat.
+
+### Level Design
+The process of creating the environments, challenges, and spatial layouts that players navigate. Level designers place geometry, enemies, items, triggers, and scripted events.
+
+### Progression System
+The systems that track and reward player advancement. Includes experience points, skill trees, unlockable abilities, gear upgrades, and story progression.
+
+### Balancing
+The process of adjusting game parameters (damage values, health pools, resource costs, difficulty curves) to ensure fair, challenging, and enjoyable gameplay.
+
+### Difficulty Curve
+The rate at which a game becomes more challenging as the player progresses. A well-tuned difficulty curve gradually introduces complexity while matching the player's growing skill.
+
+### NPC (Non-Player Character)
+Any character in the game that is not controlled by a human player. NPCs may be allies, enemies, quest givers, merchants, or ambient background characters.
+
+### HUD (Heads-Up Display)
+The on-screen overlay that presents game information to the player, such as health bars, minimaps, ammunition counts, score, and objective markers.
+
+### UI (User Interface)
+All visual elements the player interacts with, including menus, inventory screens, settings panels, dialog boxes, and the HUD.
+
+### UX (User Experience)
+The overall quality of the player's interaction with the game, encompassing usability, accessibility, intuitiveness, and satisfaction. UX design focuses on making the game easy to learn and enjoyable to play.
+
+### Spawn / Spawning
+The act of creating or placing a game entity (player character, enemy, item) into the game world at a specific location and time.
+
+### Respawn
+The process of a player character or entity reappearing in the game world after being defeated or destroyed.
+
+### Hitbox
+An invisible geometric shape attached to a game entity used for collision detection, particularly for determining whether attacks or projectiles connect with a target.
+
+### Cooldown
+A timer-based restriction that prevents a player from using an ability, item, or action again until a specified period has elapsed.
+
+### Buff / Debuff
+Temporary modifications to a character's stats or abilities. A buff enhances capabilities (increased speed, damage, defense) while a debuff reduces them (slowed movement, reduced accuracy).
+
+### Aggro
+Short for "aggravation." Refers to an enemy's hostility level toward the player. "Drawing aggro" means attracting an enemy's attention and attacks.
+
+### AoE (Area of Effect)
+An attack, spell, or ability that affects all entities within a defined area rather than a single target.
+
+### DPS (Damage Per Second)
+A metric measuring the average amount of damage a character, weapon, or ability inflicts per second. Used for balancing and comparing combat effectiveness.
+
+### RNG (Random Number Generation)
+The use of randomized outcomes in game mechanics, such as loot drops, critical hit chances, or procedural generation. "RNG" is also used colloquially to refer to luck-based outcomes.
+
+### Proc (Programmed Random Occurrence)
+An event triggered by a random chance during gameplay, such as a special effect activating on a weapon hit based on a probability percentage.
+
+### Meta / Metagame
+The strategies, character builds, or tactics that emerge as the most effective within a game's competitive community. The meta evolves as players discover optimal approaches.
+
+### Nerf
+A game balance change that reduces the power or effectiveness of a character, weapon, ability, or strategy. The opposite of a buff.
+
+### Sandbox
+A game design approach that gives players freedom to explore and interact with the game world without a strict linear progression. Emphasizes player-driven experiences.
+
+### Procedural Generation
+The algorithmic creation of game content (levels, terrain, items, quests) at runtime rather than by hand. Enables vast, varied game worlds with less manual content creation.
+
+### Permadeath
+A game mechanic where a character's death is permanent, with no option to reload or respawn. The player must start over, often with a new character.
+
+### Roguelike / Roguelite
+Game genres characterized by procedurally generated levels, permadeath, and turn-based or real-time combat. Roguelites are a lighter variant that may allow some persistent progression between runs.
+
+---
+
+## Programming and Technical Terms
+
+### Game Engine
+The core software framework that provides the foundational systems for building a game, including rendering, physics, audio, input, scripting, and asset management. Examples: Unity, Unreal Engine, Godot, custom engines.
+
+### Rendering / Renderer
+The process and system responsible for drawing the game's visuals to the screen. Includes 2D sprite rendering, 3D polygon rendering, lighting, shadows, and post-processing effects.
+
+### Frame Rate (FPS -- Frames Per Second)
+The number of individual images (frames) rendered and displayed per second. Higher frame rates produce smoother animation. Common targets: 30 FPS, 60 FPS, 120 FPS.
+
+### Tick Rate
+The frequency at which the game server or simulation updates game state, measured in hertz (Hz). A 64-tick server updates 64 times per second.
+
+### Delta Time
+The elapsed time between the current frame and the previous frame. Used to ensure game logic runs consistently regardless of frame rate variations.
+
+### Physics Engine
+A system that simulates physical behaviors such as gravity, collisions, rigid body dynamics, soft body deformation, and ragdoll effects. Examples: Box2D, Bullet, PhysX, Havok.
+
+### Collision Detection
+The process of determining when two or more game objects intersect or come into contact. Methods include bounding box (AABB), sphere, capsule, and mesh-based collision.
+
+### Raycasting
+A technique that projects an invisible ray from a point in a specified direction to detect intersections with game objects. Used for line-of-sight checks, bullet trajectory, mouse picking, and visibility testing.
+
+### Pathfinding
+Algorithms that calculate navigation routes through the game world for AI-controlled characters. Common approaches include A* (A-star), Dijkstra's algorithm, and navigation meshes (NavMesh).
+
+### State Machine (FSM -- Finite State Machine)
+A programming pattern where an entity exists in one of a defined set of states, with rules governing transitions between states. Widely used for AI behavior, animation systems, and game flow management.
+
+### Shader
+A program that runs on the GPU to control how vertices and pixels are rendered. Vertex shaders transform geometry; fragment (pixel) shaders compute color and lighting per pixel.
+
+### LOD (Level of Detail)
+A technique that reduces the visual complexity of distant objects by swapping in lower-polygon models, simpler textures, or reduced effects. Improves rendering performance.
+
+### Occlusion Culling
+An optimization that avoids rendering objects hidden behind other objects. If an object is completely occluded from the camera's view, it is excluded from the rendering pipeline.
+
+### Frustum Culling
+An optimization that excludes objects outside the camera's visible cone (view frustum) from rendering calculations.
+
+### Draw Call
+A command sent to the GPU instructing it to render a set of geometry. Reducing draw calls through batching and instancing is a key optimization strategy.
+
+### Sprite
+A 2D image or animation used in games. Sprites represent characters, items, effects, and environmental elements in 2D games.
+
+### Sprite Sheet / Sprite Atlas
+A single image file containing multiple sprites arranged in a grid or packed layout. Reduces draw calls and texture swaps during rendering.
+
+### Tilemap
+A technique for building 2D game levels from a grid of reusable tiles. Each cell references a tile type from a tileset, enabling efficient level construction and rendering.
+
+### API (Application Programming Interface)
+A set of defined interfaces and protocols for building and interacting with software. In game development, APIs include graphics APIs (OpenGL, DirectX, Vulkan, Metal), audio APIs, and platform APIs.
+
+### SDK (Software Development Kit)
+A collection of tools, libraries, documentation, and code samples provided for developing applications for a specific platform or framework.
+
+### Middleware
+Third-party software libraries or tools integrated into a game engine or pipeline to provide specific functionality, such as physics (Havok), audio (FMOD, Wwise), or animation.
+
+### Serialization
+The process of converting game objects, state, or data structures into a format suitable for storage or transmission (JSON, binary, XML). Deserialization is the reverse process.
+
+### Latency
+The delay between an action being initiated and its result being observed. In networking, latency (ping) is the round-trip time for data to travel between client and server.
+
+### Netcode
+The networking code and architecture of a multiplayer game, including client-server communication, state synchronization, lag compensation, and prediction.
+
+### Client-Server Architecture
+A networking model where a central server maintains the authoritative game state and clients send inputs and receive state updates. Reduces cheating and ensures consistency.
+
+### Peer-to-Peer (P2P)
+A networking model where game clients communicate directly with each other without a central server. Simpler to set up but harder to secure against cheating.
+
+### Lag Compensation
+Techniques used in multiplayer games to mitigate the effects of network latency, such as client-side prediction, server reconciliation, and entity interpolation.
+
+### Rubber Banding
+A visual artifact in multiplayer games where a player or object appears to snap back to a previous position due to a mismatch between client prediction and server correction.
+
+---
+
+## Art and Visual Terms
+
+### Asset
+Any piece of content used in the game, including 3D models, textures, sprites, sounds, music, animations, scripts, and level data.
+
+### Texture
+A 2D image applied to the surface of a 3D model or used directly in 2D rendering. Textures provide color, detail, and material appearance.
+
+### Normal Map
+A texture that stores surface orientation data per pixel, allowing flat surfaces to appear to have depth, bumps, and fine detail without additional geometry.
+
+### Mesh
+The 3D geometric structure of a model, defined by vertices, edges, and faces (polygons). Meshes form the shape of characters, objects, and environments.
+
+### Polygon / Poly
+A flat geometric shape (typically a triangle) that forms the building block of 3D meshes. "Poly count" refers to the total number of polygons in a model or scene.
+
+### Rigging
+The process of creating a skeletal structure (armature) inside a 3D model so it can be animated. Bones are placed and weighted to influence mesh deformation.
+
+### Skinning
+The process of binding a 3D mesh to its skeleton so that the mesh deforms naturally when the skeleton is animated. Each vertex is weighted to one or more bones.
+
+### Keyframe Animation
+An animation technique where poses are defined at specific points in time (keyframes) and the system interpolates the movement between them.
+
+### Skeletal Animation
+Animation driven by a hierarchical bone structure. Moving parent bones propagates transformations to child bones, enabling realistic character movement.
+
+### Particle System
+A system that generates and manages large numbers of small visual elements (particles) to simulate effects like fire, smoke, rain, sparks, explosions, and magic.
+
+### Parallax Scrolling
+A 2D visual technique where background layers move at different speeds relative to the foreground, creating an illusion of depth.
+
+### Voxel
+A volumetric pixel -- a value on a 3D grid analogous to a pixel on a 2D grid. Used in games like Minecraft for block-based world construction and in medical/scientific visualization.
+
+### Anti-Aliasing
+A rendering technique that smooths jagged edges (aliasing) along the borders of polygons and high-contrast boundaries. Methods include MSAA, FXAA, and TAA.
+
+### Post-Processing
+Visual effects applied to the rendered frame after the main scene rendering is complete. Examples include bloom, motion blur, depth of field, color grading, and ambient occlusion.
+
+---
+
+## Audio Terms
+
+### SFX (Sound Effects)
+Short audio clips triggered by in-game events such as footsteps, weapon fire, item pickups, UI interactions, and environmental sounds.
+
+### BGM (Background Music)
+The musical soundtrack that plays during gameplay, cutscenes, or menus. BGM sets the mood and enhances the emotional tone of the game.
+
+### Adaptive Audio / Dynamic Music
+Music and sound that changes in response to gameplay events, player actions, or game state. For example, combat music intensifying as more enemies appear.
+
+### FMOD / Wwise
+Industry-standard audio middleware tools used for implementing complex sound design, mixing, and adaptive audio in games.
+
+### Spatial Audio / 3D Audio
+Audio processing that simulates the position and movement of sound sources in 3D space, providing directional cues to the player.
+
+---
+
+## Platform and Distribution Terms
+
+### Platform
+The hardware or software environment on which a game runs: PC, PlayStation, Xbox, Nintendo Switch, mobile (iOS/Android), web browser, VR headsets.
+
+### Cross-Platform
+The ability for a game to run on multiple platforms, and often for players on different platforms to play together (cross-play).
+
+### Port / Porting
+The process of adapting a game developed for one platform to run on a different platform. May require significant technical rework for different hardware capabilities.
+
+### DLC (Downloadable Content)
+Additional game content released after the initial launch, available for download. May include new levels, characters, story chapters, items, or cosmetics.
+
+### Microtransaction
+A small in-game purchase, often for cosmetic items, virtual currency, or gameplay advantages. A common monetization model in free-to-play games.
+
+### Free-to-Play (F2P)
+A business model where the base game is free to download and play, with revenue generated through optional in-game purchases.
+
+### Games as a Service (GaaS)
+A model where a game is continuously updated with new content, events, and features over time, often supported by recurring revenue from subscriptions or microtransactions.
+
+### Early Access
+A release model where players can purchase and play a game while it is still in active development, providing feedback and funding to the developers.
+
+### Day One Patch
+A game update released on or before the official launch day to fix bugs, improve performance, or add content that was not ready when the physical copies were manufactured.
+
+### QA (Quality Assurance)
+The systematic testing of a game to identify bugs, glitches, performance issues, and design problems. QA testers play the game repeatedly under various conditions to ensure quality.
+
+### Bug
+An error, flaw, or unintended behavior in the game's code, design, or content. Bugs range from minor visual glitches to game-breaking crashes.
+
+### Exploit
+An unintended use of a game mechanic or bug that gives a player an unfair advantage. Exploits are typically patched once discovered.
+
+### Patch
+A software update released to fix bugs, address exploits, improve performance, adjust balance, or add new content to a previously released game.
+
+### Hotfix
+A small, targeted patch released urgently to fix a critical bug or issue, often deployed without the full testing cycle of a regular patch.
+
+---
+
+## Game Genre Terms
+
+### FPS (First-Person Shooter)
+A game genre where the player experiences the game from a first-person perspective and primarily engages in ranged combat.
+
+### TPS (Third-Person Shooter)
+A shooter game where the camera is positioned behind or over the shoulder of the player character.
+
+### RPG (Role-Playing Game)
+A genre where players assume the role of a character, make decisions, and develop their attributes and abilities over time through narrative and combat.
+
+### MMORPG (Massively Multiplayer Online RPG)
+An RPG with large numbers of players simultaneously inhabiting a persistent online world.
+
+### RTS (Real-Time Strategy)
+A strategy game where players manage resources, build structures, and command units in real time rather than taking turns.
+
+### MOBA (Multiplayer Online Battle Arena)
+A team-based competitive genre where players control individual characters with unique abilities, working together to destroy the opposing team's base.
+
+### Battle Royale
+A genre where a large number of players compete to be the last one standing in a shrinking play area.
+
+### Metroidvania
+A sub-genre of action-adventure games characterized by interconnected maps, ability-gated exploration, and backtracking to previously inaccessible areas with newly acquired abilities.
+
+### Soulslike
+Games inspired by the Dark Souls series, characterized by challenging combat, stamina-based mechanics, minimal hand-holding, and a strong emphasis on learning from failure.
diff --git a/skills/game-engine/references/web-apis.md b/skills/game-engine/references/web-apis.md
new file mode 100644
index 000000000..76f9194ab
--- /dev/null
+++ b/skills/game-engine/references/web-apis.md
@@ -0,0 +1,1394 @@
+# Web APIs for Game Development
+
+A comprehensive reference covering the web platform APIs most relevant to building browser-based games. Each section describes what the API is, why it matters for games, its key interfaces and methods, and provides brief code examples where applicable.
+
+---
+
+## asm.js
+
+### What It Is
+
+asm.js is a strict, highly optimizable subset of JavaScript designed for near-native performance. It restricts JavaScript to a narrow set of constructs -- integers, floats, arithmetic, simple functions, and heap accesses -- and disallows objects, strings, closures, and anything requiring heap allocation. The result is completely valid JavaScript that any engine can run, but that supporting engines can compile aggressively ahead of time.
+
+### Why It Matters for Games
+
+- **Near-native speed**: Emscripten-compiled C/C++ game engines run with near-native performance across browsers.
+- **Predictable performance**: The restricted feature set yields highly consistent frame rates.
+- **C/C++ portability**: Existing native game engines can be compiled to asm.js with Emscripten and deployed on the web.
+- **No plugins required**: Runs as standard JavaScript in every modern browser.
+
+### Key Concepts
+
+| Concept | Description |
+|---------|-------------|
+| Allowed constructs | `while`, `if`, numbers (strict int/float), top-level named functions, arithmetic, function calls, heap accesses |
+| Disallowed constructs | Objects, strings, closures, dynamic type coercion, heap-allocating constructs |
+| Compiler toolchain | Emscripten compiles C/C++ to asm.js |
+| Engine recognition | Browsers detect the `"use asm"` directive and apply ahead-of-time compilation |
+
+### Deprecation Notice
+
+asm.js is deprecated. **WebAssembly (Wasm)** is the modern successor and offers better performance, broader tooling, and wider industry support. New projects should target WebAssembly instead.
+
+### Code Example
+
+```javascript
+// asm.js module pattern (simplified)
+function MyModule(stdlib, foreign, heap) {
+ "use asm";
+
+ var sqrt = stdlib.Math.sqrt;
+ var HEAP32 = new stdlib.Int32Array(heap);
+
+ function distance(x1, y1, x2, y2) {
+ x1 = +x1; y1 = +y1; x2 = +x2; y2 = +y2;
+ var dx = 0.0, dy = 0.0;
+ dx = +(x2 - x1);
+ dy = +(y2 - y1);
+ return +sqrt(dx * dx + dy * dy);
+ }
+
+ return { distance: distance };
+}
+```
+
+---
+
+## Canvas API
+
+### What It Is
+
+The Canvas API provides a means for drawing 2D graphics via JavaScript and the HTML `` element. It is one of the primary rendering surfaces for browser-based games, supporting game graphics, animation, image manipulation, and real-time video processing.
+
+### Why It Matters for Games
+
+- **2D rendering surface**: The standard way to draw sprites, tilemaps, particles, and HUD elements in browser games.
+- **Pixel-level control**: Direct access to pixel data via `ImageData` for custom effects, collision maps, and procedural generation.
+- **High performance**: Hardware-accelerated in modern browsers, suitable for 60 fps game loops.
+- **Broad ecosystem**: Libraries like Phaser, Konva.js, EaselJS, and p5.js build on Canvas for game development.
+
+### Key Interfaces
+
+| Interface | Purpose |
+|-----------|---------|
+| `HTMLCanvasElement` | The `` HTML element |
+| `CanvasRenderingContext2D` | The main 2D drawing interface |
+| `ImageData` | Raw pixel data for direct manipulation |
+| `ImageBitmap` | Bitmap image data for efficient drawing |
+| `Path2D` | Reusable path objects |
+| `OffscreenCanvas` | Offscreen rendering, usable in Web Workers |
+| `CanvasPattern` | Repeating image patterns |
+| `CanvasGradient` | Color gradients |
+| `TextMetrics` | Text measurement data |
+
+### Key Methods (CanvasRenderingContext2D)
+
+- `fillRect()`, `strokeRect()`, `clearRect()` -- rectangle operations
+- `drawImage()` -- draw images, sprites, or other canvases
+- `beginPath()`, `arc()`, `lineTo()`, `fill()`, `stroke()` -- path drawing
+- `getImageData()`, `putImageData()` -- pixel manipulation
+- `save()`, `restore()` -- state management
+- `translate()`, `rotate()`, `scale()`, `transform()` -- transformations
+
+### Code Example
+
+```html
+
+```
+
+```javascript
+const canvas = document.getElementById("game");
+const ctx = canvas.getContext("2d");
+
+// Clear the frame
+ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+// Draw a filled rectangle (e.g., a platform)
+ctx.fillStyle = "green";
+ctx.fillRect(100, 400, 200, 20);
+
+// Draw a sprite
+const sprite = new Image();
+sprite.src = "player.png";
+sprite.onload = () => {
+ ctx.drawImage(sprite, playerX, playerY, 32, 32);
+};
+
+// Game loop
+function gameLoop(timestamp) {
+ update(timestamp);
+ render(ctx);
+ requestAnimationFrame(gameLoop);
+}
+requestAnimationFrame(gameLoop);
+```
+
+---
+
+## CSS (Cascading Style Sheets)
+
+### What It Is
+
+CSS is the language used to describe the presentation of web documents. In the context of game development, CSS handles styling of UI overlays, HUD elements, menus, transitions, animations, and visual effects that sit on top of or alongside the game canvas.
+
+### Why It Matters for Games
+
+- **UI and HUD styling**: Style health bars, score displays, inventory panels, dialog boxes, and menus without touching the game canvas.
+- **CSS Animations and Transitions**: Hardware-accelerated animations for UI elements (fade-ins, slide-outs, pulsing effects) with minimal JavaScript.
+- **CSS Transforms**: Translate, rotate, scale, and skew DOM elements for visual effects and UI positioning.
+- **Flexbox and Grid**: Lay out complex game UIs (settings panels, leaderboards, lobby screens) responsively.
+- **Custom Properties (CSS Variables)**: Theme game UIs dynamically by changing variable values at runtime.
+- **Pointer and cursor control**: Customize or hide cursors, control pointer events on overlay elements.
+- **Media queries**: Adapt game UI across screen sizes and device types.
+
+### Key Properties for Games
+
+| Property / Feature | Use Case |
+|--------------------|----------|
+| `transform` | Rotate, scale, translate UI elements |
+| `transition` | Smooth property changes (e.g., health bar width) |
+| `animation` / `@keyframes` | Looping or triggered UI animations |
+| `opacity` | Fade effects for overlays and modals |
+| `pointer-events` | Let clicks pass through overlay layers to the canvas |
+| `cursor` | Set custom cursors or hide the cursor (`cursor: none`) |
+| `z-index` | Layer UI above the game canvas |
+| `position: fixed / absolute` | Anchor HUD elements to viewport |
+| `display: flex / grid` | Responsive layout for menus and panels |
+| `filter` | Blur, brightness, contrast effects on DOM elements |
+| `mix-blend-mode` | Blend overlay effects with the canvas |
+| `will-change` | Hint the browser to optimize animated properties |
+
+### Code Example
+
+```css
+/* Game HUD overlay */
+.hud {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ padding: 10px;
+ pointer-events: none; /* clicks pass through to canvas */
+ z-index: 10;
+ font-family: "Press Start 2P", monospace;
+ color: white;
+ text-shadow: 2px 2px 0 black;
+}
+
+/* Health bar with smooth transitions */
+.health-bar {
+ width: 200px;
+ height: 20px;
+ background: #333;
+ border: 2px solid white;
+}
+.health-bar-fill {
+ height: 100%;
+ background: limegreen;
+ transition: width 0.3s ease;
+ will-change: width;
+}
+
+/* Pulsing damage indicator */
+@keyframes damage-flash {
+ 0%, 100% { opacity: 0; }
+ 50% { opacity: 0.4; }
+}
+.damage-overlay {
+ position: fixed;
+ inset: 0;
+ background: red;
+ animation: damage-flash 0.3s ease;
+ pointer-events: none;
+}
+```
+
+---
+
+## Fullscreen API
+
+### What It Is
+
+The Fullscreen API provides methods to present a specific element (and its descendants) in fullscreen mode, removing all browser chrome and UI elements. It allows entering and exiting fullscreen programmatically and reports the current fullscreen state.
+
+### Why It Matters for Games
+
+- **Immersive experience**: Fullscreen removes all browser distractions, providing a console-like gaming experience.
+- **Maximum screen real estate**: The entire display is available for the game viewport.
+- **Games are a primary use case**: The MDN documentation explicitly lists online games as a target application.
+
+### Key Interfaces and Methods
+
+| API | Description |
+|-----|-------------|
+| `Element.requestFullscreen()` | Enters fullscreen mode. Returns a `Promise`. |
+| `Document.exitFullscreen()` | Exits fullscreen mode. Returns a `Promise`. |
+| `Document.fullscreenElement` | The element currently in fullscreen, or `null`. |
+| `Document.fullscreenEnabled` | Boolean indicating whether fullscreen is available. |
+| `fullscreenchange` event | Fired when fullscreen state changes. |
+| `fullscreenerror` event | Fired if entering/exiting fullscreen fails. |
+
+### Code Example
+
+```javascript
+const gameContainer = document.getElementById("game-container");
+
+// Enter fullscreen on button click
+document.getElementById("fullscreenBtn").addEventListener("click", () => {
+ if (document.fullscreenEnabled) {
+ gameContainer.requestFullscreen().catch(err => {
+ console.error("Fullscreen request failed:", err);
+ });
+ }
+});
+
+// Toggle fullscreen with a key press
+document.addEventListener("keydown", (e) => {
+ if (e.key === "F11") {
+ e.preventDefault();
+ if (!document.fullscreenElement) {
+ gameContainer.requestFullscreen();
+ } else {
+ document.exitFullscreen();
+ }
+ }
+});
+
+// Respond to fullscreen changes (resize canvas, adjust UI)
+document.addEventListener("fullscreenchange", () => {
+ if (document.fullscreenElement) {
+ resizeCanvasToFullscreen();
+ } else {
+ resizeCanvasToWindowed();
+ }
+});
+```
+
+### Notes
+
+- Fullscreen can only be requested in response to a user gesture (click, key press).
+- Users can always exit via the Escape key or F11.
+- For embedded games in iframes, the `allowfullscreen` attribute is required.
+- Check `Document.fullscreenEnabled` before offering the feature in your UI.
+
+---
+
+## Gamepad API
+
+### What It Is
+
+The Gamepad API provides a standardized interface for detecting and reading input from gamepads and game controllers. It exposes button presses, analog stick positions, and controller connection events, enabling console-style controls in browser games.
+
+### Why It Matters for Games
+
+- **Console-quality input**: Support Xbox, PlayStation, and generic controllers in browser games.
+- **Multiple controllers**: Detect and handle several gamepads simultaneously for local multiplayer.
+- **Analog input**: Read analog stick axes and pressure-sensitive triggers for nuanced control.
+- **Haptic feedback**: Experimental support for vibration via `GamepadHapticActuator`.
+
+### Key Interfaces
+
+| Interface | Description |
+|-----------|-------------|
+| `Gamepad` | Represents a connected controller with buttons, axes, and metadata |
+| `GamepadButton` | Represents a single button -- `pressed` (boolean) and `value` (pressure 0..1) |
+| `GamepadEvent` | Event object for `gamepadconnected` and `gamepaddisconnected` events |
+| `GamepadHapticActuator` | Hardware interface for haptic feedback (experimental) |
+
+### Key Methods and Events
+
+| API | Description |
+|-----|-------------|
+| `navigator.getGamepads()` | Returns an array of `Gamepad` objects for all connected controllers |
+| `gamepadconnected` event | Fired on `window` when a controller is connected |
+| `gamepaddisconnected` event | Fired on `window` when a controller is disconnected |
+
+### Code Example
+
+```javascript
+// Detect controller connections
+window.addEventListener("gamepadconnected", (e) => {
+ console.log(`Gamepad connected: ${e.gamepad.id}`);
+});
+
+window.addEventListener("gamepaddisconnected", (e) => {
+ console.log(`Gamepad disconnected: ${e.gamepad.id}`);
+});
+
+// Poll gamepad state each frame
+function pollGamepads() {
+ const gamepads = navigator.getGamepads();
+ for (const gp of gamepads) {
+ if (!gp) continue;
+
+ // Read analog sticks (axes)
+ const leftStickX = gp.axes[0]; // -1 (left) to 1 (right)
+ const leftStickY = gp.axes[1]; // -1 (up) to 1 (down)
+
+ // Read buttons
+ if (gp.buttons[0].pressed) {
+ // A button / Cross -- jump
+ player.jump();
+ }
+ if (gp.buttons[7].value > 0.1) {
+ // Right trigger -- accelerate (analog pressure)
+ player.accelerate(gp.buttons[7].value);
+ }
+ }
+ requestAnimationFrame(pollGamepads);
+}
+requestAnimationFrame(pollGamepads);
+```
+
+---
+
+## IndexedDB API
+
+### What It Is
+
+IndexedDB is a low-level, asynchronous, transactional, client-side database built into the browser. It stores significant amounts of structured data (including files and blobs) using key-indexed object stores, and supports indexes for high-performance queries.
+
+### Why It Matters for Games
+
+- **Save game state**: Persist player progress, inventory, character stats, and level completion across sessions.
+- **Cache assets locally**: Store textures, audio files, level data, and other assets to reduce network requests and enable offline play.
+- **Large storage capacity**: Handles far more data than `localStorage` (which caps at ~5 MB).
+- **Non-blocking**: Asynchronous operations keep the game loop running smoothly during save/load operations.
+- **Transactional**: Atomic read/write operations prevent data corruption during saves.
+
+### Key Interfaces
+
+| Interface | Purpose | Game Use Case |
+|-----------|---------|---------------|
+| `indexedDB.open()` | Open or create a database | Initialize the game database on startup |
+| `IDBDatabase` | Database connection | Manage the connection lifetime |
+| `IDBTransaction` | Scope and access control for reads/writes | Atomic save-game operations |
+| `IDBObjectStore` | Primary data container | Store player profiles, level data, settings |
+| `IDBIndex` | Secondary lookup keys | Query items by type, rarity, or other properties |
+| `IDBCursor` | Iterate over records | Batch operations on game data |
+| `IDBKeyRange` | Define key ranges for queries | Fetch scores within a range, recent save slots |
+| `IDBRequest` | Async operation handle | Manage callbacks for all database operations |
+
+### Code Example
+
+```javascript
+// Open (or create) the game database
+const request = indexedDB.open("MyGameDB", 1);
+
+request.onupgradeneeded = (event) => {
+ const db = event.target.result;
+ // Create an object store for save data
+ const saveStore = db.createObjectStore("saves", { keyPath: "slotId" });
+ saveStore.createIndex("timestamp", "timestamp");
+};
+
+request.onsuccess = (event) => {
+ const db = event.target.result;
+
+ // Save game state
+ function saveGame(slot, gameState) {
+ const tx = db.transaction("saves", "readwrite");
+ const store = tx.objectStore("saves");
+ store.put({
+ slotId: slot,
+ timestamp: Date.now(),
+ playerHealth: gameState.health,
+ playerPosition: gameState.position,
+ inventory: gameState.inventory,
+ });
+ }
+
+ // Load game state
+ function loadGame(slot) {
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction("saves", "readonly");
+ const store = tx.objectStore("saves");
+ const req = store.get(slot);
+ req.onsuccess = () => resolve(req.result);
+ req.onerror = () => reject(req.error);
+ });
+ }
+};
+```
+
+---
+
+## JavaScript
+
+### What It Is
+
+JavaScript is a lightweight, dynamically typed, prototype-based programming language with first-class functions. It is the scripting language of the web and the foundational language for all browser-based game logic, supporting imperative, functional, and object-oriented paradigms.
+
+### Why It Matters for Games
+
+- **Runtime environment**: JavaScript is the language in which browser game logic executes.
+- **Event-driven architecture**: Native event handling supports input, timers, and async resource loading.
+- **First-class functions**: Callbacks and closures enable patterns like game loops, event handlers, behavior trees, and state machines.
+- **Dynamic objects**: Runtime object creation and modification supports entity-component systems and data-driven designs.
+- **Modern class syntax**: ES6+ classes provide clean inheritance hierarchies for game entities.
+- **Async/await**: Clean asynchronous control flow for asset loading, server communication, and scene transitions.
+- **Garbage collection**: Automatic memory management (though awareness of GC pauses is important for smooth frame rates).
+
+### Key Language Features for Games
+
+| Feature | Game Application |
+|---------|------------------|
+| Classes and inheritance | Entity hierarchies (GameObject, Player, Enemy) |
+| Closures | Encapsulated state in callbacks and event handlers |
+| `requestAnimationFrame` | The core game loop driver |
+| Promises / async-await | Asset loading, server calls, scene transitions |
+| Destructuring and spread | Clean configuration and state passing |
+| `Map` and `Set` | Entity lookup tables, unique ID tracking, collision sets |
+| Template literals | Debug output, dynamic text rendering |
+| Modules (import/export) | Organize game code into systems and components |
+
+### Code Example
+
+```javascript
+// ES6+ game entity pattern
+class GameObject {
+ constructor(x, y) {
+ this.x = x;
+ this.y = y;
+ this.active = true;
+ }
+ update(dt) { /* override in subclasses */ }
+ render(ctx) { /* override in subclasses */ }
+}
+
+class Player extends GameObject {
+ constructor(x, y) {
+ super(x, y);
+ this.health = 100;
+ this.speed = 200;
+ }
+ update(dt) {
+ if (input.left) this.x -= this.speed * dt;
+ if (input.right) this.x += this.speed * dt;
+ }
+ render(ctx) {
+ ctx.fillStyle = "blue";
+ ctx.fillRect(this.x, this.y, 32, 32);
+ }
+}
+
+// Game loop using requestAnimationFrame
+let lastTime = 0;
+function gameLoop(timestamp) {
+ const dt = (timestamp - lastTime) / 1000;
+ lastTime = timestamp;
+
+ for (const entity of entities) {
+ entity.update(dt);
+ }
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ for (const entity of entities) {
+ entity.render(ctx);
+ }
+ requestAnimationFrame(gameLoop);
+}
+requestAnimationFrame(gameLoop);
+```
+
+---
+
+## Pointer Lock API
+
+### What It Is
+
+The Pointer Lock API (formerly Mouse Lock API) provides access to raw mouse movement deltas rather than absolute cursor positions. It locks mouse events to a single element, removes cursor movement boundaries, and hides the cursor -- essential for first-person camera controls and similar mechanics.
+
+### Why It Matters for Games
+
+- **First-person camera control**: Move the camera by physically moving the mouse with no screen-edge limits.
+- **No cursor distraction**: The cursor is hidden, creating immersion.
+- **Persistent lock**: Once engaged, movement data flows continuously regardless of mouse button state.
+- **Raw input option**: The `unadjustedMovement` flag disables OS-level mouse acceleration for consistent aim in competitive games.
+- **Frees mouse buttons**: With movement handled by deltas alone, clicks can be mapped to game actions (shoot, interact).
+
+### Key Interfaces and Methods
+
+| API | Description |
+|-----|-------------|
+| `element.requestPointerLock(options?)` | Locks the pointer to the element. Returns a `Promise`. |
+| `document.exitPointerLock()` | Releases the pointer lock. |
+| `document.pointerLockElement` | The element currently holding the lock, or `null`. |
+| `MouseEvent.movementX` | Horizontal delta since the last `mousemove` event. |
+| `MouseEvent.movementY` | Vertical delta since the last `mousemove` event. |
+| `pointerlockchange` event | Fired when lock state changes. |
+| `pointerlockerror` event | Fired if locking or unlocking fails. |
+
+### Code Example
+
+```javascript
+const canvas = document.getElementById("game");
+
+// Request pointer lock on click (user gesture required)
+canvas.addEventListener("click", async () => {
+ if (!document.pointerLockElement) {
+ await canvas.requestPointerLock({
+ unadjustedMovement: true, // raw input, no OS acceleration
+ });
+ }
+});
+
+// Respond to lock state changes
+document.addEventListener("pointerlockchange", () => {
+ if (document.pointerLockElement === canvas) {
+ document.addEventListener("mousemove", handleMouseMove);
+ } else {
+ document.removeEventListener("mousemove", handleMouseMove);
+ }
+});
+
+// Use movement deltas for camera rotation
+const sensitivity = 0.002;
+function handleMouseMove(e) {
+ camera.yaw += e.movementX * sensitivity;
+ camera.pitch += e.movementY * sensitivity;
+ camera.pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, camera.pitch));
+}
+```
+
+### Notes
+
+- Pointer lock can only be requested in response to a user gesture (click, key press).
+- Users can exit at any time with the Escape key.
+- Sandboxed iframes need the `allow-pointer-lock` attribute.
+
+---
+
+## SVG (Scalable Vector Graphics)
+
+### What It Is
+
+SVG is an XML-based markup language for describing two-dimensional vector graphics. Unlike raster formats (PNG, JPEG), SVG images scale to any resolution without quality loss. SVG integrates with CSS, the DOM, and JavaScript, making elements scriptable and interactive.
+
+### Why It Matters for Games
+
+- **Resolution independence**: A single SVG asset looks crisp on any screen size or pixel density -- ideal for responsive game UIs.
+- **Lightweight**: Text-based and compressible, reducing download sizes for UI art and icons.
+- **Scriptable via the DOM**: SVG elements can be created, modified, and animated with JavaScript in real time.
+- **CSS styling**: SVG shapes accept CSS rules for fill, stroke, opacity, transforms, filters, and animations.
+- **Built-in animation**: SMIL animation elements (``, ``, ``) for declarative motion.
+- **Filters and effects**: Gaussian blur, drop shadows, color matrices, and blend modes through SVG filter primitives.
+
+### Key Elements
+
+| Element | Game Use Case |
+|---------|---------------|
+| `` | Health bars, UI panels, platforms |
+| ``, `` | Targets, particles, indicators |
+| `` | Complex vector art, custom shapes |
+| ``, `` | Grid overlays, wireframe elements |
+| `` | Group elements for collective transforms |
+| ``, ``, `` | Reusable sprite definitions |
+| ``, `` | Score displays, labels, dialog |
+| `` | Blur, shadow, and color effects |
+| ``, `` | Viewport clipping, reveal effects |
+| ``, `` | Shading and depth effects |
+| ``, `` | Declarative UI animations |
+
+### Code Example
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```javascript
+// Update health bar programmatically
+function setHealth(percent) {
+ const maxWidth = 214;
+ document.getElementById("health-fill")
+ .setAttribute("width", maxWidth * (percent / 100));
+}
+```
+
+---
+
+## Typed Arrays
+
+### What They Are
+
+Typed Arrays are array-like views over raw binary data buffers (`ArrayBuffer`). Unlike regular JavaScript arrays, each typed array has a fixed element type and size, providing predictable memory layout and efficient data access. There is no single `TypedArray` constructor; instead, specific constructors like `Float32Array`, `Uint8Array`, and `Uint16Array` are used.
+
+### Why They Matter for Games
+
+- **WebGL vertex and index buffers**: WebGL methods accept typed arrays directly for positions, normals, texture coordinates, colors, and indices.
+- **Web Audio buffers**: Audio sample data is stored and manipulated as `Float32Array`.
+- **Binary asset loading**: Parse binary file formats (models, textures, level data) directly.
+- **Memory-efficient**: Fixed-size elements with no boxing overhead.
+- **WebAssembly interop**: Share memory between JavaScript and Wasm modules via `SharedArrayBuffer` and typed array views.
+- **Network serialization**: Efficiently pack game state for multiplayer transmission.
+
+### Key Types
+
+| Type | Bytes | Range | Game Use Case |
+|------|-------|-------|---------------|
+| `Float32Array` | 4 | ~3.4e38 | Vertex positions, normals, UVs, physics values |
+| `Float64Array` | 8 | ~1.8e308 | High-precision calculations, simulation |
+| `Uint8Array` | 1 | 0 -- 255 | Texture/pixel data, color channels |
+| `Uint8ClampedArray` | 1 | 0 -- 255 (clamped) | `ImageData` pixel manipulation |
+| `Uint16Array` | 2 | 0 -- 65535 | Index buffers (small meshes) |
+| `Uint32Array` | 4 | 0 -- ~4.3 billion | Index buffers (large meshes), IDs |
+| `Int16Array` | 2 | -32768 -- 32767 | Audio samples, quantized normals |
+| `Int32Array` | 4 | ~-2.1 billion -- ~2.1 billion | Integer game data |
+
+### Key Properties and Methods
+
+```javascript
+const verts = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
+
+verts.buffer; // The underlying ArrayBuffer
+verts.byteLength; // Total size in bytes
+verts.byteOffset; // Byte offset into the buffer
+verts.length; // Number of elements
+verts.BYTES_PER_ELEMENT; // 4 for Float32Array
+
+// Write data
+verts.set([1, 2, 3], 0); // Copy values at offset
+verts.copyWithin(6, 0, 3); // Duplicate first vertex to third slot
+
+// Read sub-views (no copy)
+const firstTriangle = verts.subarray(0, 9);
+
+// Functional methods
+const scaled = verts.map(v => v * 2);
+const max = verts.reduce((a, v) => Math.max(a, v), -Infinity);
+```
+
+### Code Example
+
+```javascript
+// Build a quad for WebGL rendering
+const positions = new Float32Array([
+ -0.5, -0.5, 0, // bottom-left
+ 0.5, -0.5, 0, // bottom-right
+ 0.5, 0.5, 0, // top-right
+ -0.5, 0.5, 0, // top-left
+]);
+
+const indices = new Uint16Array([
+ 0, 1, 2, // first triangle
+ 0, 2, 3, // second triangle
+]);
+
+// Upload to WebGL
+const posBuf = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, posBuf);
+gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
+
+const idxBuf = gl.createBuffer();
+gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, idxBuf);
+gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+
+// Generate a 440 Hz sine wave for Web Audio
+const sampleRate = 44100;
+const audioBuffer = new Float32Array(sampleRate); // 1 second
+for (let i = 0; i < sampleRate; i++) {
+ audioBuffer[i] = Math.sin(2 * Math.PI * 440 * i / sampleRate);
+}
+```
+
+---
+
+## Web Audio API
+
+### What It Is
+
+The Web Audio API is a high-level system for controlling audio on the web. It uses a modular routing graph where audio sources are connected through effect nodes to a destination (speakers). It provides high-precision timing, low latency, and built-in 3D spatial audio based on a source-listener model.
+
+### Why It Matters for Games
+
+- **Low-latency playback**: Sound effects respond to game events with minimal delay.
+- **3D spatial audio**: Position sounds in 3D space relative to the player/listener for directional and distance-based audio.
+- **Modular effects pipeline**: Chain gain, reverb, filters, compression, and distortion nodes for dynamic soundscapes.
+- **Precise scheduling**: Schedule sounds to exact sample-accurate times for rhythm games, sequenced music, and timed events.
+- **Real-time analysis**: `AnalyserNode` provides frequency and waveform data for audio-reactive visuals.
+- **Procedural audio**: `OscillatorNode` generates waveforms for synthesized sound effects and UI tones.
+
+### Key Interfaces
+
+| Interface | Purpose |
+|-----------|---------|
+| `AudioContext` | The main audio-processing graph; must be created first |
+| `AudioBufferSourceNode` | Plays pre-loaded audio (SFX, music) from an `AudioBuffer` |
+| `OscillatorNode` | Generates waveforms (sine, square, triangle, sawtooth) |
+| `GainNode` | Controls volume / amplitude |
+| `BiquadFilterNode` | Low-pass, high-pass, band-pass filters |
+| `ConvolverNode` | Convolution reverb using impulse responses |
+| `DelayNode` | Delay-line effect (echo, chorus) |
+| `DynamicsCompressorNode` | Prevents clipping when mixing many sounds |
+| `PannerNode` | Positions a sound source in 3D space |
+| `AudioListener` | Represents the player's ears in 3D space |
+| `StereoPannerNode` | Simple left/right panning |
+| `AnalyserNode` | Real-time frequency and time-domain analysis |
+| `AudioWorkletNode` | Custom audio processing off the main thread |
+
+### Common Routing Patterns
+
+| Use Case | Routing Graph |
+|----------|---------------|
+| Background music | `BufferSource` -> `GainNode` -> `Destination` |
+| Positional SFX | `BufferSource` -> `PannerNode` -> `GainNode` -> `Destination` |
+| Reverb environment | `BufferSource` -> `ConvolverNode` -> `GainNode` -> `Destination` |
+| UI feedback tone | `OscillatorNode` -> `GainNode` -> `Destination` |
+| Master mix | Multiple sources -> individual `GainNode` -> `DynamicsCompressorNode` -> `Destination` |
+
+### Code Example
+
+```javascript
+const audioCtx = new AudioContext();
+
+// Load and play a sound effect
+async function playSFX(url) {
+ const response = await fetch(url);
+ const arrayBuffer = await response.arrayBuffer();
+ const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
+
+ const source = audioCtx.createBufferSource();
+ source.buffer = audioBuffer;
+
+ // Add gain control
+ const gainNode = audioCtx.createGain();
+ gainNode.gain.value = 0.8;
+
+ // Connect: source -> gain -> speakers
+ source.connect(gainNode);
+ gainNode.connect(audioCtx.destination);
+
+ source.start(0);
+}
+
+// 3D positional audio
+function playPositionalSound(buffer, x, y, z) {
+ const source = audioCtx.createBufferSource();
+ source.buffer = buffer;
+
+ const panner = audioCtx.createPanner();
+ panner.panningModel = "HRTF";
+ panner.distanceModel = "inverse";
+ panner.refDistance = 1;
+ panner.maxDistance = 100;
+ panner.positionX.value = x;
+ panner.positionY.value = y;
+ panner.positionZ.value = z;
+
+ source.connect(panner);
+ panner.connect(audioCtx.destination);
+ source.start(0);
+}
+
+// Update listener position each frame (matches camera/player)
+function updateListener(playerPos, playerForward, playerUp) {
+ const listener = audioCtx.listener;
+ listener.positionX.value = playerPos.x;
+ listener.positionY.value = playerPos.y;
+ listener.positionZ.value = playerPos.z;
+ listener.forwardX.value = playerForward.x;
+ listener.forwardY.value = playerForward.y;
+ listener.forwardZ.value = playerForward.z;
+ listener.upX.value = playerUp.x;
+ listener.upY.value = playerUp.y;
+ listener.upZ.value = playerUp.z;
+}
+```
+
+---
+
+## WebGL API
+
+### What It Is
+
+WebGL (Web Graphics Library) is a JavaScript API for rendering hardware-accelerated 2D and 3D graphics within the browser. It implements a profile conforming to OpenGL ES 2.0 (WebGL 1) and OpenGL ES 3.0 (WebGL 2), operating through the HTML `` element and using the device GPU for rendering.
+
+### Why It Matters for Games
+
+- **GPU-accelerated rendering**: Real-time 3D graphics at high frame rates using the device GPU.
+- **Shader programming**: Vertex and fragment shaders in GLSL enable custom visual effects, lighting, shadows, and post-processing.
+- **3D and 2D**: Suitable for both full 3D games and high-performance 2D rendering.
+- **No plugins**: Runs natively in all modern browsers.
+- **Rich ecosystem**: Libraries like three.js, Babylon.js, PlayCanvas, and Pixi.js simplify WebGL game development.
+
+### Key Interfaces
+
+| Interface | Purpose |
+|-----------|---------|
+| `WebGLRenderingContext` | WebGL 1 rendering context (OpenGL ES 2.0) |
+| `WebGL2RenderingContext` | WebGL 2 rendering context (OpenGL ES 3.0) |
+| `WebGLProgram` | Linked vertex + fragment shader program |
+| `WebGLShader` | Individual vertex or fragment shader |
+| `WebGLBuffer` | GPU memory buffer (vertices, indices) |
+| `WebGLTexture` | Texture data for surfaces |
+| `WebGLFramebuffer` | Off-screen render target (shadow maps, post-processing) |
+| `WebGLRenderbuffer` | Non-texture render buffer (depth, stencil) |
+| `WebGLVertexArrayObject` | Cached vertex attribute configuration (WebGL 2) |
+| `WebGLUniformLocation` | Reference to a shader uniform variable |
+| `WebGLSampler` | Texture sampling parameters (WebGL 2) |
+| `WebGLTransformFeedback` | GPU-to-GPU data streaming (WebGL 2) |
+
+### WebGL 2 Features Important for Games
+
+- **3D Textures**: Volumetric rendering, lookup tables.
+- **Instanced Rendering**: `drawArraysInstanced()` / `drawElementsInstanced()` for drawing thousands of identical objects efficiently.
+- **Multiple Render Targets**: `drawBuffers()` for deferred rendering pipelines.
+- **Uniform Buffer Objects**: Efficient sharing of shader data across draw calls.
+- **Transform Feedback**: Capture vertex shader output for GPU-driven particle systems and simulations.
+- **Vertex Array Objects**: Cache vertex state to reduce per-draw setup overhead.
+
+### Context Management Events
+
+| Event | Description |
+|-------|-------------|
+| `webglcontextlost` | GPU context lost (device disconnect, resource limits). Games must handle this gracefully. |
+| `webglcontextrestored` | GPU context recovered. Games should reload GPU resources. |
+| `webglcontextcreationerror` | Context initialization failed. |
+
+### Code Example
+
+```javascript
+const canvas = document.getElementById("game");
+const gl = canvas.getContext("webgl2");
+
+// Vertex shader
+const vsSource = `#version 300 es
+ in vec4 aPosition;
+ uniform mat4 uModelViewProjection;
+ void main() {
+ gl_Position = uModelViewProjection * aPosition;
+ }
+`;
+
+// Fragment shader
+const fsSource = `#version 300 es
+ precision mediump float;
+ out vec4 fragColor;
+ void main() {
+ fragColor = vec4(1.0, 0.5, 0.2, 1.0);
+ }
+`;
+
+function compileShader(gl, source, type) {
+ const shader = gl.createShader(type);
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+ console.error(gl.getShaderInfoLog(shader));
+ gl.deleteShader(shader);
+ return null;
+ }
+ return shader;
+}
+
+const vs = compileShader(gl, vsSource, gl.VERTEX_SHADER);
+const fs = compileShader(gl, fsSource, gl.FRAGMENT_SHADER);
+
+const program = gl.createProgram();
+gl.attachShader(program, vs);
+gl.attachShader(program, fs);
+gl.linkProgram(program);
+gl.useProgram(program);
+
+// Upload vertex data
+const positions = new Float32Array([0, 0.5, 0, -0.5, -0.5, 0, 0.5, -0.5, 0]);
+const buffer = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
+
+const aPos = gl.getAttribLocation(program, "aPosition");
+gl.enableVertexAttribArray(aPos);
+gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 0, 0);
+
+// Render
+gl.clearColor(0, 0, 0, 1);
+gl.clear(gl.COLOR_BUFFER_BIT);
+gl.drawArrays(gl.TRIANGLES, 0, 3);
+```
+
+### Recommended Libraries
+
+| Library | Description |
+|---------|-------------|
+| three.js | Full-featured 3D engine |
+| Babylon.js | Complete game engine with physics, audio, and networking |
+| PlayCanvas | Cloud-based game engine |
+| Pixi.js | Lightweight 2D renderer |
+| glMatrix | Matrix and vector math library |
+
+---
+
+## WebRTC API
+
+### What It Is
+
+WebRTC (Web Real-Time Communication) enables peer-to-peer communication between browsers for audio, video, and arbitrary data exchange -- without requiring plugins or intermediary relay servers (though signaling servers and STUN/TURN are used for connection setup and NAT traversal).
+
+### Why It Matters for Games
+
+- **Peer-to-peer multiplayer**: Establish direct connections between players, reducing latency and eliminating dedicated game servers for small-scale games.
+- **Low-latency data channels**: `RTCDataChannel` sends binary game state updates with minimal overhead, supporting both reliable and unreliable delivery modes.
+- **Voice chat**: Built-in audio/video streaming enables in-game voice communication.
+- **Reduced server costs**: Direct peer connections offload bandwidth and processing from centralized servers.
+
+### Key Interfaces
+
+| Interface | Purpose |
+|-----------|---------|
+| `RTCPeerConnection` | Manages the connection between two peers, including media streams and data channels |
+| `RTCDataChannel` | Bi-directional channel for arbitrary data (game state, commands, chat) |
+| `RTCSessionDescription` | Session negotiation via SDP (offer/answer model) |
+| `RTCIceCandidate` | Connectivity candidate for NAT/firewall traversal |
+| `RTCRtpSender` / `RTCRtpReceiver` | Manage audio/video encoding and transmission |
+| `RTCStatsReport` | Connection statistics (latency, packet loss, bandwidth) for optimization |
+
+### Key Events
+
+| Event | Description |
+|-------|-------------|
+| `datachannel` | Remote peer opened a data channel |
+| `connectionstatechange` | Peer connection state changed |
+| `icecandidate` | New ICE candidate available |
+| `track` | Incoming media track (audio/video) |
+
+### Connection Lifecycle
+
+1. Create `RTCPeerConnection` on each peer.
+2. Exchange SDP offers/answers via a signaling server (typically WebSocket).
+3. Exchange ICE candidates for NAT traversal.
+4. Peers connect directly.
+5. Open `RTCDataChannel` for game data and/or add media tracks for voice.
+6. Monitor performance with `RTCStatsReport`.
+7. Close channels and connection when the session ends.
+
+### Code Example
+
+```javascript
+// Peer A: Create connection and data channel
+const peerA = new RTCPeerConnection({
+ iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
+});
+
+const gameChannel = peerA.createDataChannel("game", {
+ ordered: false, // Allow out-of-order delivery (lower latency)
+ maxRetransmits: 0, // Unreliable mode (like UDP)
+});
+
+gameChannel.onopen = () => {
+ // Send game state updates
+ gameChannel.send(JSON.stringify({ type: "move", x: 10, y: 20 }));
+};
+
+gameChannel.onmessage = (event) => {
+ const data = JSON.parse(event.data);
+ applyRemoteGameState(data);
+};
+
+// Peer B: Receive the data channel
+const peerB = new RTCPeerConnection({
+ iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
+});
+
+peerB.ondatachannel = (event) => {
+ const channel = event.channel;
+ channel.onmessage = (e) => {
+ const data = JSON.parse(e.data);
+ applyRemoteGameState(data);
+ };
+};
+
+// Signaling (offer/answer exchange via your signaling server)
+async function connect() {
+ const offer = await peerA.createOffer();
+ await peerA.setLocalDescription(offer);
+ // Send offer to Peer B via signaling server...
+
+ // Peer B receives offer, sets remote description, creates answer
+ await peerB.setRemoteDescription(offer);
+ const answer = await peerB.createAnswer();
+ await peerB.setLocalDescription(answer);
+ // Send answer back to Peer A via signaling server...
+
+ await peerA.setRemoteDescription(answer);
+}
+```
+
+---
+
+## WebSockets API
+
+### What It Is
+
+The WebSocket API enables persistent, full-duplex communication between a browser and a server over a single TCP connection. Unlike HTTP request-response, a WebSocket connection stays open, allowing the server to push data to the client at any time.
+
+### Why It Matters for Games
+
+- **Real-time multiplayer**: Stream player positions, game events, and world state between clients and servers with minimal latency.
+- **Server-push updates**: The server can broadcast game state changes instantly to all connected players without polling.
+- **Low overhead**: No repeated HTTP headers on every message; just framed data on a persistent connection.
+- **Binary data support**: Send `ArrayBuffer` and `Blob` data for efficient game state serialization.
+- **Web Worker compatible**: Run WebSocket communication in a background thread to keep the game loop unblocked.
+
+### Key Interface: WebSocket
+
+| Member | Description |
+|--------|-------------|
+| `new WebSocket(url, protocols?)` | Opens a connection to the server |
+| `send(data)` | Transmit data (string, ArrayBuffer, Blob) |
+| `close(code?, reason?)` | Gracefully close the connection |
+| `readyState` | Current state: CONNECTING (0), OPEN (1), CLOSING (2), CLOSED (3) |
+| `bufferedAmount` | Bytes queued but not yet sent (for flow control) |
+| `binaryType` | Set to `"arraybuffer"` or `"blob"` for binary data |
+
+### Events
+
+| Event | Description |
+|-------|-------------|
+| `open` | Connection established and ready |
+| `message` | Data received from server (access via `event.data`) |
+| `close` | Connection closed (access code/reason via `CloseEvent`) |
+| `error` | An error occurred |
+
+### Code Example
+
+```javascript
+// Connect to the game server
+const socket = new WebSocket("wss://game.example.com/ws");
+socket.binaryType = "arraybuffer";
+
+socket.addEventListener("open", () => {
+ // Authenticate and join a game room
+ socket.send(JSON.stringify({
+ type: "join",
+ room: "room-42",
+ playerId: "player-1"
+ }));
+});
+
+socket.addEventListener("message", (event) => {
+ if (typeof event.data === "string") {
+ const msg = JSON.parse(event.data);
+ switch (msg.type) {
+ case "state":
+ updateWorldState(msg.state);
+ break;
+ case "playerJoined":
+ addRemotePlayer(msg.player);
+ break;
+ case "playerLeft":
+ removeRemotePlayer(msg.playerId);
+ break;
+ }
+ } else {
+ // Binary data -- e.g., compressed game state
+ const view = new DataView(event.data);
+ processRawGameState(view);
+ }
+});
+
+socket.addEventListener("close", (event) => {
+ console.log(`Disconnected: ${event.code} ${event.reason}`);
+ showReconnectPrompt();
+});
+
+// Send player input to the server each tick
+function sendInput(input) {
+ if (socket.readyState === WebSocket.OPEN) {
+ socket.send(JSON.stringify({
+ type: "input",
+ keys: input.keys,
+ mouseX: input.mouseX,
+ mouseY: input.mouseY,
+ timestamp: performance.now(),
+ }));
+ }
+}
+```
+
+### Notes
+
+- Close the WebSocket connection when the player navigates away to avoid blocking the browser's back-forward cache.
+- For games requiring unreliable (UDP-like) delivery, consider WebRTC data channels or the newer WebTransport API.
+- Popular server libraries: Socket.IO, ws (Node.js), Gorilla WebSocket (Go), SignalR (.NET).
+
+---
+
+## WebVR API (Deprecated)
+
+### What It Is
+
+The WebVR API provides interfaces for accessing virtual reality devices (head-mounted displays such as Oculus Rift and HTC Vive) from the browser. It exposes display properties, head-tracking pose data, and stereo rendering capabilities for immersive VR experiences.
+
+### Why It Matters for Games
+
+- **Immersive VR gaming**: Render stereoscopic 3D scenes driven by real-time head tracking.
+- **Room-scale experiences**: `VRStageParameters` describes the physical play area dimensions.
+- **Controller integration**: VR controllers are accessible through the Gamepad API, linking each controller to a `VRDisplay` via `gamepad.displayId`.
+
+### Deprecation Notice
+
+The WebVR API is **deprecated and non-standard**. It was never ratified as a web standard and has been superseded by the **WebXR Device API**, which supports both VR and AR, has broader browser support, and is on track for standardization. All new VR game development should target WebXR.
+
+### Key Interfaces
+
+| Interface | Purpose |
+|-----------|---------|
+| `VRDisplay` | Represents a VR headset. Core methods: `requestPresent()`, `requestAnimationFrame()`, `getFrameData()`, `submitFrame()`. |
+| `VRFrameData` | Pose, view matrices, and projection matrices for the current frame. |
+| `VRPose` | Position, orientation, velocity, and acceleration at a given timestamp. |
+| `VREyeParameters` | Per-eye field of view and rendering offset. |
+| `VRStageParameters` | Room-scale play area dimensions and transform. |
+| `VRDisplayCapabilities` | Device capability flags (has position tracking, has external display, etc.). |
+| `Navigator.getVRDisplays()` | Returns a promise resolving to an array of connected `VRDisplay` objects. |
+
+### Key Events
+
+| Event | Description |
+|-------|-------------|
+| `vrdisplayconnect` | A VR headset was connected |
+| `vrdisplaydisconnect` | A VR headset was disconnected |
+| `vrdisplaypresentchange` | The headset entered or exited presentation mode |
+| `vrdisplayactivate` | The headset is ready to present |
+
+### Code Example
+
+```javascript
+// Check for WebVR support
+if (navigator.getVRDisplays) {
+ navigator.getVRDisplays().then(displays => {
+ if (displays.length === 0) return;
+ const vrDisplay = displays[0];
+
+ // Start presenting to the headset
+ vrDisplay.requestPresent([{ source: canvas }]).then(() => {
+ const frameData = new VRFrameData();
+
+ function renderLoop() {
+ vrDisplay.requestAnimationFrame(renderLoop);
+ vrDisplay.getFrameData(frameData);
+
+ // Render left eye
+ gl.viewport(0, 0, canvas.width / 2, canvas.height);
+ renderScene(frameData.leftProjectionMatrix, frameData.leftViewMatrix);
+
+ // Render right eye
+ gl.viewport(canvas.width / 2, 0, canvas.width / 2, canvas.height);
+ renderScene(frameData.rightProjectionMatrix, frameData.rightViewMatrix);
+
+ vrDisplay.submitFrame();
+ }
+ renderLoop();
+ });
+ });
+}
+```
+
+### Migration to WebXR
+
+For new projects, use the **WebXR Device API** instead. Frameworks that support WebXR include:
+
+- **A-Frame** -- Declarative entity-component VR framework
+- **Babylon.js** -- Full-featured 3D/game engine with WebXR support
+- **three.js** -- Lightweight 3D library with WebXR integration
+- **WebXR Polyfill** -- Backwards-compatibility layer for older browsers
+
+---
+
+## Web Workers API
+
+### What It Is
+
+The Web Workers API enables running JavaScript in background threads separate from the main thread. Workers operate in their own global scope (`DedicatedWorkerGlobalScope` or `SharedWorkerGlobalScope`), cannot access the DOM directly, and communicate with the main thread via message passing (`postMessage` / `onmessage`).
+
+### Why It Matters for Games
+
+- **Offload heavy computation**: Move physics simulation, pathfinding, AI, procedural generation, and collision detection to background threads so the main thread stays at 60 fps.
+- **Parallel asset processing**: Decode images, decompress data, or parse level files without blocking rendering.
+- **OffscreenCanvas**: Render to a canvas from within a worker, enabling parallel rendering pipelines.
+- **Non-blocking networking**: Perform `fetch()` or XHR calls in a worker to keep the game loop smooth.
+
+### Worker Types
+
+| Type | Description | Game Use Case |
+|------|-------------|---------------|
+| Dedicated Worker (`Worker`) | Single-owner background thread | Physics, AI, pathfinding for one game instance |
+| Shared Worker (`SharedWorker`) | Shared across multiple windows/tabs | Multi-tab or multi-iframe game scenarios |
+| Service Worker | Network proxy with offline support | Asset caching, offline play |
+
+### Key Interfaces
+
+| API | Description |
+|-----|-------------|
+| `new Worker(scriptURL)` | Create a dedicated worker from a script file |
+| `worker.postMessage(data)` | Send data to the worker |
+| `worker.onmessage` | Receive data from the worker (via `event.data`) |
+| `worker.terminate()` | Immediately stop the worker |
+| Inside worker: `self.postMessage(data)` | Send data back to the main thread |
+| Inside worker: `self.onmessage` | Receive data from the main thread |
+
+### Limitations
+
+- No DOM access from workers.
+- No `window` object; limited global scope.
+- Data is copied (structured clone) by default; use `Transferable` objects (ArrayBuffer, OffscreenCanvas) for zero-copy transfers.
+- Worker scripts must be same-origin.
+
+### Code Example
+
+**Main thread (game.js):**
+
+```javascript
+// Create a physics worker
+const physicsWorker = new Worker("physics-worker.js");
+
+// Send world state to the worker each frame
+function updatePhysics(entities) {
+ // Transfer the buffer for zero-copy performance
+ const buffer = serializeEntities(entities);
+ physicsWorker.postMessage({ type: "step", buffer }, [buffer]);
+}
+
+// Receive results from the worker
+physicsWorker.onmessage = (event) => {
+ const { type, buffer } = event.data;
+ if (type === "result") {
+ applyPhysicsResults(buffer);
+ }
+};
+```
+
+**Worker thread (physics-worker.js):**
+
+```javascript
+self.onmessage = (event) => {
+ const { type, buffer } = event.data;
+ if (type === "step") {
+ const positions = new Float32Array(buffer);
+
+ // Run physics simulation
+ for (let i = 0; i < positions.length; i += 3) {
+ positions[i + 1] -= 9.8 * (1 / 60); // gravity on Y axis
+ }
+
+ // Send results back, transferring the buffer
+ self.postMessage({ type: "result", buffer: positions.buffer }, [positions.buffer]);
+ }
+};
+```
+
+---
+
+## XMLHttpRequest
+
+### What It Is
+
+`XMLHttpRequest` (XHR) is a built-in browser API for making HTTP requests to servers without reloading the page. Despite its name, it can retrieve any data type -- JSON, binary (ArrayBuffer, Blob), plain text, XML, and HTML. It has been largely superseded by the Fetch API for new code, but remains widely used and fully supported.
+
+### Why It Matters for Games
+
+- **Asset loading**: Retrieve game assets (images, audio, JSON level data, binary model files) asynchronously without blocking the game loop.
+- **Binary data support**: Set `responseType` to `"arraybuffer"` or `"blob"` to load binary assets directly into typed arrays for WebGL or Web Audio.
+- **Progress tracking**: The `progress` event reports download progress, enabling loading bars.
+- **Server communication**: Submit scores, authenticate players, fetch leaderboards, and synchronize game state with backend services.
+- **Web Worker compatible**: XHR can be used inside Web Workers for background asset loading.
+
+### Key Methods
+
+| Method | Description |
+|--------|-------------|
+| `open(method, url, async?)` | Initialize a request (GET, POST, etc.) |
+| `send(body?)` | Send the request; `body` can be string, FormData, ArrayBuffer, Blob |
+| `setRequestHeader(name, value)` | Set an HTTP header (call after `open`, before `send`) |
+| `abort()` | Cancel an in-progress request |
+| `getResponseHeader(name)` | Retrieve a specific response header value |
+
+### Key Properties
+
+| Property | Description |
+|----------|-------------|
+| `response` | The response body as the type specified by `responseType` |
+| `responseType` | Expected response format: `""`, `"text"`, `"json"`, `"arraybuffer"`, `"blob"`, `"document"` |
+| `status` | HTTP status code (200, 404, etc.) |
+| `readyState` | Request lifecycle state (0 = UNSENT through 4 = DONE) |
+| `timeout` | Milliseconds before the request auto-aborts |
+| `withCredentials` | Whether to include cookies in cross-origin requests |
+
+### Events
+
+| Event | Description |
+|-------|-------------|
+| `load` | Request completed successfully |
+| `error` | Request failed |
+| `progress` | Periodic progress updates during download |
+| `abort` | Request was aborted |
+| `readystatechange` | `readyState` changed |
+
+### Code Example
+
+```javascript
+// Load a JSON level file
+function loadLevel(url) {
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.responseType = "json";
+
+ xhr.onload = () => {
+ if (xhr.status === 200) {
+ resolve(xhr.response);
+ } else {
+ reject(new Error(`Failed to load level: ${xhr.status}`));
+ }
+ };
+ xhr.onerror = () => reject(new Error("Network error"));
+ xhr.send();
+ });
+}
+
+// Load a binary asset with progress tracking
+function loadBinaryAsset(url, onProgress) {
+ return new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.responseType = "arraybuffer";
+
+ xhr.onprogress = (event) => {
+ if (event.lengthComputable && onProgress) {
+ onProgress(event.loaded / event.total);
+ }
+ };
+
+ xhr.onload = () => {
+ if (xhr.status === 200) {
+ resolve(xhr.response); // ArrayBuffer
+ } else {
+ reject(new Error(`Failed to load asset: ${xhr.status}`));
+ }
+ };
+ xhr.onerror = () => reject(new Error("Network error"));
+ xhr.send();
+ });
+}
+
+// Usage
+loadLevel("levels/level1.json").then(data => initLevel(data));
+loadBinaryAsset("models/tank.bin", pct => updateLoadingBar(pct))
+ .then(buf => parseModel(new Float32Array(buf)));
+```
+
+### Note on Fetch API
+
+For new projects, the **Fetch API** (`fetch()`) is generally preferred over XHR. It provides a cleaner promise-based interface, supports streaming via `ReadableStream`, and integrates well with async/await. However, XHR remains relevant when you need progress events on uploads or require broader compatibility with legacy codebases.