Files
City10/tests/Feature/CityPlacesTest.php
marcocosta10000 46b15ec7be feat(city): add city landmarks, stores, luminous signs, and ATC echo guard
Add university and fire station civic buildings plus 7 new store types,
each with facade and luminous blade signs. Extend landmark material
colors and sign accents. Fix ATC dispatch to reject prompt-echoing AI
responses via heuristic guard. Add CityPlacesTest for collision and
roadway avoidance, and extend AirportTest and SignLabelsTest.
2026-10-31 22:22:44 -03:00

147 lines
5.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Tests\Feature;
use App\Models\Character;
use App\Models\User;
use Database\Seeders\CityContentSeeder;
use Database\Seeders\CityWorldSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* City places (stores + civic buildings): the new landmarks exist with
* the right types, never sit on a roadway, never overlap each other and
* do not steal cells from the plaza pattern.
*/
class CityPlacesTest extends TestCase
{
use RefreshDatabase;
/**
* Boot world fixtures.
*/
protected function setUp(): void
{
parent::setUp();
// Same order as DatabaseSeeder: content (stores/agents) then world.
$this->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');
}
}