Skip to content

Commit fb17c8e

Browse files
author
DavidQ
committed
Recover blocked 95A by building a smoke-testable asteroids_new vertical slice from existing Asteroids source files only.
1 parent 8f54478 commit fb17c8e

11 files changed

+337
-16
lines changed

docs/dev/CODEX_COMMANDS.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
MODEL: GPT-5.4-codex
2-
REASONING: low
2+
REASONING: high
3+
34
COMMAND:
4-
Append provided content to RULES.md and WORKFLOW.md without deleting or modifying existing content.
5+
Execute exactly docs/pr/BUILD_PR_GAMES_95B_ASTEROIDS_NEW_TESTABLE_VERTICAL_SLICE_EXISTING_SOURCES_ONLY.md.
6+
Modify only the exact target files listed in the PR doc.
7+
Do not expand scope.
8+
Package the delta zip to:
9+
<project folder>/tmp/BUILD_PR_GAMES_95B_ASTEROIDS_NEW_TESTABLE_VERTICAL_SLICE_EXISTING_SOURCES_ONLY_delta.zip

docs/dev/COMMIT_COMMENT.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Add TESTABILITY GATE and BUILD quality requirement without modifying existing content.
1+
Recover blocked 95A by building a smoke-testable asteroids_new vertical slice from existing Asteroids source files only.

docs/dev/NEXT_COMMAND.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Next: enforce testable vertical slice builds across roadmap.
1+
After commit, inspect whether one additional exact gameplay dependency is needed or whether the vertical slice is already smoke-testable enough to validate.
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
- combine multiple previously separate asteroids_new parallel-adoption slices into one larger exact-file BUILD
2-
- copy world, debug shell, and two entity files into the parallel lane
3-
- allow only the minimum path/import fixes inside asteroids_new index/flow files
4-
- keep the original Asteroids game untouched
1+
- replace blocked 95A with an existing-sources-only vertical slice BUILD
2+
- use current asteroids_new boot/flow shell instead of missing original Asteroids boot/flow files
3+
- copy only world/debug/core-entity files that actually exist in the repo
4+
- keep original Asteroids files untouched

