HN 日本語サマリー

← 一覧へ戻る
AI・機械学習

Block Blastはどのようにしてプレイヤーから広告収入を得ているか

How Block Blast harvests its players for ad money (ayoships.com)

89 pointsby motionlessveloc40 コメント

要約

モバイルゲーム「Block Blast」は、サーバーからダウンロードされるルールに基づいてプレイヤーのゲーム体験(ピースの配置、スコア、広告表示)を操作しています。特に、高額な広告が表示されるタイミングを狙い、プレイヤーが配置できないピースを出すことでゲームを不利にし、広告収益を最大化する仕組みが詳細に分析されています。

全文翻訳

Block Blastはどのようにしてプレイヤーから広告収入を得ているか

ayoships.com

サーバーからダウンロードされたルールが、あなたのピース、スコア、そして広告を決定します。あるルールは、高額な広告が表示されるのを待ち、その後、配置できないピースのセットを要求します。

[画像: Ayo Omolewa]
2026年10月15日

このゲームに関する動画を投稿したところ1500万回以上再生されたので、コードや再現方法も含めて、技術的な詳細をすべて書き起こしたいと思いました。

なぜこれが重要なのかという背景として、2026年1月から5月にかけて、「Block Blast」は広告から推定1億2700万ドルを稼ぎました。同じ期間に「Candy Crush Saga」は8110万ドルを稼いでいます(Sensor Towerの推定)。

[動画][動画]

Block Blastの大部分はインストールするアプリには含まれていない

私たちはAndroidアプリのバージョン10.8.2から始めました。jadxでJava側を、GhidraでネイティブCocosライブラリを逆コンパイルしましたが、ストアからダウンロードするパッケージには興味深いものはほとんど見つかりませんでした。「Block Blast」は、コードが「trait(特性)」と呼ぶ小さなモジュールから構築されており、各traitには「arm(腕)」と呼ばれるバリアントがあります。アカウントなしのクリーンなAndroid 14エミュレーターで実行し、キャッシュとlogcatを監視したところ、その仕組みが明らかになりました。アプリが起動すると、サーバーはどのtraitのどのarmを取得するかを示す設定を送信し、アプリはそれらを暗号化されたJavaScriptとしてダウンロードします。私のインストールが取得した設定には、約700のtraitの名前が含まれていました。私のインストールはそれらのうち254を選択し、101がインストール後にダウンロードされました。

