Dicey Table

The Transform Rail

The transform rail is the narrow icon strip pinned to the far left of the workspace (<nav className="em-rail" role="toolbar" aria-label="Transform tools">). It is 46 pixels wide, it does not resize, and it holds the tools that decide what a click in the viewport does.

The transform rail showing Select, Move, Rotate, Scale, Snap points, Frame selection, and Exit buttons stacked vertically

Reading top to bottom: four transform tools, a divider, the snap-point tool, a divider, Frame selection, then — after a flexible spacer that pushes it to the bottom — Exit.

The four transform tools#

Button Shortcut Gizmo mode Icon
Select Q select arrow_selector_tool
Move W move open_with
Rotate E rotate cached
Scale R scale open_in_full

Move is the default. transformGizmoMode initializes to "move", not "select" — the first thing you do in a new scene is usually place something.

The active tool is shown by the active class and aria-pressed. Clicking a tool first calls exitSnapTool(), so picking up a transform tool always cancels snap-point authoring — you can never be in two modes at once.

The keyboard shortcuts are bound in the shell's window keydown handler and are ignored while you are typing: the handler bails out when the event target is an input, textarea or contentEditable element, which includes the Monaco script editor. No modifier may be held.

What the tools write#

The rail itself writes nothing to the scene document. It sets transformGizmoMode, which is handed to the runtime as transformGizmoState={{ enabled: !snapTool, mode, space }}.

Dragging the resulting gizmo is what writes: the runtime applies a transform intent and the shell mirrors the new position / rotation / scale onto the selected entity in the scene draft. The same values appear live in the Inspector's ENTITY section.

With a multi-selection the gizmo attaches to every unlocked selection root at once — Move moves them together, Rotate orbits them about their shared centre, and Scale follows the SELECTION (N) panel's Scale gizmo setting, spreading on the table plane only — and each moved entity is written the same way. See Multi-select.

space comes from the World / Local pair on the viewport toolbar, not from this rail.

Gizmo behavior, axis handles and snapping are covered in Transform and gizmos.

Snap points#

The fifth button (control_point_duplicate, tooltip "Snap points") opens a flyout of a visibility toggle plus five sub-tools. It is a toggle in both directions: if the flyout is open or a sub-tool is armed, clicking it calls exitSnapTool() and everything unwinds.

Show radii#

The first entry in the flyout is not a sub-tool. Show radii draws every snap point's marker and a translucent disc the size of its snap radius — the catchment a dropped piece is pulled into.

It is deliberately independent of the sub-tools: it stays on after you disarm one, so the snap zones remain visible while you place and drag pieces, which is when you actually need to see them. Arming any sub-tool shows the markers regardless.

The disc is drawn from the same snapRadius the drop rule reads. There is one radius per point and no display-only copy of it, so what you see is what the table snaps to.

The snap-points flyout open with Place, Move, Delete, and Grid sub-tools

Sub-tool What clicking the table does
Place Creates one snap point at the clicked position.
Move Drags an existing snap point to a new position.
Delete Removes the snap point you click.
Grid Stamps a whole configured grid of snap points, centred on the click.
Label Selects the snap point you click so you can name it — or names a whole grid at once.

Clicking the sub-tool that is already active toggles it off (activateSnapSubTool).

While any sub-tool is armed, the transform gizmo is detached (enabled: !snapTool) so it cannot intercept authoring clicks, and the runtime renders the snap-authoring rays.

The Grid sub-tool#

Choosing Grid reveals four numeric fields inside the flyout, plus the hint "Click the table to place the grid."

Field Range Step
Rows 1–20 1
Columns 1–20 1
Spacing X 0.05–5 0.05
Spacing Z 0.05–5 0.05

Spacing is in world units (feet).

While Grid is armed, a ghost of the configured layout follows the cursor, so you can see the footprint before committing.

A placed grid stays a grid#

Clicking the table records an authored grid and generates its points from it. The grid itself is saved with the scene, so it stays editable forever after: its row is selected for you the moment you place it, and it lives in the Hierarchy under Snap Points (N) with its generated points nested inside it.

From there you can change its rows, columns, spacing, rotation and radius in the Inspector, or drag it with the transform gizmos — move repositions the block, rotate yaws it, and scale changes the two spacings. Scale never adds or removes a cell; the counts are typed, so a drag cannot quietly change how many points the grid generates.

