9.3 KiB
AI Agent Instructions for TS-Games
This document provides guidelines for AI agents working with the TS-Games project.
Project Overview
TS-Games is a custom framework/build system for creating simple single-file TypeScript games. All games compile into one standalone HTML file with no external dependencies—all assets are inlined as data URLs.
Key Concepts
Two Types of Applications
-
Games - Interactive applications with game loops
- Located in
src/games/<name>/ - Entry point:
index.tsorindex.tsx - Default export:
runGame()function - Use
gameLoop()from@common/gamefor structured game development
- Located in
-
Apps - More complex applications (like AI-Story)
- Also located in
src/games/<name>/ - Entry point:
index.tsx - Default export:
main()function - May use Preact components and complex state management
- Also located in
Single HTML File Architecture
- All games build into a single
.htmlfile indist/ - No external dependencies at runtime
- All assets (images, audio, fonts, WASM) are inlined as base64 data URLs
- Build process uses Bun's plugin system to transform assets
Creating a New Game
Step 1: Create Directory Structure
mkdir -p src/games/<yourgame>/assets
Step 2: Create Entry Point
Create src/games/<yourgame>/index.ts or index.tsx:
// Simple game example (index.ts)
import { gameLoop } from "@common/game";
import Input from "@common/input";
const setup = () => {
// Initialize game state
return { x: 0, y: 0 };
};
const frame = (dt: number, state: ReturnType<typeof setup>) => {
// Update game state
state.x += Input.getHorizontal() * dt;
state.y += Input.getVertical() * dt;
};
export default gameLoop(setup, frame);
// Preact component example (index.tsx)
import { render } from "preact";
import App from "./components/app";
export default function main() {
render(<App />, document.body);
}
Step 3: Add Assets (Optional)
Place assets in src/games/<yourgame>/assets/:
- Images:
.png,.jpg,.jpeg→ Import asHTMLImageElement - Audio:
.wav,.mp3,.ogg→ Import asHTMLAudioElement - CSS:
.css,.module.css→ Import as styles - Fonts:
.font.css→ Import font faces - WASM:
.c,.cpp,.wasm→ Import as WASM module - Icons:
favicon.ico→ Auto-used as page icon - PWA:
pwa_icon.png→ Used for PWA manifest
Step 4: Import Assets
// Import image
import spritesheet from './assets/spritesheet.png';
console.log(spritesheet); // <img src="data:image/png;base64,..." />
// Import audio
import heal from './assets/heal.ogg';
heal.play();
// Import CSS
import './assets/styles.css';
// Import CSS modules
import styles from './assets/styles.module.css';
<div className={styles.root} />
// Import WASM (C/C++)
import wasmModule from './assets/game.c';
// Access exports: wasmModule.memory, wasmModule.functionName()
// Import GLSL shader
import shader from './assets/shader.glsl';
Development Workflow
Running Development Server
bun start
Navigate to http://localhost:3000 to see the game list. Games rebuild on each page reload.
To run a specific game:
bun start --game=<yourgame>
Building for Production
Build a specific game:
bun run build <yourgame>
Or select from interactive list:
bun run build
Output: dist/<yourgame>.html
Building with Options
--local: Build for local testing (no PWA, different index)--production: Minify JS/CSS/HTML (default for build)
Project Structure
tsgames/
├── src/
│ ├── games/ # All games/apps live here
│ │ ├── <game1>/ # Individual game directory
│ │ │ ├── index.ts # Entry point (required)
│ │ │ └── assets/ # Game assets
│ │ └── index/ # Special: game list page
│ ├── common/ # Shared utilities
│ │ ├── game.ts # Game loop helper
│ │ ├── input.ts # Input handling
│ │ ├── dom.ts # DOM utilities
│ │ ├── utils.ts # General utilities
│ │ ├── errors.ts # Error formatting
│ │ ├── components/ # Shared Preact components
│ │ ├── hooks/ # React-like hooks
│ │ ├── display/ # Canvas/graphics utilities
│ │ └── physics/ # Physics utilities
│ └── types.d.ts # Global type definitions
├── build/
│ ├── build.ts # Build script
│ ├── server.ts # Dev server
│ ├── html.ts # HTML generation
│ ├── isGame.ts # Game detection
│ ├── imagePlugin.ts # Image bundler plugin
│ ├── audioPlugin.ts # Audio bundler plugin
│ ├── fontPlugin.ts # Font bundler plugin
│ ├── wasmPlugin.ts # WASM bundler plugin
│ └── filePlugin.ts # Generic file plugin
├── dist/ # Built HTML files
└── test/ # Tests
Technical Details
Bun Configuration
- Use
bunfor all package management and script running .envfiles are automatically loaded (no dotenv needed)- Use
Bun.serve()for servers (not express) - Use
Bun.file()for file operations (not fs) - Use
Bun.$for shell commands (not execa)
TypeScript Configuration
- JSX:
react-jsxwith Preact - Module resolution:
bundler - Path aliases:
@common/*→./src/common/* - Strict mode enabled
Asset Processing
All asset processing happens at build time via Bun plugins:
-
Images (
.png,.jpg,.jpeg):- Converted to base64 data URLs
- Return
HTMLImageElementthat's ready to use
-
Audio (
.wav,.mp3,.ogg):- Converted to base64 data URLs
- Return
HTMLAudioElementwith loaded source
-
CSS:
- Bundled and inlined in
<style>tag - CSS modules supported (
.module.css) - Modern CSS features transpiled (nesting, vendor prefixes)
- Bundled and inlined in
-
Fonts:
- Import via
.font.cssfiles - Base64 inlined
- Import via
-
WASM:
.c/.cpp: Compiled to WASM at build time (requires clang).wasm: Direct inclusion- All inlined as base64 data URLs
WASM Development
For C/C++ files:
// Example: src/games/life/life.c
EXPORT(init) void init();
EXPORT(step) void step();
EXPORT(getCell) int getCell(int x, int y);
// Must export memory
Requirements:
- Install:
sudo apt install clang lld wabt - Only function exports and
memorysupported
PWA Support
Production builds include PWA manifest with:
- App name from game directory name (capitalized)
- Icon from
favicon.icoorpwa_icon.png - Fullscreen display mode
Publishing
Configure .env:
PUBLISH_LOCATION=ssh.example.com:/var/www/games/
PUBLISH_URL=https://example.com/
Build and publish:
bun run build <yourgame>
Common Patterns
Game Loop Pattern
import { gameLoop } from "@common/game";
import Input from "@common/input";
interface State {
playerX: number;
playerY: number;
score: number;
}
const setup = (): State => {
return {
playerX: 100,
playerY: 100,
score: 0,
};
};
const frame = (dt: number, state: State): State => {
// Update based on input
state.playerX += Input.getHorizontal() * 200 * dt;
state.playerY += Input.getVertical() * 200 * dt;
// Update score
state.score += dt;
// Return new state (optional)
return state;
};
export default gameLoop(setup, frame);
Preact Component Pattern
import { useState, useEffect } from "preact/hooks";
import styles from './app.module.css';
export function App() {
const [count, setCount] = useState(0);
return (
<div className={styles.container}>
<h1>Count: {count}</h1>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</div>
);
}
Testing
Run tests:
bun test
Tests use Bun's built-in test framework:
import { test, expect } from "bun:test";
test("example", () => {
expect(1 + 1).toBe(2);
});
Important Notes
- No external runtime dependencies - Everything must be bundled
- Single entry point - Each game has one default export
- Game name from directory - Directory name becomes game name and URL path
- Hot reload - Dev server rebuilds on each page reload
- Mobile support - Use
?mobile=trueto enable Eruda debugger - Production debugging - Use
?production=trueto test production builds in dev
Examples
Study existing games for patterns:
- Simple:
playground/- Basic game loop example - Canvas:
binario/- Canvas-based puzzle game - Preact:
ai-story/- Complex app with components and state - WASM:
life/- C code compiled to WASM - Text-based:
text-dungeon/- Text adventure game
Troubleshooting
Build fails with "No entry point found"
- Ensure your game has
index.tsorindex.tsxwith default export
Assets not loading
- Check file extensions are supported
- Verify assets are in
src/games/<name>/assets/
WASM compilation fails
- Install clang and WASM toolchain
- Check C/C++ code compiles with
-Werror
TypeScript errors
- Run
bunx tsc --noEmit --skipLibCheckto check types - Use
@common/*path alias for common imports