fix(web): mascot physics, drag reliability, and speech-bubble polish
Audits and fixes ground-teleport/flat-fall/toss-momentum physics bugs, fixes drag getting stuck via missing pointercancel handling, replaces sprite-based speech bubbles with real HTML text/emoji bubbles, adds drag-onto-icon "investigate" reactions and idle chatter, merges the name badge and reaction bubble into one floating element, and caps the bubble to one line with a teleprompter-style auto-scroll instead of ellipsizing overflow text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -48,9 +48,11 @@
|
||||
reactAnim: null,
|
||||
bounds: { w: 800, h: 600 },
|
||||
groundY: 600,
|
||||
bubble: null,
|
||||
bubbleText: null,
|
||||
bubbleUntil: 0,
|
||||
blinkUntil: 0
|
||||
blinkUntil: 0,
|
||||
fallPhaseAt: 0,
|
||||
investigateOnLand: false
|
||||
})
|
||||
|
||||
let menuPos = $state<{ x: number; y: number } | null>(null)
|
||||
@@ -105,7 +107,7 @@
|
||||
function onPet() {
|
||||
// Plain-click pet: briefly show a happy bubble. The actual model.pet()
|
||||
// call already happened in Mascot.svelte.
|
||||
runtime.bubble = '/mascot/bubble-love.png'
|
||||
runtime.bubbleText = '❤️'
|
||||
runtime.bubbleUntil = performance.now() + 1500
|
||||
}
|
||||
|
||||
@@ -149,6 +151,13 @@
|
||||
// behind it.
|
||||
const detach = attachStimuli((reaction) => {
|
||||
if (getModel().stage === 'egg') return
|
||||
// Dragging always wins — never let a reaction interrupt an active
|
||||
// drag (previously it could visually flash a reaction animation
|
||||
// mid-drag, even though position tracking stayed correct; see the
|
||||
// physics audit's Finding 5). Sleep is only interrupted by
|
||||
// reactions that explicitly opt in via interruptsSleep.
|
||||
if (runtime.behavior === 'dragged') return
|
||||
if (runtime.behavior === 'sleep' && !reaction.interruptsSleep) return
|
||||
// Reaction dispatch: respect priority + cooldown (handled in stimuli.ts);
|
||||
// here we just force the behavior.
|
||||
const anim = reaction.anim as AnimName
|
||||
@@ -156,7 +165,7 @@
|
||||
runtime.reactAnim = anim
|
||||
forceBehavior(runtime, id, { anim, durationMs: reaction.durationMs })
|
||||
if (reaction.bubble) {
|
||||
runtime.bubble = reaction.bubble
|
||||
runtime.bubbleText = reaction.bubble
|
||||
runtime.bubbleUntil = performance.now() + reaction.durationMs
|
||||
}
|
||||
if (reaction.effect) reaction.effect()
|
||||
@@ -180,9 +189,13 @@
|
||||
|
||||
<div bind:this={host} class="pointer-events-none absolute inset-0 z-[45]">
|
||||
<Mascot
|
||||
{runtime}
|
||||
bind:runtime
|
||||
onContextMenu={openMenu}
|
||||
onPet={onPet}
|
||||
onRequestName={() => {
|
||||
nameDialogMode = 'hatch'
|
||||
nameDialogOpen = true
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user