diff --git a/README.md b/README.md
index 23aa2e8..52f13cf 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ A web platform for comparing fun, cool-looking AI-generated games. Play each ver
| 💣 Minesweeper | Every click is a calculated gamble. Use logic to uncover safe tiles while avoiding hidden explosives. | 5 |
| 🧱 Tetris | Falling tetrominoes demand split-second decisions. Stack them perfectly or watch entropy consume the board. | 6 |
| ⚫ Reversi | A war of strategic placement. Flip your opponent's pieces and dominate the 8×8 battlefield. | 6 |
-| 🏓 Breakout | Shatter neon bricks with a blazing ball. Catch power-ups, chain combos, and clear every level before your lives run out. | 6 |
+| 🏓 Breakout | Shatter neon bricks with a blazing ball. Catch power-ups, chain combos, and clear every level before your lives run out. | 7 |
| 🔢 2048 | Slide, merge, and strategize on a 4×4 grid. Chase the elusive 2048 tile before the board fills up. | 7 |
| 🏃 Endless Runner 3D | A neon-soaked 3D sprint through an endless cyberpunk corridor with obstacles, collectibles, and power-ups. | 6 |
| 🔮 Marble Madness | Roll a glowing marble across neon platforms suspended in space. Master physics, dodge hazards, and race the clock. | 6 |
@@ -41,7 +41,7 @@ A web platform for comparing fun, cool-looking AI-generated games. Play each ver
| OpenAI GPT 5.4 | 1,763 lines | 1,875 lines | 2,278 lines | 1,482 lines | 1,419 lines | 779 lines | 1,960 lines | 2,279 lines | 1,837 lines | 2,401 lines | 2,852 lines | 3,116 lines | 2,135 lines | 3,371 lines | N/A | 3,093 lines | N/A | N/A | N/A |
| OpenAI GPT 5.5 | N/A | N/A | N/A | N/A | N/A | 2,031 lines | 2,369 lines | N/A | N/A | N/A | 2,413 lines | 1,643 lines | 2,058 lines | N/A | 1,398 lines | 2,155 lines | N/A | N/A | N/A |
| OpenAI GPT 5.6 Terra | N/A | N/A | N/A | N/A | N/A | N/A | N/A | N/A | 930 lines | N/A | N/A | N/A | N/A | N/A | N/A | N/A | 147 lines | N/A | N/A |
-| OpenAI GPT 5.6 Luna | N/A | N/A | 1,444 lines | N/A | N/A | N/A | N/A | N/A | N/A | N/A | 893 lines | N/A | N/A | N/A | N/A | N/A | 289 lines | N/A | N/A |
+| OpenAI GPT 5.6 Luna | N/A | N/A | 1,444 lines | N/A | 492 lines | N/A | N/A | N/A | N/A | N/A | 893 lines | N/A | N/A | N/A | N/A | N/A | 289 lines | N/A | N/A |
| OpenAI GPT 5.6 Sol | N/A | N/A | N/A | 1,736 lines | N/A | N/A | N/A | 304 lines | 2,421 lines | 2,134 lines | 3,263 lines | N/A | N/A | N/A | 278 lines | 1,542 lines | 2,447 lines | 715 lines | N/A |
| OpenAI GPT 5.4 Mini | 1,309 lines | 1,223 lines | 1,267 lines | 1,389 lines | 570 lines | 541 lines | 1,309 lines | 1,687 lines | 1,486 lines | 1,345 lines | 2,093 lines | 1,035 lines | N/A | N/A | 2,734 lines | 3,965 lines | N/A | N/A | N/A |
| Google Gemini 3.1 Pro | 60 lines | 184 lines | 394 lines | 230 lines | 63 lines | 58 lines | 44 lines | 46 lines | 65 lines | N/A | N/A | N/A | N/A | N/A | N/A | N/A | N/A | N/A | N/A |
diff --git a/games-metadata.json b/games-metadata.json
index 11eeb7d..25003db 100644
--- a/games-metadata.json
+++ b/games-metadata.json
@@ -2154,6 +2154,19 @@
]
}
]
+ },
+ {
+ "model": "GPT 5.6 Luna",
+ "modelId": "gpt-5-6-luna",
+ "date": "2026-07-31",
+ "linesOfCode": 492,
+ "path": "/games/breakout/gpt-5-6-luna/index.html",
+ "features": [
+ "sound",
+ "music",
+ "particles",
+ "powerups"
+ ]
}
]
},
diff --git a/public/games/breakout/gpt-5-6-luna/index.html b/public/games/breakout/gpt-5-6-luna/index.html
new file mode 100644
index 0000000..eb0d9ca
--- /dev/null
+++ b/public/games/breakout/gpt-5-6-luna/index.html
@@ -0,0 +1,491 @@
+
+
+
+
+
+
+ LUNAR SHATTER // BREAKOUT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
THE ECLIPSE
BEGINS
+
Break the crystal array. Build your combo.
Catch the lunar gifts before they fall into the void.
+
+
A / D OR ← → STEER · SPACE LAUNCH · P PAUSE · M AUDIO
PRISM · LENS · NOVA · TIDE · MAGNET · AEGIS
+
+
+
+ ORBIT
PAUSED
The observatory is holding position. Your combo is safe.
PRESS P TO RESUME
+
+
+ 01
FIRST LIGHT
The first crystals are waiting in the dark.
CALIBRATING ORBITAL TRAJECTORY
+
+
+ SIGNAL
LOST
The array outlasted the pilot.
Score000000
Level01
Best000000
PRESS R TO TRY AGAIN
+
+
+ S-RANK
UMBRA
SHATTERED
Six arrays cleared. The lunar archive is yours.
Score000000
Best000000
Medals0 / 6
THE ARRAY WILL REMEMBER
+
+
+
+
+
+
diff --git a/tests/breakout-luna.spec.ts b/tests/breakout-luna.spec.ts
new file mode 100644
index 0000000..f4f7994
--- /dev/null
+++ b/tests/breakout-luna.spec.ts
@@ -0,0 +1,211 @@
+import { expect, test, type Page } from "@playwright/test";
+
+const GAME_PATH = "/games/breakout/gpt-5-6-luna/index.html";
+const TEST_PATH = `${GAME_PATH}?test=1`;
+
+type LunaSnapshot = {
+ screen: string;
+ score: number;
+ highScore: number;
+ level: number;
+ lives: number;
+ combo: number;
+ bricksRemaining: number;
+ paddle: { x: number; targetX: number; y: number; w: number };
+ balls: Array<{ x: number; y: number; vx: number; vy: number; stuck: boolean; captured: boolean; nova: boolean }>;
+ activePowerups: Record;
+ layout: { width: number; height: number; boardX: number; boardY: number; boardW: number; boardH: number; brickTop: number; paddleY: number };
+ medals: string[];
+ muted: boolean;
+};
+
+async function snapshot(page: Page): Promise {
+ return page.evaluate(() => {
+ const api = (window as typeof window & {
+ __lunaBreakoutTest: { snapshot: () => LunaSnapshot };
+ }).__lunaBreakoutTest;
+ return api.snapshot();
+ });
+}
+
+async function advance(page: Page, milliseconds: number) {
+ return page.evaluate((ms) => {
+ const api = (window as typeof window & { __lunaBreakoutTest: { advance: (value: number) => LunaSnapshot } }).__lunaBreakoutTest;
+ return api.advance(ms);
+ }, milliseconds);
+}
+
+test.describe("GPT 5.6 Luna Breakout", {
+ tag: ["@spec:breakout-luna", "@game:breakout/gpt-5-6-luna"],
+}, () => {
+ test("loads the lunar title state without page errors", async ({ page }) => {
+ const errors: string[] = [];
+ page.on("pageerror", (error) => errors.push(error.message));
+
+ await page.goto(GAME_PATH);
+
+ await expect(page).toHaveTitle("LUNAR SHATTER // BREAKOUT");
+ await expect(page.locator('[data-testid="breakout-canvas"]')).toBeVisible();
+ await expect(page.locator('[data-testid="start-button"]')).toBeVisible();
+ expect(errors).toEqual([]);
+ });
+
+ test("starts audio, moves by pointer, and supports pause and mute", async ({ page }) => {
+ await page.addInitScript(() => {
+ const root = window as typeof window & { __lunaAudioStarts?: number };
+ root.__lunaAudioStarts = 0;
+ if (!("OscillatorNode" in window)) return;
+ const proto = window.OscillatorNode.prototype;
+ const originalStart = proto.start;
+ proto.start = function (...args) {
+ root.__lunaAudioStarts = (root.__lunaAudioStarts ?? 0) + 1;
+ return originalStart.apply(this, args);
+ };
+ });
+
+ await page.setViewportSize({ width: 1280, height: 720 });
+ await page.goto(TEST_PATH);
+ await page.locator('[data-testid="start-button"]').click();
+ await advance(page, 1600);
+
+ const before = await snapshot(page);
+ await page.mouse.move(120, 400);
+ await advance(page, 120);
+ const after = await snapshot(page);
+
+ expect(after.screen).toBe("playing");
+ expect(after.paddle.targetX).toBeLessThan(before.paddle.targetX);
+ expect(after.paddle.x).toBeLessThan(before.paddle.x);
+ await page.keyboard.press("Space");
+ await expect.poll(() => page.evaluate(() => (window as typeof window & { __lunaAudioStarts?: number }).__lunaAudioStarts ?? 0)).toBeGreaterThan(0);
+
+ await page.evaluate(() => {
+ const api = (window as typeof window & { __lunaBreakoutTest: { forcePowerUp: (type: string) => LunaSnapshot } }).__lunaBreakoutTest;
+ api.forcePowerUp("lens");
+ });
+ await page.locator('[data-testid="pause-button"]').click();
+ await expect(page.locator("#statusText")).toHaveText("HOLDING POSITION");
+ const lensTimer = (await snapshot(page)).activePowerups.lens;
+ await advance(page, 1000);
+ expect((await snapshot(page)).activePowerups.lens).toBe(lensTimer);
+ await page.locator("#resumeButton").click();
+ await advance(page, 1000);
+ expect((await snapshot(page)).activePowerups.lens).toBeLessThan(lensTimer);
+ await page.keyboard.press("m");
+ expect((await snapshot(page)).muted).toBe(true);
+ await page.locator('[data-testid="audio-button"]').click();
+ expect((await snapshot(page)).muted).toBe(false);
+ });
+
+ test("exposes deterministic collision, combo, power-up, shield, and game-over hooks", async ({ page }) => {
+ await page.goto(TEST_PATH);
+ await page.locator('[data-testid="start-button"]').click();
+ await advance(page, 1600);
+
+ const initial = await snapshot(page);
+ const brick = {
+ x: initial.layout.boardX + initial.layout.boardW / 2 - 32,
+ y: initial.layout.brickTop + 28,
+ w: 64,
+ h: 25,
+ type: "crystal",
+ hp: 1,
+ };
+ await page.evaluate((entry) => {
+ const api = (window as typeof window & { __lunaBreakoutTest: { setBricks: (value: unknown[]) => void; setBall: (value: unknown) => void } }).__lunaBreakoutTest;
+ api.setBricks([entry]);
+ api.setBall({ x: entry.x + entry.w / 2, y: entry.y + 48, vx: 0, vy: -340, stuck: false });
+ }, brick);
+ const collision = await advance(page, 220);
+
+ expect(collision.score).toBeGreaterThan(0);
+ expect(collision.combo).toBe(1);
+ expect(collision.bricksRemaining).toBe(0);
+ expect(collision.highScore).toBe(collision.score);
+
+ const effects = await page.evaluate(() => {
+ const api = (window as typeof window & { __lunaBreakoutTest: { forcePowerUp: (type: string) => LunaSnapshot } }).__lunaBreakoutTest;
+ const lens = api.forcePowerUp("lens");
+ const nova = api.forcePowerUp("nova");
+ return { lens, nova };
+ });
+ expect(effects.lens.activePowerups.lens).toBeGreaterThan(0);
+ expect(effects.lens.paddle.w).toBeGreaterThan(initial.paddle.w);
+ expect(effects.nova.activePowerups.nova).toBeGreaterThan(0);
+ expect(effects.nova.balls[0].nova).toBe(true);
+
+ await page.evaluate(() => {
+ const api = (window as typeof window & { __lunaBreakoutTest: { setLevel: (value: number) => LunaSnapshot; forcePowerUp: (type: string) => LunaSnapshot; setBall: (value: unknown) => LunaSnapshot } }).__lunaBreakoutTest;
+ const level = api.setLevel(1);
+ api.forcePowerUp("magnet");
+ return api.setBall({ x: level.paddle.x, y: level.paddle.y - 60, vx: 0, vy: 340, stuck: false, captured: false });
+ });
+ const magnetCatch = await advance(page, 220);
+ expect(magnetCatch.balls[0].captured).toBe(true);
+ expect(magnetCatch.balls[0].vx).toBe(0);
+ const relaunched = await page.evaluate(() => {
+ const api = (window as typeof window & { __lunaBreakoutTest: { launch: () => boolean } }).__lunaBreakoutTest;
+ return api.launch();
+ });
+ expect(relaunched).toBe(true);
+ expect((await snapshot(page)).balls[0].captured).toBe(false);
+
+ await page.evaluate(() => {
+ const api = (window as typeof window & { __lunaBreakoutTest: { setLives: (value: number) => void; forcePowerUp: (type: string) => void; setBall: (value: unknown) => void; clearAudioEvents: () => void } }).__lunaBreakoutTest;
+ api.setLives(1);
+ api.forcePowerUp("aegis");
+ api.clearAudioEvents();
+ api.setBall({ x: 640, y: 760, vx: 0, vy: 340, stuck: false });
+ });
+ const shieldSave = await advance(page, 40);
+ expect(shieldSave.screen).toBe("playing");
+ expect(shieldSave.lives).toBe(1);
+ expect(shieldSave.activePowerups.aegis).toBeUndefined();
+ expect(await page.evaluate(() => (window as typeof window & { __lunaBreakoutTest: { audioEvents: () => Array<{ name: string }> } }).__lunaBreakoutTest.audioEvents().some((event) => event.name === "shieldSave"))).toBe(true);
+
+ await page.evaluate(() => {
+ const api = (window as typeof window & { __lunaBreakoutTest: { setBall: (value: unknown) => void } }).__lunaBreakoutTest;
+ api.setBall({ x: 640, y: 760, vx: 0, vy: 340, stuck: false });
+ });
+ const over = await advance(page, 40);
+ expect(over.screen).toBe("gameover");
+ expect(over.lives).toBe(0);
+ await expect(page.locator('[data-testid="restart-button"]')).toBeVisible();
+ });
+
+ test("presents the final rank and medal summary after the last stage clears", async ({ page }) => {
+ await page.goto(TEST_PATH);
+ await page.locator('[data-testid="start-button"]').click();
+ await advance(page, 1600);
+
+ await page.evaluate(() => {
+ const api = (window as typeof window & { __lunaBreakoutTest: { setLevel: (value: number) => void; setBricks: (value: unknown[]) => void; setBall: (value: unknown) => void } }).__lunaBreakoutTest;
+ api.setLevel(6);
+ api.setBricks([{ x: 600, y: 170, w: 64, h: 25, type: "standard", hp: 1 }]);
+ api.setBall({ x: 632, y: 220, vx: 0, vy: -340, stuck: false });
+ });
+ await advance(page, 260);
+
+ await expect(page.locator("#victoryOverlay")).toBeVisible();
+ await expect(page.locator("#finalRank")).toHaveText(/[B-S]-RANK/);
+ await expect(page.locator("#victoryMedals")).toHaveText("6 / 6");
+ });
+
+ test("keeps the playfield inside desktop and compact viewports", async ({ page }) => {
+ for (const viewport of [{ width: 1280, height: 720 }, { width: 320, height: 480 }]) {
+ await page.setViewportSize(viewport);
+ await page.goto(TEST_PATH);
+ const canvas = page.locator('[data-testid="breakout-canvas"]');
+ const box = await canvas.boundingBox();
+ expect(box).not.toBeNull();
+ if (box) {
+ expect(box.x).toBeGreaterThanOrEqual(0);
+ expect(box.y).toBeGreaterThanOrEqual(0);
+ expect(box.x + box.width).toBeLessThanOrEqual(viewport.width + 1);
+ expect(box.y + box.height).toBeLessThanOrEqual(viewport.height + 1);
+ }
+ expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(viewport.width);
+ expect(await page.evaluate(() => document.documentElement.scrollHeight)).toBeLessThanOrEqual(viewport.height);
+ }
+ });
+});