Cursor reflects game state over the 3D scene again
Two regressions made the cursor stop communicating game state: - The canvas had a hardcoded `cursor: grab`, overriding the document-level process-state cursor everywhere over the 3D scene (always a hand). Removed it so the canvas inherits the state cursor; grab is now shown only transiently while right-drag-rotating the camera. - normalizeProcessState pinned ready/waiting-generating to the playing (feather) cursor whenever playbackCoordinator.isPlaying was set, which lingered at choice prompts — so an open choice showed the feather instead of the input cursor. Now, when an input prompt is open AND no sentence is actively playing (timeline's webglBookPlaybackActive), the playback overlay is stripped (playing-ready->ready, playing-generating->waiting-generating) and the input/server cursor shows. Opening an input mode also refreshes the cursor immediately. Verified live over the canvas: feather while a sentence plays, input arrow at a choice/idle, and they switch correctly with playback state (no stuck feather, no constant grab). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -308,6 +308,18 @@ class UIInputHandlerModule extends BaseModule {
|
||||
normalizeProcessState(state) {
|
||||
const playbackCoordinator = this.getModule('playback-coordinator');
|
||||
const isPlaying = Boolean(playbackCoordinator?.isPlaying);
|
||||
// The player is in control when an input prompt is open AND the book is not actively
|
||||
// playing a sentence (the timeline owns webglBookPlaybackActive). Then the cursor must
|
||||
// show the input/server state, never the playback feather — even if a stale playing-*
|
||||
// state lingers — so strip the playback overlay. While a sentence is actually playing
|
||||
// the feather wins, even if an input mode is still set from the previous turn.
|
||||
const playbackActive = document.documentElement.dataset.webglBookPlaybackActive === 'true';
|
||||
const awaitingPlayer = !playbackActive && ['choice', 'text', 'end'].includes(this.inputMode);
|
||||
if (awaitingPlayer) {
|
||||
if (state === 'playing-ready') return 'ready';
|
||||
if (state === 'playing-generating') return 'waiting-generating';
|
||||
return state;
|
||||
}
|
||||
|
||||
if (isPlaying && state === 'ready') {
|
||||
return 'playing-ready';
|
||||
@@ -345,6 +357,12 @@ class UIInputHandlerModule extends BaseModule {
|
||||
this.setInputModeDataset();
|
||||
const state = document.documentElement.dataset.processState || 'loading';
|
||||
this.setInputAvailability(this.inputMode === 'text' && state === 'ready');
|
||||
// Opening an input-awaiting prompt hands control to the player; reflect that in the
|
||||
// cursor immediately instead of leaving the prior playback state showing (the live
|
||||
// flow does not always dispatch a fresh process-state when the prompt appears).
|
||||
if (this.inputMode !== 'none') {
|
||||
this.setProcessState('ready', { reason: `input-mode:${this.inputMode}` });
|
||||
}
|
||||
}
|
||||
|
||||
setInputModeDataset() {
|
||||
|
||||
Reference in New Issue
Block a user