CTAgloReviveTraitCTReviveTimeOutSkipSplashTraitClassNewDeathOptimizeTraitDeadlyToHardTraitDecreaseReviveUIShowByLimitHighScoreTraitDifferentStrategyDieTraitEndlessDeathBoardTraitFastHandPunishTraitGLBFDieReplayTraitIsCacheReviveNumTraitIsClassADReviveNoInerestingTraitIsOpenInAdvanceDeathByLowEfficiencyGiveReviveTraitIsOpenRandomToRandomNoDieTraitLevelReviveCondiTraitLevelReviveCountTraitLevelReviveTraitMABXiBaiTraitMultiClearBoostThenKillTraitRepateCPlusPlusRandomNoDeathTraitReviveBuffTraitReviveCleanBoardTraitReviveCountTraitReviveLimitTraitRewardHighValueAdHarvestTraitAdLevelTraitAdPlusMinusStrategyTraitAddClearTipsExTraitAddMoreGameTraitAdjustPercentChTraitAlgoFillSortEdgeTraitAlgoSDK_modifyBlockPosTraitAlgoSDK_useAlgoSoAloneTraitAlgoSameBlockShangZengTraitAlgoSymmetryBlockTraitBComboScoreChangeTraitBCommentDisplayTraitBPuzzleTimeTraitBReplaceBinaryTraitBackSpaceClickLimitTraitBaseplateConditionDiffTraitBaseplateLaneTraitBaseplateTraitBlockLimitClassicsTraitBlockLimitLevelTraitBlockPoolRoundAdjustV2TraitBlockPoolStrategyExperimentTraitButtonClickAddShakeTraitCTAdvertisementMultipleCallTraitCTAdvertisementNoHideTraitCTAgloNameSpecialTraitCTAgloNameTraitCTAgloStrategyTraitCTBAlgorithmGaohuibaoARGTraitCTChapterScoreComboTraitCTChapterSkinBonesTraitCTChapterWinOverTraitCTClassSkinBonesTraitCTComboDotTraitCTCommentskinWithFeedBackTraitCTDotClickGameEndInfoTraitCTDotClickUsrDataGameEndTraitCTDotDeadReportToUsrDataGameEndTraitCTDotFirstClickDataGameEndTraitCTEffectAccTraitTraitCTGBMClearTraitCTGBMEmptySurfaceGameStartDotTraitCTGameComboScoreTraitCTGameEndStatusDotTraitCTLowDeviceEffectTraitCTMergeBlocksAgloNameTraitCTMergeBlocksSkinBonesTraitCTSkinBonesTraitChapterAlgoStrategyTraitChapterCollectScoreFixedTraitChapterWinShowRainbowTraitClassGuideEndNoAwaitNextFrameTraitClassicsRandomReplaceTraitCleanSceneRandomSkinTraitCleanSceneUseSequenceSkinTraitClearBoardHeartRippleAniTraitClearBoardPlusBlockPosTraitClearBoardPlusTraitComboAddRemoveNumTraitComboModeCtrlTraitComboRainbowTraitComboScoreSplitTraitComboTimeTraitCombo_tips_lv_blockTraitComboflyScoreTraitConsecutiveLossRecoveryTraitContinueSameMoreRoundLimitTraitControlHighestScoreRhythmTraitCrossClearHardTraitCtrlAlgoLimitTimeTraitDailyPerformanceReviewTraitDailyWinStreakStateTraitDifficultChangeColorTraitEightHoursTravelTraitExceedAimScoreTraitExpendBoardScoreTraitFastNearTkxcClassTraitFastNearTkxcTraitFeatclearscreenbombTraitFeatclearscreenbombfixTraitFillCleanAlgoTraitFillExcludeTraitFirstGameScoreTraitFirstHardMoreClearAlgoTraitFiveRoundStatisticsTraitGLBFMiddleTipsViewTraitGLBFProgressTraitGLGameAdStrategyOptimizeTraitGLResultView6TraitGLShowAdsByGameTimeTraitGameInfoDisplayTraitGameLobbyNewEntryCtrlTraitGameLobbyTraitGameLobbyUnlockByBreakHighScoreTraitGameOverShowMiniGameIconTraitGateWinStreakStateTraitGpEmptyBoardRatioGuardTraitHighProgressShowBannerTraitHighScoreBreakthroughBoostTraitHighScoreLimitByUserAbilityTraitHighValueInterstitialReliefTraitHighWeightSessionQuestionReplacerTraitHoleFillPriorityTraitHotAreaAdaptScreenBottomStateTraitHotAreaClickUtilTraitInitBoardDelayTraitInitBoardSnakeProStyleProTraitInitialBoardDisableTraitIsOpenBottomCoverChangeTraitIsOpenChangeSkinTraitIsOpenClassEncourageEffectByScoreTraitIsOpenComboContinuousTraitIsOpenComboInterruptionStrategyReplaceBlockTraitIsOpenExtraReportTraitIsOpenGLHallMoreGamesPopupTraitIsOpenGlLoadAllGameBackTraitIsOpenLevelOverTipsChangeTraitIsOpenMoreCleanChangeTraitIsOpenMoreCleanTraitIsOpenNoVideoNoRevivieTraitIsOpenOperaPosTraitIsOpenRainbowPutBlocksEffectTraitIsOpenRightPutTraitIsOpenShang1ToShang3TraitIsOpenVibrateTraitIsOpenZhiJueToKunNanTraitIsOpenZiMuShangZengTraitIsPuzzleTimeTraitIsShowAchievementTraitIsStatisticalDataCollectTraitIsTravelMoreDiamondAwardTraitIsTurnAroundSurfaceTraitIsWarmhandsTraitJewelNewOutBlockTraitLDEnableNoSyncHighScoreRollingTraitLWMDualConditionHardPuzzleTraitLaneSchemeTraitLevelConfigTraitLevelExpAlgorithmTraitLevelLoopTraitLevelNoThemeTraitLevelRandomReplaceTraitLevelRemoteThemeTraitLevelWayHelpConfigTraitLevelWayHelpTraitLimitHighComboTraitLimitNoviceBlockSizeTraitLineDynamicSendTraitLoadingTieringTraitLocalThemeSaveTraitLoginTimeFirstDifficultyRatioTraitLongThinkClearAssistTraitLowFeedbackPleasurableOfferTraitLowHardSetFps30TraitLowWeightFillBlockSortAdjustAlgoTraitLowWeightFillBlockSortTraitMaturityDevelopmentV2TraitMergeBlocksIsPuzzleTimeTraitMergeBlocksLaneSchemeTraitMoreClearStopComboTraitMoreTimeToOpreateStateTraitMultiElementCollectTraitNewAbilityModelTraitNewBottomOfferTraitNewPlayerContinuePlayIncentiveTraitNewSeasonNotifyTraitNewStructTraitNewUserLevelTraitNew_player_direct_guide_to_thirdTraitNewbieScoreRegulationTraitNoBannerAdNewUserByDaysTraitOpenChangeContinueHardTraitOpenComboModelTraitOpenLevelModelTraitOriginSkinShowGreyDefultSetBtnTraitOverHighScoreClearBoardTraitParallelClearHardTraitParam230012TraitPixelBitControllerTraitPlaceBackAdaptTraitPlaceHabitAdaptPathTraitPlaytimeNoInterstitial1TraitPostGameToServerTraitPreClearScreenAnimOptimizeTraitProblemIntervalTimeRandomBetaTraitPromotionMoreGamesInBlockCtrlTraitPutBlockOptimizeTraitPuzzle100TraitPuzzle100WeightConditionTraitPuzzleAfterTimingTraitPuzzleBeforeTimingTraitPuzzleConditionDiffTraitPuzzleFail100TraitPuzzleSuccess100TraitPuzzleTiming100TraitPuzzleTimingMaxPriorityTraitRatioAdjustTraitRatioDownTraitReduceTravelSettlementTraitReplaceBoardTraitReplaceClearnScreenAniTraitRightPutHintCtrTraitRobotModelEventDataTraitSequencePredictDynamicDifficultyTraitServerControl_board_init_v2TraitSetAddDefultSkinBtnTraitSettleFireworkTraitSetupNewTraitShangZengAddRandomTraitShangzeng3ReplaceZimushangzengTraitShowSSSWhenSkinChangeTraitShowTravellClearAnimaTraitSimpleMultiSolutionTuning2TraitSkinChangeAblePushTraitSkinPreEliminateGoodPerformanceTraitSkinRandomTraitSkinThree24NewColorsTraitSkinThreeOptimizationsTraitSkinThreeStageTraitSlowPlayerAnimTraitSmallgameLocalResTraitSolidColorTraitTerminateRefreshScoreTraitTheMoreEliminateTheMoreGoodEffectTraitTravelEntryClockShadowFixTraitTravelEntryClockShadowTraitTravelHappyOverTraitTravelNoSkinTraitTravelResultOptimizingTraitTravelScoreCutoffByProgressTrait

