seed(CityWorldSeeder::class); } /** * Needs decay on the first tick for awake characters. */ public function test_tick_decays_needs_once(): void { $character = Character::factory()->create([ 'state' => Character::STATE_AWAKE, 'energy' => 100, 'hunger' => 100, 'thirst' => 100, 'last_tick_at' => null, ]); $this->artisan('city:tick')->assertSuccessful(); $character->refresh(); $this->assertLessThan(100, $character->hunger); $this->assertLessThan(100, $character->thirst); $this->assertNotNull($character->last_tick_at); $hungerAfterFirst = $character->hunger; $this->artisan('city:tick')->assertSuccessful(); $character->refresh(); $this->assertSame($hungerAfterFirst, $character->hunger, 'Second immediate tick must be a no-op'); } /** * Sleeping restores energy and wakes the character at full energy. */ public function test_tick_recovers_energy_while_sleeping(): void { $character = Character::factory()->create([ 'state' => Character::STATE_SLEEPING, 'energy' => 50, 'last_tick_at' => now()->subMinute(), ]); $this->artisan('city:tick')->assertSuccessful(); $character->refresh(); $this->assertGreaterThan(50, $character->energy); // Force another tick window. $character->forceFill(['last_tick_at' => now()->subMinute(), 'energy' => 95])->save(); $this->artisan('city:tick')->assertSuccessful(); $character->refresh(); $this->assertSame(100, $character->energy); $this->assertSame(Character::STATE_AWAKE, $character->state); } /** * Working characters accrue salary each tick. */ public function test_tick_pays_salary_while_working(): void { $character = Character::factory()->create([ 'state' => Character::STATE_WORKING, 'money' => 100, 'last_tick_at' => now()->subMinute(), ]); $character->job()->associate(Job::firstOrFail()); $character->save(); $before = (float) $character->money; $this->artisan('city:tick')->assertSuccessful(); $character->refresh(); $this->assertGreaterThan($before, (float) $character->money); } /** * Incomplete buildings advance and complete houses attach to the owner. */ public function test_tick_progresses_construction(): void { $character = Character::factory()->create(); $building = Building::create([ 'city_id' => $character->city_id, 'owner_character_id' => $character->id, 'type' => Building::TYPE_HOUSE, 'name' => 'Casa', 'pos_x' => 20, 'pos_z' => -70, 'width' => 10, 'depth' => 10, 'height' => 5, 'is_complete' => false, 'build_progress' => 90, ]); $this->artisan('city:tick')->assertSuccessful(); $building->refresh(); $character->refresh(); $this->assertTrue($building->is_complete); $this->assertSame(100, $building->build_progress); $this->assertSame($building->id, $character->house_id); } /** * Scheduler registers the ticker every minute. */ public function test_scheduler_includes_city_tick(): void { $this->artisan('schedule:list') ->expectsOutputToContain('city:tick') ->assertSuccessful(); } }