Skip to content

Commit 71acacb

Browse files
committed
Smooth Mission Run burnout comments
1 parent 751ab06 commit 71acacb

3 files changed

Lines changed: 126 additions & 16 deletions

File tree

src/data/site.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,40 @@ export const miniGame = {
6767
'NO REST',
6868
],
6969
voidPressureBursts: ['ONE MORE?', 'URGENT?', 'JUST FIX', 'ANY UPDATE?', 'WHY SLOW?', 'NO OWNER'],
70+
voidComments: [
71+
'CAN YOU PATCH?',
72+
'PROD IS DOWN',
73+
'NO REPRO',
74+
'WORKS HERE',
75+
'JUST UPGRADE',
76+
'ONE MORE PR',
77+
'ETA?',
78+
'P0 FOR FREE',
79+
'PLEASE REVIEW',
80+
'RELEASE TODAY',
81+
'HOTFIX NOW',
82+
'SECURITY?',
83+
'TINY CHANGE',
84+
'ALL CAPS',
85+
'WHY NO FIX?',
86+
'CAN WE CALL?',
87+
'STALE BOT',
88+
'TRIAGE LATER',
89+
'AFTER HOURS',
90+
'ON VACATION?',
91+
'NEEDS TESTS',
92+
'BROKE PROD',
93+
'PATCH FRIDAY',
94+
'WHO OWNS THIS?',
95+
'ISSUE TEMPLATE?',
96+
'JUST ONE LINE',
97+
'CAN YOU HELP?',
98+
'DOCS ARE WRONG',
99+
'SAME BUG',
100+
'ANY NEWS?',
101+
'FREE SUPPORT',
102+
'NO MAINTAINER',
103+
],
70104
voidGameOverTitle: 'BURNOUT CAUGHT UP',
71105
voidGameOverPrefix: 'you lasted',
72106
voidSecondLabel: 'second',