私のAndroidテストインストールに割り当てられた254のtrait。敗北、復活、広告に関するものはハイライトされています。

したがって、隣り合ってプレイしている2人のプレイヤーは異なるゲームを実行している可能性があり、スタジオはアプリのアップデートなしにあなたのゲームを変更できます。これら254のうちのほとんどは普通です:チュートリアル、アニメーション、サウンド。少数はおそらくあなたが負ける時に関するものです。そのうちの1つは広告に関するものです。

高額な広告を待つルール

これはRewardHighValueAdHarvestTraitと呼ばれています。ゲームがネイティブライブラリでこれらのファイルを開く場所を見つけることで、15,616バイトのプレーンなミニファイされたJavaScriptにデコードしました。そのコンストラクタはパラメータを設定します。

RewardHighValueAdHarvestTrait.js · lines 63–67

t.HIGH_PRICE_MULTIPLIER = 3.5;
t.MEDIUM_PRICE_MULTIPLIER = 1.5;
t.MIN_HISTORY_AD_WATCH_COUNT = 3;
t.RECENT_AD_NO_ESCAPE_COUNT = 3;
t.TARGET_AD_LEVEL = "high";

3つの条件が揃うのを待ちます:最後の3つのリワード広告を完了したこと、現在読み込まれている広告が前回より少なくとも3.5倍の価値があること、そしてハイスコアを1,000点超えていること。これら3つすべてが1つの関数でチェックされます。ミニファイされたnullチェックをオプショナルチェイニングで書き直したものが以下です。ロジックは同じです。

shouldTriggerDeath · lines 262–267 (reformatted)

shouldTriggerDeath() {
  if (!this.canTrigger()) return false;                  // 最後の3つのリワード広告を完了したこと
  const d = this.storageData;
  return d.currentGameNum === hs.classGameInfo?.gameNum   // 同じゲーム
      && !d.currentGameHasTriggeredDeath                 // まだ発火していない
      && !(hs.classScoreInfo?.score < this.currentScoreThreshold)  // ハイスコア+1,000を超えている
      && !!this.isTargetAdReady();                       // 読み込まれた広告が前回広告の≥3.5倍であること
}

それぞれをどのようにチェックするかを以下に示します。

1. 最後の3つのリワード広告を完了したこと。 リワード広告が開始されるたびに、ルールはリストにfalseをプッシュし、最新の3つを保持します(lines 174–183)。広告SDKがリワードが獲得されたと報告した場合、そのエントリをtrueに反転させます(191–199)。チェックでは、すべてが正確にtrueである必要があります。

canTrigger · lines 268–275

t.prototype.canTrigger = function() {
var e = this.storageData;
if (e.totalRewardShowCount < this.MIN_HISTORY_AD_WATCH_COUNT) return !1;
var t = e.recentRewardResults.slice(0, this.RECENT_AD_NO_ESCAPE_COUNT);
return !(t.length < this.RECENT_AD_NO_ESCAPE_COUNT) && t.every(function(e) {
return !0 === e;
});
};

このフラグは、JavaレイヤーのAppLovin MAXのonUserRewardedコールバックから来るため、早期に閉じた広告はfalseのままです。

2. 現在読み込まれている広告が前回より少なくとも3.5倍の価値があること。 ルールは、NativeAd.callNativeGetMaxEcpmNumber("2")(line 304)を使用して、現在読み込まれている最高の報酬広告の価値を読み取ります。Javaレイヤーでは、その呼び出しは準備完了の各報酬広告スロットを通過し、AppLovin MAXが報告する収益を取得し、それを1,000倍して最高値を返します。最後の報酬広告の価値も同様に×1,000されます(line 310)。そのため、比較は同等です。

classifyLoadedRewardLevel · lines 290–293

t.prototype.classifyLoadedRewardLevel = function(e) {
var t = this.storageData.lastRewardShowEcpm;
return !Number.isFinite(t) || t <= 0 ? "none" : e >= t * this.HIGH_PRICE_MULTIPLIER ? "high" : e >= t * this.MEDIUM_PRICE_MULTIPLIER ? "medium" : "none";
};

3. 前回のハイスコアを上回っていること。 各ゲームの開始時に、ルールはハイスコアを保存します(line 145)。この基準は、ハイスコアにscoreDeltaStep(lines 107–110)を加えたものです。各プレイヤーは1つのarmを取得します。私のarm、332243014は1,000を使用し、アプリにバンドルされた設定には他のものが500と750で含まれています。ルールが発火した後に復活した場合、基準はさらに1ステップ上昇するため、同じゲームで再度発火する可能性があります。

