feat(version): support 4.6.51

This commit is contained in:
capyb2222 committed 2026-09-29 17:14:27 -07:00
1 parent 530def48df
commit dfe7292a14
182 files changed
+811980 -87492

No files matched your search

+23 -2
View File
@@ -783,8 +783,8 @@ void runFlowTests() {
check(client.await(cmd::GetChallengePeakDataScRsp, packet), "arbitration data answered");
proto::GetChallengePeakDataScRsp peakData;
check(parseBody(packet, peakData), "arbitration data parses");
check(peakData.challenge_peak_groups.size() >= 10, "with every season in it");
check(peakData.current_peak_group_id == 10, "and the newest on show");
check(peakData.challenge_peak_groups.size() >= 11, "with every season in it");
check(peakData.current_peak_group_id == 11, "and the newest on show");
proto::SetChallengePeakMobLineupAvatarCsReq teams;
teams.peak_group_id = 9;
@@ -1277,6 +1277,27 @@ void runFlowTests() {
check(nowhere.size() == 1 && nowhere[0].find("no command") != std::string::npos,
"and an unknown command says so instead of going quiet");
// The chat box is hidden with everything else, so the UI has to come back by itself.
// Watermark pushes share the notify and are skipped.
auto awaitLua = [&](const std::string& marker, int timeoutMs) {
uint64_t deadline = util::nowMs() + static_cast<uint64_t>(timeoutMs);
net::Packet pushedPacket;
while (util::nowMs() < deadline) {
proto::ClientDownloadDataScNotify pushed;
if (!client.await(cmd::ClientDownloadDataScNotify, pushedPacket, timeoutMs)) return false;
if (parseBody(pushedPacket, pushed) && pushed.download_data &&
pushed.download_data->data.find(marker) != std::string::npos) {
return true;
}
}
return false;
};
std::vector<std::string> hidden = typed("/hideui 1");
check(hidden.size() == 1 && hidden[0].find("hidden for 1s") != std::string::npos,
"/hideui says for how long");
check(awaitLua("canvas.enabled = false", 2000), "the hide goes out at once");
check(awaitLua("canvas.enabled = true", 3000), "and the UI comes back after the delay");
std::vector<std::string> chatter = typed("hello");
check(chatter.size() == 1 && chatter[0].find("/help") != std::string::npos,
"plain chat is nudged towards /help");
+47 -6
View File
@@ -713,7 +713,7 @@ void testChallengeHistory() {
// Anomaly Arbitration. Production rows have no arena, so the beta dump fills it in,
// and places season ten, which production does not have at all.
check(tables.peakGroups().size() >= 10, "every arbitration season loaded");
check(tables.peakGroups().size() >= 11, "every arbitration season loaded");
const data::PeakInfo* knight = tables.peak(101);
check(knight != nullptr && knight->mapEntranceId == 3013501 && knight->eventId == 30501011,
"a production knight gets its arena from the beta dump");
@@ -778,8 +778,9 @@ void testRosterProtos() {
const std::vector<uint32_t>& ids = game::Roster::baseAvatarIds();
check(!ids.empty(), "the roster has base ids");
// Derived from AvatarConfig; the 4.5 + 4.6-beta merge comes to exactly 88 base ids.
check(ids.size() == 88, "the derived roster has all 88 base ids");
// Derived from AvatarConfig; 4.5 + 4.6-beta + the 4.6.51 overlay come to 89 base ids.
check(ids.size() == 89, "the derived roster has all 89 base ids");
check(std::find(ids.begin(), ids.end(), 1511u) != ids.end(), "Aha from the 4.6.51 overlay");
check(std::find(ids.begin(), ids.end(), 6022u) == ids.end(), "story-only units are excluded");
check(std::find(ids.begin(), ids.end(), 1503u) != ids.end(), "beta-only avatars are included");
check(ids.size() >= 2 && ids[0] == 8001 && ids[1] == 1001, "Trailblazer and March lead");
@@ -802,9 +803,15 @@ void testRosterProtos() {
check(battle.hp == 10000, "hp is a percentage in hundredths");
check(battle.sp_bar && battle.sp_bar->max_sp >= 10000, "sp is scaled to hundredths");
check(!battle.skilltree_list.empty(), "traces are filled in");
check(!buffs.empty(), "a technique buff was produced");
check(buffs[0].wave_flag == 0xFFFFFFFFu, "technique buffs apply to every wave");
check(buffs[0].owner_index == 0, "owned by the avatar that brought it");
// A build may switch every technique off; then there is nothing to check.
auto build = game::SrTools::instance().data();
auto it = build->avatars.find(8008);
bool techniquesOff = it != build->avatars.end() && it->second.techniques.empty();
check(techniquesOff || !buffs.empty(), "a technique buff was produced");
if (!buffs.empty()) {
check(buffs[0].wave_flag == 0xFFFFFFFFu, "technique buffs apply to every wave");
check(buffs[0].owner_index == 0, "owned by the avatar that brought it");
}
}
void testSceneBuild() {
@@ -993,6 +1000,39 @@ void testEveryScenePacketFits() {
"the bag fits in one kcp message");
}
// 4.6.51 seasons come from the beta dump alone: floors with no group rows, all three
// modes in one table, Pure Fiction floors with no extras.
void testNewSeasons() {
const data::Tables& tables = data::Tables::get();
if (!tables.loaded()) return;
const data::ChallengeInfo* story = tables.challenge(20281);
const data::ChallengeInfo* boss = tables.challenge(30231);
const data::ChallengeInfo* memory = tables.challenge(5601);
check(story && story->kind == data::ChallengeKind::Story, "a 2xxx season is Pure Fiction");
check(boss && boss->kind == data::ChallengeKind::Boss, "a 3xxx season is Apocalyptic Shadow");
check(memory && memory->kind == data::ChallengeKind::Memory, "a 1xxx season is Memory");
const data::ChallengeGroupInfo* season = tables.challengeGroup(2028);
const data::ChallengeGroupInfo* previous = tables.challengeGroup(2026);
check(season && season->kind == data::ChallengeKind::Story, "the new season is listed");
check(season && previous && season->rewardLineGroupId == previous->rewardLineGroupId,
"with its mode's reward line");
check(tables.challengeGroup(1037) && tables.challengeGroup(3023), "and the other two modes'");
check(story && story->roundLimit == 5 && story->battleTargetIds == std::vector<uint32_t>{2001, 2002},
"Pure Fiction extras come from the same floor of an earlier season");
check(tables.stageInvasion(30127112) == 2, "a stage's invasion is loaded");
check(tables.stageInvasion(1) == 0, "and most stages have none");
game::Player player = makeTestPlayer();
game::BattleRequest request;
request.stageIds = {30127112};
proto::SceneBattleInfo info = game::battle::create(player, request);
int invasion = 0;
for (const proto::BattleBuff& buff : info.buff_list) {
if (buff.id == 3034002 || buff.id == 3034012) ++invasion;
}
check(invasion == 2, "the fight carries the invasion's attack and support buffs");
}
void testBattleBuild() {
if (!data::SceneRes::get().loaded() || !data::Tables::get().loaded()) return;
@@ -1324,6 +1364,7 @@ void runGameTests() {
testChallengeRun();
testTierceRun();
testEveryScenePacketFits();
testNewSeasons();
testBattleBuild();
testBattleSourceSplit();
testPropInteraction();
+8 -9
View File
@@ -167,17 +167,16 @@ void testCmdIds() {
}
void testLineupCmdIds() {
// 716 is JoinLineupScRsp and 719 ReplaceLineupScRsp; the dump's own table had them
// mislabelled. Answering a join on 719 leaves the party UI waiting forever, so the
// names are corrected in proto/cmdids.txt.
check(cmd::JoinLineupScRsp == 716, "JoinLineupScRsp is 716");
check(cmd::ReplaceLineupScRsp == 719, "ReplaceLineupScRsp is 719");
check(cmd::name(716) == "JoinLineupScRsp", "716 names itself");
check(cmd::name(719) == "ReplaceLineupScRsp", "719 names itself");
// The two replies are easy to swap: answering a join on the replace id leaves the
// party UI waiting forever. 4.6.51 has them at 771 and 715.
check(cmd::JoinLineupScRsp == 771, "JoinLineupScRsp is 771");
check(cmd::ReplaceLineupScRsp == 715, "ReplaceLineupScRsp is 715");
check(cmd::name(771) == "JoinLineupScRsp", "771 names itself");
check(cmd::name(715) == "ReplaceLineupScRsp", "715 names itself");
// The generic fallback derives the response name from the request name, so
// ReplaceLineupCsReq now resolves even without a handler.
check(cmd::id("ReplaceLineupScRsp") == 719, "the reverse lookup agrees");
check(cmd::id("JoinLineupScRsp") == 716, "and for the join");
check(cmd::id("ReplaceLineupScRsp") == 715, "the reverse lookup agrees");
check(cmd::id("JoinLineupScRsp") == 771, "and for the join");
}
void testIdleTimeout() {