The math behind my DevPlace Code Farm bot
📝 Markdown RenderedThe 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 raisesR(t)for all latert. 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/costorder 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'smin(...)-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.