seed(CityWorldSeeder::class); $this->user = User::factory()->create(['status' => User::STATUS_ACTIVE, 'is_active' => true]); $this->character = Character::ensureForUser($this->user); $this->actingAs($this->user); } /** * The world payload carries the beach geometry derived from the grid. */ public function test_city_data_includes_beach_geometry(): void { $payload = $this->getJson('/city/data')->assertOk()->json(); $this->assertArrayHasKey('beach', $payload); // Layout max X (200 for these bounds) + approach → sand starts at 204. $this->assertEquals(204.0, $payload['beach']['sand_from']); $this->assertEquals(264.0, $payload['beach']['sand_to']); $this->assertEquals(354.0, $payload['beach']['sea_to']); $this->assertEquals(-160.0, $payload['beach']['z_min']); $this->assertEquals(160.0, $payload['beach']['z_max']); } /** * Relaxing on the sand recovers one point of energy and health. */ public function test_relax_on_sand_recovers_energy_and_health(): void { $this->character->forceFill(['energy' => 40, 'health' => 50])->save(); $this->postJson('/city/actions/beach', ['pos_x' => 210, 'pos_z' => -40]) ->assertOk() ->assertJsonPath('message', __('city.beach_ok')); $this->character->refresh(); $this->assertSame(41, (int) $this->character->energy); $this->assertSame(51, (int) $this->character->health); } /** * The action is rejected away from the beach. */ public function test_relax_requires_being_on_the_beach(): void { $this->postJson('/city/actions/beach', ['pos_x' => 0, 'pos_z' => 0]) ->assertStatus(422) ->assertJsonPath('message', __('city.beach_need')); $this->postJson('/city/actions/beach', ['pos_x' => 300, 'pos_z' => 0]) ->assertStatus(422) ->assertJsonPath('message', __('city.beach_need')); } /** * Fully restored characters are told they are already at max. */ public function test_relax_at_max_stats_reports_full(): void { $this->character->forceFill(['energy' => 100, 'health' => 100])->save(); $this->postJson('/city/actions/beach', ['pos_x' => 220, 'pos_z' => 30]) ->assertOk() ->assertJsonPath('message', __('city.beach_full')); } /** * Sleeping characters cannot relax. */ public function test_sleeping_character_cannot_relax(): void { $this->character->forceFill(['state' => Character::STATE_SLEEPING])->save(); $this->postJson('/city/actions/beach', ['pos_x' => 210, 'pos_z' => 0]) ->assertStatus(422) ->assertJsonPath('message', __('city.exercise_need_awake')); } /** * Position sync persists up to the shoreline (and no further). */ public function test_position_persists_onto_the_beach(): void { $onSand = $this->postJson('/city/actions/position', ['pos_x' => 250, 'pos_z' => 0]) ->assertOk(); $this->assertEquals(250, $onSand->json('pos_x')); $pastShore = $this->postJson('/city/actions/position', ['pos_x' => 400, 'pos_z' => 0]) ->assertOk(); $this->assertEquals(266, $pastShore->json('pos_x')); } }