feat(packages): ludic.shop - prices by standing with the fountain guard, a weekly demand on its own dice, stock, buying and selling through purse and pack ports, a trade fact

Items (base price, sell fraction) and vendors (stock, buys, refuses) are the game's data handed
over at boot; standing, the day, a shortage and "buys beyond its list" are the ShopWorld port;
money is ShopPurse and the pack ShopPack. The week's wanted and glutted items come from an Rng
seeded by the week, never the world's. The balance invariants - a shop is not a fountain,
friction falls and never inverts, raw keeps its order - are the package's tests (nine).

ludic.crafting: its private names carry the package's prefix too (a private name is still one
global namespace with the game's - ludic.shop's `sp_seed` met the game's).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 06:44:44 +03:00
parent cc9cec922a
commit 12cfcb10ac
12 changed files with 663 additions and 28 deletions

View file

@ -0,0 +1,43 @@
# prices.ludic - what a thing costs and what it fetches. One standing moves both, toward each
# other and never past: a newcomer pays a little over and sells a little under.
function shop_rep01() -> float { return Math.clamp(float(ShopWorld.rep()), 0.0, 100.0) / 100.0 }
# the fraction of base a buyer pays, and a seller is paid before the item's own fraction
export function shop_buy_factor() -> float { return shop_buy_new + (shop_buy_best - shop_buy_new) * shop_rep01() }
export function shop_sell_factor() -> float { return shop_sell_new + (shop_sell_best - shop_sell_new) * shop_rep01() }
export function shop_price_buy(it: int) -> int {
if not shop_item_ok(it) { return 0 }
let p = int(float(shop_base[it]) * shop_buy_factor() * ShopWorld.buy_mult() + 0.5)
return Math.max(p, 1)
}
# what vendor `v` pays (-1: no vendor, no demand). ROUNDED, so cheap raw material keeps its
# order; and a vendor that stocks the thing never pays what it charges, not even a floor of $1
export function shop_price_sell_at(v: int, it: int) -> int {
if not shop_item_ok(it) { return 0 }
let f = shop_sell_factor() * shop_sell_frac[it] * shop_demand_mult(v, it)
var p = Math.max(int(float(shop_base[it]) * f + 0.5), 1)
if shop_stocks(v, it) { p = Math.max(Math.min(p, shop_price_buy(it) - 1), 0) }
return p
}
# what the vendor at the counter pays
export function shop_price_sell(it: int) -> int { return shop_price_sell_at(shop_at, it) }
export function shop_stocks(v: int, it: int) -> bool {
if not shop_vendor_ok(v) or shop_vendor_list[v].stock == null { return false }
let st = shop_vendor_list[v].stock
for k in 0 .. len(st) { if st[k] == it { return true } }
return false
}
# what `v` takes: its list and whatever the world says, and never the one thing it refuses
export function shop_buys(v: int, it: int) -> bool {
if not shop_vendor_ok(v) or it == shop_vendor_list[v].refuses { return false }
if ShopWorld.also_buys(v, it) { return true }
let b = shop_vendor_list[v].buys
if b == null { return false }
for k in 0 .. len(b) { if b[k] == it { return true } }
return false
}