Changing any of those regenerates the grid's points. Cell ids are stable, so growing a 4×4 into a 5×5 keeps the sixteen points it already had — including any names you gave them.

Before this, a stamped grid was an ephemeral batch edited through a floating HUD and forgotten when the tool closed: reopening the scene left you with N anonymous points that could never be re-spaced or deleted as a block. Scenes saved that way still load, and their points are simply individual ones.

The Label sub-tool#

Every snap point has a label, and every one starts as Snap point. Labels are what a table script reads to name a placeworld.getSnapPointAt is how a move log prints "Matt moved White Pawn from e2 to e4" instead of two sets of coordinates. The Label sub-tool names them two ways.

One at a time. Click a snap point: it is ringed in the viewport and a Label field opens in the flyout. Use this for layouts with no rows and columns — a Monopoly track, a discard pile, a single named slot. Labels are capped at 80 characters.

A whole grid at once. Under Name a whole grid, the flyout works out whether the scene's snap points form one regular grid, and if they do, offers to name them all:

Scheme Row 0, column 0 Example
Chess (a1–h8) a1 files are letters along the columns, ranks are numbers along the rows
Rows × columns (A1) A1 spreadsheet order: the row is lettered, the column numbered
Numbered (1–N) 1 reading order, left to right, then down

The preview is a plan view from above, far edge at the top — the same way round as the default camera, so a label in the preview's top-left corner lands on the top-left square on screen. Three toggles move the naming until it sits where you want it:

  • Flip rows — row 1 moves to the other edge.
  • Flip columns — column 1 moves to the other side.
  • Swap — the scheme's rows and columns trade places, for a board whose files run the other way.

For chess, set them so a1 lands under White's queen-side rook. Name all N writes every label in one step, and a single Ctrl+Z undoes it.

It works on a grid you placed by hand as well as one stamped by the Grid sub-tool, and it copes with a grid rotated by the grid HUD and with points nudged slightly off their line. If the points are not one regular grid — one missing, an extra one, two separate grids — it says so rather than guessing, and you name them one at a time instead. A plausible-looking wrong name on every square would be worse than none.

Lift N onto table#

When some of the scene's snap points sit under the current play surface, the flyout shows one more button: Lift N onto table, where N is how many.

This is a repair for scenes that changed tables before a table change carried snap points along (see Assigning a Room or Table). A snap point stores an absolute height, so on a taller table those points are drawn inside the tabletop, where nothing can reach them.

It only counts a point that is more than 0.1 ft below the surface and inside the table's footprint, so a marker you deliberately placed on the floor or out in the room is left alone. Pressing it sets each of those points to 0.02 ft above the surface, the height the Place tool uses. Only the height changes, and one Ctrl+Z undoes it. The button disappears once there is nothing left to lift.

Escape unwinds one layer at a time#

Escape in the shell walks back through snap authoring in this order, doing exactly one step per press:

  1. Finish the active grid batch.
  2. Disarm the active sub-tool (and its ghost).
  3. Close the flyout.
  4. Only then, clear the scene selection.

What snap authoring writes#

Every snap sub-tool writes to sceneDraft.snapPoints:

Action Effect on the document
Place Appends one new snap point.
Move Rewrites that point's position.
Delete Removes that point.
Grid place Appends every point in the grid in one go.
Grid rotate / scale Rewrites position and rotationY on every point in the batch.
Lift N onto table Rewrites position.y on every sunk point.

All of it is draft state, so it is covered by undo and by Save Draft.

Frame selection#

The center_focus_strong button, labeled "Frame selection (F)".

Its handler calls selectObject(selectedSceneObjectId) on the runtime, which re-asserts the selection: the selection glow is refreshed and the transform gizmo is re-attached to the selected entity.

The F key and View ▸ Frame Selection in the menu bar run the same handler.

⚠ The camera does not move. TabletopRuntime.selectObject() updates selection state, glow and gizmo only; there is no camera framing behind this button today. The Hierarchy context menu's Focus item behaves the same way, for the same reason. Treat it as "re-select and re-attach the gizmo" until that is fixed.

Exit#

The last button (logout, "Exit edit mode") sits below a flexible spacer at the foot of the rail. It calls the same handler as File ▸ Exit Editor — see The menu bar.