feat: Bot Lending #223

Merged
sickday merged 6 commits from feat/bot-lending into main 2026-07-24 18:04:01 +00:00
Owner
No description provided.
bot_loans table + BotLoan schema (best-effort, single-writer upserts on
the (borrower, lender) pair index), pure Game.Bot.Lending (fold-invariant
whole-period accrual, 2x-principal default threshold, skip-town roll,
demand-feed obligation units), and the Game.Bot.LoanBook singleton:
open-loan cache + obligations ETS, 30s send_after sweep that folds
interest, expires unfunded offers, trips defaults, and dispatches
queued collections (one per lender and per borrower) by having the
due bot PM its lender through the existing bot_send_pm seam.
Economy.effective_stock/1 = pool stock minus units promised to lenders,
swapped in at exactly the three price/throttle stock derivations
(market value, shop pricing, gather throttle). Obligations are claims
on future production priced as scarcity, so the fleet reallocates
toward what it owes; availability checks keep raw global_stock/1 so
processors are never falsely starved.
Game.Bot.LoanTalk parses lend/loan offers (amount + optional repay item
resolved through the trader pipeline so the id is carried from
resolution), collect and status verbs, and phrases authoritative
replies straight from the engine — loan numbers never route through
the LLM. Terms are favor-priced off the acquaintance row, so strangers
pay the risk premium. Wired ahead of the trader fallback in
handle_bot_pm_text; effectful paths degrade to an in-character
brush-off when the book is down.
Reactive path: any bot with an open loan against a trade partner
auto-accepts their request, marks the trade, auto-offers the repayment
(pool-withdrawn at elastic prices — glutted goods repay in more units),
and settles from what actually changed hands: sufficient coins activate
a pending loan (funding banked as :coin_income), delivered value records
as a payment. No virtual bank writes anywhere.

Proactive path: the LoanBook sweep arms an affordable due bot with a
loan_collect duty; the GoalRunner runs Goals.RepayLoan as a duty
override (current goal holds untouched, like freezes/teleports) —
assess, bank-anchored teleport (anchor locked at assess time), walk-up,
trade request — then the reactive machinery finishes the hand-over.
Failures release the lender's collection slot and leave the debt
standing. FriendServer gains lookup_entry/1 for signature→index.
Burned loan history (the pair's last defaulted/skipped row) drags
effective favor down half a level per burned loan, so deadbeat pairs
price at stranger rates or worse — and the offer reply says so.
Skip-town now has teeth: the runaway PMs a farewell and is despawned
2.5s later via Manager.despawn_bot (it returns from its profile on the
next boot, debt already written off). Work-off was already structural:
defaulted debt stops accruing but keeps feeding the demand signal and
gets slow-cadence collections until ground to zero. Adds ::loans
(permission player.loans, bit 82, in the player preset) listing the
caller's loans from the live book.
fix: non-trader loan partners actually complete the trade handshake
All checks were successful
ci / test (pull_request) Successful in 2m54s
79ab23d414
The reactive layer scheduled auto-accepts for loan-partner bots, but
both bot_auto_trade_accept handle_infos still gated on state.trader —
a gatherer borrower set trade_request_from and then silently no-opped,
so funding and collection trades never opened. Widened to
trader-or-loan-partner (request) / trader-or-loan-marked (screens),
with handle_info-level regression tests.

Caught by the live scratch-world probe; with the fix the full loop
passes 20/20: PM negotiate → funding trade activates and banks the
principal → due sweep arms the duty → borrower teleports to the
lender's bank, walks to their tile, trades the yew logs → balance
drops → default trips → skip-town despawns the runaway.
sickday force-pushed feat/bot-lending from 79ab23d414
All checks were successful
ci / test (pull_request) Successful in 2m54s
to e0275307d1
All checks were successful
ci / test (pull_request) Successful in 2m36s
2026-07-24 17:53:26 +00:00
Compare
sickday referenced this pull request from a commit 2026-07-24 18:04:02 +00:00
Sign in to join this conversation.
No description provided.