Spatial Picking Spec — warehouse pick / put-away as a mobile walk through the model¶
Spec for the second op-log-native addon: a phone-first "walk the aisles" app — the warehouse rendered as a BIM-like 3D model, the pick route drawn through it, each bin confirmed by a QR scan, every pick/put-away a signed op on the same tenant ledger the ERP folds. Roadmap home: item 2 of the Migrate & Compare paper's roadmap. Sibling addon: POS Addon Spec (same rails, different room).
Status: SPEC. §2 lists what is already proven; everything else is the build. The same addon contract applies as the POS spec §1: pills + dictionary + process handlers + kernel ops, no new engine, no addon-private persistence.
1. The idea¶
A picker doesn't think in table rows; they think in places. This project already renders places —
buildings stream from a SQLite db into Three.js with fly-to, lens highlighting, and tap-picking on
mobile. And the ERP already knows warehouse places — M_Locator per M_Warehouse, on-hand per
locator. The addon joins the two:
pick list (M_Movement / M_InOut lines) the ERP truth
→ route over locators ordered walk (aisle/rack/bin)
→ 3D walk: fly-to + highlight the NEXT bin the BIM viewer verbs
→ at the bin: scan its QR BarcodeDetector (proven)
→ scan == expected locator? confirm qty one tap / qty stepper
→ signed op group: the movement line enacted kernel commitGroup
→ on-hand falls/rises via the qtyOnHand fold same spine as POS backflush
Put-away is the same walk with the sign reversed (receipt → destination locator).
2. Substrate inventory — what already exists¶
ERP side (full-width ad_seed.db + the proven engine):
| Asset | Evidence | Note |
|---|---|---|
M_Locator 11 rows, M_Warehouse, m_storageonhand 20 rows |
seed (PR #265) | ⚠ X/Y/Z are TEXT labels ("Store North"), not coordinates — see §S-1 |
M_Movement(323) / M_InOut(319) FSM walked |
ad_docfsm.js (W-MOVEMENT/W-MINOUT FSM) |
inout block: no VO on completed; movement lines carry m_locator_id → m_locatorto_id |
movementSign / qtyOnHand fold |
scripts/erp_engine.js (W-FOLD-QTYONHAND) |
on-hand = fold of movements; locator→warehouse map is load-bearing (proven in W-FOLD-REPLENISH) |
| signed op groups | kernel_ops.commitGroup (W-OPGROUP, verifyChain) |
all-or-none, idempotent gid, hash-chained |
| element↔ERP linkage pattern | BIMtoERP.md §A/§B (W-ERP-PROBE/EXPORT/FOLD) | GUID-keyed, data-gated chips, idempotent export |
Viewer side (bim-ootb/viewer):
| Capability | Where | Reuse |
|---|---|---|
| camera fly-to + pivot | streaming.js A.flyTo, precision-pivot lane |
fly the picker to the next bin |
| highlight / ghost depth model | navigate_find.js (_highlightGuids, room lens, Alt+X ghost) |
rest-of-warehouse ghosted 0.1, current rack solid, target bin bright (the FIND_LENS depth model verbatim) |
| mobile tap/long-press picking | picking.js (pointer guards, pinch-guard) |
tap a bin = same raycaster path |
| QR scanning — production code | scripts/system_explorer.js:910-930 (W-QR-INPUT, glassbowl) |
BarcodeDetector + getUserMedia({facingMode:environment}), rAF loop, honest unsupported-fallback — lift the pattern as a shared module |
| camera/GPS/share precedents | sitecam.js (4D capture), share sheet | permission UX, fallback idioms |
| mobile pill idioms | pill_builder.js registry, Lucide-only |
the addon's dock |
Compiler side (the warehouse model):
| Capability | Where | Reuse |
|---|---|---|
| recursive BOM compilation (building→floor→room→furniture) | BOMBasedCompilation.md, W-BOM-ENGINE / W-BUFFER | warehouse→aisle→rack→bin is the SAME recursion, one level deeper named differently |
| verb formulas TILE / ROUTE / CLUSTER | BOM engine | TILE bins along a shelf; ROUTE is literally the picker path primitive |
| geometry from library meshes (AABB primitives) | component_library.db | racks/bins are boxes — the cheapest possible geometry; no IFC needed |
3. The spec¶
§S-1 The warehouse model — compile it, don't survey it¶
M_Locator.X/Y/Z are text labels, so positions are compiled, not read: a small
warehouse.yaml recipe (EXTRACT: one line per aisle/rack/bin naming its M_Locator.Value)
compiles via the existing BOM recursion into a warehouse.db the viewer streams like any
building — aisles as rooms, racks as furniture, bins as TILEd children, each mesh stamped with
its m_locator_id as the element GUID (the BIMtoERP linkage key, reversed: here the ERP id
is the GUID).
- Witness W-WH-COMPILE:
§WH bins=<n> == m_locator rows mapped+ W-BUFFER space contract holds (bins fit racks fit aisles). §FALSIFIER: a recipe bin naming a locator absent fromm_locatormust fail the compile (no invented bins). - Render gate applies (no-cubes rule): distinct-vertex check before serving.
- Out of scope v1: scale drawings/real CAD import. The recipe is honest about being schematic — topology + walk order matter, millimetres don't.
§S-2 The pick list → route¶
- Source docs: drafted
M_Movement(transfer/pick) orM_InOut(receipt → put-away). Lines carry product, qty, from/to locator — already in the dictionary, already FSM-walked. - Route = ORDER BY the walk sequence (aisle/rack/bin from the recipe tree — the ROUTE verb's
order), not by line number. Pure function:
route(lines, tree) → [step]. - Witness W-WH-ROUTE: same lines + same tree ⇒ same route (deterministic); steps cover ALL lines exactly once. §FALSIFIER: a line whose locator is off-model must surface as an explicit "unroutable" step, never silently dropped.
- POS handoff (design 2026-06-12,
docs/POS_ADDON_SPEC.md §P-12): a POS sale commits anM_InOut(its shipment) into the same ledger the walk reads.draftPick(wh_walk.js) should gain an option to source the route from open POS-generatedM_InOut/M_Movementdocs, alongside the current replenishment draft. This is the "finish the sale → go pick it" fold — two lenses, one ledger, NO coupling. Additive §S-2 selector, not new machinery. Another device picking the same list = the multi-device version of the same fold. Open-docs only (engine note, Fable5 review 2026-06-12): the selector sourcesdocstatus IN ('DR','IP')— the WR cash-and-carry sale completes itsM_InOutINSIDE the sale group (W-POS-WR: nothing left to pick, on-hand already moved; routing it would double-move stock). The pickable sale is the deliver-later shape (plainSOO— seed doctype132 Standard Order— shipment stays DR; the walk's §S-4 scan-commit is what completes it and moves on-hand). That sale variant is engine glue, built engine-side first; seePOS_ADDON_SPEC.md §P-12engine note. STATUS 2026-06-12 — the ENGINE half is ✅ DONE (POS_GAP_CLOSE §G-1, W-POS-DELIVERLATER, bim-compiler 5bc4b389):pos_core.buildDeliverLaterGroup(order CO +M_InOutborn DR, policy verbatim from the doctype-132 row, invoice timing named fromInvoiceRule) +pos_core.completeShipmentOps(scan-commit completes the DR shipment by the PICKED qty; doctype-148IsPickQAConfirmREFUSES → theinout_confirm.jsW-WH-CONFIRM gate). The selector query itself is witnessed against a folded ledger (§S-2 selector open-pos-docs=[… DR], empties after the pick). Access-path hardening rode bim-ootb #280 (viewer sw v647): PWA-resume dead-link self-heal (§PWA_RESUME_CLEAR→ landing) and W-WH-LIVE-PAGES retargeted to the in-repo GH Pages db (OCI duplicate retired); the walk engine itself is byte-unchanged (W-WH-LIVE regression green).
§S-2b The walk-side selector (prompts/WH_POS_PICK_LANE.md, design 2026-06-13)¶
STATUS 2026-06-13 — ✅ LIVE (bim-ootb PR #283, erp sw v663 / viewer sw v648). The whole loop
is witnessed end-to-end on the served pages — W-WH-POS-PICK-LIVE PASS: a deliver-later sale made
on the live POS (§POS-DELIVERLATER … persisted=Y) is offered in the live walk
(§WH SRC pos-docs=1 [910032(oplog)]), short-picked (§WH PICK-COMPLETE inout=910032 CO
via=completeShipmentOps(120) picked=2/3 diffs=0 chainOk=Y), and written back so the reload selector
empties (§WH SRC pos-docs=0). W-WH-LIVE + W-POS-LIVE regressions byte-honest. One live-only bug
fixed in the train: wh_walk.js opened the IDB cache at version 1 (below scene.js's version 2
openCacheDB) → VersionError → the sidecar was never read; all three open sites now route through a
shared _openCacheDB() (the same kernel_ops.js §KRN_PERSIST_FIX idiom). Headless could not catch it
(in-memory sql.js, no scene.js IDB).
The "finish the sale → go pick it" fold, walk side. Witness W-WH-POS-PICK (headless) +
W-WH-POS-PICK-LIVE (scripts/poc_wh_pos_pick_live.js) +
live §-logs (§WH SRC pos-docs=N, §WH PICK-COMPLETE inout=… CO picked=…).
- The honest source seam (EXTRACTED, not assumed): the walk reads the STATIC
../erp/ad_seed.db(wh_walk.js ensureDeps), but a live POS sale exists only as SIGNED OPS in the ERP page's op log (window.ERP.opDb= the KanbanHost projection DB), persisted to IndexedDBbim_ootb_cache/ storedbs/ keyidmp_kanban_proj(kanban_host.jsIDB/STORE +idempiere.html _KPROJ). Today ONLY Kanban dispatch persists (KanbanHost.persistafter each ok drag); a POS commit stays in page memory (kernel_ops._persistToIdbis a no-op on ERP pages — noAPP.DB_URL). Therefore the deliver-later door must persist the op log at sale time (host passespersist=KanbanHost.persist(opDb, _KPROJ)intoPosLens.open) — otherwise the walk page cannot see the sale and any selector hit would be fake. - The selector = seed SQL + sidecar fold, merged: (a) the witnessed SQL over the static seed
(
SELECT … FROM m_inout io JOIN c_order o … WHERE io.docstatus IN ('DR','IP') AND o.c_pos_id IS NOT NULL, poc_pos_deliverlater.js) — the seeded-fixture path; (b) a PURE op-log foldopenPosDocsFromOps(opRows)(new verb-shaped helper inbuild/erp/wh_route.js, the §S-2 module; the viewer copy stays a UMD of it): C_OrderCREATE_DOCUMENTwithc_pos_id⇒ POS order; M_InOutCREATE_DOCUMENTwhosesource_idis such an order ⇒ its shipment; followingCREATE_LINE M_InOutLineops in the SAME gid are its lines (buildDoc emits no line→doc id — gid adjacency IS the link, extracted from the op shape); latestSET_STATUS M_InOutper id folds the status; open = born-DR with no CO/RE. The WR cash-and-carry sale self-filters (itsSET_STATUS M_InOut COis in-group — W-POS-WR). Cross-device sourcing stays the §P-5 relay/sync story — OUT OF SCOPE here, named. - Bin resolution (shipment lines carry NO locator): the pick bin per line = the
m_storageonhandrow holding the product (ORDER BY qtyonhand DESC, m_locator_id — deterministic), the same EXTRACT source the replenish draft rides. No storage row → the route's explicitunroutablestep (never dropped, never invented). - Per-step act on a shipment route = ANNOTATE only: scan-confirm commits ONE signed
ANNOTATE … note:'PICK_CONFIRM'group (the §S-3 exception-trail shape) — NO movement op, because the oracle puts the storage update AT completion (MInOut.completeIt storage below the gate; W-POS-DELIVERLATER "on-hand moves at the pick-COMPLETE"). The M± per-step enact stays the M_Movement route's shape only. - Completion branches by the SHIPMENT doctype row (dictionary, not code): plain (seed 120) →
POSCore.completeShipmentOps(inout, lines, dt, {pickedQtyOf})= UPDATE_LINE short-picks + SET_STATUS CO, ONE group. Confirm-demanding (148 IsPickQAConfirm / 147 IsShipConfirm) → it REFUSESconfirm-gated→inout_confirm.createConfirmationOps(spawn) →completeConfirmOps(confirmedqty = the walked PICKED qty) →completeInOutGatere-check → the bareSET_STATUS M_InOut CO(the below-the-gate act, W-WH-CONFIRM sequence verbatim). On-hand fold then moves by PICKED qty (C-, per product). Falsifiers: double-complete refused (FSM not-open) · non-target tap holds the step (existing) · WR sales NEVER appear in the selector. - Route-source choice UI: both sources non-empty (replenish draft is always draftable + open POS docs exist) → minimal chooser; otherwise walk the single source directly (no decision trees).
- Completion WRITE-BACK (one ledger, not a private log): the walk's completion groups (and the confirm groups on the gated path) are RE-COMMITTED into the sidecar blob under the SAME deterministic gids (commitGroup gid-idempotency makes the write-back replay-safe) and the blob is persisted back to the same IDB key — otherwise the shipment stays DR in the shared ledger and the selector would re-offer an already-picked doc on the next open (a double-pick door). Seed-source docs have no live ledger blob → write-back skips with a named log line (walk-local completion, honest). Same-device demo only; concurrent ERP-page writes racing the blob = the §P-5 sync story.
§S-3 The walk — viewer UX¶
- Mobile-first surface (a pill on the warehouse model's viewer page): the FIND-lens depth model drives focus — everything ghosted, the current rack solid, the target bin bright + camera flown to it; a strip shows step i/N, product, qty.
- Tap the lit bin (or the strip) → the scan screen. Long-press = skip-with-reason (logged as an op annotation, the exception trail).
- Witness W-WH-WALK:
§WH step=3/12 locator=50003 fly=done lit=1§-lines per step; falsifier: tapping a NON-target bin must not advance the step.
§S-4 Scan = the one clean act¶
- Each bin carries a printed QR encoding its
m_locator_id(+ a tenant salt). The app reuses the W-QR-INPUT pattern (BarcodeDetector; honest fallback = type the locator code — the same non-invent act, lower tech). - Scan match → qty confirm (default = line qty, stepper for short-pick) → one signed op group enacting the line: movement enacted (M± on the qty spine), short-pick = qty delta with the remainder left open on the doc. This is POSLens §1 verbatim: collapse input to a single authentic act at the place itself; everything downstream is a fold.
- Witness W-WH-SCAN: scan of the WRONG bin refuses (
§WH scan=50004 expected=50003 REFUSED); right bin commits a group whose replay movesqtyOnHandby exactly the confirmed qty; verifyChain ok. §FALSIFIER: hand-typed locator code goes through the same refuse/commit gate.
§S-5 Completion + the books¶
- Last step → doc Complete via
ad_docfsm.dispatchFor(323/319 walked sets); postings via the frozenderivePostingswhere the doc posts; on-hand now folds to the new truth — the SAME numbers the ERP, the POS replenishment, and the Posting-Preview see, because it is one ledger. - Witness W-WH-COMPLETE: after the walk,
qtyOnHandfold == per-locator expected deltas for every touched (product, locator); doc status CO; TB unchanged-or-balanced per doc class.
§S-6 Put-away + cycle-count (same walk, different verbs)¶
- Put-away: receipt lines route to destination locators (sign +). Cycle-count: the walk visits
bins and the act is "count what you see" →
M_Inventorylines (the W-FOLD lane already folds MInventory I± rule-consistently). Named for v2; the §S-1..§S-5 rails are identical.
4. Honest gaps (named)¶
- No coordinates in the ERP — the model is a compiled schematic (§S-1); claiming "BIM-accurate warehouse" would be invention. It is a navigable topology with boxes, which is what picking needs.
- BarcodeDetector support varies (no Safari/iOS guarantee) — the typed-code fallback is part of the spec, not an afterthought; no third-party QR wasm in v1.
- GardenWorld scale: 11 locators / 20 on-hand rows = demo aisle. Real tenants bring rows via the install lifecycle.
- Reservation semantics (
m_storagereservation) are read by the replenish fold but not yet enacted by picks — short-pick → reservation release is v2. - Offline walk rides the same sync-FSM story as POS §P-5 — out of v1 scope, never re-invent.
5. Done-when¶
Each §S is ✅ (witness + on-screen verify on a phone-sized viewport + deploy) or ⛔ with the one
blocking fact. The demo: compile the GardenWorld warehouse, draft a 3-line movement, walk it on a
phone, scan three printed QRs, and watch qtyOnHand + the ERP window agree to the unit.
6. Extension arc (proven rails — downhill from here)¶
The §S-1..§S-5 proof establishes the moat: spatial model + signed op-log + same ledger as the financial fold, zero-install browser. Everything below extends that without re-inventing the rails.
§S-7 Packing order¶
Route verb variant: packRoute(lines, products) sorts the walk by pack priority instead of aisle
sequence — heaviest/bulkiest first, fragile last, same locator-bin targeting.
Pack priority derives from m_product attributes already in the seed (weight, m_product_category
flags, or a packing_order attribute injected at compile time).
The strip UI, scan gate, and op-group are identical to §S-3..§S-5 — only the sort key changes.
Architecture cost: one new pure verb. No new screen, no new kernel path.
§S-8 Special handling overlays¶
The ANNOTATE verb (§S-3 long-press skip trail) already writes exception annotations to the op-log.
Extend it inward: before the scan prompt, read hazmat / fragile / two-person flags from the current
step's m_product row and render a visual overlay on the bin — a flashing colour, an icon badge, a
text chip ("Fragile — two-person lift").
The depth model (ghost/rack/bin bright) is already the animation system; the overlay is a new
material pass on the bin mesh driven by product metadata, not new UI scaffolding.
Architecture cost: metadata read + one material variant. Same fly-to, same scan gate.
§S-9 Autonomous agent (forklift / robot)¶
The route verb already outputs the walk sequence as pure data. The QR-scan callback is one
implementation of "confirm this bin" — replace it with any other signal:
- RFID reader → onTagRead(locatorId) instead of onScanResult(locatorId)
- Robot position API → poll robot.currentLocator() and auto-confirm when reached
- Conveyor sensor → event stream
The commitGroup call, the op-log entry, and the ledger reconciliation are unchanged.
The virtual walk highlight (fly-to + bright bin) becomes a live mirror of the physical agent's
position — the same rAF loop, just driven by telemetry instead of a human tap.
Architecture cost: swap the scan callback. Everything downstream is untouched.
The hardware integration contract (positioning system, comms protocol) is always custom — that is
the real work. The ERP side is already done.
§S-9a Robot bridge stub (WH_ROBOTICS_LANE.md)¶
window.WHWalk.robot is the meet-in-the-middle seam. The ERP side publishes; the robot
controller subscribes and pushes confirmations back. A session card
(prompts/WH_ROBOTICS_LANE.md) specifies the implementation lane.
ERP side exposes (stub, not yet wired):
window.WHWalk.robot = {
// Read — robot controller polls or subscribes
currentStep: function () {
// returns { locatorId, locatorGuid, product, qty, xyz: {x,y,z}|null }
// xyz is null until a metric IFC survey provides real coordinates (honest gap)
},
stepCount: function () { return { total: W.steps.length, done: doneCount(), remaining: … }; },
// Write — robot controller calls on arrival / pick-complete
confirm: function (locatorId) {
WHWalk.scanInput(locatorId, 'robot'); // same gate as QR — wrong bin still refuses
},
skip: function (reason) {
WHWalk.skipStep('robot-skip: ' + reason);
},
// Optional telemetry — drives the 3D mirror (fly-to replaced by live position)
telemetry: function (x, y, z, locatorId) {
// update robot avatar position in scene; highlight current bin if locatorId matches step target
}
};
Robot controller side must provide:
- Navigation to an (x,y,z) or a named locator — the walk supplies both
- A pick-completion event → calls WHWalk.robot.confirm(locatorId)
- Optionally: streaming position → WHWalk.robot.telemetry(x,y,z,locatorId) for the 3D mirror
?mode=robot URL param (stub): suppresses the human scan screen and "Confirm bin" button;
the strip shows robot telemetry instead. Human can still override via confirmHere().
§S-9b NVIDIA Cosmos — what it is and where it fits¶
What Cosmos is (open source, Apache-2.0, github.com/nvidia-cosmos): Cosmos is NVIDIA's world-foundation-model suite for physical AI. Its core capability is predicting the future visual state of the world given a current frame and a robot action — used to generate synthetic training data at scale without physical hardware.
Key open models:
- Cosmos-1.0-Diffusion-7B/14B — video world model (generates realistic robot-POV sequences)
- Cosmos-1.0-Tokenizer — tokenises video frames to/from a compact latent space
- Cosmos-1.0-Predict — causal video prediction (frame N+1…N+k from frame N + action)
- Runs via NVIDIA Isaac Sim / Isaac Lab for physics-backed simulation
Where it fits in the WH walk:
| What we have | What Cosmos adds |
|---|---|
| 3D compiled warehouse model (GUIDs, locator positions, bin geometry) | Synthetic camera/sensor data from that model via IsaacSim |
| Route sequence (ordered bin steps, §S-2) | Robot navigation policy trained on that sequence |
WHWalk.robot.confirm(id) stub |
Robot calling confirm after physical navigation to the bin |
| Schematic coordinates (text labels, not metric) | Metric once real IFC survey feeds the compiler |
The honest gap: Cosmos trains policies (what action to take next) and generates synthetic data (what the camera would see at each step). It does NOT provide: a physical robot, motor drivers, a SLAM positioning system, or the metric IFC survey that makes coordinates real. Those are always hardware-specific.
The cheapest useful experiment:
1. Export the compiled warehouse model to USD or glTF (the viewer geometry is already WebGL-renderable)
2. Load it into Isaac Sim (free) — Cosmos-world-model can then generate camera sequences along bin routes
3. Use those sequences to train a simple navigation policy (bin A → bin B via Cosmos-Predict)
4. That policy's output = a locatorId sequence → feeds WHWalk.robot.confirm(id) in the stub
Meeting halfway: the robot implementer brings the Isaac Sim setup + trained policy. The ERP side supplies: the route sequence, the confirm API, the 3D model export, and the ledger reconciliation. Neither side needs to know the other's internals.
See prompts/WH_ROBOTICS_LANE.md for the implementation card.
§S-10 AR overlay via Walk + SiteCam¶
The viewer already ships sitecam.js — the device camera feed composited under the 3D scene.
The Walk lens (§S-3) runs inside the same viewer. Combining them:
1. Open the warehouse db in the viewer with the SiteCam layer active
2. The virtual bins overlay on the live camera feed, aligned to the compiled spatial topology
3. The Walk pill drives the same fly-to + highlight — the highlight appears on the physical aisle
as an AR overlay rather than on a dark background
No new architecture: sitecam.js + wh_walk.js + the existing A.HOME_URL nav chain already in
production (sw v654). The alignment accuracy is topology-based (compiled locator positions), not
sensor-fused — sufficient for guided picking, honest about the gap.
Architecture cost: wire SiteCam layer active when ?mode=ar param is present. One init flag.
WebXR sensor-fused AR = future footnote when device APIs mature and a real IFC building survey
provides metric coordinates.
§S-11 Bin share deep link (Walk-to-Snag)¶
Ask: can a warehouse worker share "look at this bin" with a colleague the same way the BIM viewer shares a picked element or a clash deep link?
How it works (build on existing rails):
share.js already captures ?pick=<guid> for element picks and ?cam=/?tgt= for camera;
on load the viewer restores the pick highlight and flies the camera. The WH Walk equivalent is
?wh_locator=<m_locator_id> — the bin's locator ID (which IS its element GUID in the compiled
db, §S-1 stamp).
- Share (in the Walk strip): add a share icon button to the strip; on tap build
viewer.html?db=../buildings/warehouse_gardenworld.db&wh_locator=<current step locator id>and callnavigator.share/ clipboard fallback. Log§WH-STEP-SHARE locator=<id>. - Receive (viewer load):
config.jsreadsA.WH_LOCATOR = _params.get('wh_locator') || null.wh_walk.jsgate poll: ifA.WH_LOCATORis set after gate=on, callfocusStepfor that locator in view-only mode (no draftM_Movementcreated — just fly-to + bright highlight + ghost rest). Log§WH-SNAG locator=<id> view=only. - No login required for the recipient — it is the viewer URL, the same zero-install surface as any building share. The bin highlight works off the compiled db geometry alone.
Architecture cost: config.js +1 param read · wh_walk.js +view-only branch on gate+param ·
strip +share button. Same focusStep, same depth model. No new kernel path, no auth gate.
§S-3b Walk mode engages + ⌂ home — ✅ SHIPPED 2026-06-12 (sw v645, W-WH-WALKMODE)¶
User direction: the walk should engage a distinct mode, not sit as a strip over BIM. Built
(presentation only, engine untouched): open() hides #mobile-pill (the BIM construction chrome),
close() restores it verbatim — §MODE walk-on/off. A ⌂ home button on the walk strip navigates to
A.HOME_URL (?home=, e.g. iDempiere) or ../index.html (bubbles landing) — single-hop escape,
because browser-back walks the whole nav stack down to the landing.
§S-4b Desktop / manual confirm — ✅ SHIPPED 2026-06-12 (sw v645, W-WH-WALKMODE) — was OBSERVED GAP¶
Observed (desktop F12): mid-walk (§WH step=1/3 locator=101 fly=done lit=1) a mouse click on
the LIT bin resolved to a guid-less mesh — §PICK hits=12 chosen=0 … §PICK no guid for mesh.id=55
(top hit op=0.6 = the highlight overlay) — so the scan screen never opened. The phone-witnessed tap
path worked (W-WH-LIVE L2); on desktop the bin/rack OVERLAY mesh intercepts the raycast and carries
no GUID.
Spec:
1. Overlay must not eat the pick: the walk's overlay meshes get raycast = noop (or carry the
target locator id as GUID so the hit resolves to the bin). Either way a click on the lit bin
MUST reach WHWalk.onPick. Witness line: §WH TAP via=overlay|mesh bin=<id>.
2. Desktop demo confirm (user-dictated): no camera on a desktop — double-click (or long-press)
the lit bin = confirm, equivalent to a matching scan, qty confirm still shown (the one clean
act stays). Single click keeps opening the scan/typed screen. Logged distinctly:
§WH scan=<id> via=dblclick MATCH — the op group is IDENTICAL to the QR path (same gate, §S-4).
Wrong bin double-clicked → same REFUSE line. This is a DEMO affordance, not a bypass: same
refuse/commit gate, same signed group.
- Witness W-WH-DESKTOP: scripted desktop run — click lit bin opens scan; dblclick lit bin
commits step; dblclick WRONG bin refused. §FALSIFIER: overlay click must never advance the step
without the gate line.
§S-12 ERP info drawer on pick (browse mode) — OBSERVED GAP 2026-06-12¶
Observed (live test, F12 + screenshot): with §WH PILL gate=on, tapping a rack
(§PICK IfcFurnishingElement "Store Central rack") shows the GENERIC IFC info card only —
Class/Name/GUID/Storey/Material. No M_Locator, no on-hand, no M_Movement context. The §S-1
linkage (bin GUID == m_locator_id) exists in the db but is only read inside the Walk lens;
the browse-mode pick path never touches the ERP side.
Spec — data-gated ERP chips appended to the info card (BIMtoERP §A Check-ERP drawer pattern, reversed: here the GUID is the ERP id):
- Trigger:
ELEMENT_PICKwhileWHWalkgate=on (same gate, no second probe). Lazy-load the seed via the existingwh_walk.js ensureDeps(ad_seed.db fetch + ERPEngine UMDs — already written, share it, don't duplicate). - Bin pick (numeric GUID =
m_locator_id): chips show locatorValue+ warehouse name · on-hand per product = theERPEngine.qtyOnHandfold (seed + op-log — the §S-5 truth, never a rawm_storageonhandread) · openM_Movement/M_InOutlines touching this locator (from/to) ·m_replenish.level_minif present. - Rack / aisle pick (non-numeric GUID
RACK_*/AISLE_*): aggregate of child bins from them_bom_linetree —n bins · n products on hand · n open lines; tap a chip → drill to the bin list. (The observed test picked a RACK — this case is first-class, not an edge.) - Honest-empty: a bin with nothing on hand shows
on-hand: —(zero rows), never invented rows. - Witness W-WH-INFO:
§WH-INFO guid=<g> locator=<Value> onhand=<n> openlines=<n>per pick. §FALSIFIER 1: gate=off building → ZERO chips added (code inert, generic card byte-identical). §FALSIFIER 2: locator with no movement lines showsopenlines=0, not a fabricated list.
Architecture cost: one render hook on the pick card + reuse of ensureDeps + two pure
queries. No new kernel path, no write.
§S-13 WH context layer — ADDITIVE, never subtractive (user direction 2026-06-12)¶
Direction (user, live test): the warehouse viewer retaining ALL the BIM goodies — Find panel, Fly, Shadow earth, Night, x-ray — is a feature, not a bug. Do NOT strip or profile the pill bar. The BIM capabilities are camera/render machinery, orthogonal to ERP context; the WH context is a read-only overlay family on the same GUID↔locator key, added beside them.
Everything load-bearing already exists: §S-1 linkage in the db · wh_walk.js ensureDeps (lazy
seed + engine) · ERPEngine.qtyOnHand fold · the depth/palette highlight machinery · the Walk
pill's data-gate (non-WH buildings stay byte-identical). What remains is presentation wiring:
- §S-13a Find panel ERP facet — Find already builds storey/discipline/type trees; when
gate=on, add a Products branch: product → bins holding it (qtyOnHand fold > 0) → tap =
the existing fly-to + highlight (room-lens behavior verbatim). The Find panel BECOMES the
spatial stock browser — same UX the user already knows. Witness W-WH-FIND:
§WH-FIND facet=products rows=<n> bins=<n>; §FALSIFIER: gate=off → no Products branch, Find tree byte-identical. - §S-13b Stock lens pill — ONE new data-gated pill (sibling of Walk, same gate): color
bins by on-hand level via the palette/SC-coloring idiom (e.g. empty=ghost · low=amber ·
ok=green, thresholds from
m_replenish.level_minwhere present). Composes with Night/ Shadow/Fly — layers, not modes. Witness W-WH-STOCK:§WH-STOCK bins=<n> colored=<n> low=<n>; §FALSIFIER: a bin with no on-hand rows renders ghost, never a colored invention. - §S-13c Movement lens (v2) — open
M_Movementlines drawn as from→to arrows over the floor (the measure/grid overlay line-drawing idiom); tap arrow → its doc / step into the Walk. Named v2: needs arrow geometry + doc drill, the only piece without a 1:1 existing idiom.
All three are read-only folds — no new kernel path, no write, no fork of any BIM module. Sequencing: §S-12 chips first (its queries feed 13a/13b), then 13b (small), then 13a, 13c v2.
Architecture cost: 13a = one facet builder on the existing Find tree · 13b = one pill + one material pass · 13c = v2. Zero behavior change for non-WH buildings (same gate).
What a real IFC survey adds¶
The current model is a compiled schematic (§S-1 honest gap — X/Y/Z are text labels, not metric
coordinates). A real warehouse survey via phone LiDAR → IFC → the existing compiler pipeline
produces a metric db — warehouse_real.db loads into the viewer unchanged. The walk, AR overlay,
robot path, and packing route all inherit metric precision automatically. This is a content
production problem, not an architecture problem; the code path is identical.