Skip to content

Instantly share code, notes, and snippets.

@kwhinnery
Created May 4, 2026 14:14
Show Gist options
  • Select an option

  • Save kwhinnery/21ffc81310b3c80613b5f3cb0d1a417b to your computer and use it in GitHub Desktop.

Select an option

Save kwhinnery/21ffc81310b3c80613b5f3cb0d1a417b to your computer and use it in GitHub Desktop.
Three.js X-Wing fighter, 30 minutes of prompting GPT-5.5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>1993 DOS-Style Low Poly X-Wing</title>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.164.1/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.164.1/examples/jsm/"
}
}
</script>
<style>
html, body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #b8b8b0;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
color: #e8e8d8;
image-rendering: pixelated;
}
#app {
width: 100%;
height: 100%;
background: #b8b8b0;
}
.hud {
position: fixed;
left: 16px;
top: 14px;
z-index: 5;
padding: 10px 12px;
border: 1px solid #4f5f5f;
background: rgba(0, 0, 0, 0.82);
color: #d8d8c8;
max-width: 390px;
text-shadow: 1px 1px #000;
}
.hud h1 {
margin: 0 0 6px;
font-size: 14px;
line-height: 1;
color: #f0f0d8;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.hud p {
margin: 0;
font-size: 11px;
line-height: 1.4;
color: #aaa89a;
}
.tag-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
align-items: center;
}
.tag, .status {
display: inline-block;
padding: 3px 6px;
border: 1px solid #5f5f4f;
font-size: 10px;
text-transform: uppercase;
}
.tag {
color: #ff4a3d;
background: #180000;
}
.status {
color: #7cff9a;
background: #001808;
}
.status.error {
color: #ff6a5d;
background: #180000;
}
</style>
</head>
<body>
<div id="app"></div>
<div class="hud">
<h1>X-WING POLYGON OBJECT</h1>
<p>Flat shaded. Solid colors only. No textures. No PBR. Drag to orbit. Scroll to zoom.</p>
<div class="tag-row">
<span class="tag">1993 DOS STYLE</span>
<span id="test-status" class="status">Booting</span>
</div>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const mount = document.getElementById('app');
const testStatus = document.getElementById('test-status');
function setTestStatus(message, isError = false) {
testStatus.textContent = message;
testStatus.classList.toggle('error', isError);
}
function assertSmokeTest(condition, message) {
console.assert(condition, message);
if (!condition) throw new Error(`Smoke test failed: ${message}`);
}
function nearlyEqual(a, b, epsilon = 0.0001) {
return Math.abs(a - b) < epsilon;
}
try {
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xb8b8b0);
const camera = new THREE.PerspectiveCamera(44, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(8, 4.3, 11);
const renderer = new THREE.WebGLRenderer({ antialias: false, alpha: false });
renderer.setPixelRatio(1);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.shadowMap.enabled = false;
mount.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.target.set(0, 0.15, -0.2);
controls.minDistance = 5;
controls.maxDistance = 32;
function makeFlatMat(color) {
return new THREE.MeshLambertMaterial({
color,
flatShading: true,
dithering: false,
side: THREE.DoubleSide
});
}
const matHull = makeFlatMat(0xc6c4b0);
const matHullLight = makeFlatMat(0xd9d6c2);
const matHullDark = makeFlatMat(0x77766a);
const matRed = makeFlatMat(0xb51b16);
const matBlue = makeFlatMat(0x203b87);
const matBlack = makeFlatMat(0x050505);
const matGray = makeFlatMat(0x6e6d62);
const matStar = new THREE.PointsMaterial({ color: 0x5f5f58, size: 1.4, sizeAttenuation: false });
function addMesh(group, geometry, material, position = [0, 0, 0], rotation = [0, 0, 0], scale = [1, 1, 1]) {
geometry.computeVertexNormals();
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(...position);
mesh.rotation.set(...rotation);
mesh.scale.set(...scale);
group.add(mesh);
return mesh;
}
function makeWedge(length, height, widthFront, widthBack) {
const l = length / 2;
const h = height / 2;
const wf = widthFront / 2;
const wb = widthBack / 2;
const vertices = new Float32Array([
-wf, -h, -l, wf, -h, -l, wf, h, -l, -wf, h, -l,
-wb, -h, l, wb, -h, l, wb, h, l, -wb, h, l
]);
const indices = [
0, 1, 2, 0, 2, 3,
4, 6, 5, 4, 7, 6,
0, 4, 5, 0, 5, 1,
3, 2, 6, 3, 6, 7,
1, 5, 6, 1, 6, 2,
0, 3, 7, 0, 7, 4
];
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(indices);
return geometry;
}
function makeFlatNoseCone(length, radiusFront, radiusBack, sides = 8) {
const l = length / 2;
const vertices = [];
for (let i = 0; i < sides; i++) {
const a = (i / sides) * Math.PI * 2 + Math.PI / sides;
vertices.push(Math.cos(a) * radiusFront, Math.sin(a) * radiusFront, -l);
}
for (let i = 0; i < sides; i++) {
const a = (i / sides) * Math.PI * 2 + Math.PI / sides;
vertices.push(Math.cos(a) * radiusBack, Math.sin(a) * radiusBack, l);
}
const indices = [];
for (let i = 0; i < sides; i++) {
const n = (i + 1) % sides;
indices.push(i, n, sides + n, i, sides + n, sides + i);
}
for (let i = 1; i < sides - 1; i++) {
indices.push(0, i + 1, i);
indices.push(sides, sides + i, sides + i + 1);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3));
geometry.setIndex(indices);
return geometry;
}
function makeLowPolyTube(length, radiusFront, radiusBack, sides = 8) {
const l = length / 2;
const vertices = [];
for (let i = 0; i < sides; i++) {
const a = (i / sides) * Math.PI * 2 + Math.PI / sides;
vertices.push(Math.cos(a) * radiusFront, Math.sin(a) * radiusFront, -l);
}
for (let i = 0; i < sides; i++) {
const a = (i / sides) * Math.PI * 2 + Math.PI / sides;
vertices.push(Math.cos(a) * radiusBack, Math.sin(a) * radiusBack, l);
}
const indices = [];
for (let i = 0; i < sides; i++) {
const n = (i + 1) % sides;
indices.push(i, n, sides + n, i, sides + n, sides + i);
}
for (let i = 1; i < sides - 1; i++) {
indices.push(0, i, i + 1);
indices.push(sides, sides + i + 1, sides + i);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3));
geometry.setIndex(indices);
return geometry;
}
function makeTriangleWing(length, rootWidth, tipWidth, thickness) {
const x0 = 0;
const x1 = length;
const zRootFront = -rootWidth / 2;
const zRootBack = rootWidth / 2;
const zTipFront = -tipWidth / 2;
const zTipBack = tipWidth / 2;
const yTop = thickness / 2;
const yBot = -thickness / 2;
const vertices = new Float32Array([
x0, yTop, zRootFront, x0, yTop, zRootBack, x1, yTop, zTipBack, x1, yTop, zTipFront,
x0, yBot, zRootFront, x0, yBot, zRootBack, x1, yBot, zTipBack, x1, yBot, zTipFront
]);
const indices = [
0, 1, 2, 0, 2, 3,
4, 6, 5, 4, 7, 6,
0, 4, 5, 0, 5, 1,
1, 5, 6, 1, 6, 2,
2, 6, 7, 2, 7, 3,
3, 7, 4, 3, 4, 0
];
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(indices);
return geometry;
}
function makeSquaredHalfCylinderCanopyFront(length = 0.62, width = 0.72, height = 0.27) {
const z0 = -length / 2;
const z1 = length / 2;
const halfWBack = width / 2;
const halfWFront = halfWBack * 0.58;
const sideHBack = height * 0.35;
const sideHFront = sideHBack * 0.78;
const topHBack = height;
const topHFront = height * 0.72;
const vertices = new Float32Array([
-halfWFront, 0, z0,
halfWFront, 0, z0,
halfWFront, sideHFront, z0,
halfWFront * 0.42, topHFront, z0,
-halfWFront * 0.42, topHFront, z0,
-halfWFront, sideHFront, z0,
-halfWBack, 0, z1,
halfWBack, 0, z1,
halfWBack, sideHBack, z1,
halfWBack * 0.42, topHBack, z1,
-halfWBack * 0.42, topHBack, z1,
-halfWBack, sideHBack, z1
]);
const indices = [
0, 1, 2, 0, 2, 5, 5, 2, 3, 5, 3, 4,
6, 8, 7, 6, 11, 8, 11, 9, 8, 11, 10, 9,
0, 6, 7, 0, 7, 1,
1, 7, 8, 1, 8, 2,
2, 8, 9, 2, 9, 3,
3, 9, 10, 3, 10, 4,
4, 10, 11, 4, 11, 5,
5, 11, 6, 5, 6, 0
];
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(indices);
return geometry;
}
function makeSquaredHalfCylinderCanopyBack(length = 0.42, width = 0.72, height = 0.27) {
const z0 = -length / 2;
const z1 = length / 2;
const halfW = width / 2;
const sideH = height * 0.35;
const topH = height;
const vertices = new Float32Array([
-halfW, 0, z0,
halfW, 0, z0,
halfW, sideH, z0,
halfW * 0.42, topH, z0,
-halfW * 0.42, topH, z0,
-halfW, sideH, z0,
-halfW, 0, z1,
halfW, 0, z1,
halfW, sideH, z1,
halfW * 0.42, topH, z1,
-halfW * 0.42, topH, z1,
-halfW, sideH, z1
]);
const indices = [
0, 1, 2, 0, 2, 5, 5, 2, 3, 5, 3, 4,
6, 8, 7, 6, 11, 8, 11, 9, 8, 11, 10, 9,
0, 6, 7, 0, 7, 1,
1, 7, 8, 1, 8, 2,
2, 8, 9, 2, 9, 3,
3, 9, 10, 3, 10, 4,
4, 10, 11, 4, 11, 5,
5, 11, 6, 5, 6, 0
];
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(indices);
return geometry;
}
function makeOctagonalPrismZ(radius, depth) {
const zFront = -depth / 2;
const zBack = depth / 2;
const vertices = [];
for (let i = 0; i < 8; i++) {
const a = (i / 8) * Math.PI * 2 + Math.PI / 8;
vertices.push(Math.cos(a) * radius, Math.sin(a) * radius, zFront);
}
for (let i = 0; i < 8; i++) {
const a = (i / 8) * Math.PI * 2 + Math.PI / 8;
vertices.push(Math.cos(a) * radius, Math.sin(a) * radius, zBack);
}
const indices = [];
for (let i = 0; i < 8; i++) {
const n = (i + 1) % 8;
indices.push(i, n, 8 + n, i, 8 + n, 8 + i);
}
for (let i = 1; i < 7; i++) {
indices.push(0, i, i + 1);
indices.push(8, 8 + i + 1, 8 + i);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3));
geometry.setIndex(indices);
return geometry;
}
function createTubeFacetStripe({ sideX, zStart, zEnd, tubeCenterZ, tubeLength, radiusFront, radiusBack, scaleX, scaleY, name, insetA = 0.18, insetB = 0.82, lift = 0.018 }) {
const tubeFrontZ = tubeCenterZ - tubeLength / 2;
function radiusAt(z) {
const t = THREE.MathUtils.clamp((z - tubeFrontZ) / tubeLength, 0, 1);
return THREE.MathUtils.lerp(radiusFront, radiusBack, t);
}
const a0 = sideX > 0 ? -Math.PI / 8 : 7 * Math.PI / 8;
const a1 = sideX > 0 ? Math.PI / 8 : 9 * Math.PI / 8;
const normalAngle = sideX > 0 ? 0 : Math.PI;
const normalLift = new THREE.Vector3(Math.cos(normalAngle) * lift, Math.sin(normalAngle) * lift, 0);
function pointOnFacet(z, inset) {
const r = radiusAt(z);
const edgeA = new THREE.Vector3(Math.cos(a0) * r * scaleX, Math.sin(a0) * r * scaleY, z);
const edgeB = new THREE.Vector3(Math.cos(a1) * r * scaleX, Math.sin(a1) * r * scaleY, z);
return edgeA.lerp(edgeB, inset).add(normalLift);
}
const corners = [
pointOnFacet(zStart, insetA),
pointOnFacet(zStart, insetB),
pointOnFacet(zEnd, insetB),
pointOnFacet(zEnd, insetA)
];
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(corners.flatMap((p) => [p.x, p.y, p.z])), 3));
geometry.setIndex([0, 1, 2, 0, 2, 3]);
const stripe = addMesh(fuselage, geometry, matRed);
stripe.name = name;
return stripe;
}
function createTubeTopStripe({ zStart, zEnd, tubeCenterZ, tubeCenterY = 0, tubeLength, radiusFront, radiusBack, scaleX, scaleY, name, insetA = 0.30, insetB = 0.70, lift = 0.01 }) {
const tubeFrontZ = tubeCenterZ - tubeLength / 2;
function radiusAt(z) {
const t = THREE.MathUtils.clamp((z - tubeFrontZ) / tubeLength, 0, 1);
return THREE.MathUtils.lerp(radiusFront, radiusBack, t);
}
const a0 = 3 * Math.PI / 8;
const a1 = 5 * Math.PI / 8;
const normalLift = new THREE.Vector3(0, lift, 0);
function pointOnFacet(z, inset) {
const r = radiusAt(z);
const edgeA = new THREE.Vector3(Math.cos(a0) * r * scaleX, tubeCenterY + Math.sin(a0) * r * scaleY, z);
const edgeB = new THREE.Vector3(Math.cos(a1) * r * scaleX, tubeCenterY + Math.sin(a1) * r * scaleY, z);
return edgeA.lerp(edgeB, inset).add(normalLift);
}
const corners = [
pointOnFacet(zStart, insetA),
pointOnFacet(zStart, insetB),
pointOnFacet(zEnd, insetB),
pointOnFacet(zEnd, insetA)
];
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(corners.flatMap((p) => [p.x, p.y, p.z])), 3));
geometry.setIndex([0, 1, 2, 0, 2, 3]);
const stripe = addMesh(fuselage, geometry, matRed);
stripe.name = name;
return stripe;
}
const root = new THREE.Group();
root.name = 'xwing_1993_low_poly_root';
root.rotation.y = Math.PI * 0.14;
root.rotation.x = -Math.PI * 0.035;
scene.add(root);
const fuselage = new THREE.Group();
fuselage.name = 'fuselage';
root.add(fuselage);
const mainFuselage = addMesh(fuselage, makeLowPolyTube(3.40, 0.56, 0.66, 8), matHull, [0, 0, -0.605], [0, 0, 0], [1.04, 0.86, 1]);
mainFuselage.name = 'solid_main_fuselage_tube';
const forwardFuselage = addMesh(fuselage, makeLowPolyTube(3.55, 0.34, 0.48, 8), matHull, [0, -0.02, -3.72], [0, 0, 0], [1.04, 0.86, 1]);
forwardFuselage.name = 'solid_forward_fuselage';
const noseCone = addMesh(fuselage, makeFlatNoseCone(0.86, 0.15, 0.49, 8), matHullDark, [0, -0.03, -5.93], [0, 0, 0], [1.04, 0.86, 1]);
noseCone.name = 'solid_nose_cone';
const rearCapZ = -0.605 + 3.40 / 2;
const rearPanelDepth = 0.18;
const rearPanelZ = rearCapZ + rearPanelDepth / 2;
const rearMachineryPanels = [
addMesh(fuselage, makeOctagonalPrismZ(0.38, rearPanelDepth), matHullDark, [0, 0, rearPanelZ], [0, 0, 0])
];
rearMachineryPanels[0].name = 'rear_centered_octagon_machinery_panel';
const cockpitGlass = addMesh(fuselage, makeSquaredHalfCylinderCanopyFront(), matBlack, [0, 0.43, -1.42], [0, 0, 0]);
cockpitGlass.name = 'squared_half_cylinder_cockpit_glass';
const cockpitRear = addMesh(fuselage, makeSquaredHalfCylinderCanopyBack(), matHullLight, [0, 0.43, -0.90], [0, 0, 0]);
cockpitRear.name = 'solid_light_gray_cockpit_rear';
const astromechSocket = addMesh(fuselage, new THREE.CylinderGeometry(0.30, 0.30, 0.045, 8), matHullDark, [0, 0.49, 0.08], [0, 0, 0]);
astromechSocket.name = 'astromech_socket';
const astromechBody = addMesh(fuselage, new THREE.CylinderGeometry(0.22, 0.22, 0.14, 8), matHullLight, [0, 0.56, 0.08], [0, 0, 0]);
astromechBody.name = 'astromech_body';
const astromechDome = addMesh(fuselage, new THREE.SphereGeometry(0.22, 8, 4, 0, Math.PI * 2, 0, Math.PI / 2), matBlue, [0, 0.63, 0.08]);
astromechDome.name = 'astromech_dome';
const fuselageSideStripes = [
createTubeFacetStripe({ sideX: 1, zStart: -4.15, zEnd: -2.05, tubeCenterZ: -3.72, tubeLength: 3.55, radiusFront: 0.34, radiusBack: 0.48, scaleX: 1.04, scaleY: 0.86, name: 'paint_stripe_forward_right' }),
createTubeFacetStripe({ sideX: -1, zStart: -4.15, zEnd: -2.05, tubeCenterZ: -3.72, tubeLength: 3.55, radiusFront: 0.34, radiusBack: 0.48, scaleX: 1.04, scaleY: 0.86, name: 'paint_stripe_forward_left' }),
createTubeFacetStripe({ sideX: 1, zStart: -2.05, zEnd: 0.70, tubeCenterZ: -0.605, tubeLength: 3.40, radiusFront: 0.56, radiusBack: 0.66, scaleX: 1.04, scaleY: 0.86, name: 'paint_stripe_rear_right', insetA: 0.18, insetB: 0.82, lift: 0.02 }),
createTubeFacetStripe({ sideX: -1, zStart: -2.05, zEnd: 0.70, tubeCenterZ: -0.605, tubeLength: 3.40, radiusFront: 0.56, radiusBack: 0.66, scaleX: 1.04, scaleY: 0.86, name: 'paint_stripe_rear_left', insetA: 0.18, insetB: 0.82, lift: 0.02 })
];
const forwardTopPaint = createTubeTopStripe({ zStart: -4.45, zEnd: -3.95, tubeCenterZ: -3.72, tubeCenterY: -0.02, tubeLength: 3.55, radiusFront: 0.34, radiusBack: 0.48, scaleX: 1.04, scaleY: 0.86, name: 'paint_panel_forward_top' });
addMesh(fuselage, new THREE.BoxGeometry(0.42, 0.04, 0.38, 1, 1, 1), matRed, [0, 0.46, 0.95]);
const wings = [];
const engines = [];
const engineOuterNacelles = [];
const cannons = [];
const cannonSleeves = [];
const wingShoulders = [];
for (const sx of [-1, 1]) {
const shoulder = addMesh(root, new THREE.BoxGeometry(0.72, 0.18, 0.92, 1, 1, 1), matHullDark, [sx * 0.62, 0.00, -0.18], [0, 0, sx * 0.08]);
shoulder.name = `wing_shoulder_${sx > 0 ? 'right' : 'left'}`;
wingShoulders.push(shoulder);
}
function createWing(sideX, sideY) {
const hinge = new THREE.Group();
hinge.name = `hinge_${sideX > 0 ? 'right' : 'left'}_${sideY > 0 ? 'upper' : 'lower'}`;
hinge.position.set(sideX * 0.32, sideY * 0.12, -0.06);
hinge.rotation.z = sideX * sideY * 0.23;
root.add(hinge);
const wing = new THREE.Group();
wing.name = `wing_${sideX > 0 ? 'right' : 'left'}_${sideY > 0 ? 'upper' : 'lower'}`;
hinge.add(wing);
const wingMesh = addMesh(wing, makeTriangleWing(4.15, 1.72, 0.82, 0.08), matHull, [0, sideY * 0.02, 0.18], [0, 0, 0], [sideX, 1, 1]);
wingMesh.name = `slab_${wing.name}`;
addMesh(wing, new THREE.BoxGeometry(0.86, 0.09, 0.48, 1, 1, 1), matRed, [sideX * 3.05, sideY * 0.035, 0.02], [0, 0, 0]);
const engine = new THREE.Group();
engine.name = `engine_${wing.name}`;
engine.position.set(sideX * 0.48, sideY * 0.10, 0.80);
wing.add(engine);
const outerNacelle = addMesh(engine, new THREE.CylinderGeometry(0.312, 0.312, 1.219, 8), matGray, [0, 0, -0.90], [Math.PI / 2, 0, 0]);
outerNacelle.name = `outer_nacelle_${engine.name}`;
engineOuterNacelles.push(outerNacelle);
addMesh(engine, new THREE.CylinderGeometry(0.198, 0.198, 1.742, 8), matGray, [0, 0, 0], [Math.PI / 2, 0, 0]);
addMesh(engine, new THREE.CylinderGeometry(0.144, 0.144, 0.05, 8), matBlack, [0, 0, 0.896], [Math.PI / 2, 0, 0]);
engines.push(engine);
const cannon = new THREE.Group();
cannon.name = `laser_${wing.name}`;
cannon.position.set(sideX * 4.06, sideY * 0.08, 0.18);
wing.add(cannon);
const rearSleeve = addMesh(cannon, new THREE.CylinderGeometry(0.082, 0.082, 1.04, 8), matGray, [0, 0, 0.00], [Math.PI / 2, 0, 0]);
rearSleeve.name = `rear_sleeve_${cannon.name}`;
cannonSleeves.push(rearSleeve);
addMesh(cannon, new THREE.CylinderGeometry(0.032, 0.032, 2.7, 6), matHullLight, [0, 0, -1.05], [Math.PI / 2, 0, 0]);
addMesh(cannon, new THREE.BoxGeometry(0.20, 0.20, 0.24, 1, 1, 1), matGray, [0, 0, -2.43]);
addMesh(cannon, new THREE.ConeGeometry(0.13, 0.32, 4), matHullDark, [0, 0, -2.66], [Math.PI / 2, Math.PI / 4, 0]);
cannons.push(cannon);
wings.push(hinge);
return hinge;
}
for (const sx of [-1, 1]) {
for (const sy of [-1, 1]) createWing(sx, sy);
}
const starsGeo = new THREE.BufferGeometry();
const starCount = 180;
const starPositions = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
starPositions[i * 3] = THREE.MathUtils.randFloatSpread(180);
starPositions[i * 3 + 1] = THREE.MathUtils.randFloatSpread(95);
starPositions[i * 3 + 2] = THREE.MathUtils.randFloat(-220, -35);
}
starsGeo.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
const stars = new THREE.Points(starsGeo, matStar);
stars.name = 'static_dos_starfield';
scene.add(stars);
scene.add(new THREE.AmbientLight(0x3f3f3f, 1.25));
const key = new THREE.DirectionalLight(0xffffff, 1.85);
key.position.set(4, 6, 8);
scene.add(key);
const fill = new THREE.DirectionalLight(0x888866, 0.55);
fill.position.set(-6, -3, -7);
scene.add(fill);
function runSmokeTests() {
assertSmokeTest(typeof THREE.Scene === 'function', 'THREE module is loaded');
assertSmokeTest(typeof OrbitControls === 'function', 'OrbitControls module is loaded');
assertSmokeTest(renderer.domElement instanceof HTMLCanvasElement, 'renderer created a canvas');
assertSmokeTest(document.querySelectorAll('script[type="module"]').length === 1, 'document contains exactly one module script');
assertSmokeTest(root.children.includes(fuselage), 'fuselage is attached to the starfighter root');
assertSmokeTest(fuselage.getObjectByName('solid_main_fuselage_tube')?.geometry.index.count === 84, 'main fuselage is an eight-sided solid tube');
assertSmokeTest(fuselage.getObjectByName('solid_main_fuselage_tube')?.position.z === -0.605, 'main fuselage front seam stays fixed while rear cap is shortened');
assertSmokeTest(fuselage.getObjectByName('solid_forward_fuselage')?.geometry.index.count === 84, 'forward fuselage is an eight-sided solid tube');
assertSmokeTest(fuselage.getObjectByName('solid_nose_cone')?.geometry.index.count === 84, 'nose cone is an eight-sided flat-capped truncated cone');
assertSmokeTest(rearMachineryPanels.length === 1, 'one centered rear machinery surface panel was created');
assertSmokeTest(rearMachineryPanels[0].geometry.index.count === 84, 'rear machinery panel is an extruded octagonal prism');
assertSmokeTest(rearMachineryPanels[0].geometry.attributes.position.count === 16, 'rear machinery panel has eight front vertices and eight rear vertices');
assertSmokeTest(rearMachineryPanels[0].geometry.attributes.position.getX(0) > 0.35, 'rear machinery panel is about 75 percent of the fuselage rear diameter');
assertSmokeTest(Math.abs(rearMachineryPanels[0].geometry.attributes.position.getZ(8) - rearMachineryPanels[0].geometry.attributes.position.getZ(0) - rearPanelDepth) < 0.0001, 'rear machinery panel has shallow physical depth');
assertSmokeTest(rearMachineryPanels[0].position.x === 0 && rearMachineryPanels[0].position.y === 0, 'rear machinery panel is centered on the rear fuselage cap');
assertSmokeTest(Math.abs((rearMachineryPanels[0].position.z - rearPanelDepth / 2) - rearCapZ) < 0.0001, 'rear machinery panel front face is seated on the rear fuselage cap');
assertSmokeTest(rearMachineryPanels[0].rotation.x === 0 && rearMachineryPanels[0].rotation.y === 0 && rearMachineryPanels[0].rotation.z === 0, 'rear machinery panel protrudes straight back without angled mesh rotation');
assertSmokeTest(!fuselage.getObjectByName('cockpit_base_seated_on_fuselage'), 'cockpit base wedge has been removed');
assertSmokeTest(fuselage.getObjectByName('squared_half_cylinder_cockpit_glass')?.geometry.index.count === 60, 'cockpit glass uses a tapered front with the same rear cross-section as the rear section');
assertSmokeTest(Math.abs(fuselage.getObjectByName('squared_half_cylinder_cockpit_glass').geometry.attributes.position.getX(0)) < Math.abs(fuselage.getObjectByName('squared_half_cylinder_cockpit_glass').geometry.attributes.position.getX(6)), 'cockpit glass tapers narrower at the front while staying full width at the rear');
assertSmokeTest(fuselage.getObjectByName('squared_half_cylinder_cockpit_glass')?.position.z === -1.42, 'cockpit glass is positioned flush against the rear section');
assertSmokeTest(fuselage.getObjectByName('squared_half_cylinder_cockpit_glass')?.position.y === 0.43, 'cockpit glass sits close to the top of the fuselage');
assertSmokeTest(fuselage.getObjectByName('solid_light_gray_cockpit_rear')?.geometry.index.count === 60, 'cockpit has a solid light-gray squared half-cylinder rear section');
assertSmokeTest(fuselage.getObjectByName('solid_light_gray_cockpit_rear')?.position.y === 0.43, 'cockpit rear sits close to the top of the fuselage');
assertSmokeTest(Math.abs((fuselage.getObjectByName('squared_half_cylinder_cockpit_glass').position.z + 0.31) - (fuselage.getObjectByName('solid_light_gray_cockpit_rear').position.z - 0.21)) < 0.0001, 'cockpit glass rear edge lines up flush with solid rear front edge');
assertSmokeTest(fuselage.getObjectByName('astromech_socket'), 'astromech socket exists on the main fuselage');
assertSmokeTest(fuselage.getObjectByName('astromech_body')?.position.z === 0.08, 'astromech body is seated forward on the fuselage, not floating off the back');
assertSmokeTest(fuselage.getObjectByName('astromech_socket')?.position.y === 0.49, 'astromech socket is lowered onto the fuselage');
assertSmokeTest(fuselage.getObjectByName('astromech_body')?.position.y === 0.56, 'astromech body is lowered into its socket');
assertSmokeTest(fuselage.getObjectByName('astromech_dome')?.position.y < 0.66, 'astromech dome is low enough to sit in its socket');
assertSmokeTest(fuselageSideStripes.length === 4, 'four side paint stripe segments were created');
assertSmokeTest(fuselage.getObjectByName('paint_stripe_forward_right'), 'right forward paint stripe exists');
assertSmokeTest(fuselage.getObjectByName('paint_stripe_forward_left'), 'left forward paint stripe exists');
assertSmokeTest(fuselage.getObjectByName('paint_stripe_rear_right'), 'right rear paint stripe continuation exists');
assertSmokeTest(fuselage.getObjectByName('paint_stripe_rear_left'), 'left rear paint stripe continuation exists');
assertSmokeTest(fuselage.getObjectByName('paint_panel_forward_top'), 'forward top paint panel exists');
assertSmokeTest(forwardTopPaint.geometry.index.count === 6, 'forward top paint panel is a flat two-triangle surface panel');
assertSmokeTest(forwardTopPaint.geometry.attributes.position.getY(0) < 0.37, 'forward top paint panel is flush with the forward fuselage instead of floating above it');
assertSmokeTest(fuselageSideStripes.every((stripe) => stripe.geometry.index.count === 6), 'each side paint stripe is a flat two-triangle panel');
assertSmokeTest(fuselage.getObjectByName('paint_stripe_forward_right').geometry.attributes.position.getZ(2) === fuselage.getObjectByName('paint_stripe_rear_right').geometry.attributes.position.getZ(0), 'right forward and rear fuselage stripes touch continuously');
assertSmokeTest(fuselage.getObjectByName('paint_stripe_forward_left').geometry.attributes.position.getZ(2) === fuselage.getObjectByName('paint_stripe_rear_left').geometry.attributes.position.getZ(0), 'left forward and rear fuselage stripes touch continuously');
assertSmokeTest(wingShoulders.length === 2, 'two wing shoulder connector blocks were created');
assertSmokeTest(wings.length === 4, 'four transform-hinged S-foils were created');
assertSmokeTest(wings.every((hinge) => {
const wing = hinge.children[0];
const slab = wing?.getObjectByName(`slab_${wing.name}`);
const positions = slab?.geometry.attributes.position;
if (!positions) return false;
const rootChord = positions.getZ(1) - positions.getZ(0);
const tipChord = positions.getZ(2) - positions.getZ(3);
return nearlyEqual(rootChord, 1.72) && nearlyEqual(tipChord, 0.82);
}), 'wing root and tip chords are broadened by about 50 percent');
assertSmokeTest(Math.abs(wings[0].position.x) < 0.4, 'wing hinges sit close enough to overlap the fuselage');
assertSmokeTest(wings.every((hinge) => nearlyEqual(hinge.position.z, -0.06)), 'wing assemblies are scooted forward to match the shortened rear fuselage');
assertSmokeTest(wingShoulders.every((shoulder) => nearlyEqual(shoulder.position.z, -0.18)), 'wing shoulder blocks move forward with the wing assemblies');
assertSmokeTest(engines.length === 4, 'four faceted engines were created');
assertSmokeTest(engines.every((engine) => nearlyEqual(Math.abs(engine.position.x), 0.48)), 'inner and outer engine assemblies are moved outward from the fuselage together');
assertSmokeTest(engines.every((engine) => {
const body = engine.children.find((child) => child.geometry?.parameters?.height === 1.742);
return body && nearlyEqual(body.geometry.parameters.radiusTop, 0.198) && nearlyEqual(body.geometry.parameters.radiusBottom, 0.198);
}), 'inner engine cylinders remain about 40 percent smaller in diameter and 30 percent longer');
assertSmokeTest(engineOuterNacelles.length === 4, 'four wider outer engine nacelles were created');
assertSmokeTest(engineOuterNacelles.every((nacelle) => nearlyEqual(nacelle.geometry.parameters.radiusTop, 0.312) && nearlyEqual(nacelle.geometry.parameters.radiusBottom, 0.312) && nearlyEqual(nacelle.geometry.parameters.height, 1.219)), 'outer engine nacelles are 30 percent smaller in diameter and remain 70 percent as long as the inner cylinders');
assertSmokeTest(engineOuterNacelles.every((nacelle) => nearlyEqual(nacelle.position.z, -0.90)), 'outer engine nacelles are moved forward on the wings');
assertSmokeTest(engines.every((engine) => {
const nacelle = engine.children.find((child) => child.name.startsWith('outer_nacelle_'));
if (!nacelle) return false;
const wingLocalFront = engine.position.z + nacelle.position.z - nacelle.geometry.parameters.height / 2;
return wingLocalFront < -0.68;
}), 'front of each outer nacelle overlaps the front edge of the broadened wing root');
assertSmokeTest(cannons.length === 4, 'four laser cannons were created');
assertSmokeTest(cannonSleeves.length === 4, 'four rear laser cannon sleeves were created');
assertSmokeTest(cannonSleeves.every((sleeve) => sleeve.geometry.parameters.radialSegments === 8), 'rear laser cannon sleeves are low-poly thin cylinders');
assertSmokeTest(cannonSleeves.every((sleeve) => nearlyEqual(sleeve.geometry.parameters.height, 1.04)), 'rear laser cannon sleeves span the widened wing tips');
assertSmokeTest(cannons.every((cannon) => Math.abs(cannon.position.x) < 4.15), 'laser cannon mounts overlap the wing tips instead of floating beyond them');
assertSmokeTest(cannons.every((cannon) => nearlyEqual(cannon.position.z, 0.18)), 'laser cannon hardpoints are centered on the broadened wing tip chord');
assertSmokeTest(stars.geometry.attributes.position.count === starCount, 'starfield has the expected number of stars');
assertSmokeTest(renderer.getPixelRatio() === 1, 'pixel ratio is locked to 1 for a retro jagged render');
assertSmokeTest(!renderer.shadowMap.enabled, 'shadows are disabled for flat retro rendering');
setTestStatus('Tests passed');
}
runSmokeTests();
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const t = clock.getElapsedTime();
root.rotation.y = Math.PI * 0.14 + Math.sin(t * 0.22) * 0.08;
root.position.y = Math.sin(t * 0.55) * 0.045;
controls.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
} catch (error) {
console.error(error);
setTestStatus('Error', true);
const message = document.createElement('pre');
message.textContent = error instanceof Error ? error.message : String(error);
message.style.position = 'fixed';
message.style.left = '16px';
message.style.bottom = '16px';
message.style.maxWidth = 'min(720px, calc(100vw - 32px))';
message.style.whiteSpace = 'pre-wrap';
message.style.padding = '10px 12px';
message.style.border = '1px solid #ff4a3d';
message.style.background = '#180000';
message.style.color = '#ffd0cc';
message.style.zIndex = '10';
document.body.appendChild(message);
}
</script>
</body>
engine.position.set(sideX * 0.48, sideY * 0.10, 0.80);
wing.add(engine);
// Larger outer nacelle: same color, 50% wider diameter, 70% as long.
// It overlaps the existing inner engine cylinder to create a simple multi-part engine look.
const outerNacelle = addMesh(engine, new THREE.CylinderGeometry(0.312, 0.312, 1.219, 8), matGray, [0, 0, -0.90], [Math.PI / 2, 0, 0]);
outerNacelle.name = `outer_nacelle_${engine.name}`;
engineOuterNacelles.push(outerNacelle);
addMesh(engine, new THREE.CylinderGeometry(0.198, 0.198, 1.742, 8), matGray, [0, 0, 0], [Math.PI / 2, 0, 0]);
addMesh(engine, new THREE.CylinderGeometry(0.144, 0.144, 0.05, 8), matBlack, [0, 0, 0.896], [Math.PI / 2, 0, 0]);
engines.push(engine);
// Extremely simple laser cannon: line-like 6-sided cylinder plus chunky tip.
const cannon = new THREE.Group();
cannon.name = `laser_${wing.name}`;
// Mount slightly inside the wing tip and centered on the widened wing's tip chord.
// After broadening the wings, the old Z hardpoint was too far forward.
cannon.position.set(sideX * 4.06, sideY * 0.08, 0.18);
wing.add(cannon);
const rearSleeve = addMesh(cannon, new THREE.CylinderGeometry(0.082, 0.082, 1.04, 8), matGray, [0, 0, 0.00], [Math.PI / 2, 0, 0]);
rearSleeve.name = `rear_sleeve_${cannon.name}`;
cannonSleeves.push(rearSleeve);
addMesh(cannon, new THREE.CylinderGeometry(0.032, 0.032, 2.7, 6), matHullLight, [0, 0, -1.05], [Math.PI / 2, 0, 0]);
addMesh(cannon, new THREE.BoxGeometry(0.20, 0.20, 0.24, 1, 1, 1), matGray, [0, 0, -2.43]);
addMesh(cannon, new THREE.ConeGeometry(0.13, 0.32, 4), matHullDark, [0, 0, -2.66], [Math.PI / 2, Math.PI / 4, 0]);
cannons.push(cannon);
wings.push(hinge);
return hinge;
}
for (const sx of [-1, 1]) {
for (const sy of [-1, 1]) {
createWing(sx, sy);
}
}
// Sparse pixel-like starfield.
const starsGeo = new THREE.BufferGeometry();
const starCount = 180;
const starPositions = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
starPositions[i * 3] = THREE.MathUtils.randFloatSpread(180);
starPositions[i * 3 + 1] = THREE.MathUtils.randFloatSpread(95);
starPositions[i * 3 + 2] = THREE.MathUtils.randFloat(-220, -35);
}
starsGeo.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
const stars = new THREE.Points(starsGeo, matStar);
stars.name = 'static_dos_starfield';
scene.add(stars);
// Minimal fake DOS-era lighting. No shadows, no environment maps, no glow.
scene.add(new THREE.AmbientLight(0x3f3f3f, 1.25));
const key = new THREE.DirectionalLight(0xffffff, 1.85);
key.position.set(4, 6, 8);
scene.add(key);
const fill = new THREE.DirectionalLight(0x888866, 0.55);
fill.position.set(-6, -3, -7);
scene.add(fill);
function runSmokeTests() {
assertSmokeTest(typeof THREE.Scene === 'function', 'THREE module is loaded');
assertSmokeTest(typeof OrbitControls === 'function', 'OrbitControls module is loaded');
assertSmokeTest(renderer.domElement instanceof HTMLCanvasElement, 'renderer created a canvas');
assertSmokeTest(root.children.includes(fuselage), 'fuselage is attached to the starfighter root');
assertSmokeTest(fuselage.getObjectByName('solid_main_fuselage_tube')?.geometry.index.count === 84, 'main fuselage is an eight-sided solid tube');
assertSmokeTest(fuselage.getObjectByName('solid_main_fuselage_tube')?.position.z === -0.605, 'main fuselage front seam stays fixed while rear cap is shortened');
assertSmokeTest(fuselage.getObjectByName('solid_forward_fuselage')?.geometry.index.count === 84, 'forward fuselage is an eight-sided solid tube');
assertSmokeTest(fuselage.getObjectByName('solid_nose_cone')?.geometry.index.count === 84, 'nose cone is an eight-sided flat-capped truncated cone');
assertSmokeTest(rearMachineryPanels.length === 1, 'one centered rear machinery surface panel was created');
assertSmokeTest(rearMachineryPanels[0].geometry.index.count === 84, 'rear machinery panel is an extruded octagonal prism');
assertSmokeTest(rearMachineryPanels[0].geometry.attributes.position.count === 16, 'rear machinery panel has eight front vertices and eight rear vertices');
assertSmokeTest(rearMachineryPanels[0].geometry.attributes.position.getX(0) > 0.35, 'rear machinery panel is about 75 percent of the fuselage rear diameter');
assertSmokeTest(Math.abs(rearMachineryPanels[0].geometry.attributes.position.getZ(8) - rearMachineryPanels[0].geometry.attributes.position.getZ(0) - rearPanelDepth) < 0.0001, 'rear machinery panel has shallow physical depth');
assertSmokeTest(rearMachineryPanels[0].position.x === 0 && rearMachineryPanels[0].position.y === 0, 'rear machinery panel is centered on the rear fuselage cap');
assertSmokeTest(Math.abs((rearMa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment