name: tinyworld-asset-editing description: Use when changing Tiny World Builder selection placement, freehand drawing, asset clipboard, cut/copy/paste/duplicate, saved templates, or Stamps panel navigation.
Tiny World Asset Editing
Use the existing board intent contract:
world[x][z]is intent andcellMeshes['x,z']is render state.- New asset edits must flow through
setCell(x, z, opts)or helpers that call it. - Selection state is exposed through
window.__tinyworldSelection; useworldCoords(),materialize(), andreplaceWorldCoords(). Do not parse rawselectedCellskeys in new code.
Placement rules:
- Bulk placement over a selection should call the normal
applyTool()path once per selected world coord with{ skipSelectionBulk: true }. This preserves tool variants, terrain overrides, ghost transforms, fence logic, model stamp settings, and existingsetCellside effects. - Bulk placement with a saved asset template should paste a one-shot template payload at each selected world coord and leave every placed template cell selected, not just the final paste.
- The selection properties
Actionsrow can expose anApply toolcommand; it should reuse the same bulk-placement helper rather than duplicating placement logic. - Shift-drag rectangle fill with a placement tool should leave the filled region selected so users can immediately adjust properties, move, copy, template, or apply another tool.
- Freehand drawing uses
dragMode === 'draw',drawVisitedCells,drawLastWorldCoord, andapplyDrawToolToHit(). Drawing should not repeatedly stack terrain, bridges, or same-kind objects while the pointer crosses the same cell. - Successful freehand placement strokes should replace the active selection with the drawn world coords so the Properties panel, copy/duplicate, and template flows are immediately available for the stroke.
- Freehand fence/wall/boundary drawing should de-dupe by world cell plus resolved fence side, so repeated strokes of the same side no-op but corner strokes can add another side to the tile.
- Fence, wall, and boundary are still
kind: 'fence'; usefenceSideplusfloors. Wall starts at level 4, boundary at level 5. Drawing a higher-level fence over an existing same-side fence should upgrade to that base level, not silently no-op. This also applies when the fence is stored as anextrasentry beside another occupant. - Interpolated freehand fence/wall/boundary cells should derive auto
fenceSidefrom each draw step direction, not from the final pointer edge copied onto every skipped cell. The fence tool is now a single icon with no variant flyout. The placement hologram previews the autofenceSidefrom hover (nearest tile edge), and repeat-clicking the same side levels it up1→2→3→4→5(wood → taller → wire → stone wall → steel boundary), so Wall/Boundary are reached by re-clicking rather than separate tool variants. The old fixed-direction variants (north,east,center-x, …) and the Edge/Wall/Boundary type variants were removed; the underlyingfenceSidevalues (n/e/s/w/center-x/center-z) andfloorslevels are still valid for stored/rendered cells.
Clipboard and templates:
- The asset clipboard shape is
{ version, origin, size, cells: [{ dx, dz, cell }] }. - Build clipboard cells with
cloneCellIntent()so terrain, terrain height, kind, floors, building type, fence side, extras, rotation,offsetX/Y/Z, appearance, andwaterFlowsurvive copy/paste and saved templates. - Template saves live under
tinyworld:asset-templates.v1inlocalStorage. Keep these world-intent templates separate from model stamps and voxel build stamps. - Saving a template should build a one-shot clipboard payload from the active selection/hover target and must not overwrite the user's explicit copy/cut clipboard.
- Pasting or duplicating a multi-cell clipboard should call
replaceWorldCoords()with placed cells so the pasted region stays selected for immediate follow-up edits. - Duplicating selected cells should use a one-shot payload and must not overwrite the user's explicit copy/cut clipboard.
- Moving selected cells between board tiles should reuse the clipboard payload shape internally but must not overwrite the user's explicit copy/cut clipboard.
- Select-tool dragging from inside an active selection should move the selected cells cell-by-cell through the same internal move path, preserving selection and leaving the user's explicit copy/cut clipboard untouched.
- Paste actions should target the hovered cell first, then fall back to the selected region origin. Clear stale hover when the pointer leaves the canvas so this fallback remains reachable. The latest-template shortcut should paste a one-shot template payload, not overwrite the user's explicit copy/cut clipboard.
- Saved asset templates should also surface in the Stamps panel under
Templates; selecting one should place from its one-shot template payload and preserve the user's explicit copy/cut clipboard. - Template cards in Stamps should provide a delete control that removes the
localStorageentry, refreshes Stamps counts/cards, and clears stale selected-template tool state. - Saved template names should summarize their copied cell contents so template cards stay readable and searchable without a separate naming dialog.
Delete/Backspaceshould clear the active selection or hovered cell without writing to the asset clipboard; keep this separate from cut/copy semantics.- The selection properties
Actionsrow should expose the same non-clipboard Delete path so mouse users can clear selected assets without using Cut. - Keyboard tile moves should keep selection behavior consistent with the property panel:
Shift+Arrowshifts selected cells through the internal move path without replacing the user's explicit clipboard, while arrows without an active selection keep camera/ghost behavior.
Selection properties:
- Keep selection property controls grouped by durable sections (
Edit,Transform,Appearance,Ground) so dense multi-selection actions stay scannable. - Selection properties must remain available when AI interfaces are disabled (
?ai=0/html.ai-disabled); hide prompt/chat controls, not the selected-object properties surface, and open selected objects directly to Properties so the property rows are visible. - Split dense edit actions into scannable rows when needed, but keep routing through durable row keys such as
selectionActionso behavior remains centralized. - Section changes should be presentation-only unless the edit contract changes; preserve existing row keys and route behavior through
applySelectionProperty(). - Property group tabs are presentation state only (
tinyworld:selection-props-active-tab.v1); keep the durable row keys/actions intact underneath tabbed Edit/Transform/Appearance/Ground views. - Collapsible property sections should persist in
tinyworld:selection-props-collapsed.v1; toggling sections must not remove or rename underlying row keys/actions. - Icon/round-button treatments for rotate, nudge, scale, and history controls should preserve full labels through
aria-label/title; the glyph is visual shorthand, not the action contract. - Use
currentValueplusaria-pressed/.activeon property chips when a selected value is uniform, and leave mixed selections unpressed. - Colour rows and preview quick chips should route through
bodyColor/topColorfor any supported built-in kind, not just buildings. ExpandapplyAppearanceToObject()material buckets when exposing new colour rows so the world render and selection preview actually change. - Colour rows should include a
Defaultoption that clears only the matchingbodyColor/topColoroverride while preserving materials, style, transform, and the other colour row. - Selected-object transform reset controls should clear rotation, offsets, object scale, and per-axis scale while preserving non-transform appearance fields like model/voxel stamp IDs, materials, colours, and style.
- Selected-object scale rows should also provide per-scale reset controls that clear only
objectScale,scaleX,scaleY, orscaleZ, preserving materials, colours, model IDs, and style. - Selected-object nudge controls should include a recenter path that clears only
offsetX/Y/Z, preserving rotation, scale, materials, colours, model IDs, and style. - Selected-object material scale controls should offer a reset path that clears only the matching texture-scale key and keeps the chosen texture/material, colours, model IDs, and style intact.
- The in-scene transform gizmo is constrained to selected object transforms: within-tile X/Z offset, lift, Y rotation, and object scale. It should update the same cell fields as the Properties panel and stay undoable as a single drag batch.
- Duplicate islands are board-level transforms, not selected object
transforms. The
new-islandtool should select/create an editable island board and route gizmo movement/rotation to the island group, while normal object transform rows continue to operate on selected cells only. - Selection overlays on duplicate islands must be drawn in island-local cell axes. When an island is rotated, transform both selected tile centers and edge-strip offsets through the island group instead of offsetting borders in world X/Z.
- The Layers panel is a read-only world hierarchy surface. It should select
cells through
window.__tinyworldSelection.replaceWorldCoords(), not by mutating selection internals, and should refresh fromtinyworld:selection-changed,tinyworld:world-changed, andtinyworld:grid-changedevents. - The radial root X and the Escape key must close by exiting sub-edit when
active, then clearing the active selection through
window.__tinyworldSelection.clear()/clearSelection(). That clear path must deselect cells, editable-island engines, and whole editable islands (selectEditableIsland(null)), not merely hide the radial DOM. - Sub-object editing is surfaced through
window.__tinyworldSubEdit, not a parallel inspector state. Layers should show part rows from__tinyworldSubEdit.hierarchy(), select them withselectPart(partKey), and refresh fromtinyworld:sub-selection-changed. The shared transform gizmo can target__tinyworldSubEdit.selectedGizmoTarget(); usemovePart(..., { snap: !shiftKey }),scalePart(), androtatePart()so keyboard, Properties, and in-scene handles stay consistent. - Sub-object edit eligibility is centralized in
isVoxelSubEditableKind(). Radial menu and Properties must use that helper instead of maintaining separate kind lists. When a supported built-in object enters sub-edit,renderCellObjectImpl()forces the voxel renderer for that active cell so rock/fence/crop/animal/micro voxel objects get real part keys. Entering edit should not auto-explode; explicit Explode remains a user action. A plain canvas click that misses a selectable part exits sub-edit and must not fall through to cell selection or placement on the same click. - Model stamps should expose All material / All mat scale controls, but Body/Top material controls should be limited to selected asset kinds with known Tiny World material buckets; mixed selections must not write part-material fields onto model stamps.
- Generated
voxel-build/ customParts objects are editable through the same Layers / Properties appearance rows as built-ins: All material, Body material, Top material, Body/Top colour, and matching material-scale resets. Keepmodel-stampexcluded from Body/Top material rows, but do not excludevoxel-build.
Stamps panel:
- Stamps navigation is client-side.
stampBuilderAllTools()builds model, voxel, and built-in stamp tools; filtering combines active category and search text. - Include terrain/landscape tools in Stamps alongside objects so the panel can replace toolbar hunting for grass/path/dirt/water/stone/lava/sand/snow placement.
- Include every normal placeable tool in Stamps, including small plant tools like
tuft; do not leave toolbar-only assets out of the searchable stamp library. - Keep Tiny World's voxel-build stamp library aligned with the standalone voxel builder concepts where practical, especially tree/garden/utility stamps that users expect under Stamps.
- Category counts should reflect the current search. Search is token-based: every whitespace-separated term must match the tool search text. Status text should reflect the number of shown stamps and the active category/search.
- Stamps search should support fast keyboard selection: Enter activates the first selectable visible stamp, ArrowDown focuses the first selectable card, and Escape clears a non-empty search before closing the panel.
- Stamps card thumbnail rendering should stay responsive: draw cheap fallback thumbnails immediately, cancel stale thumbnail queues on re-render, and build expensive 3D card thumbs in small requestAnimationFrame batches.
- The
Recentstamps category is derived fromtinyworld:stamp-builder-recent.v1and should use the samestampBuilderSelectionKey()values as selected-card state. Keep it ordered by most recent selection and remove deleted template keys. - Toolbar, flyout, and keyboard selections that correspond to stamp-builder tools should update
Recent; ignore Select, Erase, Auto, hidden tools, and other non-stamps so stale keys do not crowd out real stamps. - Model-stamp categories are inferred from labels, paths, formats, URLs, and sidecars. Do not add generator manifest fields unless a durable category contract is explicitly needed.
- Model-stamp OBJ/MTL support must preserve filenames with spaces in
mtllibandmap_Kdlines. VoxEdit-styleTr 1.000000should not make a textured model fully transparent; treat it as opaque unless addissolve value says otherwise. - Rigged GLTF/GLB model stamps need a skinned-mesh-aware clone path. Plain
clone(true)can leave skeletons tied to the cached source scene when multiple stamps are placed. - Preserve
gltf.animationsin the model-stamp asset cache. Runtime systems such as crowd character replacements use those clips after cloning the cached scene. - Drag/drop imports live in
43-drag-drop-import.js. Model files register throughwindow.__tinyworldRegisterDroppedModelStamps, including local sidecars and object URLs, while chat attachments exposewindow.__tinyworldAgentDropAttachmentsfor image/model prompt context. Canvas model drops must still place viasetCell()and keep selection sync throughwindow.__tinyworldSelection. - Chat-attached model imports must keep the exact dropped
modelStampId: the prompt context should use MUST-level wording, and generatedmodel-stampcells should be repaired to the attached id beforeapplyState()/applyStatePatch()when there is exactly one attached model. - Dropped model loader failures must surface through the drop status and
window.__tinyworldModelStampLoadState; do not leave the generic model-stamp placeholder as the only feedback. - Canvas model drops should wait for the dropped model to reach loader
readybefore callingsetCell(). If the GLB/GLTF fails, show the loader error and do not stamp the placeholder into the world. - Dropped model stamps must persist their source files outside the world JSON
(currently IndexedDB) and restore them into the model-stamp registry on boot.
Saved cells may keep only
appearance.modelStampId, but that id must resolve after reload and schedule a model-stamp refresh instead of staying on the generic placeholder.
Validation:
- Run the inline script syntax check,
npm test, andnpm run build. - Browser-check Stamps category plus search, selected bulk placement, draw wall/boundary behavior, copy/paste/templates, duplicate,
1/E,R/F, clear, perspective toggle, and console errors.
Layers dialog (redesigned)
32-layers-panel.js + #layers-panel. Styled like the blocks panel: a grab-cue
drag bar (no title), a block-style Layers / Properties tab bar, glass +
resize: both. The tree is island-grouped — collectIslands() builds a top-level
<details> per board (Home Island + each editableIslands entry) whose
children are that board's terrain cells -> objects/extras (world coords as the
cell id). Clicking a tree item selects its cell and switches to the Properties
tab, which relocates the shared #agent-selection-properties node (rendered
by module 28) into #layers-props-host; switching back to Layers / closing the
panel restores that node to the agent panel (selPropsHome). Keep the tree dense
(small summary padding, 14px branch indent, 1px gaps).
The old agent-panel Preview/Properties selection dialog has been retired: module
28 renders the shared properties node as a hidden staging element, while radial
More/Style/Move and multi-cell selection should open window.openLayersPropertiesPanel().
Because that staging element is hidden, updateSelectionPreview(target) must
clear the preview and avoid starting the preview WebGL requestAnimationFrame
loop unless the preview UI is genuinely visible again.
Canvas selection and Layers selection must stay visually synced. The tree should
derive selected rows from window.__tinyworldSelection.worldCoords(), include a
selected default grass tile even when it would otherwise be omitted as an empty
cell, highlight matching child object rows as well as the terrain parent, force
the selected branch open, and scroll the selected row into view when the panel is
open.