更新一个 关卡时间AB方案

This commit is contained in:
COMPUTER\EDY
2026-10-10 18:35:48 +08:00
parent eb2fe6175a
commit 7302fccb48
4 changed files with 225 additions and 1 deletions
+6 -1
View File
@@ -4,6 +4,7 @@ import { GAMEPLAY_ATLAS_PATHS } from "../Tool/GameplayAtlasFrames";
import { MiniGameSdk } from "../../Sdk/MiniGameSdk";
import { GameTool } from "../Tool/GameTool";
import BlockAssetManager from "../Tool/BlockAssetManager";
import { LEVEL_TIME_BONUS } from "./LevelTimeBonusConfig";
interface GameplayEntryPreparation {
startedAt: number;
@@ -1365,7 +1366,11 @@ export class GameConfig {
static applyLevelData(jsonData: any, type: any, time: any, levelNumber = Number(jsonData.LEVEL_INFO[0].id), preparation?: GameplayEntryPreparation) {
this.loadedLevelNumber = levelNumber;
this.BLOCK_INFO = jsonData["BLOCK_INFO"];
this.LEVEL_INFO = jsonData["LEVEL_INFO"];
const extraTime = this.GM_INFO?.abTests?.layer_three === 2 ? LEVEL_TIME_BONUS[levelNumber] || 0 : 0;
// 只调整本局时限,保留资源缓存中的原始时间,避免重开时重复加时。
this.LEVEL_INFO = extraTime
? jsonData["LEVEL_INFO"].map((info, index) => index === 0 ? { ...info, time: info.time + extraTime } : info)
: jsonData["LEVEL_INFO"];
this.WALL_INFO = jsonData["WALL_INFO"];
console.log("关卡配置加载完成");
@@ -0,0 +1,103 @@
// layer_three B 组的逐关加时,单位:秒。
export const LEVEL_TIME_BONUS: { readonly [level: number]: number } = {
114: 30,
132: 20,
222: 10,
259: 30,
302: 30,
317: 20,
329: 10,
383: 30,
400: 30,
411: 20,
416: 20,
422: 20,
426: 30,
433: 30,
444: 40,
452: 30,
504: 30,
517: 40,
521: 20,
524: 30,
535: 50,
536: 20,
542: 20,
548: 30,
557: 20,
563: 20,
566: 20,
595: 10,
604: 20,
612: 20,
630: 30,
648: 20,
651: 30,
666: 20,
688: 30,
690: 30,
745: 20,
748: 40,
750: 30,
789: 20,
798: 30,
799: 30,
828: 20,
868: 20,
881: 20,
895: 30,
916: 20,
962: 30,
963: 40,
981: 20,
991: 20,
1003: 30,
1008: 20,
1023: 20,
1024: 20,
1026: 30,
1027: 30,
1043: 20,
1061: 20,
1063: 30,
1077: 20,
1087: 20,
1093: 20,
1107: 20,
1122: 40,
1123: 40,
1129: 20,
1137: 10,
1139: 30,
1143: 30,
1146: 20,
1154: 40,
1164: 20,
1166: 30,
1171: 40,
1173: 20,
1175: 10,
1185: 30,
1195: 20,
1202: 20,
1204: 20,
1205: 30,
1206: 20,
1207: 40,
1209: 20,
1210: 30,
1212: 40,
1222: 60,
1225: 30,
1227: 20,
1228: 20,
1239: 30,
1241: 30,
1242: 40,
1264: 20,
1266: 30,
1268: 40,
1275: 60,
1279: 40,
1281: 40,
};
@@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "df6e40f7-1279-4724-afe3-55fade9817c0",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}
+106
View File
@@ -0,0 +1,106 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { loadMembers, loadModule, resolveFile } = require('./new-player-ab-test-support.cjs');
const { LEVEL_TIME_BONUS } = loadModule('assets/Script/module/Config/LevelTimeBonusConfig.ts');
const configFile = 'assets/Script/module/Config/GameConfig.ts';
const readLevel = level => JSON.parse(fs.readFileSync(resolveFile(`assets/custom/Json/level${level}.json`), 'utf8'));
const plain = value => JSON.parse(JSON.stringify(value));
function config(info = {}) {
const Config = loadMembers(configFile, ['applyLevelData', 'LEVEL_INFO_init', 'getLevelConfigPath'], {
LEVEL_TIME_BONUS, console: { log() {} }, cc,
});
Config.GM_INFO = info;
return Config;
}
// Exercise the actual loader so file numbers, help levels and endless levels use the same key.
const requests = [];
const cc = {
v2: (x, y) => ({ x, y }), director: { getScene: () => null },
fx: { GameTool: { maxLevel: () => false } },
assetManager: {
loadBundle(_name, _options, done) { done(null, {}); },
loadAny(request, _options, done) {
requests.push(request.path);
done(null, { json: readLevel(Number(request.path.replace('Json/level', ''))) });
},
},
};
test('A, unassigned and other groups retain the original time for every configured level', () => {
assert.equal(Object.keys(LEVEL_TIME_BONUS).length, 100);
for (const info of [undefined, {}, { abTests: {} }, ...[0, 1, 3].map(layer_three => ({ abTests: { layer_three } }))]) {
const Config = config(info);
for (const level of Object.keys(LEVEL_TIME_BONUS)) {
const data = readLevel(level);
Config.applyLevelData(data, false, 0, Number(level));
assert.equal(Config.LEVEL_INFO[0].time, data.LEVEL_INFO[0].time);
assert.equal(Config.LEVEL_INFO, data.LEVEL_INFO);
}
}
});
test('B adds time once, preserves cached data and returns to original time when switched to A', () => {
const Config = config({ abTests: { layer_three: 2 } });
for (const [level, bonus] of Object.entries(LEVEL_TIME_BONUS)) {
assert.ok(Number.isInteger(bonus) && bonus > 0);
const data = readLevel(level), before = structuredClone(data);
Config.GM_INFO.abTests.layer_three = 2;
for (let retry = 0; retry < 2; retry++) {
Config.applyLevelData(data, false, 0, Number(level));
const expected = structuredClone(before.LEVEL_INFO);
expected[0].time += bonus;
assert.deepEqual(plain(Config.LEVEL_INFO), expected);
assert.equal(Config.BLOCK_INFO, data.BLOCK_INFO);
assert.equal(Config.WALL_INFO, data.WALL_INFO);
assert.deepEqual(data, before);
}
Config.GM_INFO.abTests.layer_three = 1;
Config.applyLevelData(data, false, 0, Number(level));
assert.equal(Config.LEVEL_INFO[0].time, before.LEVEL_INFO[0].time);
}
});
test('B leaves unlisted levels unchanged and uses the file number instead of a stale internal id', () => {
const Config = config({ abTests: { layer_three: 2 } });
for (const level of [1, 113, 115, 1282]) {
const data = readLevel(level);
Config.applyLevelData(data, false, 0, level);
assert.equal(Config.LEVEL_INFO, data.LEVEL_INFO);
}
const data = readLevel(222);
assert.equal(data.LEVEL_INFO[0].id, '158');
Config.applyLevelData(data, false, 0, 222);
assert.equal(Config.LEVEL_INFO[0].time, 70);
assert.equal(Config.LEVEL_INFO[0].id, '158');
});
test('loader applies B bonuses to the requested mainline, help and endless level', () => {
for (const mode of ['mainline', 'help', 'endless']) {
const Config = config({ level: mode === 'mainline' ? 221 : 0,
otherLevel: mode === 'help' ? 222 : 0, randomLevel: 222, abTests: { layer_three: 2 } });
cc.fx.GameConfig = Config;
Config.levelLoadRequest = Config.sceneLoadRequest = 0;
Config.takeEntryDownloadRelease = () => null;
Config.LEVEL_INFO_init(false, 0, mode === 'help', mode === 'endless');
assert.equal(requests.at(-1), 'Json/level222');
assert.equal(Config.LEVEL_INFO[0].time, 70, mode);
}
});
test('server A/B assignments select original and bonus times before entering the scene', () => {
const Manager = loadMembers('assets/Script/GameManager.ts', ['setABTests'], { cc });
const manager = new Manager();
const Config = config();
cc.fx.GameConfig = Config;
const seen = [];
Config.enterGameSceneWhenReady = (time, preparation) => seen.push([Config.LEVEL_INFO[0].time, time, preparation]);
const preparation = {};
for (const [group, expected] of [['A', 135], ['B', 165], [1, 135], [2, 165]]) {
manager.setABTests({ layer_3: { group } });
Config.applyLevelData(readLevel(114), true, 0.5, 114, preparation);
assert.deepEqual(seen.at(-1), [expected, 0.5, preparation]);
}
});