6.3 礼包性价比与价值锚点

礼包值不值,不能靠「感觉超值」。本节用 config/pricing.json 里的价值锚点(1 玉璧换 100 资源、60 加速)把每个礼包的内容折算成玉璧等价价值,算出性价比千分比(1000‰ = 与直购玉璧 1:1),再用 S 层脚本跑出一张可排序的性价比表,并给出 d30 / d50 折扣后排序如何翻转。

6.2 算的是「抽一次要花多少玉璧」,回答的是消耗侧的问题。本节换个方向,回答供给侧的问题:同样一笔钱,买玉璧直充、买资源礼包、还是买加速包,哪种更划算?这个问题在 SLG 里天天出现,答案却常常是「拍脑袋觉得超值」。本节把它变成一张可排序、可复算的性价比表。定价档位本身留到第 10 章,定价锚点与档位设计 讲的是「卖多少钱」,本节讲的是「这个价买到的东西值多少」。

礼包是 SLG 商业化里最灵活的一类付费点:它不改变抽卡概率,也不改变定价档位,只改变**「一次买多少、打包成什么样」**。正因为灵活,它最容易失控——同样一笔钱,可以打包成纯玉璧、资源、或加速,三者的性价比天差地别。本节给出的,就是把这三种打包方式放到同一把尺子上比较的方法。

礼包为什么需要「价值锚点」

一个礼包的内容是混合的:可能给玉璧、给资源(木石铁粮)、给加速道具。三种东西单位不同,没法直接比。要比较,就得先把它们折算到同一个尺子上——这把尺子就是价值锚点。

本套的锚点写在 config/pricing.json 里(由主控维护,本卷只读):

  • resourcePerJade: 100:1 玉璧 ≈ 100 单位资源。
  • speedupPerJade: 60:1 玉璧 ≈ 60 分钟加速。
  • 玉璧本身按 1:1 计(锚点的单位就是玉璧)。

有了锚点,任何礼包都能折算成一个玉璧等价价值。再用它的售价(按 10 玉璧/元折成玉璧)去除,就得到一个无量纲的性价比千分比:

性价比‰ = 礼包内容的玉璧等价价值 / 礼包售价(折玉璧) × 1000

1000‰ 是基准线:等于「你花的玉璧,买回了等值的东西」。高于 1000‰ 是溢价(礼包比直充划算),低于 1000‰ 是折价(不如直充玉璧)。这条基准线就是锚点存在的意义——没有基准线,「超值」只是文案。

从内容到价值:一个礼包的账

以「混装大礼包」为例(售价 32800 分 = 3280 玉璧),内容是 3280 玉璧 + 50000 资源 + 3000 分钟加速:

  • 玉璧:3280
  • 资源:50000 // 100 = 500 玉璧等价
  • 加速:3000 // 60 = 50 玉璧等价
  • 合计价值:3280 + 500 + 50 = 3830 玉璧
  • 性价比:3830 × 1000 // 3280 = 1167‰

也就是说这个礼包比直充玉璧多给约 17% 的价值。礼包的设计目标就是让这个数落在某个区间里:新手礼包可以给到 1500‰(拉首充),常规补给包通常贴着 1000‰(不亏不赚),而加速包如果定价偏高会掉到 1000‰ 以下(玩家会直接用玉璧买加速)。

把每个礼包的价值拆成三列,就能看清「钱花在哪」:

礼包玉璧价值资源价值加速价值合计售价(玉璧)
新手礼包09009060
资源补给包01200120120
加速包00150150300
玉璧包68000680680
混装大礼包32805005038303280

这张表暴露了一个规律:纯玉璧礼包和纯资源礼包的性价比都贴近 1000‰,只有加速包严重偏低。因为玉璧是锚点的单位(1:1),资源按锚点 1:100 折算后自然贴合,而加速的锚点(1:60)是三者里最「贵」的——同样是 1 玉璧,玩家心里能换到的加速比锚点给的多。锚点定得越「抠」,对应礼包的性价比就越低。这也解释了一个反直觉的现象:改一个锚点数字,所有相关礼包的性价比就全变了——性价比不是礼包的固有属性,而是「礼包内容 × 锚点假设」的产物。

仿真:一份可排序的性价比表

下面这段是 S 层仿真脚本。它读 config/n06_gacha.json 的礼包与 config/pricing.json 的锚点、折扣档,算出每个礼包的基础性价比与 d30 / d50 折扣后的性价比,输出 JSON + CSV + SVG:

