> */ public array $calls = []; public function __construct(private readonly RNG $inner) { } public static function getMaxInt(): int { return LiteHashDRBG::getMaxInt(); } public function nextBytes(int $bytes): string { $value = $this->inner->nextBytes($bytes); $this->record('nextBytes', ['bytes' => $bytes], bin2hex($value)); return $value; } public function nextBits(int $bits): string { $value = $this->inner->nextBits($bits); $this->record('nextBits', ['bits' => $bits], bin2hex($value)); return $value; } public function nextInt(?int $max = null): int { $value = $this->inner->nextInt($max); $this->record('nextInt', ['maxInclusive' => $max], $value); return $value; } public function nextFloat1(): float { $value = $this->inner->nextFloat1(); $this->record('nextFloat1', [], $value); return $value; } private function record(string $operation, array $arguments, mixed $result): void { $this->calls[] = [ 'seq' => $this->sequence++, 'operation' => $operation, 'arguments' => $arguments, 'result' => $result, ]; } } function comparisonExtractRankVar(array $raw): Map { $rankVars = new Map(); foreach ($raw as $rawKey => $rawValue) { $key = RankColumn::tryFrom($rawKey); if ($key !== null) { $rankVars[$key] = $rawValue; } } return $rankVars; } function comparisonBuildGeneral( array $raw, array $city, array $nation, int $year, int $month, bool $defender = false, ): General { $aux = []; if (array_key_exists('inheritBuff', $raw)) { $aux['inheritBuff'] = $raw['inheritBuff']; } $raw['aux'] = Json::encode($aux); $raw['owner'] = 0; return new General( $raw, comparisonExtractRankVar($raw), null, $city, $nation, $year, $month, $defender, ); } function comparisonRunBattle(array $fixture): array { $seed = (string)($fixture['seed'] ?? 'battle-differential'); $year = (int)$fixture['year']; $month = (int)$fixture['month']; $startYear = (int)($fixture['startYear'] ?? 180); $rawAttacker = $fixture['attackerGeneral']; $rawAttackerCity = $fixture['attackerCity']; $rawAttackerNation = $fixture['attackerNation']; $rawDefenderCity = $fixture['defenderCity']; $rawDefenderNation = $fixture['defenderNation']; $tracingRng = new ComparisonTracingRNG(new LiteHashDRBG($seed)); $warRng = new RandUtil($tracingRng); $attacker = new WarUnitGeneral( $warRng, comparisonBuildGeneral($rawAttacker, $rawAttackerCity, $rawAttackerNation, $year, $month), $rawAttackerNation, true, ); $city = new WarUnitCity($warRng, $rawDefenderCity, $rawDefenderNation, $year, $month, $startYear); $defenderList = []; foreach ($fixture['defenderGenerals'] as $rawDefender) { $defenderList[] = new WarUnitGeneral( $warRng, comparisonBuildGeneral($rawDefender, $rawDefenderCity, $rawDefenderNation, $year, $month, true), $rawDefenderNation, false, ); } if (count($defenderList) && extractBattleOrder($city, $attacker) > 0) { $defenderList[] = $city; } usort( $defenderList, fn(WarUnit $lhs, WarUnit $rhs): int => -(extractBattleOrder($lhs, $attacker) <=> extractBattleOrder($rhs, $attacker)), ); $iterDefender = new \ArrayIterator($defenderList); $iterDefender->rewind(); $finishedDefenders = []; $getNextDefender = function (?WarUnit $previous, bool $requestNext) use ( $iterDefender, $attacker, &$finishedDefenders, ): ?WarUnit { if ($previous !== null) { $finishedDefenders[] = buildWarTraceUnitSnapshot($previous); } if (!$requestNext || !$iterDefender->valid()) { return null; } $next = $iterDefender->current(); if (extractBattleOrder($next, $attacker) <= 0) { return null; } $iterDefender->next(); return $next; }; $events = []; $conquered = processWar_NG( $seed, $attacker, $getNextDefender, $city, static function (array $event) use (&$events): void { $events[] = $event; }, ); return [ 'engine' => 'ref', 'seed' => $seed, 'conquered' => $conquered, 'attacker' => buildWarTraceUnitSnapshot($attacker), 'city' => buildWarTraceUnitSnapshot($city), 'finishedDefenders' => $finishedDefenders, 'events' => $events, 'rng' => $tracingRng->calls, ]; } $fixturePath = $argv[1] ?? null; if ($fixturePath === null || ($fixturePath !== '-' && !is_file($fixturePath))) { fwrite(STDERR, "usage: php compare/battle_trace.php \n"); exit(2); } try { $fixtureJson = $fixturePath === '-' ? stream_get_contents(STDIN) : file_get_contents($fixturePath); $fixture = json_decode((string)$fixtureJson, true, flags: JSON_THROW_ON_ERROR); echo json_encode( comparisonRunBattle($fixture), JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION, ), PHP_EOL; } catch (\Throwable $throwable) { fwrite(STDERR, $throwable::class . ': ' . $throwable->getMessage() . PHP_EOL); exit(1); }