diffs / #50
feat(#50): double jump for everyone
Requested-by: demo replay (synthetic wallet)
+ BUILD+ UNIT TESTS+ LINT+ DIFF GATE+ PAGE CLASSIFIER+ PLAYWRIGHT
src/features/r50-jump-puff.jsNEW+15 −0
| 1 | +// #50: double jump for everyone. A small puff under the cat on the second jump. | ||
| 2 | +import { PAL } from "../palette.js"; | ||
| 3 | +export default { | ||
| 4 | + id: "jump-puff-50", | ||
| 5 | + order: 55, | ||
| 6 | + layer: "fg", | ||
| 7 | + draw(g, w) { | ||
| 8 | + const p = w.player; if (!p || !p.puffT || w.reduced) return; | ||
| 9 | + const r = Math.round((0.25 - p.puffT) * 24); | ||
| 10 | + g.fillStyle = PAL.playerShade; | ||
| 11 | + g.fillRect(Math.round(p.x) - 3 - r, Math.round(p.y) + 1, 2, 1); | ||
| 12 | + g.fillRect(Math.round(p.x) + 2 + r, Math.round(p.y) + 1, 2, 1); | ||
| 13 | + g.fillRect(Math.round(p.x) - 1, Math.round(p.y) + 2 + (r >> 1), 2, 1); | ||
| 14 | + }, | ||
| 15 | +}; |
1+// #50: double jump for everyone. A small puff under the cat on the second jump.
2+import { PAL } from "../palette.js";
3+export default {
4+ id: "jump-puff-50",
5+ order: 55,
6+ layer: "fg",
7+ draw(g, w) {
8+ const p = w.player; if (!p || !p.puffT || w.reduced) return;
9+ const r = Math.round((0.25 - p.puffT) * 24);
10+ g.fillStyle = PAL.playerShade;
11+ g.fillRect(Math.round(p.x) - 3 - r, Math.round(p.y) + 1, 2, 1);
12+ g.fillRect(Math.round(p.x) + 2 + r, Math.round(p.y) + 1, 2, 1);
13+ g.fillRect(Math.round(p.x) - 1, Math.round(p.y) + 2 + (r >> 1), 2, 1);
14+ },
15+};
src/player.js+6 −2
| 4 | export const SPEED = 82, JUMP = 205, GRAV = 560; | 4 | export const SPEED = 82, JUMP = 205, GRAV = 560; |
| 5 | export function makePlayer() { | 5 | export function makePlayer() { |
| 6 | return { | 6 | return { |
| 7 | − x: 24, y: 40, vx: 0, vy: 0, f: 1, onGround: false, walk: 0, emote: null, emoteT: 0, jumps: 1, | 7 | + x: 24, y: 40, vx: 0, vy: 0, f: 1, onGround: false, walk: 0, emote: null, emoteT: 0, jumps: 2, puffT: 0, |
| 8 | update(dt, w, input) { | 8 | update(dt, w, input) { |
| 9 | const dir = (input.held("right") ? 1 : 0) - (input.held("left") ? 1 : 0); | 9 | const dir = (input.held("right") ? 1 : 0) - (input.held("left") ? 1 : 0); |
| 10 | this.vx = dir * SPEED; if (dir) this.f = dir; | 10 | this.vx = dir * SPEED; if (dir) this.f = dir; |
| 11 | this.walk = dir && this.onGround ? this.walk + dt : 0; | 11 | this.walk = dir && this.onGround ? this.walk + dt : 0; |
| 12 | − if (input.take("jump") && (this.onGround || this.jumpsLeft > 0)) { if (!this.onGround) this.jumpsLeft--; this.vy = -JUMP; this.onGround = false; } | 12 | + if (input.take("jump") && (this.onGround || this.jumpsLeft > 0)) { |
| 13 | + if (!this.onGround) { this.jumpsLeft--; this.puffT = 0.25; } | ||
| 14 | + this.vy = -JUMP; this.onGround = false; | ||
| 15 | + } | ||
| 16 | + this.puffT = Math.max(0, this.puffT - dt); | ||
| 17 | for (let i = 0; i < 4; i++) if (input.take("e" + i)) { this.emote = i; this.emoteT = 2; } | 17 | for (let i = 0; i < 4; i++) if (input.take("e" + i)) { this.emote = i; this.emoteT = 2; } |
| 18 | this.emoteT = Math.max(0, this.emoteT - dt); if (!this.emoteT) this.emote = null; | 18 | this.emoteT = Math.max(0, this.emoteT - dt); if (!this.emoteT) this.emote = null; |
| 19 | this.vy = Math.min(320, this.vy + GRAV * dt); | 19 | this.vy = Math.min(320, this.vy + GRAV * dt); |
4 export const SPEED = 82, JUMP = 205, GRAV = 560;
5 export function makePlayer() {
6 return {
7− x: 24, y: 40, vx: 0, vy: 0, f: 1, onGround: false, walk: 0, emote: null, emoteT: 0, jumps: 1,
7+ x: 24, y: 40, vx: 0, vy: 0, f: 1, onGround: false, walk: 0, emote: null, emoteT: 0, jumps: 2, puffT: 0,
8 update(dt, w, input) {
9 const dir = (input.held("right") ? 1 : 0) - (input.held("left") ? 1 : 0);
10 this.vx = dir * SPEED; if (dir) this.f = dir;
11 this.walk = dir && this.onGround ? this.walk + dt : 0;
12− if (input.take("jump") && (this.onGround || this.jumpsLeft > 0)) { if (!this.onGround) this.jumpsLeft--; this.vy = -JUMP; this.onGround = false; }
12+ if (input.take("jump") && (this.onGround || this.jumpsLeft > 0)) {
13+ if (!this.onGround) { this.jumpsLeft--; this.puffT = 0.25; }
14+ this.vy = -JUMP; this.onGround = false;
15+ }
16+ this.puffT = Math.max(0, this.puffT - dt);
17 for (let i = 0; i < 4; i++) if (input.take("e" + i)) { this.emote = i; this.emoteT = 2; }
18 this.emoteT = Math.max(0, this.emoteT - dt); if (!this.emoteT) this.emote = null;
19 this.vy = Math.min(320, this.vy + GRAV * dt);


CI log tail
$ ci:build (677 ms, exit 0) $ ci:test (160 ms, exit 0) TAP version 13 # Subtest: palette: valid hex, at most 40 colours ok 1 - palette: valid hex, at most 40 colours --- duration_ms: 1.08386 type: 'test' ... # Subtest: sprites: rectangular-ish rows of known keys ok 2 - sprites: rectangular-ish rows of known keys --- duration_ms: 1.921427 type: 'test' ... # Subtest: levels: every room has a name, platforms and a draw function ok 3 - levels: every room has a name, platforms and a draw function --- duration_ms: 4.821918 type: 'test' ... # Subtest: bosses: every file exports a class with update/draw ok 4 - bosses: every file exports a class with update/draw --- duration_ms: 0.880719 type: 'test' ... # Subtest: features: default export with a unique id and only known hooks ok 5 - features: default export with a unique id and only known hooks --- duration_ms: 6.453744 type: 'test' ... # Subtest: no network, storage or parent access anywhere in src ok 6 - no network, storage or parent access anywhere in src --- duration_ms: 1.498105 type: 'test' ... 1..6 # tests 6 # suites 0 # pass 6 # fail 0 # cancelled 0 # skipped 0 # todo 0 # duration_ms 105.519077 $ ci:lint (652 ms, exit 0) $ playwright 960x540 fps 60 long tasks 0 console errors 0