108 lines
4.0 KiB
PHP
108 lines
4.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use sammo\Json;
|
|
use sammo\LiteHashDRBG;
|
|
use sammo\NpcPossessionSelector;
|
|
use sammo\RandUtil;
|
|
|
|
require_once __DIR__ . '/../src/sammo/NpcPossessionSelector.php';
|
|
|
|
final class NpcPossessionSelectorTest extends TestCase
|
|
{
|
|
/** @return array<int, array{id: int, leadership: int, strength: int, intel: int, keepCnt: int}> */
|
|
private function candidates(): array
|
|
{
|
|
$rows = [
|
|
['id' => 1, 'leadership' => 40, 'strength' => 50, 'intel' => 60, 'keepCnt' => 3],
|
|
['id' => 2, 'leadership' => 73, 'strength' => 44, 'intel' => 81, 'keepCnt' => 3],
|
|
['id' => 3, 'leadership' => 91, 'strength' => 32, 'intel' => 47, 'keepCnt' => 3],
|
|
['id' => 4, 'leadership' => 55, 'strength' => 88, 'intel' => 38, 'keepCnt' => 3],
|
|
['id' => 5, 'leadership' => 66, 'strength' => 67, 'intel' => 68, 'keepCnt' => 3],
|
|
['id' => 6, 'leadership' => 99, 'strength' => 20, 'intel' => 21, 'keepCnt' => 3],
|
|
['id' => 7, 'leadership' => 35, 'strength' => 93, 'intel' => 72, 'keepCnt' => 3],
|
|
['id' => 8, 'leadership' => 84, 'strength' => 75, 'intel' => 69, 'keepCnt' => 3],
|
|
];
|
|
$result = [];
|
|
foreach ($rows as $row) {
|
|
$result[$row['id']] = $row;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public function testSharedKernelPreservesThePreviousEndpointLoop(): void
|
|
{
|
|
$candidates = $this->candidates();
|
|
$weights = [];
|
|
foreach ($candidates as $id => $candidate) {
|
|
$weights[$id] = pow(
|
|
$candidate['leadership'] + $candidate['strength'] + $candidate['intel'],
|
|
1.5,
|
|
);
|
|
self::assertSame($weights[$id], NpcPossessionSelector::weight($candidate));
|
|
}
|
|
$seed = NpcPossessionSelector::buildSeed(
|
|
'npc-possession-differential-seed',
|
|
7701,
|
|
'2026-08-01 00:00:01',
|
|
);
|
|
|
|
$legacyRng = new RandUtil(new LiteHashDRBG($seed));
|
|
$legacyPicked = [];
|
|
$legacyDraws = [];
|
|
$pickLimit = min(count($candidates), 5);
|
|
while (count($legacyPicked) < $pickLimit) {
|
|
$id = $legacyRng->choiceUsingWeight($weights);
|
|
$legacyDraws[] = (int)$id;
|
|
if (!array_key_exists($id, $legacyPicked)) {
|
|
$legacyPicked[$id] = $candidates[$id];
|
|
}
|
|
}
|
|
|
|
$sharedDraws = [];
|
|
$sharedPicked = NpcPossessionSelector::select(
|
|
$candidates,
|
|
$weights,
|
|
[],
|
|
new RandUtil(new LiteHashDRBG($seed)),
|
|
static function (int|string $id) use (&$sharedDraws): void {
|
|
$sharedDraws[] = (int)$id;
|
|
},
|
|
);
|
|
|
|
self::assertSame([1, 8, 1, 4, 5, 5, 8, 8, 5, 3], $legacyDraws);
|
|
self::assertSame($legacyDraws, $sharedDraws);
|
|
self::assertSame($legacyPicked, $sharedPicked);
|
|
}
|
|
|
|
public function testKeepAndReservedHelpersPreserveEndpointSemantics(): void
|
|
{
|
|
$candidates = $this->candidates();
|
|
$weights = array_map(
|
|
static fn(array $candidate): float => NpcPossessionSelector::weight($candidate),
|
|
$candidates,
|
|
);
|
|
NpcPossessionSelector::removeReserved(
|
|
$candidates,
|
|
$weights,
|
|
[
|
|
Json::encode((object)[2 => ['keepCnt' => 3], 7 => ['keepCnt' => 3]]),
|
|
],
|
|
);
|
|
self::assertSame([1, 3, 4, 5, 6, 8], array_keys($candidates));
|
|
self::assertSame([1, 3, 4, 5, 6, 8], array_keys($weights));
|
|
|
|
$oldPick = array_intersect_key($this->candidates(), array_flip([1, 8, 4, 5, 3]));
|
|
$partial = NpcPossessionSelector::applyKeep($oldPick, [1, 4]);
|
|
self::assertFalse($partial['cancelled']);
|
|
self::assertSame(2, $partial['picked'][1]['keepCnt']);
|
|
self::assertSame(2, $partial['picked'][4]['keepCnt']);
|
|
|
|
$all = NpcPossessionSelector::applyKeep($oldPick, [1, 8, 4, 5, 3]);
|
|
self::assertTrue($all['cancelled']);
|
|
self::assertSame([1, 8, 4, 5, 3], array_keys($all['picked']));
|
|
}
|
|
}
|