src/scripts/game.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ export function createMascotGame(): Game {
119119
const TRANSITION_LONG = 56;
120120
const PIPE_DESCENT_FRAMES = 62;
121121
const PIPE_SCENE_TRANSITION_FRAMES = PIPE_DESCENT_FRAMES * 2;
122+
const VOID_COMMENT_SPEED = 0.18;
123+
const VOID_COMMENT_SPACING = 86;
124+
const VOID_COMMENT_LANES = [60, 72, 84] as const;
122125

123126
const sound = (name: string) => {
124127
(window as Window & { __missionSound?: MissionSoundBridge }).__missionSound?.play?.(name);
@@ -1400,6 +1403,47 @@ export function createMascotGame(): Game {
14001403
}
14011404
}
14021405
const textW = (str: string, s: number) => str.length * 4 * s - s;
1406+
type VoidComment = {
1407+
slot: number;
1408+
text: string;
1409+
x: number;
1410+
y: number;
1411+
color: string;
1412+
};
1413+
const voidCommentPool = () => [...miniGame.voidSignLines, ...miniGame.voidComments];
1414+
function visibleVoidComments(): VoidComment[] {
1415+
const comments: readonly string[] = voidCommentPool();
1416+
if (comments.length === 0) return [];
1417+
const trackW = Math.max(VIEW_W + 180, comments.length * VOID_COMMENT_SPACING);
1418+
const travel = voidT * VOID_COMMENT_SPEED;
1419+
const items: VoidComment[] = [];
1420+
const colors = [C.subs, C.mobile, C.rent, C.electric, C.tax, C.water, C.fees, C.life];
1421+
1422+
for (let slot = 0; slot < comments.length; slot++) {
1423+
const text = comments[(slot * 11 + 5) % comments.length];
1424+
const width = textW(text, 1) + 8;
1425+
const raw = (slot * VOID_COMMENT_SPACING - travel) % trackW;
1426+
const x = ((raw + trackW) % trackW) - 80;
1427+
if (x < 4 || x + width > VIEW_W - 4) continue;
1428+
1429+
items.push({
1430+
slot,
1431+
text,
1432+
x,
1433+
y: VOID_COMMENT_LANES[(slot * 5 + 1) % VOID_COMMENT_LANES.length],
1434+
color: colors[(slot * 7 + 3) % colors.length],
1435+
});
1436+
}
1437+
1438+
return items;
1439+
}
1440+
function drawVoidComment(comment: VoidComment) {
1441+
const w = textW(comment.text, 1) + 8;
1442+
const wx = camX + comment.x;
1443+
block(wx - 4, comment.y - 4, w, 13, C.sign);
1444+
block(wx - 4, comment.y - 4, w, 1, comment.color);
1445+
text(comment.text, wx, comment.y - 2, 1, C.starHi);
1446+
}
14031447
function drawPixelMask(x: number, y: number, rows: string[], color: string) {
14041448
for (let r = 0; r < rows.length; r++)
14051449
for (let c = 0; c < rows[r].length; c++)
@@ -1821,15 +1865,7 @@ export function createMascotGame(): Game {
18211865
block(x, VOID_GROUND + 3, TILE, 2, C.sign);
18221866
}
18231867

1824-
const signColors = [C.subs, C.mobile, C.rent, C.electric, C.tax, C.water, C.fees, C.life];
1825-
miniGame.voidSignLines.forEach((line, i) => {
1826-
const wx = 120 + i * 220;
1827-
const y = 58 + (i % 2) * 20;
1828-
const w = textW(line, 1) + 8;
1829-
block(wx - 4, y - 4, w, 13, C.sign);
1830-
block(wx - 4, y - 4, w, 1, signColors[i % signColors.length]);
1831-
text(line, wx, y - 2, 1, C.starHi);
1832-
});
1868+
for (const comment of visibleVoidComments()) drawVoidComment(comment);
18331869

18341870
text(miniGame.voidTitle, camX + 12, 14, 2, C.rent);
18351871
text(miniGame.voidSubtitle.toUpperCase(), camX + 12, 34, 1, C.mobile);
@@ -2389,6 +2425,14 @@ export function createMascotGame(): Game {
23892425
voidWarning: miniGame.voidWarning,
23902426
voidSignLines: [...miniGame.voidSignLines],
23912427
voidPressureBursts: [...miniGame.voidPressureBursts],
2428+
voidCommentsCount: voidCommentPool().length,
2429+
voidCommentSpeed: VOID_COMMENT_SPEED,
2430+
visibleVoidComments: visibleVoidComments().map((comment) => ({
2431+
slot: comment.slot,
2432+
text: comment.text,
2433+
x: Math.round(comment.x),
2434+
y: comment.y,
2435+
})),
23922436
voidSeconds: voidSeconds(),
23932437
voidDragonX: Math.round(voidDragon.x),
23942438
voidGap: Math.round(voidGap()),

tests/functional.spec.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,9 @@ test.describe('Mission Run mini-game', () => {
948948
voidWarning?: string;
949949
voidSignLines?: readonly string[];
950950
voidPressureBursts?: readonly string[];
951+
voidCommentsCount?: number;
952+
voidCommentSpeed?: number;
953+
visibleVoidComments?: Array<{ slot: number; text: string; x: number; y: number }>;
951954
voidSeconds?: number;
952955
voidDragonX?: number;
953956
voidGap?: number;
@@ -1226,6 +1229,11 @@ test.describe('Mission Run mini-game', () => {
12261229
'WHY SLOW?',
12271230
'NO OWNER',
12281231
]);
1232+
expect(voidStart?.voidCommentsCount ?? 0).toBeGreaterThanOrEqual(20);
1233+
expect(voidStart?.voidCommentsCount ?? 0).toBeLessThanOrEqual(40);
1234+
expect(voidStart?.voidCommentSpeed ?? 0).toBeGreaterThan(0);
1235+
expect(voidStart?.voidCommentSpeed ?? 0).toBeLessThan(0.5);
1236+
expect(voidStart?.visibleVoidComments?.length ?? 0).toBeGreaterThanOrEqual(2);
12291237
expect(voidStart?.voidGap ?? 0).toBeGreaterThan(120);
12301238

12311239
await page.evaluate(() => {
@@ -1248,14 +1256,38 @@ test.describe('Mission Run mini-game', () => {
12481256
__mgameHoldVoidDirection?: (dir?: -1 | 0 | 1) => void;
12491257
};
12501258
w.__mgameHoldVoidDirection?.(1);
1251-
w.__mgameAdvanceVoid?.(15 * 60);
1259+
w.__mgameAdvanceVoid?.(20 * 60);
12521260
});
12531261

1254-
const afterFifteenSeconds = await readGame();
1255-
expect(afterFifteenSeconds?.state).toBe('void');
1256-
expect(afterFifteenSeconds?.voidSeconds ?? 0).toBeGreaterThanOrEqual(15);
1257-
expect(afterFifteenSeconds?.x ?? 0).toBeGreaterThan((voidStart?.x ?? 0) + 100);
1258-
expect(afterFifteenSeconds?.voidGap ?? 0).toBeGreaterThan(26);
1262+
const afterTwentySeconds = await readGame();
1263+
expect(afterTwentySeconds?.state).toBe('void');
1264+
expect(afterTwentySeconds?.voidSeconds ?? 0).toBeGreaterThanOrEqual(20);
1265+
expect(afterTwentySeconds?.x ?? 0).toBeGreaterThan((voidStart?.x ?? 0) + 100);
1266+
expect(afterTwentySeconds?.voidGap ?? 0).toBeGreaterThan(26);
1267+
expect(afterTwentySeconds?.visibleVoidComments?.length ?? 0).toBeGreaterThanOrEqual(2);
1268+
const smoothComment = afterTwentySeconds?.visibleVoidComments?.find(
1269+
(comment) => comment.x > 60 && comment.x < 240
1270+
);
1271+
expect(
1272+
smoothComment,
1273+
'expected a readable mid-screen burnout comment after 20 seconds'
1274+
).toBeTruthy();
1275+
1276+
await page.evaluate(() => {
1277+
const w = window as Window & {
1278+
__mgameAdvanceVoid?: (frames?: number) => void;
1279+
};
1280+
w.__mgameAdvanceVoid?.(30);
1281+
});
1282+
1283+
const afterSmoothMove = await readGame();
1284+
const sameComment = afterSmoothMove?.visibleVoidComments?.find(
1285+
(comment) => comment.slot === smoothComment?.slot
1286+
);
1287+
expect(sameComment, 'expected the same comment to remain visible while moving').toBeTruthy();
1288+
const movedPx = (smoothComment?.x ?? 0) - (sameComment?.x ?? 0);
1289+
expect(movedPx).toBeGreaterThanOrEqual(3);
1290+
expect(movedPx).toBeLessThanOrEqual(12);
12591291

12601292
await page.evaluate(() => {
12611293
const w = window as Window & {
@@ -1269,7 +1301,7 @@ test.describe('Mission Run mini-game', () => {
12691301

12701302
const afterBackpedal = await readGame();
12711303
expect(afterBackpedal?.state).toBe('void');
1272-
expect(afterBackpedal?.vx ?? 0).toBeLessThan(afterFifteenSeconds?.vx ?? 0);
1304+
expect(afterBackpedal?.vx ?? 0).toBeLessThan(afterTwentySeconds?.vx ?? 0);
12731305

12741306
await page.evaluate(() => {
12751307
const w = window as Window & {

0 commit comments

Comments
 (0)