feat/grand-exchange #110
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/grand-exchange"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The setup panel is drawn entirely from varps this server owns. Script 773 registers an IF_SETONVARTRANSMIT on 1151, 563 and 1043 and redraws from them, so a control changes server state, writes a varp, and the screen follows -- nothing is held client-side and nothing is read back. varp 1151 the item varbit 4396 the quantity (varp 563, bits 0-30) varbit 4397 buy or sell (varp 563, bit 31) varbit 4398 price per item (varp 1043, bits 0-30) Which field is which came out of script 779, the one that *draws* them, and the first attempt did not: assigning meaning from the transmit trigger list put the item and the quantity the wrong way round, which renders as an item box that will not hold its item and a quantity in the thousands -- the item id, drawn where the count belongs. A trigger list says which varps matter, never what they mean. GET_VARP 1151 feeding OC_NAME is the line that settles it, because OC_NAME takes an object. An empty item slot is -1 and not 0. Obj 0 is a real definition called Dwarf remains, so a buy screen cleared the honest way opens showing a pile of bones. Both money fields are 31-bit varbits, so each is clamped below 2^31 - 1; an overflowing price would wrap negative and the panel would draw it. Percentage steps round towards a change, because five per cent of a one-coin guide price is zero and a button that visibly does nothing is worse than one that moves by the least it can. Choosing an item seeds quantity 1 and the cache's own value as the price, and Buy opens the search directly -- Choose item is for changing your mind, not for starting. Typed quantity and price ride the same integer-input path the bank and trade screens use, tagged :exchange. Verified live: Blue partyhat at 1,131 x 15, the client's own total agreeing at 16,965.The first table in this schema that is not a fact about a player. Every other one describes the account -- where it stands, what it wears, what it knows -- and is written from a World.Player snapshot when a session saves. An offer has no snapshot to ride: it changes on a tick when its owner may be logged out, it belongs to the world rather than to any session, and it must be durable at the moment it changes rather than at the next autosave. An offer the book has taken coins for and the database has never heard of is the one failure this feature cannot come back from. So Database.ExchangeWriter is a second writer with the same mailbox-is-the-queue reasoning as the one next door: the world is a single serialized sender, so receive order is write order. The tick may not write directly, because a synchronous round trip in the world process adds the database's latency to every player's frame, and a database that is merely slow would present as the world stalling. `placed` persists because it is a sequence and not a time. It decides which of two offers is resting, and therefore the price a fill happens at, so the order has to be total across a restart -- two offers placed on the same tick would share a timestamp and the tie would be broken differently on every boot. World.Exchanges.restore/1 seeds the counter past the highest stored value for that reason and no other. The two enumerated columns are mapped explicitly rather than through String.to_existing_atom/1, which is the obvious way to write it and the wrong one. That function raises unless the atom happens to be interned at the moment of the call, and the moment of this call is World.init/1 -- before most of the modules that would have interned :stable are loaded. The failure is a boot-time raise naming neither the column nor the table: the whole book loads as empty and the log says only "not an already existing atom". The explicit map also refuses a value outside the set, which turns a corrupt row into one skipped offer rather than a world that will not start. Only an account can hold an offer. The book is keyed by identity, which is either {:account, id} or {:name, canonical}, and the second is a player seated without a row -- tests, the bench, the admin panel. It is persisted as a no-op, the same answer every other path here gives.Confirm, abort, collect, the matching pass in the tick, and the sell-side pack. The screens have been working since the setup panel landed; this is everything behind them. Nothing here calls the world, because nothing in a session may. The three things a player can do to the book -- place, abort, collect -- leave as claims on the barrier report, the fifth tenant, and the world folds them after every player has taken their tick. The answer comes back as an op. That is the trade screens' design for the trade screens' reason: the world performing both halves means no session ever performs half of one. The escrow leaves at Confirm and not at the fill, so between the click and the fold there is no moment where the player holds the coins and the book expects them. The player's row is saved on the same breath, which is an ordering rather than an optimisation: the offer becomes durable about a tick later, and without the save the two facts land in the wrong order and a crash between them reloads a player who has the coins and an offer that also has them. Matching runs only on a tick where a claim arrived. The book changes only through claims and guide prices cannot move while the server is up, so a book nobody touched can produce no fill it would not already have produced. The cost on a quiet tick is one comparison. State went where the 32-key cliff allowed and both homes are right anyway: the claim in pending_world, the eight-slot mirror on World.Account, since an offer belongs to neither a session nor a seat. The market maker's floor is now two coins. It would otherwise sell a red partyhat to anyone bidding a coin, out of nothing -- this cache prices 6,563 items at 0, 4,356 at exactly 1 and 9,094 above, and a 1 is the cache declining to price something never sold in a shop. The floor catches the unpriced and not the valuable: a Santa hat is valued 160 here, so it can still be minted at 160, and closing that needs a real price table. Also fixed: the offer type was closed, and an Elixir map type is closed unless it says otherwise. A spec naming three of ten keys means "a map with three keys", which an offer is not, so the book could not legally call the contract at all. Dialyzer had been reporting it as invalid_contract and no_return on functions that were fine, never naming the type; the gate had been red for a release with every test passing. It is now clean. A live sitting then found six controls the cache never names, on a screen that renders identically whether or not the server answers them. The catch-all click log found all six, each as an unmapped-click line repeated while a player clicked again: * Offer did not start a sale. It is how a sale begins -- it claims the next free slot, opens the setup panel and names the item. * 465:23, the status panel's collect boxes, were unmapped. Script 816, invoked by 803 with its local 9. * Abort offer acted as View. The slot boxes are the one place on 465 that puts two ops on one child, and only a right-click menu can show it; everywhere else the child index is the whole message. * Collecting the last of a payout releases the slot, leaving the client drawing a setup panel for a slot the book no longer has -- with varp 1151 at 0, and obj 0 is a real definition called Dwarf remains. The panel can be invalidated by the world and not only by a click, so the viewing varbit is reset when that happens. * 465:6, the overview's Collect, was unmapped. Two ops on one component: to inventory at 1, to bank at 2. * 402:3 and 402:4, the Collection Box's Inventory and Bank, were unmapped. Here the destination is the component rather than the op, which is the same act addressed two ways on one screen. The destination rides the claim rather than session state, because the goods come back on a later tick and by then the click is long gone. Two log lines also lied and are corrected: the collect-all read "nothing this server maps" while succeeding, and a payout sent to the bank on purpose logged the overflow warning. A warning that fires on the ordinary path trains the reader to skip the one that matters. Left deliberately: 383 history, which is a subsystem rather than a binding -- the interface has no on_load at all, and an offer is deleted from the book when its last payout is collected, so there is nothing to show without a new persisted record.