tinyworld-asset-editing
GitHub规范 Tiny World Builder 中资产编辑、绘制及生成操作的交互与状态管理,强调预览优先、意图与渲染分离及安全约束。
Trigger Scenarios
Install
npx skills add jasonkneen/tiny-world-builder --skill tinyworld-asset-editing -g -y
SKILL.md
Frontmatter
{
"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. -
Select-tool dragging of an already-selected cell/object should be preview-first: clone the current rendered object/extras into a separate hologram group, update only that virtual offset during pointermove, and commit the real cut/paste once on pointerup. Do not call
shiftSelectedCellIntent()repeatedly from pointermove, because intermediate cut/paste steps can wipe cells along the drag path. -
The dedicated Build Brush dock is intentionally top-centered under the world selector/top chrome, not near the bottom toolbar, so users can find it before painting.
-
Brush shape modes should keep the same safety contract: Single may use the existing live freehand draw path, but Line/Rectangle/Fill/Scatter must render a holographic footprint first and commit through
applyToolToCell()only on pointer-up. Fill previews a bounded flood fill of matching connected cells; Scatter previews a deterministic organic subset inside the dragged rectangle. Keep the current brush mode inwindow.__tinyworldBrushModesso the mode indicator and UI stay in sync. Non-single brush modes should still feel like paint: selecting one from Select/Island/non-drawable tools (and pressing the canvas while one is active) must switch to a safe drawable fallback, the brush pointer path must win over editable-island widgets and orbit fallback, the ground footprint should update during pointermove before the drag threshold, and release should apply the paint without requiring a second click. -
Duplicate, paste, and latest-template placement should be preview-first: copy/normalize the payload into a pending placement state, show a holographic representation following hover (clone live meshes when available; otherwise use a lightweight clipboard footprint), and commit only on the next board click. Keep Escape as a cancel path and keep any immediate helper explicitly opt-in/fallback-only.
-
Radial color changes should be preview-first: swatches should arm a tinted hologram for the selected object/cells and commit through
updateSelectedBoardObjects()only on the next board click; Escape cancels. -
Radial Generate should be contextual: summarize the current selection/selected object into the generator prompt/status instead of opening a blank generic modal. Do not imply a scoped apply unless the generator actually supports one; phrase it as context for the preview.
-
Generate should be preview-first: after the model returns validated world JSON, stage it as a holographic diff overlay with Apply / Regenerate / Discard controls. Do not call
applyState()until the user applies the preview; discard must dispose preview geometries/materials. -
Selected-cell rotation should be preview-first too: radial/keyboard rotate arms a holographic rotated clone, commits the real
sel.rotate()on the next board click, and cancels with Escape. Keep immediate rotation as an explicit fallback only.
Contextual selected-object menus:
- The selected-object popup is
engine/world/33-radial-menu.js; prefer extending that radial menu over adding a second floating menu for object actions. - Radial actions should reuse existing edit helpers (
duplicateActiveCellIntent(),deleteActiveCellIntent(),openGenerateModal(), selection API methods) rather than writing directly toworld. - Keep destructive actions visually separated with
data-pos-type="danger"; keep generation/AI actions on tertiary/orange styling so users can distinguish “create” from “mutate/delete”.
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. - The Stamps panel has a Manage library mode. Hidden asset keys live in
tinyworld:stamp-builder-hidden.v1, normal category/recent/flyout rendering must exclude hidden tools, and theHiddencategory is the only normal place to show them again. Manage cards can hide/unhide removable tools and delete local templates, voxel-build stamps, and dropped model stamps. - File import/export, voxel JSON import, agent file attachments, and GLB/GLTF
drag/drop imports are owner-only. Gate new file surfaces through
window.__tinyworldOwnerToolsAllowed()so non-owner builders can still use the shared asset library without importing arbitrary local files. - 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 plus portable data-url records for dropped models) and
restore them into the model-stamp registry on boot. Saved cells may keep only
appearance.modelStampId, but shared/exported world JSON must embed the referenceddroppedModelStampsrecords so another browser can resolve the same model id instead of rendering 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.
Brush visibility
-
Brush modes should be discoverable in the dedicated floating
#brush-mode-dockas well as any compact appbar copy. Both usedata-brush-mode, so_brushInitToolbar()and_brushSetMode()update every visible copy together. If users report not seeing Brush, prioritize the floating dock's position/visibility before adding more hidden appbar controls. -
Brush shapes are modifiers for the active paint/build tool, not standalone Select actions. If a user chooses Line/Rect/Fill/Scatter while still in Select, auto-switch to a safe paint tool (Grass) so dragging the board starts a holographic brush preview instead of orbiting/spinning the island.
Version History
- 2ffaf91 Current 2026-07-06 00:19


