@@ -2893,7 +2893,8 @@ export const FREEBUFF_CLOUD_PLANNER_TURN_LIMIT = 12
28932893/**
28942894 * Models available to limited-region Freebuff Web users.
28952895 *
2896- * **Widened 2026-09-04 to the whole Web free catalog except GPT-5.6 Luna.**
2896+ * **Widened 2026-09-04 to the whole Web free catalog except the rows a plan
2897+ * unlocks at limited access.**
28972898 * It used to alias `LIMITED_FREEBUFF_MODEL_IDS` (Flash, MiMo, Solar), which
28982899 * left limited regions without the CHEAPEST row we serve — GLM 5.3 Flash at 5
28992900 * Freebucks an hour, a third of the Flash price — so the tier that can least
@@ -2906,10 +2907,12 @@ export const FREEBUFF_CLOUD_PLANNER_TURN_LIMIT = 12
29062907 * on a row that goes further. Before the meter, catalog WAS the control, which
29072908 * is why this list was narrow.
29082909 *
2909- * Luna is the deliberate exception: it stays plan-gated at this tier
2910+ * Luna is the deliberate full-access exception: it stays plan-gated only at
2911+ * the limited tier
29102912 * (`isFreebuffSubscriptionModelIdForAccessTier` admits it only with a live
2911- * paid plan), and the Web picker lists it locked rather than hiding it, so the
2912- * row is an offer instead of an absence.
2913+ * paid plan there), while a full-access account may spend its ordinary premium
2914+ * allowance on it. The Web picker lists it locked at limited access rather
2915+ * than hiding it, so the row is an offer instead of an absence.
29132916 *
29142917 * Its own name, and NOT `LIMITED_FREEBUFF_MODEL_IDS`, because this is a
29152918 * broader browser catalog: CLI/Desktop also offer GLM 5.3 Flash, while
@@ -2931,17 +2934,13 @@ export const FREEBUFF_CLOUD_PLANNER_TURN_LIMIT = 12
29312934 * cannot import that one — freebuff-subscriptions.ts already imports this
29322935 * module, so the dependency only runs one way.
29332936 *
2934- * That it is needed here at all is the point. The limited tier's catalog used
2935- * to exclude Luna by NAME, which was correct only while Luna was the sole row
2936- * a plan stood in front of; the moment a second one existed, the free limited
2937- * catalog silently handed it out. One definition, consumed by both, is what
2938- * stops the next Pro row repeating it.
2937+ * The limited-tier plan-only set below extends this one. Defining the global
2938+ * paid boundary once keeps a future Pro row out of the free limited catalog
2939+ * without making Luna globally Pro-only just because it has the narrower
2940+ * limited-tier restriction.
29392941 */
29402942export const FREEBUFF_PRO_ONLY_CATALOG_MODEL_IDS : readonly string [ ] =
2941- Object . freeze ( [
2942- FREEBUFF_GPT_5_6_LUNA_MODEL_ID ,
2943- FREEBUFF_GEMINI_38_FLASH_MODEL_ID ,
2944- ] )
2943+ Object . freeze ( [ FREEBUFF_GEMINI_38_FLASH_MODEL_ID ] )
29452944
29462945/** Whether the catalog marks `id` openable only on a paid session. Exact match:
29472946 * the suffix-tolerant public predicate is
@@ -2950,20 +2949,36 @@ export function isFreebuffProOnlyCatalogModelId(id: string): boolean {
29502949 return FREEBUFF_PRO_ONLY_CATALOG_MODEL_IDS . includes ( id )
29512950}
29522951
2952+ /**
2953+ * Rows an unpaid LIMITED-tier account cannot open.
2954+ *
2955+ * Every globally Pro-only row belongs here, plus Luna: Luna is part of the
2956+ * ordinary full-access premium pool, but a paid plan is still what unlocks it
2957+ * at limited access. Keeping this distinction explicit prevents the global Pro
2958+ * gate from accidentally paywalling full-access users just to preserve the
2959+ * limited-tier catalog boundary.
2960+ */
2961+ export const FREEBUFF_LIMITED_TIER_PLAN_ONLY_MODEL_IDS : readonly string [ ] =
2962+ Object . freeze ( [
2963+ FREEBUFF_GPT_5_6_LUNA_MODEL_ID ,
2964+ ...FREEBUFF_PRO_ONLY_CATALOG_MODEL_IDS ,
2965+ ] )
2966+
2967+ export function isFreebuffLimitedTierPlanOnlyModelId ( id : string ) : boolean {
2968+ return FREEBUFF_LIMITED_TIER_PLAN_ONLY_MODEL_IDS . includes ( id )
2969+ }
2970+
29532971export const FREEBUFF_WEB_GEO_EXEMPT_MODEL_IDS : readonly string [ ] = [
29542972 ...new Set < string > ( [
29552973 ...LIMITED_FREEBUFF_MODEL_IDS ,
29562974 ...FREEBUFF_WEB_MODELS . filter (
29572975 ( model ) =>
29582976 isFreebuffWebSelectableModelId ( model . id ) &&
29592977 ! isFreebuffWebGodOnlyModelId ( model . id ) &&
2960- // Every Pro row, not just Luna. This read `!== LUNA` while Luna was
2961- // the only model a plan stood in front of, so "the row a plan gates"
2962- // and "Luna" were the same set and the narrower reading was the one
2963- // written down. Gemini 3.8 Flash joining the Pro rows on 2026-09-04
2964- // separated them: the old test would have put the dearest row in the
2965- // catalog into the FREE limited catalog.
2966- ! isFreebuffProOnlyCatalogModelId ( model . id ) ,
2978+ // This is deliberately broader than the global Pro set. Luna is free
2979+ // from the premium pool at full access but still plan-locked here;
2980+ // Gemini 3.8 Flash is plan-only at every tier.
2981+ ! isFreebuffLimitedTierPlanOnlyModelId ( model . id ) ,
29672982 ) . map ( ( model ) => model . id ) ,
29682983 ] ) ,
29692984]
0 commit comments