fix: 每日首次登录时战令、月卡超出限制后当日不再弹出
This commit is contained in:
@@ -58,9 +58,6 @@ export default class HomePopupPolicy {
|
||||
if (this.record.day !== day) {
|
||||
this.record.day = day; this.record.shown = {}; this.record.blocked = {};
|
||||
}
|
||||
// Older clients persisted purchase-cap skips for the whole day. Caps are now per batch.
|
||||
delete this.record.blocked.passCheckIntro;
|
||||
delete this.record.blocked.monthlyCard;
|
||||
this.forced = level >= threshold && !this.record.tutorial.done;
|
||||
this.save(this.record);
|
||||
}
|
||||
@@ -85,7 +82,7 @@ export default class HomePopupPolicy {
|
||||
request(key: string, type: PopupFrequency = HOME_POPUP_RULES[key]?.type): boolean {
|
||||
this.refreshDay();
|
||||
if (type !== 1 && type !== 2) return false;
|
||||
if (type === 1 ? this.record.shown[key] : this.session.shown[key]) {
|
||||
if (type === 1 ? (this.record.shown[key] || this.record.blocked[key]) : this.session.shown[key]) {
|
||||
this.clearPending(key); return false;
|
||||
}
|
||||
this.session.pending[key] = true; this.savePending(); return true;
|
||||
@@ -105,7 +102,13 @@ export default class HomePopupPolicy {
|
||||
if (!this.eligible(key, type)) return false;
|
||||
this.attempts.add(key);
|
||||
const limit = HOME_POPUP_RULES[key]?.purchaseLimit;
|
||||
if (limit !== undefined && this.otherPopupCount >= limit) return false;
|
||||
if (limit !== undefined && this.otherPopupCount >= limit) {
|
||||
// Suppress this purchase for today without recording an impression.
|
||||
this.record.blocked[key] = true;
|
||||
this.save(this.record);
|
||||
this.clearPending(key);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
shown(key: string): void {
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
| 3 | 允许 | 跳过 |
|
||||
| ≥4 | 跳过 | 跳过 |
|
||||
|
||||
超限只跳过本批,不写当天禁止展示记录;旧版持久化的两个购买弹窗 blocked 标记在创建规则时清理,已成功展示的当天记录保留。已入队的类型 1/2 候选在实际显示前保留待展示状态;加载失败或离开首页不消耗次数,下次返回首页或登录时可重试,同一次首页停留不循环重试。新批次计数从零开始,手动入口不受额度限制。
|
||||
购买弹窗因限额跳过时,清除其待展示请求,并将该购买意图记为当天禁止自动展示(`blocked`)。当天返回首页、重新登录或再次触发请求均不补弹;北京时间次日零点恢复资格。跳过不计实际展示或曝光。未超限但加载失败或展示前离开首页的候选仍保留待展示状态,下次返回首页或登录可重试。新批次计数从零开始,手动入口不受额度限制。
|
||||
|
||||
战令结算准备失败释放当前队列,但不把业务结算标记为已完成。小鱼干普通弹窗排队期间转为到期结算时,释放普通意图,本批已关闭收集则等待下次返回首页;手动打开仍可直接处理结算。月卡奖励发放与通知分离,已发放但没展示的通知持久化为 pending,并登记为类型 1 补弹请求,下次返回首页或登录时展示不重复发奖。
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ for (const count of [0, 1, 2, 3, 4, 5]) test(`purchase limits use ${count} other
|
||||
if (count < 3) p.shown('monthlyCard');
|
||||
assert.equal(p.otherPopupCount, count);
|
||||
assert.equal(p.count, count + Number(count < 4) + Number(count < 3));
|
||||
assert.equal(policy(p.record).eligible('passCheckIntro'), count >= 4);
|
||||
assert.equal(policy(p.record).eligible('monthlyCard'), count >= 3);
|
||||
assert.equal(policy(p.record).eligible('passCheckIntro'), false);
|
||||
assert.equal(policy(p.record).eligible('monthlyCard'), false);
|
||||
assert.equal(policy(p.record, false).eligible('passCheckIntro'), false);
|
||||
assert.equal(policy(p.record, false).eligible('monthlyCard'), false);
|
||||
assert.equal(policy(p.record, true, day + 86400000).eligible('monthlyCard'), true);
|
||||
@@ -76,17 +76,12 @@ for (const count of [0, 2, 3, 4]) test(`settlements do not consume purchase allo
|
||||
assert.equal(p.begin('monthlyCard'), count < 3);
|
||||
});
|
||||
|
||||
test('legacy daily cap blocks are cleared but successful daily displays are preserved', () => {
|
||||
const raw = policy().record;
|
||||
raw.blocked = { passCheckIntro: true, monthlyCard: true };
|
||||
raw.shown.passCheckIntro = true;
|
||||
const p = policy(raw);
|
||||
assert.equal(p.eligible('passCheckIntro'), false);
|
||||
assert.equal(p.eligible('monthlyCard'), true);
|
||||
assert.deepEqual(p.record.blocked, {});
|
||||
p.begin('monthlyCard'); // Loading fails; next login can retry.
|
||||
assert.equal(policy(p.record).begin('monthlyCard'), true);
|
||||
assert.equal(policy(p.record).otherPopupCount, 0);
|
||||
test('a purchase that fails to load below its cap can still retry on return home', () => {
|
||||
const h = harness(), q = h.create();
|
||||
for (const key of ['monthlyCard', 'passCheckIntro']) q.enqueue(key, 0, done => done());
|
||||
h.ready(q); q.update(); q.update(); q.onDestroy();
|
||||
const next = h.create(false);
|
||||
for (const key of ['monthlyCard', 'passCheckIntro']) assert.equal(next.canCollect(key), true);
|
||||
});
|
||||
|
||||
test('Beijing day changes at 16:00 UTC and accounts do not share display records', () => {
|
||||
@@ -331,8 +326,23 @@ for (const count of [0, 1, 2, 3, 4]) test(`purchase checks run last after ${coun
|
||||
for (let i = 0; i < 10; i++) q.update();
|
||||
assert.deepEqual(order, others.concat(count < 4 ? ['passCheckIntro'] : [], count < 3 ? ['monthlyCard'] : []));
|
||||
assert.equal(q.policy.otherPopupCount, count);
|
||||
assert.equal(q.policy.record.blocked.monthlyCard, undefined);
|
||||
assert.equal(q.policy.record.blocked.passCheckIntro, undefined);
|
||||
for (const [key, limit] of [['monthlyCard', 3], ['passCheckIntro', 4]]) {
|
||||
assert.equal(!!q.policy.record.blocked[key], count >= limit);
|
||||
assert.equal(q.wasShownToday(key), count < limit, 'cap skips are not impressions');
|
||||
assert.equal(q.policy.hasPending(key), false);
|
||||
}
|
||||
q.onDestroy();
|
||||
for (const login of [false, true]) {
|
||||
const next = h.create(login);
|
||||
for (const key of ['monthlyCard', 'passCheckIntro']) {
|
||||
assert.equal(next.requestPresentation(key), false);
|
||||
assert.equal(next.canCollect(key), false);
|
||||
}
|
||||
next.onDestroy();
|
||||
}
|
||||
h.setNow(Date.UTC(2026, 9, 21, 16)); // Midnight in Beijing.
|
||||
const tomorrow = h.create();
|
||||
for (const key of ['monthlyCard', 'passCheckIntro']) assert.equal(tomorrow.canCollect(key), true);
|
||||
});
|
||||
|
||||
test('a visible previous-season offer does not suppress the current-season purchase in the same batch', () => {
|
||||
|
||||
Reference in New Issue
Block a user