次に、致命的なパズルを要求します。 次のセットを配る前に、ゲームは「ボトムオファー」ルールのリストを順番に実行します。このルールが条件を満たすと、リストは単一のジェネレーターに置き換えられます。

onBottomOfferAlgo · lines 245–251

t.prototype.onBottomOfferAlgo = function() {
if (this.shouldTriggerDeath()) {
hs.algorithmBottomSequenceInfo.setAlgorithmBottomList([ this.deathAlgorithm ]);
this.storageData.currentGameHasTriggeredDeath = !0;
this.saveStorageData();
}
};

deathAlgorithmはhs.OFFER_TYPE.ZHI_SI_TI(line 102)を返します。ゲームはその要求をパズルエンジンに送信し、返された3つのピースIDを配ります(main script lines 45062–45121 and 15074–15123)。エンジンがセットを見つけられない場合、ルールはリセットされ、次のディールで再度試行します。

それが決定する様子を見るために、ダウンロードしたファイルを変更せずにテスト広告とスコア値で実行しました。3つの条件がすべて満たされたときに致命的なパズルを要求します:28ケース、うち2つは広告が3つではなく2つ、1つの広告が早期に閉じられた、スコアが1点足りない、広告が3.5倍未満の価値である場合です。

次に、iPhoneアプリのテストコピーをAppleのシミュレーターでライブ実行し、プレイ中にルールの入力を読み取りました。記録されたリワード広告は3つありました。スコアは基準を超えていました。Google AdMobからのリワード広告が読み込まれ、準備ができていました。ルールはその広告を72.39と評価し、前回広告は37.41だったので、1.94倍です。3.5倍が必要です。したがって、iPhoneではルールはライブで、実際の広告価格を読み取り、前回広告の3.5倍の価値のある広告を待っています。

致命的なパズル

この名前は、メインスクリプトの3つのテーブルを通じてマッピングされます。

main/index.45fec.js (original line numbers)

23878  e[e.ZHI_SI_TI = 9] = "ZHI_SI_TI";
16092  … r[n.OFFER_TYPE.ZHI_SI_TI] = i.AlgoClassTypeConst.algo_must_death, …
258441 e[e.algo_must_death = 24001] = "algo_must_death";

アルゴリズム24001は2つ存在します:JavaScript内と、ゲームのネイティブC++ライブラリ内です。後者の関数はfunc_death_blcok(彼らのスペル)と呼ばれます。両者は同じ検索を行います。以下はJavaScriptコアです。

rC · main/index.45fec.js lines 318602–318678 (excerpt)

