@idlekit/cli 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8198,7 +8198,7 @@ var init_validate = __esm(() => {
8198
8198
  OFFLINE_ACTORS = new Set(["player", "automation"]);
8199
8199
  });
8200
8200
 
8201
- // ../../node_modules/.bun/typia@14.0.6+c0da6f6c08648afa/node_modules/typia/lib/internal/_validateReport.mjs
8201
+ // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_validateReport.mjs
8202
8202
  var _validateReport = (array) => {
8203
8203
  const isAncestor = (ancestor, descendant) => descendant === ancestor || descendant.startsWith(`${ancestor}.`) || descendant.startsWith(`${ancestor}[`);
8204
8204
  const reportable = (path) => {
@@ -8223,7 +8223,7 @@ var _validateReport = (array) => {
8223
8223
  };
8224
8224
  var init__validateReport = () => {};
8225
8225
 
8226
- // ../../node_modules/.bun/typia@14.0.6+c0da6f6c08648afa/node_modules/typia/lib/internal/_createStandardSchema.mjs
8226
+ // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_createStandardSchema.mjs
8227
8227
  var _createStandardSchema = (fn) => Object.assign(fn, { "~standard": {
8228
8228
  version: 1,
8229
8229
  vendor: "typia",
@@ -18125,7 +18125,7 @@ var init_checks3 = __esm(() => {
18125
18125
  FAIL_ON = ["error", "warn", "inconclusive", "unsupported"];
18126
18126
  });
18127
18127
 
18128
- // ../../node_modules/.bun/typia@14.0.6+c0da6f6c08648afa/node_modules/typia/lib/internal/_accessExpressionAsString.mjs
18128
+ // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_accessExpressionAsString.mjs
18129
18129
  var _accessExpressionAsString = (str) => variable(str) ? `.${str}` : `[${JSON.stringify(str)}]`, variable = (str) => reserved(str) === false && /^[a-zA-Z_$][a-zA-Z_$0-9]*$/g.test(str), reserved = (str) => RESERVED.has(str), RESERVED;
18130
18130
  var init__accessExpressionAsString = __esm(() => {
18131
18131
  RESERVED = /* @__PURE__ */ new Set([
@@ -18672,7 +18672,7 @@ var init_judge = __esm(() => {
18672
18672
  // ../core/dist/sim/analysis/flow.js
18673
18673
  var init_flow = () => {};
18674
18674
 
18675
- // ../../node_modules/.bun/typia@14.0.6+c0da6f6c08648afa/node_modules/typia/lib/internal/_isTypeUint32.mjs
18675
+ // ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_isTypeUint32.mjs
18676
18676
  var _isTypeUint32 = (value) => Math.floor(value) === value && MINIMUM <= value && value <= MAXIMUM, MINIMUM = 0, MAXIMUM;
18677
18677
  var init__isTypeUint32 = __esm(() => {
18678
18678
  MAXIMUM = 2 ** 32 - 1;
@@ -19020,6 +19020,363 @@ var init_jevAdvisory = __esm(() => {
19020
19020
  init__createStandardSchema();
19021
19021
  });
19022
19022
 
19023
+ // ../core/dist/ltv/projection.js
19024
+ function clamp012(v) {
19025
+ if (v < 0)
19026
+ return 0;
19027
+ if (v > 1)
19028
+ return 1;
19029
+ return v;
19030
+ }
19031
+ function deriveMonetizationConfig(scenario) {
19032
+ const m = scenario.monetization;
19033
+ const q = (m?.uncertainty?.quantiles ?? [0.5, 0.9]).filter((x) => typeof x === "number");
19034
+ const quantiles = q.length > 0 ? [...new Set(q)].sort((a, b) => a - b) : [0.5, 0.9];
19035
+ return {
19036
+ cohorts: {
19037
+ baseUsers: m?.cohorts?.baseUsers ?? 1
19038
+ },
19039
+ retention: {
19040
+ d1: m?.retention?.d1 ?? 0.42,
19041
+ d7: m?.retention?.d7 ?? 0.2,
19042
+ d30: m?.retention?.d30 ?? 0.09,
19043
+ d90: m?.retention?.d90 ?? 0.04,
19044
+ longTailDailyDecay: m?.retention?.longTailDailyDecay ?? 0.02
19045
+ },
19046
+ revenue: {
19047
+ payerConversion: m?.revenue?.payerConversion ?? 0.03,
19048
+ arppuDaily: m?.revenue?.arppuDaily ?? 0.7,
19049
+ adArpDau: m?.revenue?.adArpDau ?? 0.02,
19050
+ platformFeeRate: m?.revenue?.platformFeeRate ?? 0.3,
19051
+ grossMarginRate: m?.revenue?.grossMarginRate ?? 0.92,
19052
+ progressionRevenueLift: m?.revenue?.progressionRevenueLift ?? 0.4,
19053
+ progressionLogSpan: m?.revenue?.progressionLogSpan ?? 6
19054
+ },
19055
+ acquisition: {
19056
+ cpi: m?.acquisition?.cpi ?? 0
19057
+ },
19058
+ uncertainty: {
19059
+ enabled: m?.uncertainty?.enabled ?? false,
19060
+ draws: m?.uncertainty?.draws ?? 300,
19061
+ quantiles,
19062
+ seed: m?.uncertainty?.seed,
19063
+ sigma: {
19064
+ retention: m?.uncertainty?.sigma?.retention ?? 0.08,
19065
+ conversion: m?.uncertainty?.sigma?.conversion ?? 0.12,
19066
+ arppu: m?.uncertainty?.sigma?.arppu ?? 0.2,
19067
+ ad: m?.uncertainty?.sigma?.ad ?? 0.15
19068
+ },
19069
+ correlation: {
19070
+ retentionConversion: m?.uncertainty?.correlation?.retentionConversion ?? 0,
19071
+ retentionArppu: m?.uncertainty?.correlation?.retentionArppu ?? 0,
19072
+ retentionAd: m?.uncertainty?.correlation?.retentionAd ?? 0,
19073
+ conversionArppu: m?.uncertainty?.correlation?.conversionArppu ?? 0,
19074
+ conversionAd: m?.uncertainty?.correlation?.conversionAd ?? 0,
19075
+ arppuAd: m?.uncertainty?.correlation?.arppuAd ?? 0
19076
+ }
19077
+ }
19078
+ };
19079
+ }
19080
+ function retentionAtDay(ret, day) {
19081
+ if (day <= 0)
19082
+ return 1;
19083
+ if (day <= 1)
19084
+ return ret.d1;
19085
+ if (day <= 7)
19086
+ return ret.d1 + (ret.d7 - ret.d1) * (day - 1) / 6;
19087
+ if (day <= 30)
19088
+ return ret.d7 + (ret.d30 - ret.d7) * (day - 7) / 23;
19089
+ if (day <= 90)
19090
+ return ret.d30 + (ret.d90 - ret.d30) * (day - 30) / 60;
19091
+ const extra = day - 90;
19092
+ return clamp012(ret.d90 * Math.exp(-ret.longTailDailyDecay * extra));
19093
+ }
19094
+ function progressionFactor(startWorthLog10, endWorthLog10, span) {
19095
+ if (!Number.isFinite(startWorthLog10) || !Number.isFinite(endWorthLog10) || !Number.isFinite(span) || span <= 0) {
19096
+ return 0;
19097
+ }
19098
+ return clamp012((endWorthLog10 - startWorthLog10) / span);
19099
+ }
19100
+ function estimateLtvPerUser(args) {
19101
+ const { config, horizonDays } = args;
19102
+ const progression = clamp012(args.progression);
19103
+ const arppuDaily = config.revenue.arppuDaily * (1 + progression * config.revenue.progressionRevenueLift);
19104
+ const adArpDau = config.revenue.adArpDau * (1 + progression * config.revenue.progressionRevenueLift * 0.5);
19105
+ const payerConversion = config.revenue.payerConversion;
19106
+ const fullDays = Math.floor(Math.max(0, horizonDays));
19107
+ const remainder = Math.max(0, horizonDays - fullDays);
19108
+ let gross = 0;
19109
+ for (let day = 1;day <= fullDays; day++) {
19110
+ const active = retentionAtDay(config.retention, day);
19111
+ gross += active * (payerConversion * arppuDaily + adArpDau);
19112
+ }
19113
+ if (remainder > 0) {
19114
+ const active = retentionAtDay(config.retention, fullDays + 1);
19115
+ gross += remainder * active * (payerConversion * arppuDaily + adArpDau);
19116
+ }
19117
+ const net = gross * (1 - config.revenue.platformFeeRate) * config.revenue.grossMarginRate;
19118
+ const ltv = net - config.acquisition.cpi;
19119
+ return {
19120
+ cumulativeGrossRevenuePerUser: gross,
19121
+ cumulativeNetRevenuePerUser: net,
19122
+ cumulativeLtvPerUser: ltv
19123
+ };
19124
+ }
19125
+
19126
+ // ../core/dist/ltv/uncertainty.js
19127
+ function clamp013(v) {
19128
+ if (v < 0)
19129
+ return 0;
19130
+ if (v > 1)
19131
+ return 1;
19132
+ return v;
19133
+ }
19134
+ function clampCorrelation(v) {
19135
+ if (!Number.isFinite(v))
19136
+ return 0;
19137
+ if (v < -1)
19138
+ return -1;
19139
+ if (v > 1)
19140
+ return 1;
19141
+ return v;
19142
+ }
19143
+ function randNormal(rng) {
19144
+ const u1 = Math.max(0.000000000001, rng());
19145
+ const u2 = Math.max(0.000000000001, rng());
19146
+ return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
19147
+ }
19148
+ function buildCorrelationMatrix(base) {
19149
+ const rc = clampCorrelation(base.retentionConversion);
19150
+ const ra = clampCorrelation(base.retentionArppu);
19151
+ const rd = clampCorrelation(base.retentionAd);
19152
+ const ca = clampCorrelation(base.conversionArppu);
19153
+ const cd = clampCorrelation(base.conversionAd);
19154
+ const ad = clampCorrelation(base.arppuAd);
19155
+ return [
19156
+ [1, rc, ra, rd],
19157
+ [rc, 1, ca, cd],
19158
+ [ra, ca, 1, ad],
19159
+ [rd, cd, ad, 1]
19160
+ ];
19161
+ }
19162
+ function withOffDiagonalScale(matrix, alpha) {
19163
+ return matrix.map((row, i) => row.map((v, j) => {
19164
+ if (i === j)
19165
+ return 1;
19166
+ return v * alpha;
19167
+ }));
19168
+ }
19169
+ function cholesky4(matrix) {
19170
+ const n = 4;
19171
+ const l = Array.from({ length: n }, () => Array.from({ length: n }, () => 0));
19172
+ for (let i = 0;i < n; i++) {
19173
+ for (let j = 0;j <= i; j++) {
19174
+ let sum = 0;
19175
+ for (let k = 0;k < j; k++) {
19176
+ sum += (l[i]?.[k] ?? 0) * (l[j]?.[k] ?? 0);
19177
+ }
19178
+ if (i === j) {
19179
+ const val = (matrix[i]?.[i] ?? 0) - sum;
19180
+ if (val <= 0.000000001)
19181
+ return null;
19182
+ l[i][j] = Math.sqrt(val);
19183
+ } else {
19184
+ const denom = l[j]?.[j] ?? 0;
19185
+ if (Math.abs(denom) < 0.000000001)
19186
+ return null;
19187
+ l[i][j] = ((matrix[i]?.[j] ?? 0) - sum) / denom;
19188
+ }
19189
+ }
19190
+ }
19191
+ return l;
19192
+ }
19193
+ function multiplyLowerTriangular(l, z) {
19194
+ const out = [0, 0, 0, 0];
19195
+ for (let i = 0;i < 4; i++) {
19196
+ let sum = 0;
19197
+ for (let j = 0;j <= i; j++) {
19198
+ sum += (l[i]?.[j] ?? 0) * (z[j] ?? 0);
19199
+ }
19200
+ out[i] = sum;
19201
+ }
19202
+ return out;
19203
+ }
19204
+ function sampleCorrelatedNormals(rng, corr) {
19205
+ const z = [randNormal(rng), randNormal(rng), randNormal(rng), randNormal(rng)];
19206
+ const matrix = buildCorrelationMatrix(corr);
19207
+ let l = cholesky4(matrix);
19208
+ let alpha = 0.99;
19209
+ while (!l && alpha > 0.000001) {
19210
+ l = cholesky4(withOffDiagonalScale(matrix, alpha));
19211
+ alpha *= 0.8;
19212
+ }
19213
+ if (!l) {
19214
+ return {
19215
+ retention: z[0] ?? 0,
19216
+ conversion: z[1] ?? 0,
19217
+ arppu: z[2] ?? 0,
19218
+ ad: z[3] ?? 0
19219
+ };
19220
+ }
19221
+ const c = multiplyLowerTriangular(l, z);
19222
+ return {
19223
+ retention: c[0] ?? 0,
19224
+ conversion: c[1] ?? 0,
19225
+ arppu: c[2] ?? 0,
19226
+ ad: c[3] ?? 0
19227
+ };
19228
+ }
19229
+ function applyLogNormalShock(mean, sigma, z) {
19230
+ if (sigma <= 0)
19231
+ return mean;
19232
+ return mean * Math.exp(sigma * z - 0.5 * sigma * sigma);
19233
+ }
19234
+ function makeUncertainConfig(base, rng) {
19235
+ const shock = sampleCorrelatedNormals(rng, base.uncertainty.correlation);
19236
+ const d1 = clamp013(applyLogNormalShock(base.retention.d1, base.uncertainty.sigma.retention, shock.retention));
19237
+ const d7 = Math.min(d1, clamp013(applyLogNormalShock(base.retention.d7, base.uncertainty.sigma.retention, shock.retention)));
19238
+ const d30 = Math.min(d7, clamp013(applyLogNormalShock(base.retention.d30, base.uncertainty.sigma.retention, shock.retention)));
19239
+ const d90 = Math.min(d30, clamp013(applyLogNormalShock(base.retention.d90, base.uncertainty.sigma.retention, shock.retention)));
19240
+ return {
19241
+ ...base,
19242
+ retention: {
19243
+ ...base.retention,
19244
+ d1,
19245
+ d7,
19246
+ d30,
19247
+ d90
19248
+ },
19249
+ revenue: {
19250
+ ...base.revenue,
19251
+ payerConversion: clamp013(applyLogNormalShock(base.revenue.payerConversion, base.uncertainty.sigma.conversion, shock.conversion)),
19252
+ arppuDaily: Math.max(0, applyLogNormalShock(base.revenue.arppuDaily, base.uncertainty.sigma.arppu, shock.arppu)),
19253
+ adArpDau: Math.max(0, applyLogNormalShock(base.revenue.adArpDau, base.uncertainty.sigma.ad, shock.ad))
19254
+ }
19255
+ };
19256
+ }
19257
+ function quantileFromSorted(sorted, q) {
19258
+ if (sorted.length === 0)
19259
+ return 0;
19260
+ const idx = Math.max(0, Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * q)));
19261
+ return sorted[idx] ?? 0;
19262
+ }
19263
+ function estimateLtvDistribution(args) {
19264
+ const rng = mulberry32(args.seed);
19265
+ const grossSamples = [];
19266
+ const netSamples = [];
19267
+ const ltvSamples = [];
19268
+ const draws = Math.max(1, args.draws);
19269
+ for (let i = 0;i < draws; i++) {
19270
+ const sampled = makeUncertainConfig(args.config, rng);
19271
+ const point = estimateLtvPerUser({
19272
+ config: sampled,
19273
+ horizonDays: args.horizonDays,
19274
+ progression: args.progression
19275
+ });
19276
+ grossSamples.push(point.cumulativeGrossRevenuePerUser);
19277
+ netSamples.push(point.cumulativeNetRevenuePerUser);
19278
+ ltvSamples.push(point.cumulativeLtvPerUser);
19279
+ }
19280
+ const avg = (arr) => arr.reduce((a, b) => a + b, 0) / Math.max(1, arr.length);
19281
+ const sortedLtv = [...ltvSamples].sort((a, b) => a - b);
19282
+ const byQuantile = {};
19283
+ for (const q of args.quantiles) {
19284
+ byQuantile[`q${Math.round(q * 100)}`] = quantileFromSorted(sortedLtv, q);
19285
+ }
19286
+ return {
19287
+ mean: {
19288
+ cumulativeGrossRevenuePerUser: avg(grossSamples),
19289
+ cumulativeNetRevenuePerUser: avg(netSamples),
19290
+ cumulativeLtvPerUser: avg(ltvSamples)
19291
+ },
19292
+ quantiles: byQuantile
19293
+ };
19294
+ }
19295
+ var init_uncertainty = () => {};
19296
+
19297
+ // ../core/dist/ltv/public.js
19298
+ function assertMonetization(monetization) {
19299
+ const valid = validateScenarioV1({
19300
+ schemaVersion: 1,
19301
+ unit: { code: "LTV" },
19302
+ policy: { mode: "drop" },
19303
+ model: { id: "ltv-input", version: 1 },
19304
+ initial: { wallet: { unit: "LTV", amount: "0" } },
19305
+ clock: { stepSec: 1, durationSec: 1 },
19306
+ monetization
19307
+ });
19308
+ if (!valid.ok)
19309
+ throw new RangeError(valid.issues.map((issue) => issue.message).join("; "));
19310
+ }
19311
+ function assertQuantileValues(quantiles) {
19312
+ if (quantiles.length === 0)
19313
+ throw new RangeError("quantiles must be nonempty and in 0..1");
19314
+ for (const q of quantiles) {
19315
+ if (!Number.isFinite(q) || q < 0 || q > 1)
19316
+ throw new RangeError("quantiles must be nonempty and in 0..1");
19317
+ }
19318
+ }
19319
+ function assertConfig(config) {
19320
+ const { retention, ...other } = config;
19321
+ for (const day of ["d1", "d7", "d30", "d90"]) {
19322
+ if (!Number.isFinite(retention[day]) || retention[day] < 0 || retention[day] > 1) {
19323
+ throw new RangeError(`retention.${day} must be finite and in 0..1`);
19324
+ }
19325
+ }
19326
+ if (!Number.isFinite(retention.longTailDailyDecay) || retention.longTailDailyDecay < 0) {
19327
+ throw new RangeError("retention.longTailDailyDecay must be finite and >= 0");
19328
+ }
19329
+ const { quantiles, ...uncertainty } = other.uncertainty;
19330
+ assertQuantileValues(quantiles);
19331
+ assertMonetization({ ...other, uncertainty });
19332
+ }
19333
+ function deriveMonetizationConfig2(scenario) {
19334
+ assertMonetization(scenario.monetization ?? {});
19335
+ const config = deriveMonetizationConfig(scenario);
19336
+ assertConfig(config);
19337
+ return config;
19338
+ }
19339
+ function assertPoint(args) {
19340
+ assertConfig(args.config);
19341
+ if (!Number.isFinite(args.horizonDays) || args.horizonDays < 0 || args.horizonDays > 36500) {
19342
+ throw new RangeError("horizonDays must be finite and in 0..36500");
19343
+ }
19344
+ if (!Number.isFinite(args.progression))
19345
+ throw new RangeError("progression must be finite");
19346
+ }
19347
+ function finiteResult(result) {
19348
+ if (!Object.values(result).every((value) => typeof value === "number" && Number.isFinite(value))) {
19349
+ throw new RangeError("LTV calculation overflowed; reduce the input magnitudes");
19350
+ }
19351
+ return result;
19352
+ }
19353
+ function estimateLtvPerUser2(args) {
19354
+ assertPoint(args);
19355
+ return finiteResult(estimateLtvPerUser(args));
19356
+ }
19357
+ function estimateLtvDistribution2(args) {
19358
+ assertPoint(args);
19359
+ if (!Number.isSafeInteger(args.draws) || args.draws < 1 || args.draws > 1e5 || args.draws * Math.max(1, Math.ceil(args.horizonDays)) > 1e7) {
19360
+ throw new RangeError("draws must be 1..100000 and draws \xD7 ceil(horizonDays) at most 10000000");
19361
+ }
19362
+ if (!Number.isFinite(args.seed))
19363
+ throw new RangeError("seed must be finite");
19364
+ if (args.quantiles.length > 101)
19365
+ throw new RangeError("quantiles exceed the 101 distinct percent keys");
19366
+ assertQuantileValues(args.quantiles);
19367
+ if (new Set(args.quantiles.map((q) => Math.round(q * 100))).size !== args.quantiles.length) {
19368
+ throw new RangeError("quantiles must be nonempty, in 0..1 and have distinct rounded percent keys");
19369
+ }
19370
+ const result = estimateLtvDistribution(args);
19371
+ finiteResult(result.mean);
19372
+ finiteResult(result.quantiles);
19373
+ return result;
19374
+ }
19375
+ var init_public = __esm(() => {
19376
+ init_validate();
19377
+ init_uncertainty();
19378
+ });
19379
+
19023
19380
  // ../core/dist/index.js
19024
19381
  var init_dist2 = __esm(() => {
19025
19382
  init_types();
@@ -19085,6 +19442,7 @@ var init_dist2 = __esm(() => {
19085
19442
  init_judge();
19086
19443
  init_flow();
19087
19444
  init_jevAdvisory();
19445
+ init_public();
19088
19446
  });
19089
19447
 
19090
19448
  // src/errors.ts
@@ -19291,7 +19649,7 @@ var init_bun = __esm(() => {
19291
19649
  var require_package = __commonJS(function(exports, module) {
19292
19650
  module.exports = {
19293
19651
  name: "@idlekit/cli",
19294
- version: "0.4.0",
19652
+ version: "0.5.1",
19295
19653
  description: "Bun-first idk CLI for idle game scenario validation, simulation, tuning, and reporting.",
19296
19654
  license: "MIT",
19297
19655
  type: "module",
@@ -19344,7 +19702,7 @@ var require_package = __commonJS(function(exports, module) {
19344
19702
  "@clack/prompts": "1.8.1",
19345
19703
  "@gunshi/plugin-completion": "0.37.3",
19346
19704
  "@gunshi/plugin-suggestion": "0.37.3",
19347
- "@idlekit/core": "0.4.0",
19705
+ "@idlekit/core": "0.5.0",
19348
19706
  ajv: "^8.20.0",
19349
19707
  gunshi: "0.37.3",
19350
19708
  "proper-lockfile": "4.1.2",
@@ -21163,4 +21521,4 @@ export {
21163
21521
  workerEntryMarker
21164
21522
  };
21165
21523
 
21166
- //# debugId=C1B85E96533A30C764756E2164756E21
21524
+ //# debugId=36317EC36EA8D6E664756E2164756E21