docs/dev/reports/file_tree.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
docs/pr/BUILD_PR_GAMES_94A_ASTEROIDS_NEW_PARALLEL_BURST_ALPHA.md
1+
docs/pr/BUILD_PR_GAMES_95B_ASTEROIDS_NEW_TESTABLE_VERTICAL_SLICE_EXISTING_SOURCES_ONLY.md
22
docs/dev/codex_commands.md
33
docs/dev/commit_comment.txt
44
docs/dev/next_command.txt
@@ -9,10 +9,14 @@ games/Asteroids/game/AsteroidsWorld.js
99
games/Asteroids/debug/asteroidsShowcaseDebug.js
1010
games/Asteroids/entities/Bullet.js
1111
games/Asteroids/entities/Ship.js
12+
games/Asteroids/entities/Asteroid.js
13+
games/Asteroids/entities/Ufo.js
1214
games/asteroids_new/index.js
1315
games/asteroids_new/flow/attract.js
1416
games/asteroids_new/flow/intro.js
1517
games/asteroids_new/game/AsteroidsWorld.js
1618
games/asteroids_new/debug/asteroidsShowcaseDebug.js
1719
games/asteroids_new/entities/Bullet.js
18-
games/asteroids_new/entities/Ship.js
20+
games/asteroids_new/entities/Ship.js
21+
games/asteroids_new/entities/Asteroid.js
22+
games/asteroids_new/entities/Ufo.js
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
- single PR purpose only
22
- exact target files only
3-
- copy-only from original Asteroids files
3+
- existing source files only
44
- no original Asteroids file changes
5-
- no systems/levels/assets widening
6-
- minimum import/path fixes only
7-
- listed asteroids_new files parse and resolve together
5+
- no missing source-file assumptions
6+
- asteroids_new files parse and import cleanly together
7+
- result is substantial enough for a real smoke test
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# BUILD PR — Asteroids New Testable Vertical Slice (Existing Sources Only)
2+
3+
## Purpose
4+
Recover from the blocked 95A BUILD by producing a smoke-testable `games/asteroids_new` slice using only source files that actually exist in the repo.
5+
6+
## Exact Source Files (copy from original Asteroids only)
7+
- `games/Asteroids/game/AsteroidsWorld.js`
8+
- `games/Asteroids/debug/asteroidsShowcaseDebug.js`
9+
- `games/Asteroids/entities/Bullet.js`
10+
- `games/Asteroids/entities/Ship.js`
11+
- `games/Asteroids/entities/Asteroid.js`
12+
- `games/Asteroids/entities/Ufo.js`
13+
14+
## Exact Destination / Touched Files
15+
- `games/asteroids_new/index.js`
16+
- `games/asteroids_new/flow/attract.js`
17+
- `games/asteroids_new/flow/intro.js`
18+
- `games/asteroids_new/game/AsteroidsWorld.js`
19+
- `games/asteroids_new/debug/asteroidsShowcaseDebug.js`
20+
- `games/asteroids_new/entities/Bullet.js`
21+
- `games/asteroids_new/entities/Ship.js`
22+
- `games/asteroids_new/entities/Asteroid.js`
23+
- `games/asteroids_new/entities/Ufo.js`
24+
25+
## Required Code Changes
26+
1. Copy the listed existing source files into the matching `games/asteroids_new` locations.
27+
2. Use the already-existing `games/asteroids_new/index.js`, `games/asteroids_new/flow/attract.js`, and `games/asteroids_new/flow/intro.js` as the parallel lane boot shell.
28+
3. Make only the minimum import/path adjustments required so this parallel lane can boot as a coherent, smoke-testable vertical slice using the files listed above.
29+
4. Remove any now-unused imports created by the parallel copy process.
30+
5. Do not invent or require missing source files.
31+
32+
## Hard Constraints
33+
- exact files only
34+
- copy only from original Asteroids files; do not move or delete source files
35+
- do not modify any original `games/Asteroids/*` files
36+
- do not require or reference these missing source files:
37+
- `games/Asteroids/index.js`
38+
- `games/Asteroids/flow/attract.js`
39+
- `games/Asteroids/flow/intro.js`
40+
- `games/Asteroids/entities/Explosion.js`
41+
- do not create new gameplay files beyond the listed destination files
42+
- do not widen into levels, assets, or unrelated systems outside the exact file list
43+
- preserve gameplay behavior as closely as possible
44+
- no repo-wide cleanup
45+
- no unrelated formatting churn
46+
47+
## Validation Steps
48+
- confirm only the exact target files above changed
49+
- syntax-check all touched `games/asteroids_new` files
50+
- confirm imports across the listed `games/asteroids_new` files resolve cleanly
51+
- confirm `games/asteroids_new/index.js` can still be used as a smoke-test entry point
52+
- confirm no original `games/Asteroids/*` files changed
53+
54+
## Acceptance Criteria
55+
- `games/asteroids_new` contains a coherent parallel boot lane with:
56+
- world
57+
- flow shell
58+
- debug shell
59+
- core entity set based only on existing source files
60+
- the listed `games/asteroids_new` files parse and import cleanly together
61+
- the result is substantial enough for a real smoke test
62+
- original Asteroids files remain untouched
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# BUILD PR — Asteroids New Full Existing Subfolder Copy (Testable)
2+
3+
## Purpose
4+
Create a fully testable asteroids_new by copying ALL existing subfolders and files from games/Asteroids into games/asteroids_new.
5+
6+
## Source Scope (MUST EXIST ONLY)
7+
Copy entire directory trees if present:
8+
- games/Asteroids/game/
9+
- games/Asteroids/debug/
10+
- games/Asteroids/entities/
11+
- games/Asteroids/flow/ (only if exists)
12+
- any other existing subfolders under games/Asteroids/
13+
14+
## Destination
15+
Mirror structure into:
16+
- games/asteroids_new/
17+
18+
## Rules
19+
- COPY EVERYTHING that exists under games/Asteroids/*
20+
- DO NOT require files that do not exist
21+
- DO NOT skip subfolders
22+
- DO NOT modify source files
23+
- DO NOT partially copy folders
24+
25+
## Required Changes
26+
- After copy, update ONLY:
27+
- games/asteroids_new/index.js
28+
- any files that break due to import paths
29+
30+
- Fix imports minimally so the copied structure runs inside asteroids_new
31+
32+
## Constraints
33+
- no deletes
34+
- no refactors
35+
- no guessing missing files
36+
- full subfolder integrity required
37+
38+
## Validation
39+
- asteroids_new mirrors all existing Asteroids subfolders
40+
- no missing imports inside asteroids_new
41+
- original Asteroids untouched
42+
- asteroids_new is runnable or near-runnable (smoke testable)
43+
44+
## Acceptance
45+
- Full subfolder parity achieved
46+
- No partial copies
47+
- Import graph resolves inside asteroids_new
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Toolbox Aid
3+
David Quesenberry
4+
03/22/2026
5+
Asteroid.js
6+
*/
7+
import { TAU, randomRange, wrap } from '../../Asteroids/utils/math.js';
8+
import { transformPoints } from '../../../src/engine/vector/index.js';
9+
10+
const BASE_VECTOR_MAP = [
11+
{ x: 10, y: 40 },
12+
{ x: 50, y: 20 },
13+
{ x: 45, y: 5 },
14+
{ x: 25, y: -10 },
15+
{ x: 50, y: -35 },
16+
{ x: 30, y: -45 },
17+
{ x: 10, y: -38 },
18+
{ x: -20, y: -45 },
19+
{ x: -43, y: -18 },
20+
{ x: -43, y: 20 },
21+
{ x: -25, y: 20 },
22+
{ x: -25, y: 40 },
23+
];
24+
25+
function centerVectorMap(points) {
26+
const xs = points.map(({ x }) => x);
27+
const ys = points.map(({ y }) => y);
28+
const centerX = (Math.min(...xs) + Math.max(...xs)) / 2;
29+
const centerY = (Math.min(...ys) + Math.max(...ys)) / 2;
30+
return points.map(({ x, y }) => ({ x: x - centerX, y: y - centerY }));
31+
}
32+
33+
function maxRadius(points) {
34+
return Math.max(...points.map(({ x, y }) => Math.sqrt(x * x + y * y)));
35+
}
36+
37+
const CENTERED_VECTOR_MAP = centerVectorMap(BASE_VECTOR_MAP);
38+
const BASE_RADIUS = maxRadius(CENTERED_VECTOR_MAP);
39+
40+
const SIZE_PROFILES = {
41+
SML: { id: 1, targetRadius: 14 },
42+
MED: { id: 2, targetRadius: 22 },
43+
LRG: { id: 3, targetRadius: 34 },
44+
};
45+
46+
const SIZE_BY_ID = {
47+
1: SIZE_PROFILES.SML,
48+
2: SIZE_PROFILES.MED,
49+
3: SIZE_PROFILES.LRG,
50+
};
51+
52+
export default class Asteroid {
53+
constructor(x, y, size = 3, rng = Math.random) {
54+
const profile = SIZE_BY_ID[size] || SIZE_PROFILES.LRG;
55+
this.x = x;
56+
this.y = y;
57+
this.vx = randomRange(-70, 70, rng);
58+
this.vy = randomRange(-70, 70, rng);
59+
this.angle = randomRange(0, TAU, rng);
60+
this.spin = randomRange(-1.4, 1.4, rng);
61+
this.size = profile.id;
62+
this.sizeLabel = Object.entries(SIZE_PROFILES).find(([, value]) => value.id === profile.id)?.[0] || 'LRG';
63+
this.scale = profile.targetRadius / BASE_RADIUS;
64+
this.radius = profile.targetRadius;
65+
}
66+
67+
update(dtSeconds, bounds) {
68+
this.x = wrap(this.x + this.vx * dtSeconds, bounds.width);
69+
this.y = wrap(this.y + this.vy * dtSeconds, bounds.height);
70+
this.angle += this.spin * dtSeconds;
71+
}
72+
73+
getPoints() {
74+
return transformPoints(CENTERED_VECTOR_MAP, {
75+
x: this.x,
76+
y: this.y,
77+
rotation: this.angle,
78+
scale: this.scale,
79+
});
80+
}
81+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
Toolbox Aid
3+
David Quesenberry
4+
03/22/2026
5+
Ufo.js
6+
*/
7+
import Bullet from './Bullet.js';
8+
import { distance } from '../../../src/engine/utils/index.js';
9+
import { transformPoints } from '../../../src/engine/vector/index.js';
10+
import { randomRange } from '../../Asteroids/utils/math.js';
11+
12+
const UFO_PROFILES = {
13+
large: {
14+
radius: 26,
15+
speed: 88,
16+
points: 200,
17+
fireInterval: [1.8, 2.8],
18+
aimJitter: 0.55,
19+
},
20+
small: {
21+
radius: 18,
22+
speed: 126,
23+
points: 1000,
24+
fireInterval: [1.1, 1.8],
25+
aimJitter: 0.16,
26+
},
27+
};
28+
29+
const VECTOR_MAPS = {
30+
small: [
31+
{ x: -14, y: 3 }, { x: 14, y: 3 }, { x: 9, y: 9 }, { x: -9, y: 9 }, { x: -14, y: 3 }, { x: -9, y: -3 },
32+
{ x: -5, y: -3 }, { x: -5, y: -6 }, { x: -2, y: -9 }, { x: 2, y: -9 }, { x: 5, y: -6 }, { x: 5, y: -3 }, { x: -5, y: -3 },
33+
{ x: 9, y: -3 }, { x: 14, y: 3 },
34+
],
35+
large: [
36+
{ x: -21, y: 4.5 }, { x: 21, y: 4.5 }, { x: 13.5, y: 13.5 }, { x: -13.5, y: 13.5 }, { x: -21, y: 4.5 }, { x: -13.5, y: -4.5 },
37+
{ x: -7.5, y: -4.5 }, { x: -7.5, y: -9 }, { x: -3, y: -13.5 }, { x: 3, y: -13.5 }, { x: 7.5, y: -9 }, { x: 7.5, y: -4.5 }, { x: -7.5, y: -4.5 },
38+
{ x: 13.5, y: -4.5 }, { x: 21, y: 4.5 },
39+
],
40+
};
41+
42+
export default class Ufo {
43+
constructor(bounds, type = 'large', level = 1, rng = Math.random) {
44+
this.bounds = bounds;
45+
this.rng = typeof rng === 'function' ? rng : Math.random;
46+
this.type = UFO_PROFILES[type] ? type : 'large';
47+
this.profile = UFO_PROFILES[this.type];
48+
this.direction = this.rng() > 0.5 ? 1 : -1;
49+
this.x = this.direction > 0 ? -48 : bounds.width + 48;
50+
this.y = randomRange(120, bounds.height - 140, this.rng);
51+
this.vx = this.profile.speed * (1 + (level - 1) * 0.05) * this.direction;
52+
this.vy = randomRange(-26, 26, this.rng);
53+
this.radius = this.profile.radius;
54+
this.points = this.profile.points;
55+
this.turnTimer = randomRange(1.2, 2.4, this.rng);
56+
this.fireTimer = randomRange(...this.profile.fireInterval, this.rng);
57+
this.alive = true;
58+
}
59+
60+
update(dtSeconds) {
61+
this.x += this.vx * dtSeconds;
62+
this.y += this.vy * dtSeconds;
63+
64+
this.turnTimer -= dtSeconds;
65+
if (this.turnTimer <= 0) {
66+
this.turnTimer = randomRange(1.1, 2.2, this.rng);
67+
this.vy = randomRange(-52, 52, this.rng);
68+
}
69+
70+
if (this.y < 88) {
71+
this.y = 88;
72+
this.vy = Math.abs(this.vy);
73+
} else if (this.y > this.bounds.height - 120) {
74+
this.y = this.bounds.height - 120;
75+
this.vy = -Math.abs(this.vy);
76+
}
77+
78+
this.fireTimer -= dtSeconds;
79+
if (this.direction > 0 && this.x > this.bounds.width + 60) {
80+
this.alive = false;
81+
} else if (this.direction < 0 && this.x < -60) {
82+
this.alive = false;
83+
}
84+
}
85+
86+
canFire() {
87+
return this.fireTimer <= 0;
88+
}
89+
90+
fireAt(target) {
91+
this.fireTimer = randomRange(...this.profile.fireInterval, this.rng);
92+
const aimAngle = Math.atan2(target.y - this.y, target.x - this.x) + randomRange(-this.profile.aimJitter, this.profile.aimJitter, this.rng);
93+
const muzzleX = this.x + Math.cos(aimAngle) * (this.radius - 2);
94+
const muzzleY = this.y + Math.sin(aimAngle) * (this.radius - 2);
95+
const shotSpeed = 250;
96+
const fullScreenLife = Math.max(1.1, this.bounds.width / shotSpeed);
97+
return new Bullet(
98+
muzzleX,
99+
muzzleY,
100+
Math.cos(aimAngle) * shotSpeed,
101+
Math.sin(aimAngle) * shotSpeed,
102+
fullScreenLife,
103+
);
104+
}
105+
106+
collidesWithPoint(point, padding = 0) {
107+
return distance(this, point) <= this.radius + padding;
108+
}
109+
110+
getCollisionPolygon() {
111+
return transformPoints(VECTOR_MAPS[this.type], {
112+
x: this.x,
113+
y: this.y,
114+
});
115+
}
116+
117+
getBodyLines() {
118+
return [
119+
this.getCollisionPolygon(),
120+
];
121+
}
122+
}

0 commit comments

Comments
 (0)