for (var d = 0; d < n.length; d++) if (41 != d) {
var m = n[d];
r.canPut(m) ? o.push(m) : i.push(m);   // o: fits now, i: doesn't fit now
}
…
iC(r, o[p], o[f], i[_], h);               // 現在配置可能な2つのピースのあらゆる順序を試す
iC(r, o[p], i[_], o[f], h);               // 配置後に、配置不可能な1つのピースと組み合わせる
iC(r, o[f], o[p], i[_], h);               // ラインをクリアしながら
iC(r, o[f], i[_], o[p], h);
if (0 == h.length) {                     // どの順序でも機能しなかった場合:このセットを採用
u = [ o[p], o[f], i[_] ];
…
a.names = [ "致死题", "致死题", "致死题" ];   // "lethal puzzle" ×3

現在配置可能な2つのピースと配置不可能な1つのピースを探し、すべてのピースを配置できる順序やセットがない場合にのみセットを保持します。配置するたびにクリアされるラインを数えます。配られたとき、3つのピースのうち2つは配置可能なので、試してみるまで手札は不可能に見えません。

ゲーム自身のジェネレーターコードをブラウザで実行して、それがどのように見えるかを示します。ステップごとに確認してください。

Step 1 of 5

A normal hand

各ステップは、ブラウザでゲーム自身のジェネレーターコードを実行することから来ています。

「New random board」を押すと、ゲームのコードが新しい手札を選びます。証拠バンドルのテストボードでは、ゲームのジェネレーターは縦長のドミノ、横長のドミノ、そして3x3の正方形を致命的な手札として受け入れます。171通りの配置を試しましたが、どれも完了できませんでした。空のボードでは、同じ3つのピースが順序ごとに67,680通りの配置が可能です。

広告ルールだけが致命的なパズルを要求するわけではない

致命的なパズルはジェネレーターであり、どのルールからでも要求できます。広告ルールはその呼び出し元の一つです。iPhoneでライブテスト中に、2番目の呼び出し元がカメラの前で発火しました:PersonalMarginBreakTrait。ハイスコアを10%超えると、各ディールで確率で、20%の確率で同じ致命的なパズルを要求します。

PersonalMarginBreakTrait · onAlgorithmStrategyPriority, lines 153–159 · live config: prob 20, recordPercent 10, replaceAlgoId 9

t.prototype.onAlgorithmStrategyPriority = function() {
this.hasReplacedThisGame = !1;
if (this.state.isFirstGame) ; else if (this.isReviveRound()) ; else if (this.state.isBreakHighScore && this.isCurrentScoreOverRecordPercent()) if (100 * Math.random() >= this.prob) ; else {
var e = this.replaceAlgoId;   // 9, the lethal puzzle
…
if (this.callNewAlgorithm(e)) ; else {

私たちの記録は1,304だったので、そのラインは1,434でした。3,356ポイントでロールし、ゲームは5つの長いバー、5つの高いバー、そしてジグザグを配りました。ゲーム自身のそのディールの記録では、このルールが呼び出し元であり、アルゴリズム9が要求され使用されたことを示し、3つのピースすべてを致死题(lethal puzzle)とラベル付けしています。配置する方法はありませんでした。2つの独立した網羅的な検索が一致しています:ゼロです。ルール、ライブ設定、ディールはライブiPhoneバンドルにあります。

[動画: 3,356ポイントでのディール、リバイブボタンまで。Appleのシミュレーターで動作するiPhoneアプリのテストコピーで記録されたもの。保存されたハイスコアをリセットして、すぐにラインに到達できるようにした後のものです。]

The deal at 3,356 points, through to the Revive button. Recorded in a test copy of the iPhone app in Apple’s simulator, after we reset its saved high score so we could reach the line quickly.

敗北する場所を選ぶモデル

これまでのルールは手書きです。ゲームはまた、訓練されたモデルをダウンロードして携帯電話で実行し、そのうちの1つがレベルのどこで致命的なパズルが表示されるかを決定します。

これは、ゲームのレベルモード(コードではChapterと呼ばれる)用の500ツリーのXGBoost分類器です。そのファイル名はpredict_travel_difficulty_deadで、52の入力は主にあなたのパフォーマンスに関するものです:現在のレベル、試行回数、今週完了したゲーム数、アプリの使用期間などです。ゲームは12のオプション(設定sw_processの値4つ × 難易度レベル3つ)をフィードし、モデルが最も高いスコアを付けたものを保持します。

ClientDieModelLoadTrait · doPredictFeature, lines 1286–1312 (excerpt)

if (0 == e) for (var i = 1; i <= 4; i++) for (var l = 1; l <= 3; l++) {
var c = this.getInputFeature(this._jsonModel.learner.feature_names, i, l);
if ((u = this.xgb_clf_predict(c, …)) > n) {   // 最も高いスコアのオプションを保持
n = u;
a = i;
o = l;
}
}
…
storage.setItem("DieModel_last_predict_info", { swProcess: a, travelLvDifficulty: o, pre: n });

別のルールは、その選択をレベルのストレッチに変換します:最初の30%、30%から80%、または最後の20%です。そのストレッチに到達すると、通常それらを利用するプレイヤー(75%以上)には追加のリバイブが提供されます。その後、その4回目のディールから、致命的なパズルが要求されます。

IsOpenDeathChangeReviveCountTrait · lines 224–229 and 181–184

var t = hs.chapterConfigInfo.getChapterProgress(), i = e.getLastPredictInfo(this.traitName), r = i.swProcess, …;
…
1 == r ? s = [ 0, .3 ] : 2 == r ? s = [ .3, .8 ] : 3 == r ? s = [ .8, 1 ] : 4 == r && (s = [ 1, -1 ]);
return t < s[0] ? a.First : t <= s[1] ? a.Second : a.Third;   // そのストレッチの前、内側、または後
…
if ((n = this.saveData).needZs && n.zsRoundNum > 3) {
hs.algorithmStrategyInfo.setAlgorithmPriorityList([ hs.OFFER_TYPE.ZHI_SI_TI ]);   // the lethal puzzle
hs.algorithmStrategyInfo.setAlgorithmFailList([ hs.OFFER_TYPE.SHANG_ZENG_3 ]);
}

他のルールは同じ選択を逆に読み取り、4番目のオプションに着地したときにレベルを簡単にします。

あなたの価格を決めるモデル

2番目のモデルはあなたに価格を付けます。ゲームはサーバーからxgboost_regression_model111.jsonをダウンロードし、それをあなたの携帯電話で実行します。

これはXGBoost回帰モデルです:59の決定木、最大8レベルの深さ、27の入力。その大部分はお金に関するものです:入力の18は、今日および昨日のゲームで読み込まれた広告に関するものです。残りはあなたの場所、あなたの電話、インストール時期、そして最初のゲームの調子です。ツリーは、最初のスコア、最新の広告の価格、RAM、電話モデル、そして国に最も強く依存します。

コードはその回答をfinalEcpmと呼びます。eCPMは広告業界で1,000広告あたりの価値を示す名称であり、ゲームが広告価格を保存するのと同じ単位です。したがって、このモデルはあなたが広告でどれだけの価値があるかを推定しています。

GPT-6 Astraはゲームの入力コードとツリーウォークをポートし、そのポートはゲーム自身の出力を小数点以下まで再現します。私たちは、最初のスコアが1,000の架空の新規プレイヤーを、米国で、iPhone 15をApp Storeからインストールした場合とAndroidの場合で実行し、その後、一度に1つの入力を変更しました。

この項目のみ変更Ad value, iPhone 15Ad value, Android
なし(米国、最初のスコア1,000)25.4848.96
ブラジル在住に変更8.0817.90
インド在住に変更5.5914.83
最初のスコアが50025.7953.57
最初のスコアが2,00021.1252.64
最初のスコアが10,00020.3458.26

同じプレイヤーを米国からブラジルに移すと価値は約3分の1になり、インドではさらに低下します。最初のゲームも重要ですが、直線的ではありません:iPhoneでは、最初のスコアが500だと、2,000や10,000よりも価値が高くなります。

ゲームはこの種の価格を使用して、どのような種類の広告をどれだけ表示するかを決定します。最も明確なケースでは、サーバーに2つの価格(フルスクリーン広告用とリワード広告用)を要求し、それらを比較します。

IsOpenInterAndRewardAdvDecideReviveTrait · checkEcpm, mainTraits/index.88dc4.js lines 38529–38534

t.prototype.checkEcpm = function() {
var e, t, o, r = this.saveData.interEcpm, i = this.saveData.rewardEcpm;   // あなたのフルスクリーン広告とリワード広告の価格
…
return (…isReverse) ? r <= i * (…multiple) : i >= r * (…multiple);
};

アプリが出荷するバージョンでは、リワード広告がフルスクリーン広告の少なくとも2倍の価値がある場合(他のバージョンでは1.5倍または2.5倍)、追加のリバイブのチャンスが得られ、リバイブはリワード広告を再生します。フルスクリーン広告の方があなたにとって支払いが良い場合、そうはなりません。

他のルールも同様に機能します。FullScreenAdAddByEcpmTraitは、あなたの価格が高い場合にフルスクリーン広告を1つ追加します。IsOpenEcpmTooLowNoInterTraitは、あなたがほとんど価値がない場合にフルスクリーン広告をスキップします。さらにいくつかのは、リワード広告の価値が十分な場合に、早期または3回目のリバイブを開きます。

オンフォンのモデルを自分で試してみてください。

25.48

あなたの広告価値
1,000広告あたり(eCPM)

finalEcpm 25.47779944720001 0/59 trees moved

これは、ツリーをウォークするゲームの部分です。単純なループです:ルートから開始し、各入力で左または右に移動し、リーフの重みを合計します。

EcpmFeatureBaseInfo · runModel, main/index.45fec.js lines 63141–63175 (excerpt)

for (var m = parseFloat(a + ""), u = 0; u < t.length; u++) {   // mはベーススコアから始まります; tは59のツリーです
for (var p = t[u], f = 0, _ = 0; _ < o; ) {
_ += 1;
var h = p.left_children[f], g = p.right_children[f], y = p.split_conditions[f], C = p.default_left[f], v = s[e[p.split_indices[f]]], T = -1;
(null === v && 1 === C || v < y) && (T = h);
(null === v && 1 !== C || v >= y) && (T = g);
if (-1 === T) {
m += parseFloat(p.base_weights[f].toString());        // リーフに到達:その重みを加算
break;
}
f = T;
}
if (_ >= o) throw new Error("[EcpmFeature] 树的遍历超过最大深度,可能存在死循环。");
// "Tree traversal exceeded max depth, possible infinite loop."
}
明日アクティブでなくなる可能性を予測するモデル

93個の入力を持つ1,000ツリーのLightGBMモデルもあり、明日アクティブでなくなるかどうかを予測します。これは、アプリの使用期間、ハイスコア、RAM、平均スコアに最も強く依存します。0.69以上のスコアを付けた場合、コンパニオンルールがピンクのリバイブボタンと入れ替わり、リバイブは広告をスキップします。

NewNextDayLowActivityTrait line 1010 · NextDayLowActivityFirstReviveFreeTrait lines 112–117

return this.state.predictionScore >= this._lowActivityThreshold;   // 0.69
…
if (hs.tp.isClassAdvertisement_RewardProxyShieldPlayAdvertisement(e) || …) {
var r = cc.isValid(this._pinkReviveNode);
if (!this.isTrigger || !r) return;
e.returnValue = !0;
e.args[0] = !0;                                  // 広告をシールド:リバイブは広告なしで進行します
this.saveNextDayLowActivityReviveTime(Date.now());
}

これは1日に1回機能します。

サーバー上のモデル

一部のルールはあなたのデータを送信し、サーバーモデル(churn、LTV、revive-click、xDeepFMなどの名前)からのスコアに基づいて動作します。重みはサーバーに残るため、電話が送信するものしか表示できません。それらの要求の1つ、/v1/ecpm_predictionへの要求は、キャリア、ストレージ、時間、および最近の広告価格の統計を含む40フィールドを送信します。

ゲームを形成するその他のルール

広告ルールだけがあなたのピースやスコアを変更するわけではありません。iPhoneでライブで動作しているのを捉えました。MaturityDevelopmentV2Traitは、各セッションに3つの部分で構成される「成熟度」スコアを付けます:習慣(10.98)、スキル(14.66)、広告履歴(19.27)。私たちのスコアは合計44.91で、65の基準を下回っていたため、本来配られるはずだった難しいパズルの一部を簡単なフィルに置き換えました。別のルール、FailStreakStrategyTraitは、私たちの記録を下回る2ゲーム後に介入し、簡単なピースを要求しました。

これらはAndroidで広告ルールをトレース中に発生し、それぞれが私の研究ファイルに同じ種類の証拠を持っています。

| Rule |
| --- | コードの機能 |
| MultiClearBoostThenKillTrait | あなたのユーザーIDを2つのサーバーエンドポイントに送信し、2つのラベルを取得します。コードは両方のティアを「強制的に敗北させられることに敏感でない」(怼死不敏感)と名付けています。これらのティアのために、大きなクリアを待ち、ゲームが通常のゲームの長さになった頃に、数ラウンド後に致命的なパズルを要求します。 |
| MABXiBaiTrait | 致命的なパズルが要求されるまでの期間に関する6アームの実験:なし、または4、6、10、約17、または30分後。各アームについて、15分以内に新しいゲームを開始したかどうか、およびリバイブオファーの何パーセントを受け入れたかを報告します。 |
| FastHandPunishTrait | あなたの過去6セットの平均がそれぞれ4.2秒未満で、ハイスコアの93%を超えている場合に、難しいピースを要求します。 |
| AdPlusMinusStrategyTrait | ある種の追加リバイブオファーの後、ハイスコアの最大10%のランダムボーナスを現在のスコアに追加します。 |

人々が尋ねた質問

iPhoneでも同じですか?

はい。iPhone版の「Block Blast」バージョン7.4.9も分解し、2番目のGPT-6 Astra実行がこれらの発見をゼロからチェックしました。これは独自のアップデートサーバーから同じルールをダウンロードし、ファイルはAndroidのものと1行を除いて一致します。他はすべて同一です:3つの広告、3.5倍、スコアバー、そして同じ致命的なパズルジェネレーターです。

iPhoneアプリには、ルールの2番目のバージョン、RewardHighValueAdHarvestV2Traitもリストされており、そのバージョンでは広告履歴チェックをオフにすることができます。

RewardHighValueAdHarvestV2Trait.js (iOS) · lines 323–331

t.prototype.canTrigger = function() {
var e = this.storageData, t = this.viewCountThreshold;
if (e.totalRewardShowCount < t) return !1;
if (!this.requireRecentNoEscape) return !0;
var r = e.recentRewardResults.slice(0, t);
return !(r.length < t) && r.every(function(e) {
return !0 === e;
});
};

アプリは12バージョンを出荷しています。半分はチェックをオフにしており、1〜3個の広告が必要なので、そのうちのいくつかは早期に閉じた単一のリワード広告だけでも十分です。価格バーは、アプリの使用期間によって異なります:最初の週はセッションで読み込まれた最初の広告の3.2倍、30日までは2.7倍、それ以降は最後の広告の3倍です。

Wi-Fiなしでプレイするとどうなりますか?

このルールはダウンロード後に携帯電話に保存されるため、オフラインでも実行されます。しかし、リワード広告が読み込まれた場合にのみ発火し、広告はインターネット経由で読み込まれるため、何も読み込まれなくなると発火できません。他のルールは実行を続けます。私のAndroidインストールでは、記録の近くで速くプレイしているときに難しいピースを要求するルールは、完全に携帯電話上で実行されます。サーバーにどのプレイヤーが「強制的に敗北させられることを気にしないか」を尋ねるルールは、オフラインでは回答を得られないため、そのセッションではオフになります。

広告ブロッカーは何か効果がありますか?

ブロッカーによります。iPhoneまたはAndroidのブラウザ広告ブロッカーはブラウザ内でのみ機能するため、ゲームには影響しません。AndroidのPrivate DNSや、どちらかのデバイスのDNSまたはVPNベースのブロッカーのような、携帯電話全体をカバーするブロッカーは、リワード広告の読み込みを停止させることができ、その場合、オフラインでプレイするのと同じ理由でこのルールは発火できません。広告を使用しないルールは実行を続けます:高速プレイのルール、致命的なパズルがいつ要求できるかを時間計測する実験、そしてあなたがまだオンラインであるため、あなたについてサーバーに尋ねるルールです。

これは私がハイスコアを更新した直後にいつも負ける理由ですか?

まさにこれらのルールが狙っているところです。私たちはPersonalMarginBreakTraitがそれをライブで行っているのを捉えました:記録を10%超えると、各ディールは致命的なパズルになる確率が20%になります。広告ルールは記録を1,000点超えるのを待ちます(他のバージョンでは500点または750点)、復活後、バーを上げて再度発火できます。

回避できますか?

実際にはできません。Androidでは、元のルールは最後の3つのリワード広告が支払われる必要があるため、1つを早期に閉じると、3つ連続で良い広告が出るまでオフになります。しかしiPhoneでは、V2バージョンは完全にこのチェックをスキップし、早期に閉じた広告でも、単一の広告の後に発火できます。そして、致命的なパズルを要求する他のルール、例えばタイミング実験や大きなクリアを待つルールは、広告を全く見ません。このルールをどこでも停止させる唯一のことは、リワード広告が読み込まれていないことです。これはオフラインまたは電話全体のアドブロッカーで発生することです。

これは単なるアダプティブ難易度ではありませんか?

アダプティブ難易度は、あなたのプレイ方法に基づいてピースを変更します。このルールは、ピースをどれだけうまく配置しているかを見ていません。その入力は、最近の広告結果、メモリ内にある広告の価格、そして記録に対するスコアです。そして、それが要求するジェネレーターは、より難しいピースのセットではありません。それは、配置方法がないために選択されたセットです。

これは許可されていますか?Appleはアプリをレビューしませんか?

Appleはストアからダウンロードするアプリをレビューしますが、このルールはそれには含まれていません。アプリはインストール後にゲームのサーバーからそれをダウンロードし、サーバーはどのプレイヤーがどのバージョンを取得するかを決定します。Appleは、アップデートがアプリ自体を変更しない限り、Expo Updatesのようなツールが機能するのと同じように、JavaScriptアップデートをダウンロードすることを許可しています。広告価格に基づいてピースを変更することがそれに該当するかどうかは、Apple次第です。

なぜサーバーではなく電話で実行されるのですか?

サーバーは、誰がルールとどのバージョンを取得するかを決定します。しかし、その決定自体には、その瞬間にあなたの電話だけが知っている情報が必要です:あなたのボード、次に配られるセット、そして現在読み込まれている広告です。そのため、その部分は電話で実行され、それが分解できた理由でもあります。

これが本物だとわかる理由

  • アプリ。 Android:APKPureからダウンロードした10.8.2 XAPK、2026年10月11日ダウンロード。すべての分割APKは署名検証に合格し、バージョンはAPKMirrorの公開リストと一致します。iOS:公開ミラーからの7.4.9 IPA。メインスクリプトとカタログを含む4,670のゲームリソースは、Appleのコード署名と一致します。
  • ルールはサーバーから取得されました。 Androidアプリのキャッシュによると、ルールはbband1.afafb.comからダウンロードされました。これは、すべてのtraitを取得するのと同じホストです。そのURLを別途取得し、ファイルはアプリのキャッシュコピーとバイト単位で一致しました。iOSアプリの署名付きカタログは両方のiOSルールを名前付けし、アプリはそれらをbbios1.afafb.comから取得します。10月15日現在、3つのURLすべてが同じファイルをサービスしているため、ご自身で確認できます(コマンドは以下)。
  • 割り当てられ、ロードされました。 アカウントなしのクリーンなAndroid 14エミュレーターで、10月11日23:01:15 UTCに、アプリは設定を準備し、このルールにarm 332243014が選択され、モジュールはストレージが初期化されてロードされ、プラグインされるボトムオファーパイプラインがオンになりました。
  • デコードは正確です。 traitはXXTEAで暗号化され、Brotliで圧縮されています。デコードされたルールのハッシュは以下です。ネイティブライブラリについては、私のオフラインデコードは、実行中のアプリのメモリダンプと25,317,680バイトでゼロの不一致で一致しました。
  • Androidゲームの何も変更されていません。 唯一の変更は、エミュレーター上でゲームが描画されるようにするためのレンダリングワークアラウンドでした。スコア、広告、フラグ、または設定値は編集されていません。
ご自身で確認してください

3つの証拠バンドルがあります:Android(113 KB、SHA-256 bd55151e6a1527f628459c2fffa115d5b14a765a3fb5a3f729387fbac6f707c8)とiOS(2.7 MB、SHA-256 02bdb0e0e9b02d77844efbcf4b82f48e17f2b688fc48cbf84f83e75ae289df7d)、さらにライブiPhoneキャッチ(270 KB、SHA-256 5f36863daabd2b500c8062d005f41e6c5e7dcea5a238c89e558e19a928dcc57f)用の3番目です。これらには、デコードされたルール、暗号化されたオリジナル、各アプリの残りの抜粋(元の行番号付き)、テストスクリプト、および出所記録が含まれています。Node.jsとPython 3が必要です。

Terminal · Android bundle

# ルールを偽の入力で変更せずに実行:28回のチェック、"passed": trueと表示
node proof/verify_reward_harvest.js

# テストボードの総当たり検索:どの順序でも0ソリューション
python3 proof/verify_unsolvable.py

# そのボード上のゲーム固有のジェネレーター:ピース[2, 3, 13]を選択
node proof/verify_packaged_algorithm.js

Terminal · iOS bundle

# 両方のiOSルール、ジェネレーター、2つのソルバー、およびコード署名チェック
python3 proof/run_all.py

Terminal · サーバー上の暗号化されたルールはバンドルと一致します

curl -s https://bband1.afafb.com/update/block_android_online/remoteRes/0/remote/RewardHighValueAdHarvestTrait/index.700f5.js | shasum -a 256
# dec91aa4001f651a74bf101d512587b9a364cb71ea2a81a9cc9dbbcd2444b477
curl -s https://bbios1.afafb.com/update/block_ios_online/remoteRes/0/remote/RewardHighValueAdHarvestTrait/index.ff35e.js | shasum -a 256
# 61ac70045995f44b3c6bb22b2d062a386593ce6a632fbcec3863a51110850cc2
curl -s https://bbios1.afafb.com/update/block_ios_online/remoteRes/0/remote/RewardHighValueAdHarvestV2Trait/index.677f6.js | shasum -a 256
# e38953430980f4b6a10b29337ec9a41634bb732ee84dd5997bfe78507b26ee1e
Android base APKa520fcc1d1ba1ff0c643e30b059c87a01fde650ec46be5c85fde5d815ec82447
Android XAPK91988cf6e92cd0d346b869df0a8c6740f3a5fd8b722e21779e045426c7553015
Android rule, decodedf94a5d54d7f6ab0af097f408bda09b091e7767b17749f8ba828b41b638b330ad
Android main script8dce6532cafc25d982f2af3659647e0e0017c3902cbc31f872d375804dc3678c
iOS IPA7fc9b47e10dc8303f662e1c0ad88b93e7119e2846972eddb1a18bd205b54056a
iOS rule, decoded179bf50ae3c0c4a9bd5457281e4876faae728778f6181dbb0d3799b42ec70d61

キーとアプリの保護を回避する手順はバンドルには含まれていません。ジャーナリストまたは研究者でデコードステップを検証したい場合は、メールで連絡してください。