You know classic Diku shop tables / sedit. Neo has no sedit. A shop is a workspace prototype keyed by an existing mobile VID, edited with @shop …, reviewed with @examine shop, then committed with @save shop.
Hub: Creation Law. Build the keeper mobile first: Mobiles. Place it: Zones. Player-facing buy/list/auction flow: Shopkeepers.
The mobile is the body players talk to. The shop row (H2 table shopkeeper) is the trade config: home room, produce list, item types traded, hours, and keeper messages. When a mobile has Acts.SHOPKEEPER, runtime looks up ShopkeeperDB.at(mobile.vid()).
So the build order is: mobile (with SHOPKEEPER) → shop workspace on that same VID → zone place → zone reset. Creating @new shop before the mobile exists fails. Forgetting the Acts flag leaves a shop row that never handles list / buy.
npc prototype already @saved (same VID you will use for the shop)npc_stats: @npc_stats flags acts shopkeeper (and usually sentinel so the keeper stays put)@shop roomproduces (optional slots — up to 10)1. Confirm the mobile (build it if needed — Mobiles):
> @load npc_stats 1210 Okay. > @npc_stats flags acts shopkeeper Okay. > @npc_stats flags acts sentinel Okay. > @examine npc_stats > @save npc_stats Okay. >
2. Open the shop draft — VID is the mobile VID (must be in your mobile range; mobile must exist; no second shop draft while one is open):
> @new shop 1210 Okay. >
Echoes you will see on failure (exact Immortality strings):
A new shop entry already exists in your buffer. — @examine shop then @save shop or @clear shopA shop already exists for that <mobile-vid>.No mobile with vid <n> exists.That vid is not in your range.@new show <mobile-vid> (typo for @new shop).
3. Home room (why: Shopkeeper.check refuses @save when room is 0 — the room VID must already exist in RoomDB):
> @shop room 1200 Okay. >
4. What the keeper trades (ItemType flags via @shop trades; prefix ! to clear when the modifier supports it). At least one type is required to save:
> @shop trades weapon Okay. > @shop trades armor Okay. >
Bare @shop trades reprints @shop trades (!)<flag> plus the ItemType list from code.
5. Produce list (optional but usual — stock the keeper lists / sells at a fixed cost). Positions 1–10 (Shopkeeper.MAX_PRODUCES). Cost must be ≥ 1 gold for any filled slot or @save returns producing vid:… for 0gc:
> @shop produces 1 3100 500 Okay. > @shop produces 2 3101 50 Okay. >
Syntax: @shop produces <pos> <item-vid> <cost>. The produce setter does not verify the item prototype exists at edit time — @examine shows ItemDB.proper when the item is known.
6. Hours — hours1 is required to save (open1 must be non-zero). Both start and finish for hours1 must be positive integers, and start must be strictly less than finish. hours2 is optional and may only be set after hours1:
> @shop hours1 8 12 Okay. > @shop hours2 14 20 Okay. >
7. Keeper messages (one-line writers — all six are required by Shopkeeper.check before @save):
> @shop keeper_no_item_msg I do not have that item. > @shop customer_no_item_msg You do not seem to have that. > @shop customer_no_cash_msg You cannot afford that. > @shop keeper_no_buy_msg I do not buy that sort of thing. > @shop keeper_buy_msg That will be %d coins. > @shop keeper_sell_msg Sold. >
keeper_buy_msg is passed through String.format(…, price) on several buy paths — include a %d where you want the gold amount echoed (same idea as the default string in S.keeperBuyMsg).
8. Review and commit:
> @examine shop --- Shop --- MOBILE : 1210 ROOM : 1200 HOURS1 : 8 to 12 HOURS2 : 14 to 20 --- produces --- ... --- trades --- ... --- keeper has no item --- ... > @save shop Okay. >
If @save returns a short error from Shopkeeper.check, fix the field and save again — the draft stays open until save succeeds or you @clear shop. Exact check strings include: no room is set; no item types are set; no customerNoItemMsg set (also returned when keeper_no_item_msg is missing — code quirk); no customerNoCashMsg set; no keeperNoBuyMsg set; no keeperBuyMsg set; no keeperSellMsg set; open time must be set; producing vid:<n> for 0gc.
Why: the shop row alone does not spawn the mobile. Zone reset loads the npc into a room; players then use list / buy / auction / … against that keeper (Shopkeepers).
> @load zone 1299 Okay. > @zone npc 1210 1200 Okay. > @examine zone > @save zone Okay. > zone reset Okay. >
From ModifySocial.cmdCrShop (real subcommands only):
room <room-vid> — home / shop room (room must exist)produces <pos> <item-vid> <cost> — produce slot 1–10trades (!)<ItemType> — types the keeper will handlekeeper_no_item_msg — one-line writercustomer_no_item_msg — one-line writercustomer_no_cash_msg — one-line writerkeeper_no_buy_msg — one-line writerkeeper_buy_msg — one-line writer (often with %d)keeper_sell_msg — one-line writerhours1 <start> <finish> — required windowhours2 <start> <finish> — optional second window
Workspace companions (same pattern as rooms/items): @new shop, @examine shop, @clear shop, @save shop, @load shop <vid>, @destroy shop <vid>.
> @load shop 1210 Okay. > @shop produces 1 3100 450 Okay. > @examine shop > @save shop Okay. >
You never enter a numbered shop editor. There is no sedit. You open a shop workspace on a mobile VID, set fields with @shop, @examine, @save. Trade behavior also needs Acts.SHOPKEEPER on the mobile and a zone placement.
@new shop before the mobile prototype existsflags acts shopkeeper@new shop while a shop draft is still open@save shop without room, trades, hours1, or all six messages@zone npc + zone resetredit-style menus — workspace onlyDocumented as facts from the Java tree (not guesses):
createNewShop usage prints @new show <mobile-vid>; bad produces args print @new shop produces …; deleteShop usage prints @delete shop while the verb is @destroy; @destroy's usage line omits shop even though the handler accepts it.Shopkeeper.check: when keeper_no_item_msg is missing it still returns no customerNoItemMsg set (copy/paste quirk).hours2 validation message says “ahead of the open1 finish-hour” but the comparison uses open1() (the start).MobileScript): trades, produces/cost, keeper_no_item_msg, keeper_no_buy_msg, and keeper_buy_msg are used. room, hours1/hours2, customer_no_item_msg, customer_no_cash_msg, and keeper_sell_msg are required to save and stored, but no call sites were found that gate trading on hours/room or speak those three messages — treat as save requirements / future hooks unless you prove otherwise in a later code pass.