merge: refresh ng_compare for logical game clock

This commit is contained in:
2026-08-04 12:09:58 +00:00
124 changed files with 2713 additions and 807 deletions
+23 -6
View File
@@ -16,6 +16,18 @@ require_once 'lib.php';
require_once 'func.php';
require_once __DIR__ . '/turn_state_snapshot.php';
function comparisonGameTick(mixed $value, string $label): int
{
if (is_int($value)) {
return GameClock::requireSafeTick($value);
}
if (!is_string($value) || $value === '') {
throw new \InvalidArgumentException("{$label} must be a game tick or projected date string");
}
$gameStorage = KVStorage::getStorage(DB::db(), 'game_env');
return GameClock::fromStorage($gameStorage)->dateTimeToTick(new \DateTimeImmutable($value));
}
/** @param array<string, mixed> $values */
function comparisonMonthlyPatch(string $table, int $id, array $values): void
{
@@ -1073,6 +1085,14 @@ function comparisonMonthlyEventTraceMain(): void
if (!is_array($environment)) {
throw new \InvalidArgumentException('environment must be an object');
}
foreach (['starttime', 'turntime'] as $timeKey) {
if (array_key_exists($timeKey, $environment)) {
$environment[$timeKey] = comparisonGameTick(
$environment[$timeKey],
"environment.{$timeKey}",
);
}
}
$year = $environment['year'] ?? null;
$month = $environment['month'] ?? null;
$startYear = $environment['startyear'] ?? null;
@@ -1089,9 +1109,6 @@ function comparisonMonthlyEventTraceMain(): void
'turnterm' => $environment['turnterm'] ?? $gameStorage->turnterm,
];
if (isset($environment['starttime'])) {
if (!is_string($environment['starttime']) || $environment['starttime'] === '') {
throw new \InvalidArgumentException('environment.starttime must be a non-empty string');
}
$environmentValues['starttime'] = $environment['starttime'];
}
foreach ($environmentValues as $key => $value) {
@@ -1232,12 +1249,12 @@ function comparisonMonthlyEventTraceMain(): void
if (
!is_int($turnterm)
|| $turnterm <= 0
|| !is_string($turntime)
|| !is_int($turntime)
|| !is_int($showImgLevel)
|| !is_array($fiction)
) {
throw new \InvalidArgumentException(
"{$actionName} requires positive turnterm, string turntime, integer show_img_level, and array fiction",
"{$actionName} requires positive turnterm, game-tick turntime, integer show_img_level, and array fiction",
);
}
$actionEnvironment += [
@@ -1267,7 +1284,7 @@ function comparisonMonthlyEventTraceMain(): void
))));
} elseif ($actionName === 'MonthlyBoundary') {
$turntime = $environment['turntime'] ?? null;
if (!is_string($turntime) || $turntime === '') {
if (!is_int($turntime)) {
throw new \InvalidArgumentException('MonthlyBoundary requires turntime');
}
$gameStorage = KVStorage::getStorage($db, 'game_env');
+7
View File
@@ -42,10 +42,13 @@ function monthlySnapshotPick(array $row, array $mapping, array $jsonColumns = []
$db = DB::db();
$game = KVStorage::getStorage($db, 'game_env');
$game->resetCache();
$clock = GameClock::fromStorage($game);
$world = $game->getValues([
'scenario', 'year', 'month', 'turnterm', 'turntime', 'starttime',
'init_year', 'init_month', 'develcost', 'killturn', 'isunited',
]);
$world['turntime'] = $clock->formatTick(Util::toInt($world['turntime']), true);
$world['starttime'] = $clock->formatTick(Util::toInt($world['starttime']), true);
$generals = [];
foreach ($db->query('SELECT * FROM general ORDER BY no') as $row) {
@@ -69,6 +72,10 @@ foreach ($db->query('SELECT * FROM general ORDER BY no') as $row) {
'dex4', 'dex5', 'killTurn', 'affinity', 'birthYear', 'deathYear', 'specAge', 'specAge2'] as $key) {
$general[$key] = (int)$general[$key];
}
$general['turnTime'] = $clock->formatTick(Util::toInt($general['turnTime']), true);
$general['recentWarTime'] = $general['recentWarTime'] === null
? null
: $clock->formatTick(Util::toInt($general['recentWarTime']), true);
$generals[] = $general;
}
+12 -9
View File
@@ -110,12 +110,12 @@ function comparisonNpcState(): array
* @param array<string, mixed> $case
* @return array<string, mixed>
*/
function comparisonNpcRunCase(array $case, string|int $hiddenSeed, int $owner, string $now): array
function comparisonNpcRunCase(array $case, string|int $hiddenSeed, int $owner, int $now, GameClock $clock): array
{
$db = DB::db();
$db->delete('select_npc_token', '1=1');
$insertReserved = static function (int $reservedOwner, array $reservedIds, string $validUntil) use ($db): void {
$insertReserved = static function (int $reservedOwner, array $reservedIds, int $validUntil) use ($db, $now): void {
if ($reservedIds === []) {
return;
}
@@ -126,7 +126,7 @@ function comparisonNpcRunCase(array $case, string|int $hiddenSeed, int $owner, s
$db->insert('select_npc_token', [
'owner' => $reservedOwner,
'valid_until' => $validUntil,
'pick_more_from' => '2000-01-01 01:00:00',
'pick_more_from' => $now,
'pick_result' => json_encode((object)$reservedPick, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
'nonce' => 1,
]);
@@ -140,8 +140,8 @@ function comparisonNpcRunCase(array $case, string|int $hiddenSeed, int $owner, s
$case['expiredReservedIds'] ?? [],
'expiredReservedIds',
);
$expiredAt = (new \DateTimeImmutable($now))->modify('-1 second')->format('Y-m-d H:i:s');
$insertReserved($owner + 1, $reservedIds, '2099-12-31 23:59:59');
$expiredAt = $now - $clock->ticksFromSeconds(1);
$insertReserved($owner + 1, $reservedIds, GameClock::MAX_SAFE_TICK);
$insertReserved($owner + 2, $boundaryReservedIds, $now);
$insertReserved($owner + 3, $expiredReservedIds, $expiredAt);
@@ -156,7 +156,7 @@ function comparisonNpcRunCase(array $case, string|int $hiddenSeed, int $owner, s
$candidates,
$weights,
$db->queryFirstColumn(
'SELECT pick_result FROM select_npc_token WHERE `owner`!=%i AND valid_until >=%s',
'SELECT pick_result FROM select_npc_token WHERE `owner`!=%i AND valid_until >=%i',
$owner,
$now,
),
@@ -176,8 +176,8 @@ function comparisonNpcRunCase(array $case, string|int $hiddenSeed, int $owner, s
if ($hasPreviousToken) {
$db->insert('select_npc_token', [
'owner' => $owner,
'valid_until' => '2099-12-31 23:59:59',
'pick_more_from' => '2000-01-01 01:00:00',
'valid_until' => GameClock::MAX_SAFE_TICK,
'pick_more_from' => $now,
'pick_result' => json_encode((object)$oldPick, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
'nonce' => 2,
]);
@@ -259,6 +259,9 @@ if ($date === false || $date->format('Y-m-d H:i:s') !== $now) {
}
$db = DB::db();
$gameStorage = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStorage);
$nowTick = $clock->dateTimeToTick($date);
$db->update('general', ['npc' => 1], '1=1');
$fixtureIds = [];
foreach ($candidateFixtures as $candidate) {
@@ -295,7 +298,7 @@ foreach ($cases as $case) {
if (!is_array($case)) {
throw new \InvalidArgumentException('case must be an object');
}
$results[] = comparisonNpcRunCase($case, $hiddenSeed, $owner, $now);
$results[] = comparisonNpcRunCase($case, $hiddenSeed, $owner, $nowTick, $clock);
}
echo json_encode(
+10 -5
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
use sammo\DB;
use sammo\Event\Action\OpenNationBetting;
use sammo\GameClock;
use sammo\Json;
use sammo\KVStorage;
@@ -46,8 +47,12 @@ if ($uniqueCount === 0) {
if ($generalID <= 0) {
throw new RuntimeException('Ref GUI parity fixture requires at least one general.');
}
$openDate = new DateTimeImmutable('now');
$closeDate = $openDate->modify('+1 day');
$gameStorage = KVStorage::getStorage($db, 'game_env');
$clock = GameClock::fromStorage($gameStorage);
$openTick = $clock->nowTick();
$closeTick = GameClock::addTicks($openTick, $clock->ticksFromMinutes(24 * 60));
$latestBidCloseTick = GameClock::addTicks($openTick, $clock->ticksFromMinutes(2 * 24 * 60));
$openDate = $clock->tickToDateTime($openTick);
$detail = [
'title' => '손자병법 경매',
'hostName' => '청룡',
@@ -56,7 +61,7 @@ if ($uniqueCount === 0) {
'startBidAmount' => 1000,
'finishBidAmount' => null,
'remainCloseDateExtensionCnt' => 1,
'availableLatestBidCloseDate' => $openDate->modify('+2 days')->format('Y-m-d H:i:s'),
'availableLatestBidCloseTick' => $latestBidCloseTick,
];
$db->insert('ng_auction', [
'type' => 'uniqueItem',
@@ -64,8 +69,8 @@ if ($uniqueCount === 0) {
'target' => 'che_서적_15_손자병법',
'host_general_id' => $generalID,
'req_resource' => 'inheritPoint',
'open_date' => $openDate->format('Y-m-d H:i:s'),
'close_date' => $closeDate->format('Y-m-d H:i:s'),
'open_tick' => $openTick,
'close_tick' => $closeTick,
'detail' => Json::encode($detail),
]);
$auctionID = (int)$db->insertId();
+24 -15
View File
@@ -231,6 +231,7 @@ function comparisonTurnStateSnapshot(array $request): array
$game = KVStorage::getStorage($db, 'game_env');
$game->resetCache();
$clock = GameClock::fromStorage($game);
$worldValues = $game->getValues([
'year',
'month',
@@ -270,7 +271,7 @@ function comparisonTurnStateSnapshot(array $request): array
);
$generals = array_map(
static function (array $row): array {
static function (array $row) use ($clock): array {
$result = comparisonPickRow(
$row,
[
@@ -338,6 +339,10 @@ function comparisonTurnStateSnapshot(array $request): array
] as $key) {
$result[$key] = comparisonOptionalCode($result[$key] ?? null);
}
$result['turnTime'] = $clock->formatTick(Util::toInt($row['turntime']), true);
$result['recentWarTime'] = $row['recent_war'] === null
? null
: $clock->formatTick(Util::toInt($row['recent_war']), true);
$result['maxBelong'] = (int)($result['meta']['max_belong'] ?? 0);
$ownerId = (int)($row['owner'] ?? 0);
$activeActionPoints = 0;
@@ -576,19 +581,23 @@ function comparisonTurnStateSnapshot(array $request): array
}
$messages = array_map(
static fn(array $row): array => comparisonPickRow(
$row,
[
'id' => 'id',
'mailbox' => 'mailbox',
'type' => 'type',
'sourceId' => 'src',
'destinationId' => 'dest',
'createdAt' => 'time',
'payload' => 'message',
],
['message'],
),
static function (array $row) use ($clock): array {
$result = comparisonPickRow(
$row,
[
'id' => 'id',
'mailbox' => 'mailbox',
'type' => 'type',
'sourceId' => 'src',
'destinationId' => 'dest',
'createdAt' => 'time',
'payload' => 'message',
],
['message'],
);
$result['createdAt'] = $clock->formatTick(Util::toInt($row['time']), true);
return $result;
},
$db->query(
'SELECT id, mailbox, type, src, dest, time, message FROM message WHERE id > %i ORDER BY id',
$messageAfterId,
@@ -602,7 +611,7 @@ function comparisonTurnStateSnapshot(array $request): array
'year' => (int)$worldValues['year'],
'month' => (int)$worldValues['month'],
'tickMinutes' => (int)$worldValues['turnterm'],
'turnTime' => (string)$worldValues['turntime'],
'turnTime' => $clock->formatTick(Util::toInt($worldValues['turntime']), true),
'isUnited' => (int)$worldValues['isunited'],
'scenarioId' => (int)$worldValues['scenario'],
'initYear' => (int)$worldValues['init_year'],