test(compare): trace NPC possession selection

This commit is contained in:
2026-07-31 04:39:00 +00:00
parent 765dee831c
commit 90d078c100
4 changed files with 514 additions and 30 deletions
+107
View File
@@ -0,0 +1,107 @@
<?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']));
}
}