diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..1d67199 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - run: npm ci + + - name: Syntax and structure checks + run: npm test + + - name: Build + run: npm run build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ceba27 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.claude/ +dist/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..f32b707 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# Wings of the 90s - Flight Simulator + +A period-accurate 3D flight simulator inspired by early 90s Amiga/DOS games like *Comanche*, *F-15 Strike Eagle*, and *After Burner*. Runs in any modern browser. + +## Features + +- **Authentic retro aesthetic** — 320×200 VGA Mode X resolution, CRT post-processing (barrel distortion, scanlines, color quantization, chromatic aberration, vignette), EGA 16-color palette +- **Loading screen** with progress bar and cycling messages +- **Main menu** with keyboard navigation +- **Procedural terrain** — 2000m heightmap with vertex-colored hills, water plane, low-poly clouds +- **Airport** — Runway with markings, taxiway, control tower, hangars, fuel truck, windsock +- **Low-poly Cessna-style aircraft** with animated propeller and retractable landing gear +- **6-DOF flight physics** — Thrust, drag, lift, gravity, ground friction, auto-leveling +- **Flight HUD** — Artificial horizon, airspeed, altimeter, heading tape, vertical speed indicator, throttle bar +- **Sound** — Engine drone tied to throttle, menu beeps + +## Controls + +| Key | Action | +|---|---| +| `W` / `S` | Throttle up / down | +| `↑` / `↓` | Pitch | +| `←` / `→` | Roll | +| `Q` / `E` | Yaw (rudder) | +| `C` | Toggle cockpit / chase camera | +| `Esc` | Pause / resume | + +## Getting Started + +```bash +npm install +npm run dev +``` + +Open `http://localhost:5173` in your browser. + +## Build + +```bash +npm run build +``` + +## Tech Stack + +- Three.js r160 for 3D rendering +- Vite for dev server and builds +- Vanilla JS, no frameworks +- Web Audio API for sound diff --git a/package.json b/package.json index e2b9d43..247caf1 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "scripts": { "dev": "vite", - "build": "vite build" + "build": "vite build", + "test": "node scripts/check-syntax.js" } } diff --git a/scripts/check-syntax.js b/scripts/check-syntax.js new file mode 100644 index 0000000..c27620e --- /dev/null +++ b/scripts/check-syntax.js @@ -0,0 +1,65 @@ +import { readFileSync, readdirSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = join(__dirname, '..'); +const jsDir = join(root, 'js'); + +const files = readdirSync(jsDir).filter(f => f.endsWith('.js')); + +let errors = 0; +for (const file of files) { + const path = join(jsDir, file); + const source = readFileSync(path, 'utf-8'); + // Basic sanity: file is non-empty and has balanced braces + if (source.length < 10) { + console.log(` FAIL ${file}: too small`); + errors++; + continue; + } + const openBraces = (source.match(/{/g) || []).length; + const closeBraces = (source.match(/}/g) || []).length; + if (openBraces !== closeBraces) { + console.log(` FAIL ${file}: unbalanced braces (${openBraces} open, ${closeBraces} close)`); + errors++; + continue; + } + const openParens = (source.match(/\(/g) || []).length; + const closeParens = (source.match(/\)/g) || []).length; + if (openParens !== closeParens) { + console.log(` FAIL ${file}: unbalanced parentheses`); + errors++; + continue; + } + console.log(` PASS ${file}`); +} + +const html = readFileSync(join(root, 'index.html'), 'utf-8'); +if (html.includes(' 0) process.exit(1);