← Back to Gists

The math behind my DevPlace Code Farm bot

📝 Markdown Rendered
retoor
retoor · Level 1868 ·

The math behind my DevPlace Code Farm bot

Notation

S = build-speed multiplier = CI_speed × (1 + 0.04·growth_level) × (1 + 0.05·bare_metal_level)
X = XP multiplier          = 1 + 0.05·mentorship_level
rate(c) = xp(c) · X / (seconds(c) / S) = xp(c) · X · S / seconds(c)      xp/s, for crop c
R(t)              = capacity rate = plot_count × max(rate(c) over affordable, unlocked crops)
remaining_needed(t) = 4050 − guaranteed_xp(t)
horizon(t)        = remaining_needed(t) / R(t)                          seconds left in the cycle

guaranteed_xp = current XP already credited, plus the reward_xp of every plot currently
growing or ready (i.e. XP that is already committed even though not yet harvested).

Worked example (this account, CI tier 5 / Build Cache 10 / Bare-Metal 8 / Mentorship 10, all maxed)

S = 2.5 × (1 + 0.04·10) × (1 + 0.05·8) = 2.5 × 1.4 × 1.4 = 4.9
X = 1 + 0.05·10 = 1.5
rate(Shell Script) = 2 × 1.5 / (30 / 4.9) = 3 / 6.122 = 0.4899 xp/s
R_max = 12 × 0.4899 = 5.879 xp/s
time-to-target at R_max = 4050 / 5.879 ≈ 689 s

Measured (6 restart-uncorrupted telemetry cycles): mean 674.3s, σ=23.9s (CV 3.5%) - below the
theoretical "always at max rate" ceiling, meaning the ramp-up phase costs essentially nothing
measurable and the bot runs at its throughput limit, not merely close to it.

Purchase efficiency - one ranked queue, one unit

CI / plot / perk:   Δgain = ΔR · horizon        (a rate improvement, paid out over remaining time)
fertilize:          Δgain = (remaining_seconds / 2) · R(t)     (one-time credit, no horizon factor)
efficiency          = Δgain / cost

Every candidate is ranked by efficiency and the highest is bought; then everything is
recomputed from the new state and repeated (up to 20 times per tick). horizon → 0 once
remaining_needed ≤ 0 sends every CI/plot/perk Δgain → 0 automatically - this is the
cycle-end purchase freeze, not a separate rule bolted on top.

Per-candidate formulas:

plot:          ΔR = best_unlocked_crop_rate
CI tier t→t+1: ΔR = R(t) · (speed(t+1)/speed(t) − 1)
Build Cache:   ΔR = R(t) · (0.04 / (1 + 0.04·level))
Mentorship:    ΔR = R(t) · (0.05 / (1 + 0.05·level))
fertilize:     Δgain = (remaining/2) · R(t)     [not ΔR - a one-time XP credit, not a rate]

Tail-phase crop selection

remaining_needed ≤ max(xp(c) over unlocked c)
    → plant the crop with minimum seconds(c) among {c : xp(c) ≥ remaining_needed}
      (fastest crop overall if none qualifies alone)
else
    → plant the crop with maximum rate(c)

Proof sketches (full proofs in plan.md §9)

  • Never delay an affordable, positive-value purchase. Cost is time-independent; a purchase
    only ever raises R(t) for all later t. Delaying shrinks the window the higher rate applies
    over, so time-to-target is weakly higher. Never beneficial.
  • Greedy-by-current-efficiency is the optimal purchase order. Costs are order-independent,
    purchases are monotonic, and the final rate once everything is bought doesn't depend on order
    (multiplication/addition of the CI/perk/plot factors commutes). An adjacent-swap exchange
    argument then shows sorting into decreasing Δgain/cost order is optimal - the same argument
    behind Smith's Rule / WSPT in scheduling theory, applied to a reinvestment-rate problem instead
    of job-completion-times.
  • Per-plot crop choice. Plots are independent, so maximizing each one's own rate maximizes the
    sum - optimal by independence in steady state. In the tail phase, "fastest crop that alone clears
    the threshold" weakly dominates "highest-rate crop" whenever they differ, because making an
    option available earlier can only lower (never raise) the system's min(...)-based crossing time.

Data over assumptions

CI_SPEED, CI_UPGRADE_COST, PLOT_COST, PERK_MAX_LEVEL in the source are fallbacks only.
Every tick prefers live fields the API already reports directly: ci_speed, ci_next_cost,
ci_next_tier (0 exactly when maxed), next_plot_cost, max_plots, and each perk's own
max_level. The one number the API can't give directly - the speed of a CI tier not yet
reached - is learned: observed (tier, ci_speed) pairs are persisted to the telemetry database
and reloaded on every restart, so the bot remembers what it measured last time rather than trusting
a static guess forever. If the game ever rebalances a price or a speed value, the learned/live path
picks up the real number the next time it's observed; only a tier never yet visited falls back to
the hardcoded table.

Empirically confirmed, not assumed

13 consecutive refactors (level_before=10, prestige 281-293) gave `stars_gained = prestige_before

  • 3`, exactly, every single time - zero variance. Since every sample is at the same level, this
    data cannot show star yield depending on level at all, which directly supports "always refactor at
    level 10, never farm extra levels for more stars" as a measured conclusion rather than an assumed
    one.

Comments

No comments yet. Start the discussion.