seed(CityContentSeeder::class); $this->seed(CityWorldSeeder::class); $user = User::factory()->create(['status' => User::STATUS_ACTIVE, 'is_active' => true]); Character::ensureForUser($user); $this->actingAs($user); } /** * The payload carries every requested place with its type. */ public function test_new_city_places_exist(): void { $payload = $this->getJson('/city/data')->assertOk()->json(); $storeTypes = array_column($payload['stores'], 'type'); $storeNames = array_column($payload['stores'], 'name'); foreach ([ ['supermarket', 'Supermercado Bom Preço'], ['pharmacy', 'Drogaria Saúde Total'], ['restaurant', 'Restaurante Sabor da City'], ['salon', 'Cabeleireiro Estilo'], ['bank', 'Banco Central City10'], ['hotel', 'Hotel City10 Palace'], ['mall', 'Shopping City10'], ] as [$type, $name]) { $this->assertContains($type, $storeTypes, "Missing store type {$type}"); $this->assertContains($name, $storeNames, "Missing store {$name}"); } // Pre-existing places from the original request. $this->assertContains('bakery', $storeTypes, 'Padaria must remain'); $this->assertContains('market', $storeTypes, 'Mercado must remain'); $buildingTypes = array_column($payload['buildings'], 'type'); foreach (['hospital', 'school', 'city_hall', 'university', 'fire_station'] as $type) { $this->assertContains($type, $buildingTypes, "Missing building type {$type}"); } } /** * Full AABB audit: no structure touches a roadway (half width 4) * and no two structures overlap. */ public function test_places_avoid_roads_and_each_other(): void { $payload = $this->getJson('/city/data')->assertOk()->json(); $xs = []; $zs = []; foreach ($payload['layout']['roads'] as $road) { if ($road['axis'] === 'z') { $xs[] = (float) $road['from'][0]; } else { $zs[] = (float) $road['from'][2]; } } $xs = array_values(array_unique($xs)); $zs = array_values(array_unique($zs)); $this->assertNotEmpty($xs); $this->assertNotEmpty($zs); $structures = []; foreach ($payload['stores'] as $s) { $structures[] = [ 'label' => 'store:'.$s['name'], 'x0' => $s['position'][0] - $s['size'][0] / 2, 'x1' => $s['position'][0] + $s['size'][0] / 2, 'z0' => $s['position'][2] - $s['size'][2] / 2, 'z1' => $s['position'][2] + $s['size'][2] / 2, ]; } foreach ($payload['buildings'] as $b) { $structures[] = [ 'label' => 'building:'.$b['type'], 'x0' => $b['position'][0] - $b['size'][0] / 2, 'x1' => $b['position'][0] + $b['size'][0] / 2, 'z0' => $b['position'][2] - $b['size'][2] / 2, 'z1' => $b['position'][2] + $b['size'][2] / 2, ]; } $roadHits = []; foreach ($structures as $st) { foreach ($xs as $vx) { if ($st['x0'] < $vx + 4 && $st['x1'] > $vx - 4) { $roadHits[] = "{$st['label']} on z-road@{$vx}"; } } foreach ($zs as $vz) { if ($st['z0'] < $vz + 4 && $st['z1'] > $vz - 4) { $roadHits[] = "{$st['label']} on x-road@{$vz}"; } } } $this->assertSame([], $roadHits, 'Structures over roadways: '.implode(', ', $roadHits)); $collisions = []; for ($i = 0; $i < count($structures); $i++) { for ($j = $i + 1; $j < count($structures); $j++) { $a = $structures[$i]; $b = $structures[$j]; if ($a['x0'] < $b['x1'] && $a['x1'] > $b['x0'] && $a['z0'] < $b['z1'] && $a['z1'] > $b['z0']) { $collisions[] = "{$a['label']} × {$b['label']}"; } } } $this->assertSame([], $collisions, 'Overlapping structures: '.implode(', ', $collisions)); } /** * The new places sit off the plaza rows, so the16-plaza pattern is * unchanged. */ public function test_plaza_pattern_survives_new_places(): void { $payload = $this->getJson('/city/data')->assertOk()->json(); $this->assertCount(16, $payload['plazas'], 'Plaza count must stay 16 after adding places'); } }