@@ -86,6 +86,10 @@ pub struct AquariumEnvironment {
86
86
pub sharks : Vec < Shark > ,
87
87
/// Underwater whales.
88
88
pub whales : Vec < Whale > ,
89
+ /// Next eligible tick to spawn a ship/shark/whale when none present.
90
+ pub next_ship_spawn : u64 ,
91
+ pub next_shark_spawn : u64 ,
92
+ pub next_whale_spawn : u64 ,
89
93
}
90
94
91
95
impl Default for AquariumEnvironment {
@@ -97,6 +101,9 @@ impl Default for AquariumEnvironment {
97
101
ships : Vec :: new ( ) ,
98
102
sharks : Vec :: new ( ) ,
99
103
whales : Vec :: new ( ) ,
104
+ next_ship_spawn : 0 ,
105
+ next_shark_spawn : 0 ,
106
+ next_whale_spawn : 0 ,
100
107
}
101
108
}
102
109
}
@@ -320,45 +327,66 @@ fn ensure_environment_initialized(state: &mut AquariumState) {
320
327
pub fn update_aquarium ( state : & mut AquariumState , assets : & [ FishArt ] ) {
321
328
let ( aw, ah) = ( state. size . 0 as f32 , state. size . 1 as f32 ) ;
322
329
let dt: f32 = 0.033 ;
330
+ let fish_speed_mult: f32 = 2.0 ;
323
331
324
332
// Ensure environment exists.
325
333
ensure_environment_initialized ( state) ;
326
334
327
335
// Integrate fish and handle bounce.
328
336
329
- // Spawn default moving entities if none exist.
330
- if state. env . ships . is_empty ( ) {
331
- // Start a ship just off the left edge moving right.
332
- let ( sw, _) = measure_block ( SHIP_R ) ;
333
- state. env . ships . push ( Ship {
334
- x : -( sw as f32 ) ,
335
- y : 0 ,
336
- vx : 6.0 ,
337
- } ) ;
337
+ // Spawn entities deterministically when none present and past next spawn tick.
338
+ if state. env . ships . is_empty ( ) && state. tick >= state. env . next_ship_spawn {
339
+ // Alternate direction by epoch (simple deterministic scheme).
340
+ let right = ( state. tick / 900 ) % 2 == 0 ;
341
+ let ( sw, _) = if right {
342
+ measure_block ( SHIP_R )
343
+ } else {
344
+ measure_block ( SHIP_L )
345
+ } ;
346
+ let ( x, vx) = if right {
347
+ ( -( sw as f32 ) , 6.0 )
348
+ } else {
349
+ ( state. size . 0 as f32 + sw as f32 , -6.0 )
350
+ } ;
351
+ state. env . ships . push ( Ship { x, y : 0 , vx } ) ;
338
352
}
339
- if state. env . sharks . is_empty ( ) {
340
- // Place shark below waterlines.
353
+ if state. env . sharks . is_empty ( ) && state . tick >= state . env . next_shark_spawn {
354
+ // Place shark at a consistent depth under waterlines.
341
355
let ( _, sh) = measure_block ( SHARK_R ) ;
342
356
let base = 9 ;
343
357
let y = state. size . 1 . saturating_sub ( sh + 3 ) . max ( base) ;
344
- state. env . sharks . push ( Shark {
345
- x : -40.0 ,
346
- y,
347
- vx : 8.0 ,
348
- } ) ;
358
+ let right = ( state. tick / 1200 ) % 2 == 0 ;
359
+ let ( sw, _) = if right {
360
+ measure_block ( SHARK_R )
361
+ } else {
362
+ measure_block ( SHARK_L )
363
+ } ;
364
+ let ( x, vx) = if right {
365
+ ( -( sw as f32 ) , 8.0 )
366
+ } else {
367
+ ( state. size . 0 as f32 + sw as f32 , -8.0 )
368
+ } ;
369
+ state. env . sharks . push ( Shark { x, y, vx } ) ;
349
370
}
350
- if state. env . whales . is_empty ( ) {
351
- // Place whale at mid -depth moving left .
371
+ if state. env . whales . is_empty ( ) && state . tick >= state . env . next_whale_spawn {
372
+ // Mid -depth whale .
352
373
let y = ( state. size . 1 / 3 ) . max ( 6 ) ;
353
- state. env . whales . push ( Whale {
354
- x : state. size . 0 as f32 + 10.0 ,
355
- y,
356
- vx : -4.0 ,
357
- } ) ;
374
+ let right = ( state. tick / 1500 ) % 2 == 0 ;
375
+ let ( ww, _) = if right {
376
+ measure_block ( WHALE_R )
377
+ } else {
378
+ measure_block ( WHALE_L )
379
+ } ;
380
+ let ( x, vx) = if right {
381
+ ( -( ww as f32 ) , 4.0 )
382
+ } else {
383
+ ( state. size . 0 as f32 + ww as f32 , -4.0 )
384
+ } ;
385
+ state. env . whales . push ( Whale { x, y, vx } ) ;
358
386
}
359
387
for fish in & mut state. fishes {
360
- fish. position . 0 += fish. velocity . 0 * dt;
361
- fish. position . 1 += fish. velocity . 1 * dt;
388
+ fish. position . 0 += fish. velocity . 0 * dt * fish_speed_mult ;
389
+ fish. position . 1 += fish. velocity . 1 * dt * fish_speed_mult ;
362
390
363
391
let ( fw, fh) = assets
364
392
. get ( fish. fish_art_index )
@@ -385,7 +413,7 @@ pub fn update_aquarium(state: &mut AquariumState, assets: &[FishArt]) {
385
413
// Occasionally emit bubbles from fish mouths, deterministically based on tick.
386
414
// Emit every 24 ticks per fish to avoid randomness in the core crate.
387
415
for fish in & state. fishes {
388
- if state. tick % 24 == 0 {
416
+ if state. tick % 72 == 0 {
389
417
let ( fw, fh) = assets
390
418
. get ( fish. fish_art_index )
391
419
. map ( |a| ( a. width as f32 , a. height as f32 ) )
@@ -415,55 +443,70 @@ pub fn update_aquarium(state: &mut AquariumState, assets: &[FishArt]) {
415
443
}
416
444
state. bubbles = kept;
417
445
418
- // Move ships.
419
- for ship in & mut state. env . ships {
446
+ // Move ships and despawn when fully off-screen. Schedule next spawn.
447
+ let mut next_ships = Vec :: with_capacity ( state. env . ships . len ( ) ) ;
448
+ for mut ship in state. env . ships . drain ( ..) {
420
449
ship. x += ship. vx * dt;
421
450
let ( sw, _) = if ship. vx >= 0.0 {
422
451
measure_block ( SHIP_R )
423
452
} else {
424
453
measure_block ( SHIP_L )
425
454
} ;
426
- if ship. vx >= 0.0 && ship. x > state. size . 0 as f32 {
427
- ship. x = -( sw as f32 ) ;
428
- } else if ship. vx < 0.0 && ship. x + sw as f32 <= 0.0 {
429
- ship. x = state. size . 0 as f32 ;
455
+ let off_right = ship. x > state. size . 0 as f32 ;
456
+ let off_left = ship. x + sw as f32 <= 0.0 ;
457
+ if off_right || off_left {
458
+ // Next ship after ~20s
459
+ state. env . next_ship_spawn = state. tick + 600 ;
460
+ } else {
461
+ next_ships. push ( ship) ;
430
462
}
431
463
}
464
+ state. env . ships = next_ships;
432
465
433
- // Move sharks.
434
- for shark in & mut state. env . sharks {
466
+ // Move sharks and despawn when fully off-screen. Schedule next spawn.
467
+ let mut next_sharks = Vec :: with_capacity ( state. env . sharks . len ( ) ) ;
468
+ for mut shark in state. env . sharks . drain ( ..) {
435
469
shark. x += shark. vx * dt;
436
470
let ( sw, _) = if shark. vx >= 0.0 {
437
471
measure_block ( SHARK_R )
438
472
} else {
439
473
measure_block ( SHARK_L )
440
474
} ;
441
- if shark. vx >= 0.0 && shark. x > state. size . 0 as f32 {
442
- // Re-enter from left
443
- shark. x = -( sw as f32 ) ;
444
- } else if shark. vx < 0.0 && shark. x + sw as f32 <= 0.0 {
445
- // Re-enter from right
446
- shark. x = state. size . 0 as f32 ;
475
+ let off_right = shark. x > state. size . 0 as f32 ;
476
+ let off_left = shark. x + sw as f32 <= 0.0 ;
477
+ if off_right || off_left {
478
+ // Next shark after ~30s
479
+ state. env . next_shark_spawn = state. tick + 900 ;
480
+ } else {
481
+ next_sharks. push ( shark) ;
447
482
}
448
483
}
484
+ state. env . sharks = next_sharks;
449
485
450
- // Move whales.
451
- for whale in & mut state. env . whales {
486
+ // Move whales and despawn when fully off-screen. Schedule next spawn.
487
+ let mut next_whales = Vec :: with_capacity ( state. env . whales . len ( ) ) ;
488
+ for mut whale in state. env . whales . drain ( ..) {
452
489
whale. x += whale. vx * dt;
453
490
let ( ww, _) = if whale. vx >= 0.0 {
454
491
measure_block ( WHALE_R )
455
492
} else {
456
493
measure_block ( WHALE_L )
457
494
} ;
458
- if whale. vx >= 0.0 && whale. x > state. size . 0 as f32 {
459
- whale. x = -( ww as f32 ) ;
460
- } else if whale. vx < 0.0 && whale. x + ww as f32 <= 0.0 {
461
- whale. x = state. size . 0 as f32 ;
495
+ let off_right = whale. x > state. size . 0 as f32 ;
496
+ let off_left = whale. x + ww as f32 <= 0.0 ;
497
+ if off_right || off_left {
498
+ // Next whale after ~40s
499
+ state. env . next_whale_spawn = state. tick + 1200 ;
500
+ } else {
501
+ next_whales. push ( whale) ;
462
502
}
463
503
}
504
+ state. env . whales = next_whales;
464
505
465
506
// Advance environment phases.
466
- state. env . water_phase = state. env . water_phase . wrapping_add ( 1 ) ;
507
+ if state. tick % 4 == 0 {
508
+ state. env . water_phase = state. env . water_phase . wrapping_add ( 1 ) ;
509
+ }
467
510
state. tick = state. tick . wrapping_add ( 1 ) ;
468
511
}
469
512
@@ -638,7 +681,7 @@ pub fn render_aquarium_to_string(state: &AquariumState, assets: &[FishArt]) -> S
638
681
}
639
682
}
640
683
// Water spout above head (simple animation)
641
- let frame = ( state. tick as usize / 4 ) % SPOUT_FRAMES . len ( ) ;
684
+ let frame = ( state. tick as usize / 12 ) % SPOUT_FRAMES . len ( ) ;
642
685
let spout = SPOUT_FRAMES [ frame] ;
643
686
// Approximate blowhole position a bit right of whale x
644
687
let spx = x0 + if whale. vx >= 0.0 { 8 } else { 3 } ;
0 commit comments