11import { describe , expect , test } from "bun:test" ;
2- import { PLAYER_GEOMETRY , PLAYER_CONFIG , WORLD } from "../../src/constants.ts" ;
2+ import { PLAYER_GEOMETRY , WORLD } from "../../src/constants.ts" ;
33import type { LevelEntitySpec } from "../../src/entities/types.ts" ;
44import {
55 buildWorld ,
66 createPlayer ,
77 createPlayerOnFloor ,
88 makeInput ,
9- step ,
109 stepOnce ,
1110 withFloor ,
12- } from "./harness.ts" ;
13-
14- describe ( "Dash tech" , ( ) => {
15- test ( "dash enters state immediately and commits direction after the frozen startup" , ( ) => {
16- const specs : LevelEntitySpec [ ] = [ ] ;
17- withFloor ( specs , 20 ) ;
18- const world = buildWorld ( specs ) ;
19- const player = createPlayerOnFloor ( world , 104 , 20 ) ;
20-
21- stepOnce ( player , makeInput ( ) ) ;
22- const press = stepOnce ( player , makeInput ( { x : - 1 , dashPressed : true } ) ) ;
23- expect ( press . snapshot . state ) . toBe ( "dash" ) ;
24- expect ( press . snapshot . vx ) . toBe ( 0 ) ;
25- expect ( press . effects . some ( ( effect ) => effect . type === "dash_begin" ) ) . toBeTrue ( ) ;
26- expect ( press . effects . some ( ( effect ) => effect . type === "dash_start" ) ) . toBeFalse ( ) ;
27-
28- for ( let frame = 0 ; frame < 3 ; frame ++ ) {
29- const startup = stepOnce ( player , makeInput ( { x : 1 } ) ) ;
30- expect ( startup . snapshot . state ) . toBe ( "dash" ) ;
31- expect ( startup . snapshot . vx ) . toBe ( 0 ) ;
32- expect ( startup . effects . some ( ( effect ) => effect . type === "dash_start" ) ) . toBeFalse ( ) ;
33- }
34-
35- const commit = stepOnce ( player , makeInput ( { x : 1 } ) ) ;
36- const dashStart = commit . effects . find ( ( effect ) => effect . type === "dash_start" ) ;
37- expect ( dashStart ) . toBeTruthy ( ) ;
38- expect ( dashStart ?. dirX ) . toBeGreaterThan ( 0 ) ;
39- expect ( commit . snapshot . vx ) . toBeGreaterThan ( 0 ) ;
40- } ) ;
41-
42- test ( "dash trail effects follow the canonical start, 0.08s, end schedule with no freeze-start trails" , ( ) => {
43- const specs : LevelEntitySpec [ ] = [ ] ;
44- withFloor ( specs , 20 ) ;
45- const world = buildWorld ( specs ) ;
46- const player = createPlayerOnFloor ( world , 104 , 20 ) ;
47-
48- stepOnce ( player , makeInput ( ) ) ;
49-
50- const press = stepOnce ( player , makeInput ( { x : 1 , dashPressed : true } ) ) ;
51- expect ( press . effects . some ( ( effect ) => effect . type === "dash_trail" ) ) . toBeFalse ( ) ;
52-
53- for ( let frame = 0 ; frame < 3 ; frame ++ ) {
54- const frozen = stepOnce ( player , makeInput ( { x : 1 } ) ) ;
55- expect ( frozen . effects . some ( ( effect ) => effect . type === "dash_trail" ) ) . toBeFalse ( ) ;
56- }
57-
58- const trailFrames : number [ ] = [ ] ;
59- const trailXs : number [ ] = [ ] ;
60- for ( let frame = 0 ; frame < 20 ; frame ++ ) {
61- const result = stepOnce ( player , makeInput ( { x : 1 } ) ) ;
62- const trail = result . effects . find ( ( effect ) => effect . type === "dash_trail" ) ;
63- if ( trail ) {
64- trailFrames . push ( frame ) ;
65- trailXs . push ( trail . trailX ?? NaN ) ;
66- }
67- }
68-
69- expect ( trailFrames ) . toEqual ( [ 0 , 5 , 10 ] ) ;
70- expect ( trailXs [ 0 ] ) . toBeLessThan ( trailXs [ 1 ] ) ;
71- expect ( trailXs [ 1 ] ) . toBeLessThan ( trailXs [ 2 ] ) ;
72- } ) ;
11+ } from "../support/harness.ts" ;
7312
13+ describe ( "Checklist dash tech" , ( ) => {
7414 test ( "superdash gives 260 horizontal speed and full jump height" , ( ) => {
7515 const specs : LevelEntitySpec [ ] = [ ] ;
7616 withFloor ( specs , 20 ) ;
@@ -91,39 +31,6 @@ describe("Dash tech", () => {
9131 expect ( jump . snapshot . vy ) . toBeCloseTo ( - 105 , 5 ) ;
9232 } ) ;
9333
94- test ( "jump resolves before dash motion on the coroutine commit frame" , ( ) => {
95- const specs : LevelEntitySpec [ ] = [ ] ;
96- withFloor ( specs , 20 ) ;
97- const world = buildWorld ( specs ) ;
98- const probe = createPlayerOnFloor ( world , 104 , 20 ) ;
99-
100- stepOnce ( probe , makeInput ( ) ) ;
101- stepOnce ( probe , makeInput ( { x : 1 , y : 1 , dashPressed : true } ) ) ;
102-
103- let commitFrame = - 1 ;
104- for ( let frame = 0 ; frame < 10 ; frame ++ ) {
105- const result = stepOnce ( probe , makeInput ( { x : 1 , y : 1 } ) ) ;
106- if ( result . effects . some ( ( effect ) => effect . type === "dash_start" ) ) {
107- commitFrame = frame ;
108- break ;
109- }
110- }
111-
112- expect ( commitFrame ) . toBeGreaterThanOrEqual ( 0 ) ;
113-
114- const player = createPlayerOnFloor ( world , 104 , 20 ) ;
115- stepOnce ( player , makeInput ( ) ) ;
116- stepOnce ( player , makeInput ( { x : 1 , y : 1 , dashPressed : true } ) ) ;
117- step ( player , makeInput ( { x : 1 , y : 1 } ) , commitFrame ) ;
118- const jump = stepOnce ( player , makeInput ( { x : 1 , y : 1 , jump : true , jumpPressed : true } ) ) ;
119-
120- expect ( jump . effects . some ( ( effect ) => effect . type === "super" ) ) . toBeTrue ( ) ;
121- expect ( jump . effects . some ( ( effect ) => effect . type === "hyper" ) ) . toBeFalse ( ) ;
122- expect ( jump . snapshot . state ) . toBe ( "normal" ) ;
123- expect ( jump . snapshot . vx ) . toBeCloseTo ( 260 , 5 ) ;
124- expect ( jump . snapshot . vy ) . toBeCloseTo ( - 105 , 5 ) ;
125- } ) ;
126-
12734 test ( "hyperdash gives 325 horizontal speed and half jump height" , ( ) => {
12835 const specs : LevelEntitySpec [ ] = [ ] ;
12936 withFloor ( specs , 20 ) ;
@@ -150,11 +57,7 @@ describe("Dash tech", () => {
15057 const world = buildWorld ( specs ) ;
15158 const startX = 104 ;
15259 const startY = 20 * WORLD . tile - 24 ;
153- const probe = createPlayer (
154- world ,
155- startX ,
156- startY ,
157- ) ;
60+ const probe = createPlayer ( world , startX , startY ) ;
15861
15962 stepOnce ( probe , makeInput ( ) ) ;
16063 stepOnce ( probe , makeInput ( { x : 1 , y : 1 , dashPressed : true } ) ) ;
@@ -331,13 +234,32 @@ describe("Dash tech", () => {
331234 expect ( wallbounce . snapshot . vy ) . toBeCloseTo ( - 160 , 5 ) ;
332235 } ) ;
333236
334- test ( "reference dash constants stay aligned with the Celeste player source" , ( ) => {
335- expect ( PLAYER_CONFIG . dash . speed ) . toBe ( 240 ) ;
336- expect ( PLAYER_CONFIG . dash . endSpeed ) . toBe ( 160 ) ;
337- expect ( PLAYER_CONFIG . dash . duration ) . toBe ( 0.15 ) ;
338- expect ( PLAYER_CONFIG . dash . freezeTime ) . toBe ( 0.05 ) ;
339- expect ( PLAYER_CONFIG . dash . cooldown ) . toBe ( 0.2 ) ;
340- expect ( PLAYER_CONFIG . dash . refillCooldown ) . toBe ( 0.1 ) ;
341- expect ( PLAYER_CONFIG . dash . attackTime ) . toBe ( 0.3 ) ;
237+ test ( "spiked wallbounce is survivable when the wallbounce moves away from the spikes" , ( ) => {
238+ const specs : LevelEntitySpec [ ] = [ ] ;
239+ withFloor ( specs , 20 ) ;
240+ for ( let row = 10 ; row <= 20 ; row ++ ) {
241+ specs . push ( { kind : "solidTile" , col : 15 , row } ) ;
242+ specs . push ( { kind : "spike" , col : 15 , row, dir : "right" } ) ;
243+ }
244+ const world = buildWorld ( specs ) ;
245+ const player = createPlayer (
246+ world ,
247+ 15 * WORLD . tile - PLAYER_GEOMETRY . hitboxW * 0.5 - 1 ,
248+ 20 * WORLD . tile ,
249+ ) ;
250+
251+ stepOnce ( player , makeInput ( ) ) ;
252+ stepOnce ( player , makeInput ( { y : - 1 , dashPressed : true } ) ) ;
253+ stepOnce ( player , makeInput ( { y : - 1 } ) ) ;
254+ stepOnce ( player , makeInput ( { y : - 1 } ) ) ;
255+ stepOnce ( player , makeInput ( { y : - 1 } ) ) ;
256+ stepOnce ( player , makeInput ( { y : - 1 } ) ) ;
257+ stepOnce ( player , makeInput ( { y : - 1 } ) ) ;
258+ const wallbounce = stepOnce ( player , makeInput ( { x : 1 , y : - 1 , jump : true , jumpPressed : true } ) ) ;
259+
260+ expect ( wallbounce . effects . some ( ( effect ) => effect . type === "wall_jump" ) ) . toBeTrue ( ) ;
261+ expect (
262+ world . collidesWithSpike ( player . getHurtboxBounds ( ) , wallbounce . snapshot . vx , wallbounce . snapshot . vy ) ,
263+ ) . toBeNull ( ) ;
342264 } ) ;
343265} ) ;
0 commit comments