fix(editor): shelf deletion plays item-delete SFX; refresh delete sounds (#379)

* fix(editor): shelf deletion plays item-delete SFX; refresh delete sounds

Shelves are furniture-like placeable objects, so deleting one should use
the lighter item-delete cue instead of the structure-delete one. Add a
shared emitDeleteSFX(nodeType) helper that maps item-like node types
(item, shelf) to item-delete and everything else to structure-delete,
and route the three delete sites (sledgehammer, floating action menu,
keyboard) through it.

Also refresh the item_delete/structure_delete audio assets and reduce
their pan jitter for a steadier delete cue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(editor): update item_delete SFX asset

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-06 21:14:09 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 1419709131
commit dd0215643b
7 changed files with 24 additions and 20 deletions
Binary file not shown.
Binary file not shown.
@@ -33,7 +33,7 @@ import { useFrame } from '@react-three/fiber'
import { useCallback, useRef } from 'react' import { useCallback, useRef } from 'react'
import * as THREE from 'three' import * as THREE from 'three'
import { duplicateRoofSubtree } from '../../lib/roof-duplication' import { duplicateRoofSubtree } from '../../lib/roof-duplication'
import { sfxEmitter } from '../../lib/sfx-bus' import { emitDeleteSFX, sfxEmitter } from '../../lib/sfx-bus'
import { duplicateStairSubtree } from '../../lib/stair-duplication' import { duplicateStairSubtree } from '../../lib/stair-duplication'
import useEditor from '../../store/use-editor' import useEditor from '../../store/use-editor'
import { formatMeasurement, MeasurementPill } from './measurement-pill' import { formatMeasurement, MeasurementPill } from './measurement-pill'
@@ -514,11 +514,7 @@ export function FloatingActionMenu() {
(e: React.MouseEvent) => { (e: React.MouseEvent) => {
e.stopPropagation() e.stopPropagation()
if (!selectedId) return if (!selectedId) return
if (node?.type === 'item') { emitDeleteSFX(node?.type)
sfxEmitter.emit('sfx:item-delete')
} else {
sfxEmitter.emit('sfx:structure-delete')
}
setSelection({ selectedIds: [] }) setSelection({ selectedIds: [] })
useScene.getState().deleteNode(selectedId as AnyNodeId) useScene.getState().deleteNode(selectedId as AnyNodeId)
}, },
@@ -50,7 +50,7 @@ import {
hasActivePaintMaterial, hasActivePaintMaterial,
resolveActivePaintMaterialFromSelection, resolveActivePaintMaterialFromSelection,
} from '../../lib/material-paint' } from '../../lib/material-paint'
import { sfxEmitter } from '../../lib/sfx-bus' import { emitDeleteSFX } from '../../lib/sfx-bus'
import useEditor, { import useEditor, {
type MaterialTargetRole, type MaterialTargetRole,
type Phase, type Phase,
@@ -1555,11 +1555,7 @@ export const SelectionManager = () => {
event.stopPropagation() event.stopPropagation()
// Play appropriate SFX // Play appropriate SFX
if (node.type === 'item') { emitDeleteSFX(node.type)
sfxEmitter.emit('sfx:item-delete')
} else {
sfxEmitter.emit('sfx:structure-delete')
}
useScene.getState().deleteNode(node.id as AnyNodeId) useScene.getState().deleteNode(node.id as AnyNodeId)
if (node.parentId) useScene.getState().dirtyNodes.add(node.parentId as AnyNodeId) if (node.parentId) useScene.getState().dirtyNodes.add(node.parentId as AnyNodeId)
+2 -6
View File
@@ -7,7 +7,7 @@ import {
copySelectedNodesToEditorClipboard, copySelectedNodesToEditorClipboard,
pasteEditorClipboardToLevel, pasteEditorClipboardToLevel,
} from '../lib/scene-clipboard' } from '../lib/scene-clipboard'
import { sfxEmitter } from '../lib/sfx-bus' import { emitDeleteSFX, sfxEmitter } from '../lib/sfx-bus'
import { toggleWindowOpenState } from '../lib/window-interaction' import { toggleWindowOpenState } from '../lib/window-interaction'
import useEditor from '../store/use-editor' import useEditor from '../store/use-editor'
@@ -322,11 +322,7 @@ export const useKeyboard = ({
// Play appropriate SFX based on what's being deleted // Play appropriate SFX based on what's being deleted
if (selectedNodeIds.length === 1) { if (selectedNodeIds.length === 1) {
const node = useScene.getState().nodes[selectedNodeIds[0]!] const node = useScene.getState().nodes[selectedNodeIds[0]!]
if (node?.type === 'item') { emitDeleteSFX(node?.type)
sfxEmitter.emit('sfx:item-delete')
} else {
sfxEmitter.emit('sfx:structure-delete')
}
} else { } else {
sfxEmitter.emit('sfx:structure-delete') sfxEmitter.emit('sfx:structure-delete')
} }
+16
View File
@@ -55,3 +55,19 @@ export function initSFXBus() {
export function triggerSFX(event: keyof SFXEvents) { export function triggerSFX(event: keyof SFXEvents) {
sfxEmitter.emit(event) sfxEmitter.emit(event)
} }
/**
* Node types whose deletion should use the lighter item-delete cue rather
* than the heavier structure-delete one. Shelves are furniture-like placeable
* objects, so they sound like items being removed, not structures demolished.
*/
const ITEM_DELETE_NODE_TYPES = new Set(['item', 'shelf'])
/**
* Emit the delete SFX appropriate for a deleted node's type.
*/
export function emitDeleteSFX(nodeType: string | undefined) {
sfxEmitter.emit(
nodeType && ITEM_DELETE_NODE_TYPES.has(nodeType) ? 'sfx:item-delete' : 'sfx:structure-delete',
)
}
+2 -2
View File
@@ -39,7 +39,7 @@ export const SFX: Record<string, SFXConfig> = {
src: '/audios/sfx/item_delete.mp3', src: '/audios/sfx/item_delete.mp3',
rateRange: [0.9, 1.1], rateRange: [0.9, 1.1],
volumeRange: [0.9, 1.0], volumeRange: [0.9, 1.0],
panJitter: 0.15, panJitter: 0.05,
}, },
itemPick: { itemPick: {
src: '/audios/sfx/item_pick.mp3', src: '/audios/sfx/item_pick.mp3',
@@ -77,7 +77,7 @@ export const SFX: Record<string, SFXConfig> = {
src: '/audios/sfx/structure_delete.mp3', src: '/audios/sfx/structure_delete.mp3',
rateRange: [0.9, 1.1], rateRange: [0.9, 1.1],
volumeRange: [0.9, 1.0], volumeRange: [0.9, 1.0],
panJitter: 0.15, panJitter: 0.08,
}, },
snapshotCapture: { snapshotCapture: {
// Shutter should sound consistent — no variation. // Shutter should sound consistent — no variation.