Feature - Implemented well_stocked ASH function

The van/duffel nerf brought with it a significant and meaningful change to a behind-the-scenes function that KoL uses, which, for the purposes of this thread, I'll call well_stocked. This function is used in generating the pools for things like van keys and duffels, obviously, but also the party rat, the boxing daycare, guzzlr boozes, and the gift basket from this most recent Crimbo.

well_stocked works something like this: it takes an input of a price and a quantity, and it returns an array of all items which have a sufficient supply under that price. In other words, well_stocked(11, 23) would return all items such that at least 11 are available for 23 or less meat in the mall. Until recently, this would bypass store limits, but now it counts store limits the same way mafia's mall_price does.

I know mafia is very careful about the ways it deals with the mall. It returns the 5th smallest mall price, paying attention to store limits, and doesn't update prices frequently. I think it even messes with visit_url calls to the mall, which makes perfect sense. That being said, now that KoL's well_stocked function pays attention to store limits, I think a similar function may be appropriate to add to mafia.

The mafia version would be primarily different insofar as it would be a boolean with an additional item input, rather than something that iterates over the set of all items, because obviously ding a mallsearch for every item in the game is a bad idea. This behavior cannot currently be replicated by using mafia's mall_price function, so it would be helpful to be able to access this sort of information.

I'm curious to see how other people feel about this. I know that mafia is rightfully cautious about the way it approaches the mall, but I do think that a well_stocked function would be appropriate, given that a function similar to that exists behind-the-scenes in KoL, and the behavior WRT store limits means that it would be harder to abuse this for mallbotting reasons.
 

heeheehee