# file: sim/n06_bundle_value.py
# tier: S
# verify: python3 sim/n06_bundle_value.py --config config/ --out out/
"""礼包性价比仿真:读 config/n06_gacha.json 的礼包与 config/pricing.json 的价值锚点
(resourcePerJade / speedupPerJade)与折扣档,把礼包内容折算成玉璧等价价值,算性价比千分比
(1000‰ = 与直购玉璧 1:1),并给出 d30 / d50 折扣后的性价比。
产出 out/n06_bundle.json + out/n06_bundle.csv + out/n06_bundle.svg。"""

from __future__ import annotations

import argparse
import csv
import json
import os

from svg_line import render


def load(cfg_dir: str, name: str) -> dict:
    with open(os.path.join(cfg_dir, f"{name}.json"), encoding="utf-8") as fh:
        return json.load(fh)


def bundle_value_jade(bundle: dict, anchor: dict) -> int:
    """礼包内容的玉璧等价价值:玉璧 + 资源/资源锚 + 加速/加速锚(整数地板除)。"""
    return (bundle["jade"] + bundle["resource"] // anchor["resourcePerJade"]
            + bundle["speedupMinutes"] // anchor["speedupPerJade"])


def main() -> int:
    here = os.path.dirname(os.path.abspath(__file__))
    ap = argparse.ArgumentParser()
    ap.add_argument("--config", default=os.path.join(here, "..", "config"))
    ap.add_argument("--out", default=os.path.join(here, "..", "out"))
    args = ap.parse_args()
    os.makedirs(args.out, exist_ok=True)

    cfg, pricing = load(args.config, "n06_gacha"), load(args.config, "pricing")
    anchor = pricing["bundleAnchor"]
    cents_per_jade = 100 // pricing["currency"]["perYuan"]
    disc = {d["id"]: d["offPct"] for d in pricing["discount"]["tiers"]}

    rows = []
    for b in cfg["bundles"]:
        vj = bundle_value_jade(b, anchor)
        price_jade = b["priceCents"] // cents_per_jade
        price30 = (b["priceCents"] * (100 - disc["d30"]) // 100) // cents_per_jade
        price50 = (b["priceCents"] * (100 - disc["d50"]) // 100) // cents_per_jade
        rows.append({"id": b["id"], "name": b["name"], "priceCents": b["priceCents"],
                     "valueJade": vj, "priceJade": price_jade,
                     "basePermille": vj * 1000 // price_jade,
                     "d30Permille": vj * 1000 // price30, "d50Permille": vj * 1000 // price50})
    rows.sort(key=lambda r: r["basePermille"], reverse=True)

    with open(os.path.join(args.out, "n06_bundle.csv"), "w", encoding="utf-8", newline="") as fh:
        w = csv.writer(fh)
        w.writerow(["id", "name", "priceCents", "valueJade", "priceJade", "basePermille", "d30Permille", "d50Permille"])
        for r in rows:
            w.writerow([r["id"], r["name"], r["priceCents"], r["valueJade"], r["priceJade"],
                        r["basePermille"], r["d30Permille"], r["d50Permille"]])

    render([r["basePermille"] for r in rows], os.path.join(args.out, "n06_bundle.svg"),
           "bundle value permille (1000 = parity with jade)")

    report = {"version": 1, "anchor": anchor, "centsPerJade": cents_per_jade, "bundles": rows,
              "best": rows[0]["id"], "worst": rows[-1]["id"], "medianPermille": rows[len(rows) // 2]["basePermille"]}
    with open(os.path.join(args.out, "n06_bundle.json"), "w", encoding="utf-8") as fh:
        json.dump(report, fh, ensure_ascii=False, indent=2)
    print(json.dumps(report, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

本机实跑产出的性价比表 out/n06_bundle.csv(按基础性价比降序):

id,name,priceCents,valueJade,priceJade,basePermille,d30Permille,d50Permille
b_starter,新手礼包,600,90,60,1500,2142,3000
b_mixed,混装大礼包,32800,3830,3280,1167,1668,2335
b_resource,资源补给包,1200,120,120,1000,1428,2000
b_jade,玉璧包,6800,680,680,1000,1428,2000
b_speedup,加速包,3000,150,300,500,714,1000

五个礼包的性价比从 1500‰ 排到 500‰,基准线 1000‰ 正好把「划算」与「不划算」切开。几个读法:

  • 新手礼包 1500‰:60 玉璧的价格给到 90 玉璧的价值,是典型的「拉首充」设计——新手期给溢价,是为了把非付费玩家推过第一道付费门槛。
  • 资源补给包与玉璧包都是 1000‰:贴着基准线。这说明礼包内容就是「按锚点定价」的——资源包给 12000 // 100 = 120 玉璧等价资源,卖 120 玉璧,不多给不少给。它们是「不想动脑时的等价兑换」。
  • 加速包 500‰:只有基准线的一半。同样 300 玉璧,直充能买 300 玉璧的东西,加速包只给 150 玉璧等价。这是锚点在说「加速道具在玩家心里比锚点更便宜」——要么锚点定错了,要么这个礼包就是设计成不划算的(逼玩家直充)。

三个礼包、三种定价意图

把表拆开看,这几个数不是随机的,它们对应三种不同的商业化意图:

  • 1500‰(新手礼包)——拉门槛。新手期的核心指标是「首充转化」,溢价是买转化的成本。玩家花了 6 元,拿到的东西比直充多 50%,这个「多」是设计给的,不是算错。
  • 1000‰(资源包 / 玉璧包)——等价兑换。它们是货架上的「标准品」,给的是「不想比较时闭眼买」的选项。性价比贴着基准线,意味着礼包内容严格按锚点定价。
  • 500‰(加速包)——引导直充。加速道具在玩家心里的价值低于锚点,如果礼包给到 1000‰,玩家会拿礼包当「加速优惠券」大量囤积,冲击直充玉璧。把它压到 500‰,是有意让礼包不如直充,保护直充这个基本盘。

这三档意图合起来说明一件事:性价比不是一个「越高越好」的指标,而是一个「该高就高、该低就低」的设计旋钮。新手礼包要高(拉转化),日常补给要平(不亏不赚),战略物资(加速)要低(护直充)。

折扣怎么改变性价比排序

折扣档来自 config/pricing.json 的 discount.tiers:d30 是 7 折、d50 是 5 折。折后性价比 = 价值不变、售价打折,所以折扣直接乘在性价比上:

礼包基础d30(7 折)d50(5 折)
新手礼包1500‰2142‰3000‰
混装大礼包1167‰1668‰2335‰
资源补给包1000‰1428‰2000‰
玉璧包1000‰1428‰2000‰
加速包500‰714‰1000‰

关键观察在最后一行:加速包基础 500‰ 是全场最差,但打 5 折后变成 1000‰,正好拉回基准线。这解释了 SLG 里一个常见现象——折扣是「把不划算的东西暂时变成划算」的工具。加速包平时放在那里没人买,一旦挂上 5 折标签,性价比瞬间对齐直充玉璧,玩家会觉得自己「捡到了」。折扣没有改变礼包的内容,只改变了分母。

折后性价比 = 基础性价比 × 100 / (100 − offPct)。d30(offPct = 30)的倍率是 100 / 70,d50 的倍率是 100 / 50 = 2。用整数算:加速包 500 × 100 / 70 = 714‰,500 × 100 / 50 = 1000‰——和表里的数完全对上。记住这个倍率,就不用每次重跑脚本:折扣对性价比的抬升与礼包内容无关,只与折扣率有关。

第 10 章会把折扣的副作用(价格锚点被击穿、原价购买者不满、复购预期)讲透,本节只给出可复算的性价比口径——任何一次折扣活动上线前,先用这张表算一遍折后排序,就知道会不会「把最划算的礼包折成最不划算的」。

锚点从哪里来

锚点不是凭空写的。resourcePerJade: 100 背后是一套换算逻辑:玩家每小时产出多少资源(卷一第 10 章的产出曲线)、直充 1 元得 10 玉璧、以及「1 玉璧换多少资源玩家才觉得值」的市场参照。三者对齐后,锚点才成立。

举个具体的对齐例子:直充 1 元得 10 玉璧(perYuan: 10)。如果玩家一小时能产出约 1000 资源,而「1 元 = 10 玉璧 = 1000 资源」在直觉上成立,那么锚点就是 1000 / 10 = 100 资源/玉璧。锚点本质上是「把时间价值换算成货币价值」的汇率:资源是时间产出的,玉璧是钱买的,锚点就是两者之间的那座桥。桥搭得准,礼包的性价比才有意义;桥搭歪了,再漂亮的性价比表也是错的。

这里必须划清可复现边界:锚点的数值来源不可复现。它要么来自真实付费数据(玩家实际愿意用多少玉璧换 100 资源),要么来自竞品的公开可观察定价,两者都属于 X 层——需真实业务环境,命令行不可复现。本节的脚本能做的是:给定锚点,算出所有礼包的性价比;它做不了的是:告诉你锚点该定成 100 还是 80。后者要靠真实投放数据回归,不在本套范围内。

锚点的三个用法

价值锚点不是一个数字,是一套口径。它在商业化里有三个用法:

  1. 定价:礼包定价先按锚点算出价值,再决定给几成溢价。溢价区间(比如新手 1500‰、常规 1000‰)是有意的设计选择,不是随机数。
  2. 排序:所有礼包放进同一张表,性价比自动排序。运营做「超值礼包」活动时,选的是排序靠前的礼包,而不是文案写「超值」的礼包。
  3. 折扣校准:折后性价比 = 基础 × 100 / (100 - offPct)。用这条公式,任何一个折扣档对任何一个礼包的效果都能提前算出来,不用等上线看数据。

最后强调边界:锚点本身是设计假设,不是客观真理。resourcePerJade: 100 意味着「设计者认为 1 玉璧值 100 资源」;如果玩家实际用脚投票(都去直充玉璧买资源),说明锚点定高了。锚点的校准需要真实付费数据,属于上一节说的 X 层边界。

两个常见错误

用这张表做礼包设计时,有两个反复出现的错误:

  • 把性价比当成唯一指标。性价比高不等于该上架。一个 3000‰ 的礼包会瞬间击穿所有直充的性价比,玩家会等这个礼包而不直充——溢价本身会稀释直充的价值。所以新手礼包 1500‰ 只在「首充一次」的约束下成立,不能天天卖。
  • 忘了锚点是相对值。性价比是「内容 ÷ 售价」的相对量,两个礼包比的是彼此,不是绝对价值。如果锚点整体上调 20%,所有礼包的性价比会同比例下降,但排序不变。排序才是设计要盯的东西,绝对值随锚点走。

礼包性价比 vs 抽卡成本

6.2 算过:抽到首个 SSR 平均要 40.4 抽 × 150 玉璧 = 6067 玉璧。把 6.3 的礼包放进这个坐标里,就能回答玩家最常问的一个问题——买礼包能不能省下抽卡的钱。

以玉璧包(6800 分 = 680 玉璧)为例:它的性价比是 1000‰,也就是买它和直充玉璧等价,换算成抽数就是 680 // 150 = 4 抽。它不会让抽卡变便宜,只是把「充 68 元」拆成一个更好接受的礼包形态。

而新手礼包(600 分 = 60 玉璧,性价比 1500‰)折算的「等价玉璧」是 90 玉璧,90 // 150 = 0——连一抽都不到。这说明礼包的性价比溢价要落到玩家真正需要的东西上才有意义:新手礼包给的是资源不是玉璧,它的价值在开荒期,不在抽卡。

把两节的坐标对齐,一个付费点的三条线就清楚了:

  • 6.1 定结构:卡池的概率表怎么公示。
  • 6.2 定成本:一次抽卡的期望成本是多少玉璧。
  • 6.3 定性价比:一笔钱换成礼包,能抵多少次抽卡、多少资源。

三者串起来,才是「一个付费点从结构到定价的完整账」。

小结

礼包性价比 = 内容玉璧等价价值 / 售价玉璧 × 1000,基准线 1000‰。本节的 S 层脚本用 config/pricing.json 的锚点把五个礼包算成一张可排序的表,跑出新手礼包 1500‰、加速包 500‰ 的分布,并演示了折扣如何把 500‰ 拉回 1000‰。礼包值不值,从今天起是一个能被脚本复算的数,而不是一句文案。

一句话记住这一章:结构看公示、成本看期望、性价比看锚点。三个数都能跑,三个数都能对账。

回到这一章的三节:6.1 定结构、6.2 算抽卡成本、6.3 算礼包性价比——一个付费点的结构、成本、性价比三件事,都有可跑的账。下一章离开单个付费点,进入「跟谁打」的问题:匹配与分段。

阅读导航:上一节:6.2 期望成本与「多少抽出货」 · 下一节:7.1 匹配分与 ELO/Glicko 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「slg」更多文章

  1. 12.3 留存与付费的联合分析
  2. 12.2 流失预警与召回
  3. 附录 C:全书公式、参数与数据集索引