Developer
Staff member
Are there any limitations on this well-stocked function? (link to spading results?) For instance, if the first parameter must be >= 5, then this does not leak any more information about cheapest prices to ASH scripts. Depending on the granularity of the parameters, it may be able to leak information about next-cheapest prices -- for instance, if an item shows up for well_stocked(5, mall_price(it) but not for well_stocked(6, mall_price(it)), you know that the next item is more expensive; you can then do a (binary) search to figure out what the price is of the 6th-most expensive item.

obviously d[o]ing a mallsearch for every item in the game is a bad idea.
Citation needed, especially with heavy caching, and doubly so if you can restrict it to a preset category of items (e.g. food). See: mall_prices.
 

lostcalpolydude

Developer
Staff member
The fact that this is seems to be reasonably spaded and seems to be a thing people want to control makes me feel like I should push in /dev for it to not be a thing that KoL uses. On KoL's end, it was only ever meant to work in the context where people didn't know how it works.

If adding this function to KoLmafia has any practical benefit, there is a decent chance I would feel inclined to push for KoL to change until it no longer has a practical benefit.
 

fronobulax

Developer
Staff member
Interesting.

If there is a KoL feature that is supposed to be obscure and has now become spaded enough to be revealed then only reasonable actions, IMO are for KoL to accept that the feature is no longer obscure or for KoL to take steps to invalidate the spading. That is a KoL decision and not a KoLmafia one. I am intrigued by the apparent existence of an arms race between game designers and spaders. In general "security by obscurity is no security" which reinforces my sense that KoL needs to do something, if this function would be considered a problem.

If TPTB asked that KoLmafia not do something I would certainly do my best to honor that request but since KoLmafia is open source and supports scripting the barrier to unofficially avoiding the request is pretty low.

My usual reaction to a proposal like this is to ask whether it can be easily scripted and ask whether it can be "exploited". My first answers suggest "no" and "maybe not". I think we probably have something like boolean well_stocked(item, quantity, price) which is always false if quantity < 5. I can see using this to build a table of price and quantity but since that is already available in GUIs I'm not sure that is a bad thing in terms of information now being revealed. Server hits might be a concern...
 

gausie

D̰͕̝͚̤̥̙̐̇̑͗̒e͍͔͎͈͔ͥ̉̔̅́̈l̠̪̜͓̲ͧ̍̈́͛v̻̾ͤe͗̃ͥ̐̊ͬp̔͒ͪ
Staff member
I think @phreddrickk wants to have this function so that scripts can know what the average return can be expected to be for running (for example) Party Mouse. This function couldn't be used to abuse the spading of how that function works KoL-side.
 

fronobulax

Developer
Staff member
I think @phreddrickk wants to have this function so that scripts can know what the average return can be expected to be for running (for example) Party Mouse. This function couldn't be used to abuse the spading of how that function works KoL-side.

Once again we have an example of a change request being driven by a script I have never heard of (and this time could not find on GitHub either). At least I have stumbled across the belief that the van key and duffle yields are variable and based upon mall data. I think something I really wanted to buy was in the mall but at a store that was ignoring me. The shop owner posted that they were ignoring everyone and stocking the store in hopes of influencing the van key and duffle yields.

I understood @lostcalpolydude 's comment to mean that TPTB did not want this mechanic to be understood by players and if spading became sufficiently accurate TPTB could change the mechanic. Is there a use case for this function besides the collection of data to be analyzed to help players understand this mechanic?
 

gausie

D̰͕̝͚̤̥̙̐̇̑͗̒e͍͔͎͈͔ͥ̉̔̅́̈l̠̪̜͓̲ͧ̍̈́͛v̻̾ͤe͗̃ͥ̐̊ͬp̔͒ͪ
Staff member
What script? I'm just guessing what phredrickk wants the function for. The use case I described in my last post is nothing to do with collection of data to be analyzed by a third party - it would help a script make a decision at runtime on what familiar to use if you're trying to maximize profits. So it's like tracking the charge and drops for an item dropping familiar.

Zarqon and rinn both have published scripts that attempt to swap familiars based on profitability, both could use such a function to consider a wider set of familiars.
 

fronobulax

Developer
Staff member
What script? I'm just guessing what phredrickk wants the function for. The use case I described in my last post is nothing to do with collection of data to be analyzed by a third party - it would help a script make a decision at runtime on what familiar to use if you're trying to maximize profits. So it's like tracking the charge and drops for an item dropping familiar.

Zarqon and rinn both have published scripts that attempt to swap familiars based on profitability, both could use such a function to consider a wider set of familiars.

OK. Somedays the only exercise I get is from jumping to conclusions. Since the OP referenced Neverending Party items and I completely forgot that there was a familiar Party Mouse, I seized upon "running (for example) Party Mouse" as an exemplar for a family of scripts, perhaps that farmed areas for something other than area quest items. Sorry.
 
Frankly, I don't have any particular scripts in mind I would use this for--post vanduffel nerf, I don't think the party mouse is ever worth running, and thus rejiggering the math on it seems like a bad idea.

I don't actually have any scripts I'd like to use this in right now. I could, as @gausie suggests, use this to more accurately value the party mouse for libram's bjorn/crown of thrones support, but post vanduffel I cannot imagine that it'd ever be worth running. The motivation here comes primarily from the fact that we know that this is something KoL tracks, so it seems like it could be helpful for mafia to track it as well.

Realistically, I would be more likely to use this in a diet script than I would to actually predict the outputs of things like the party mouse, boxing daycare, and guzzlr, because it might provide more useful information than mall_price does.

re: TPTB changing the mechanic, I'm not really sure what the impetus would be. The two biggest exploits of the mechanic have been removed--the baleet list is capped at 1000, and it now pays attention to store limits. Security through obscurity is a bad policy in a game full of spaders (James or otherwise), and I don't personally see the danger in a player being able to predict what items a greedghost can and can't ask you to buy. Obviously if a message comes down from TPTB that we shouldn't implement this, we shouldn't implement this, but I don't really understand @lostcalpolydude's motivation for wanting TPTB to change the mechanic.

I feel like the "this could be used to glean too much information about the mall" obstacle is probably surmountable. As fronobulax pointed out, we can make it always return false if the inputted volume is < 5, and there is probably a way to prevent people from just iterating over a thousand different numbers to get a super-accurate image of the mall. Although, I mean, if you're putting in that much effort, it's probably easier for you to just build mafia locally and rip out the parts that stop you from seeing the mall.
 

fronobulax

Developer
Staff member
boolean well_stocked(item, quantity, price)

item must be an item and in the mall otherwise return false
if quantity < 5 return false
price must be > 0 or return false
price must be >= min mall price or return false

otherwise ask the question - can quantity of item be purchased from the mall paying no more than price for each item? and return true or false

if we have item a

1 101
1 101
2 101
1 102
1 102

(a, 5, 101) is false
(a, 5, 102) is true
(a, 6, 102) is true

since only 5 stores stock a and there are six for sale (a, x, y) will be false for x > 6 regardless of y
similarly (a, 5, y) for y >= 102 is always true and (a, 6, y) is also true for y >= 102

I'm thinking out loud but if the conditions and edge cases are touched on above this sounds like there might not be a lot of useful information. Is that because there are only a small amount for sale and nothing is priced to the 9's?
 
I just thought of a pretty weird use-case: buffing up with a gnome or with a riftlet.

When you use a riftlet-like to farm (this happens on halloween, mostly, but I could imagine it being viable elsewhere given that the gnome is a fairy), it essentially acts as a superlinear multiplier to your MPA. That is, having more weight makes more weight more valuable. But, you only care if that weight carries you through the end of the day.

Right now, we have two tools at our disposal: mallPrice, and buy. You can, with mallPrice and historical price data and whatever, estimate a maximally profitable array of buyable buffs. Maybe you're running some turns of chorale, some green hearts, etc. You then try to buy() them, passing a price input like any sane human being.

Uh oh.

There are only 20 Chorale recordings in the mall less than or equal to your equilibrium price. That's 400 turns of a buff you wanted to run for 1000 turns. Normally that wouldn't be the end of the world, I would just not run this buff. But because of the superlinear scaling on the value of weight for the gnome, not having those +10 lbs means that all of a sudden some of these other buffs we bought aren't profitable. If we can't get the +10 lbs from chorale, we probably shouldn't have poured all of our meat into silver face paint. Now we're broke, vegetarian and alone, cursing the heavens for our own hubris.

With wellStocked, however, we could check ahead of time whether a potion has enough in the mall to viably run all day. We determine the optimal set of buffs, and farm, say, transdermal smoke patches until the cows come home. We sell them at 6k a pop, and use our winnings to buy a few CI charters for an anti-raffle. Everyone is happy and cool and good.

Admittedly this is very niche--it only comes into play because of the superlinear scaling on the value of this buff--but it's not too hard to think of analogous scenarios. wellStocked wouldn't just tell you if you can run a buff for an instantaneous profit, but it would tell you if you can run a buff all day at a profit.
 

fronobulax

Developer
Staff member
See https://github.com/kolmafia/kolmafia/pull/242

As implemented the assumption is that the items are "purchased" in order of price and the price parameter is the price of the last item purchased.

Calculation respects store limits but the results are otherwise based upon mall parameters. If you are not ignoring stores that ignore you then they will be included.
 
This behavior looks correct to me, although my understanding of java is limited specifically to "helping high schoolers with their homework and then getting confused by the fact that arrays are of fixed length".
 

Veracity

Developer
Staff member
Huh. I am now investigating the MallSearchRequest and I discovered that there exactly 5 places in KoLmafia that create one:

- The Mall Search Frame
- Look at a players store from chat
- Look up mall prices for a single in MallPriceManager (accessible from ASH)
- Look up mall prices for an entire category of items in MallPriceManager (accessible from ASH)

And now this, the well_stocked() ASH function. Which I had never heard of and couldn't imagine why it was useful, after studying the code. From the name, I assumed it referred to ...something... about how well stocked your own store is. But, no.

Live and learn!
 
Top