Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: app <-> renderer REWORK. Add new layers for stability #315

Open
wants to merge 37 commits into
base: next
Choose a base branch
from

Conversation

zardoy
Copy link
Owner

@zardoy zardoy commented Mar 18, 2025

User description

Since the resources for the development are very limited and I want to have a client with a full vanilla experience I have to first bring the stability to key aspects of it. Having performant renderer is a top priority and I also want try avoiding regressions in it. I didn’t really touch the rendering pipeline in the last 2 years (only a huge amount of patches to fix old bugs), because it worked mostly fine, and I didn’t have much time to re-rework everything as even a minor change could break everything (which happened a few times anyway).
So with this PR now I’m splitting rendering engine in which I’m not good and the other app code in which I’m much better by introducing another layer between app (mostly mineflayer related) and the graphics code. Eg it won’t be possible to access code from renderer anymore. For now this new layer is called AppViewer (I would call it GraphicsBackendProvider or similar but want to preserve viewer global access that is easy to remember).

fixes #303

Also refactor old viewer class which had following issues:

  • was not clear why everything could not be done in worldrenderer
  • World renderer state resetting after world disconnect was not clear: some properties we have to reset some not, they could be set after disconnect etc. Now instead not just the scene but whole renderer is recreated after the world disconnect
  • Some resources properties were in worldrenderer, now refactored into resourcesmanager so it’s much cleaner
  • Data flow was not clear and mixed. For example should be data set directly with the provided method from the renderer backend or updated in some reactive property on the layer like data provider. Obviously the second variant makes it easier to debug but on the other hand might create an unnecessary layer a lot of unnecessary code.

Goals I set:

  • app should be running fine without any rendering backend, this also should make it easier to understand performance issues. Isolating renderer backend and removing any connection between renderer and app code also would allow it to easily put it into the worker which what I might do in the future. This opens a great flexibility for the rendering cases
  • Api should be convenient for both current rendering backends: current webgl three.js and webgpu which is slightly different, not complete but extremely performant

I understand that it both can break some existing things and would make any current developing features would resolve into conflicts because of a lot of code changes. That’s why I think it should be merged asap. This would allow me to finally start perf debugging and make big rendering pipeline changes. If you have any other ideas for improvements it would be better to implement them now as I really want to avoid such refactorings in the future.

Plans for the future:

  • rewrite to material shader for:
  • introduce fog at the edges of the chunk
  • Fix tiles clipping
  • Fix huge rendering loop spikes

perf +15%
broken functionality +20%


PR Type

Enhancement, Other


Description

  • Introduced AppViewer as a new stable layer to manage rendering backends, isolating the app logic from the rendering pipeline.

  • Refactored rendering components (WorldRendererThree, DocumentRenderer, PanoramaRenderer) for modularity and integration with AppViewer.

  • Added ResourcesManager for centralized asset and texture management, simplifying resource handling across the application.

  • Enhanced performance debugging tools and FPS tracking with new utilities and integrations.

  • Simplified and modularized various components, including inventory windows, camera controls, and VR handling, to align with the new architecture.

  • Temporarily disabled or commented out certain logic (e.g., entity-related logic, day cycle) for future refinement.

  • Deleted obsolete files and replaced them with updated implementations.


Changes walkthrough 📝

Relevant files
Enhancement
38 files
worldrendererCommon.ts
Refactor and enhance world renderer with resource management.

renderer/viewer/lib/worldrendererCommon.ts

  • Introduced ResourcesManager and WorldDataEmitter integration.
  • Added new configuration options to defaultWorldRendererConfig.
  • Refactored updateAssetsData to use ResourcesManager.
  • Implemented new methods for managing world rendering and connecting to
    WorldDataEmitter.
  • +195/-121
    index.ts
    Transition to `appViewer` for rendering management.           

    src/index.ts

  • Removed direct Viewer initialization and replaced it with appViewer.
  • Added backend loading logic with createGraphicsBackend.
  • Simplified initialization and rendering logic.
  • +111/-164
    worldrendererThree.ts
    Refactor three.js world renderer for modularity.                 

    renderer/viewer/lib/worldrendererThree.ts

  • Refactored to integrate with appViewer and ResourcesManager.
  • Added methods for entity updates and scene resetting.
  • Enhanced camera handling and rendering logic.
  • +112/-24
    panorama.ts
    Introduce panorama rendering for background visuals.         

    renderer/viewer/three/panorama.ts

  • Added a new PanoramaRenderer class for rendering background panoramas.
  • Implemented support for both classic and world block panoramas.
  • Integrated with DocumentRenderer for rendering.
  • +204/-0 
    resourcesManager.ts
    Introduce `ResourcesManager` for centralized asset management.

    src/resourcesManager.ts

  • Added ResourcesManager class for managing assets and textures.
  • Implemented methods for loading and updating resources.
  • Centralized resource handling logic.
  • +180/-3 
    documentRenderer.ts
    Add `DocumentRenderer` for rendering lifecycle management.

    renderer/viewer/three/documentRenderer.ts

  • Added DocumentRenderer class for managing rendering lifecycle.
  • Implemented FPS tracking and dynamic resizing.
  • Integrated stats tracking for performance monitoring.
  • +238/-0 
    watchOptions.ts
    Update option watchers to integrate with `appViewer`.       

    src/watchOptions.ts

  • Updated to integrate with appViewer for rendering configuration.
  • Added logic for dynamic FPS adjustment based on window focus.
  • Refactored option watchers for modularity.
  • +60/-41 
    appViewer.ts
    Add `AppViewer` for unified rendering backend management.

    src/appViewer.ts

  • Introduced AppViewer class for managing rendering backends.
  • Added methods for initializing and switching rendering modes.
  • Centralized rendering logic and backend management.
  • +192/-0 
    entities.ts
    Refactor `Entities` class for modular rendering.                 

    renderer/viewer/lib/entities.ts

  • Refactored Entities class to integrate with WorldRendererThree.
  • Updated rendering logic to use the new scene structure.
  • Improved entity visibility and rendering updates.
  • +12/-12 
    resourcePack.ts
    Refactor resource pack handling with `ResourcesManager`. 

    src/resourcePack.ts

  • Updated to use ResourcesManager for resource pack handling.
  • Refactored texture update logic for modularity.
  • Simplified resource pack reload process.
  • +21/-18 
    graphicsBackend.ts
    Add `GraphicsBackend` for three.js rendering management. 

    renderer/viewer/three/graphicsBackend.ts

  • Added createGraphicsBackend for initializing three.js rendering.
  • Implemented methods for panorama and world rendering.
  • Integrated with DocumentRenderer and WorldRendererThree.
  • +99/-0   
    devtools.ts
    Enhance devtools with appViewer integration and performance debugging.

    src/devtools.ts

  • Updated debug utilities to integrate with appViewer.
  • Added performance debugging tools for advanced analysis.
  • +49/-2   
    cameraShake.ts
    Refactor camera shake to use `appViewer` backend.               

    src/mineflayer/cameraShake.ts

  • Updated camera shake logic to use appViewer backend.
  • Simplified roll angle application.
  • +6/-16   
    threeJsSound.ts
    Add `ThreeJsSound` for positional audio management.           

    renderer/viewer/lib/threeJsSound.ts

  • Added ThreeJsSound class for managing positional audio.
  • Implemented sound playback and cleanup logic.
  • Integrated with WorldRendererThree.
  • +61/-0   
    worldDataEmitter.ts
    Enhance `WorldDataEmitter` with reactive state.                   

    renderer/viewer/lib/worldDataEmitter.ts

  • Added reactive state for cursor block and breaking stage.
  • Simplified event handling and initialization.
  • +6/-8     
    connect.ts
    Simplify connection logic and integrate `ResourcesManager`.

    src/connect.ts

  • Removed unused imports and simplified loadMinecraftData.
  • Updated logic to integrate with ResourcesManager.
  • +1/-6     
    inventoryWindows.ts
    Refactor inventory windows to use `ResourcesManager`.       

    src/inventoryWindows.ts

  • Updated texture loading logic to use ResourcesManager.
  • Simplified game load checks.
  • +3/-3     
    progressReporter.ts
    Add null progress reporter and enhance console logging.   

    src/core/progressReporter.ts

  • Added createNullProgressReporter for silent progress tracking.
  • Enhanced createConsoleLogProgressReporter with optional grouping.
  • +14/-3   
    rendererUtils.ts
    Refactor FOV handling to use `appViewer`.                               

    src/rendererUtils.ts

  • Updated FOV handling to use appViewer backend.
  • Removed direct viewer camera manipulation.
  • +3/-3     
    globals.d.ts
    Update global type definitions with `appViewer`.                 

    src/globals.d.ts

  • Added appViewer to global declarations.
  • Updated type definitions for rendering components.
  • +1/-0     
    guiRenderer.ts
    Simplify GUI renderer imports and utilities.                         

    renderer/viewer/lib/guiRenderer.ts

    • Updated imports to use common utilities.
    • Removed redundant code.
    +1/-2     
    utils.ts
    Add utilities for version parsing and mapping.                     

    renderer/viewer/common/utils.ts

  • Added utility functions for version parsing and mapping.
  • Centralized version-related logic.
  • [link]   
    vr.ts
    Refactor VR camera handling to use `appViewer`.                   

    src/vr.ts

  • Updated VR camera handling to use appViewer backend.
  • Removed direct viewer camera manipulation.
  • +2/-2     
    loadSave.ts
    Simplify load/save logic with common utilities.                   

    src/loadSave.ts

  • Updated imports to use common utilities.
  • Simplified version handling logic.
  • +1/-1     
    cameraRotationControls.ts
    Refactor camera rotation controls to use `appViewer`.       

    src/cameraRotationControls.ts

  • Updated camera rotation logic to use appViewer backend.
  • Removed direct viewer camera manipulation.
  • +1/-1     
    chatUtils.ts
    Simplify chat utilities with common imports.                         

    src/chatUtils.ts

  • Updated imports to use common utilities.
  • Simplified version handling logic.
  • +1/-1     
    playerState.ts
    Update `PlayerStateManager` constructor visibility.           

    src/mineflayer/playerState.ts

  • Made PlayerStateManager constructor public.
  • Simplified initialization logic.
  • +1/-1     
    botSoundSystem.ts
    Simplify bot sound system with common imports.                     

    src/sounds/botSoundSystem.ts

  • Updated imports to use common utilities.
  • Simplified version handling logic.
  • +1/-1     
    minecraftData.ts
    Simplify Minecraft data shims with common imports.             

    src/shims/minecraftData.ts

  • Updated imports to use common utilities.
  • Simplified version handling logic.
  • +1/-1     
    soundsMap.ts
    Simplify sounds map with common imports.                                 

    src/sounds/soundsMap.ts

  • Updated imports to use common utilities.
  • Simplified version handling logic.
  • +1/-1     
    botUtils.ts
    Simplify bot utilities with common imports.                           

    src/botUtils.ts

  • Updated imports to use common utilities.
  • Simplified version handling logic.
  • +1/-1     
    ping.ts
    Simplify ping plugin with common imports.                               

    src/mineflayer/plugins/ping.ts

  • Updated imports to use common utilities.
  • Simplified version handling logic.
  • +1/-1     
    optimizeJson.ts
    Simplify JSON optimization with common imports.                   

    src/optimizeJson.ts

  • Updated imports to use common utilities.
  • Simplified version handling logic.
  • +1/-1     
    DebugOverlay.tsx
    Refactor debug overlay to use `appViewer`.                             

    src/react/DebugOverlay.tsx

  • Updated debug overlay to use appViewer backend.
  • Simplified entity count and custom entries handling.
  • +5/-6     
    HotbarRenderApp.tsx
    Refactor Hotbar rendering to use `appViewer` resource manager.

    src/react/HotbarRenderApp.tsx

  • Updated references from viewer to appViewer for resource management.
  • Replaced event listeners to align with the new
    appViewer.resourcesManager.
  • Adjusted cleanup logic to use the updated event names.
  • +3/-3     
    reactUi.tsx
    Add browser and device-specific CSS adjustments.                 

    src/reactUi.tsx

  • Added browser detection for Firefox and applied custom CSS properties.
  • Added device detection for iPhone and adjusted HUD bottom margin
    dynamically.
  • +13/-0   
    MinimapProvider.tsx
    Update utility import path in MinimapProvider.                     

    src/react/MinimapProvider.tsx

  • Updated import path for versionToNumber utility to reflect new
    directory structure.
  • +1/-1     
    BookProvider.tsx
    Update utility import path in BookProvider.                           

    src/react/BookProvider.tsx

  • Updated import path for versionToNumber utility to reflect new
    directory structure.
  • +1/-1     
    Miscellaneous
    5 files
    controls.ts
    Add debug keybinding for delayed load toggle.                       

    src/controls.ts

  • Added debug keybinding for delayed load toggle.
  • Simplified debug event handling.
  • +8/-1     
    entities.ts
    Temporarily disable entity logic and update imports.         

    src/entities.ts

  • Disabled entity-related logic temporarily.
  • Updated imports to use common utilities.
  • +2/-1     
    mouse.ts
    Simplify mouse plugin initialization.                                       

    src/mineflayer/plugins/mouse.ts

  • Commented out unused display manager creation.
  • Simplified plugin initialization.
  • +1/-1     
    dayCycle.ts
    Temporarily disable day cycle logic.                                         

    src/dayCycle.ts

  • Disabled day cycle logic temporarily.
  • Simplified function implementation.
  • +1/-0     
    IndicatorEffectsProvider.tsx
    Temporarily disable chunk rendering wait logic.                   

    src/react/IndicatorEffectsProvider.tsx

  • Commented out logic related to viewer.waitForChunksToRender.
  • Left the event listener for dirty unchanged.
  • +4/-4     
    Additional files
    5 files
    index.js +0/-6     
    viewer.ts +0/-325 
    viewerWrapper.ts +0/-122 
    panorama.ts +0/-198 
    topRightStats.ts +0/-129 

    Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • Summary by CodeRabbit

    • New Features

      • Introduced enhanced VR integration and support for custom 3D rendering.
      • Added an immersive sound system for improved 3D audio playback.
      • Added a new CursorBlock class for managing visual representations of cursor blocks in 3D.
      • Added a DocumentRenderer class for rendering graphics using Three.js.
      • Introduced a PanoramaRenderer class for rendering 3D panorama scenes.
      • Added a getItemUv function for retrieving UV coordinates and rendering information for items.
      • Added a registerChannel function for managing custom channels and handling incoming data.
    • Improvements

      • Streamlined resource management and world rendering for smoother animations and performance.
      • Updated user interface elements—including hotbar, minimap, and on-screen overlays—for better responsiveness on modern browsers and mobile devices.
      • Enhanced logging capabilities with a new optional parameter for contextual logging.
      • Improved handling of resource updates and event emissions for more efficient rendering.
    • Removed Features

      • Discontinued cloud world saving via Google Drive and legacy debug statistics panels.
      • Removed dynamic rendering of signs in the ModuleSignsViewer component.
      • Removed the Viewer and ViewerWrapper classes, centralizing rendering functionality under the WorldRendererThree class.
      • Removed the topRightStats functionality for performance monitoring.

    Copy link

    codesandbox bot commented Mar 18, 2025

    Review or Edit in CodeSandbox

    Open the branch in Web EditorVS CodeInsiders

    Open Preview

    Copy link

    coderabbitai bot commented Mar 18, 2025

    Walkthrough

    This pull request removes several legacy viewer modules and refactors core rendering functionality. The changes centralize rendering by replacing the old Viewer class with a new WorldRendererThree architecture. New modules for sound, document rendering, VR, and backend management have been introduced. Additionally, resource and state management has been consolidated under an AppViewer class with updates to API signatures, event handling, and import paths. Minor UI and debugging enhancements and adjustments to configuration settings are also included.

    Changes

    File Group(s) Change Summary
    Core Viewer Removal
    renderer/viewer/index.js, .../lib/viewer.ts, .../lib/viewerWrapper.ts, src/panorama.ts, src/topRightStats.ts
    Removed legacy Viewer, ViewerWrapper, panorama cleanup, and stats modules to deprecate outdated rendering controls.
    Rendering Architecture & Entities Updates
    renderer/viewer/lib/entities.ts, .../lib/guiRenderer.ts, .../lib/worldDataEmitter.ts, .../lib/worldrendererCommon.ts, .../lib/worldrendererThree.ts
    Refactored APIs to use WorldRendererThree instead of Viewer; updated constructor and method signatures; added reactive state and event methods.
    New Sound & Three.js Modules
    renderer/viewer/lib/threeJsSound.ts, renderer/viewer/three/documentRenderer.ts, renderer/viewer/three/graphicsBackend.ts, renderer/viewer/three/panorama.ts, renderer/viewer/three/threeJsMethods.ts, renderer/viewer/three/world/{vr.ts,cursorBlock.ts}, renderer/viewer/three/appShared.ts
    Introduced a Three.js-based sound system; added document renderer, graphics backend, VR support, and new helper methods for Three.js interactions.
    Centralized Resource & AppViewer Enhancements
    src/appViewer.ts, src/resourcesManager.ts, plus assorted import updates in multiple src/* files
    Added AppViewer class to centralize resource management and rendering control; updated API imports (e.g., versionToNumber) and refactored resource handling logic.
    Miscellaneous Updates & UI Enhancements
    src/botUtils.ts, src/chatUtils.ts, src/connect.ts, src/inventoryWindows.ts, src/dayCycle.ts, src/entities.ts, various UI/react modules, .gitignore
    Adjusted import paths, updated event handling for camera, animations, and block interactions; implemented minor UI and debug overlay improvements.

    Sequence Diagram(s)

    sequenceDiagram
        participant WR as WorldRendererThree
        participant TS as ThreeJsSound
        participant Loader as THREE.AudioLoader
        participant Scene as THREE.Scene
    
        WR->>TS: playSound(position, path, volume, pitch)
        TS->>Loader: Load audio asynchronously
        Loader-->>TS: Return audio buffer
        TS->>Scene: Attach audio to listener and play
        TS->>TS: Monitor playback and clean up resources
    
    Loading

    Assessment against linked issues

    Objective (Issue #303) Addressed Explanation
    Disable rendering of unknown entity placeholders (#303) No explicit implementation or setting change for disabling placeholders was found.

    Possibly related PRs

    Suggested labels

    Review effort 3/5

    Poem

    In a forest of code beneath the starry night,
    I hop along paths so shiny and bright.
    Old viewers vanish; new worlds arise,
    With sounds and scenes that mesmerize.
    I nibble on bugs with a joyful cheer,
    For a modular realm brings a future so clear!
    🐇✨ Happy hops in every line of code!

    ✨ Finishing Touches
    • 📝 Generate Docstrings

    🪧 Tips

    Chat

    There are 3 ways to chat with CodeRabbit:

    • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
      • I pushed a fix in commit <commit_id>, please review it.
      • Generate unit testing code for this file.
      • Open a follow-up GitHub issue for this discussion.
    • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
      • @coderabbitai generate unit testing code for this file.
      • @coderabbitai modularize this function.
    • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
      • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
      • @coderabbitai read src/utils.ts and generate unit testing code.
      • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
      • @coderabbitai help me debug CodeRabbit configuration file.

    Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

    CodeRabbit Commands (Invoked using PR comments)

    • @coderabbitai pause to pause the reviews on a PR.
    • @coderabbitai resume to resume the paused reviews.
    • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
    • @coderabbitai full review to do a full review from scratch and review all the files again.
    • @coderabbitai summary to regenerate the summary of the PR.
    • @coderabbitai generate docstrings to generate docstrings for this PR.
    • @coderabbitai resolve resolve all the CodeRabbit review comments.
    • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
    • @coderabbitai help to get help.

    Other keywords and placeholders

    • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
    • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
    • Add @coderabbitai anywhere in the PR title to generate the title automatically.

    CodeRabbit Configuration File (.coderabbit.yaml)

    • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
    • Please see the configuration documentation for more information.
    • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

    Documentation and Community

    • Visit our Documentation for detailed information on how to use CodeRabbit.
    • Join our Discord Community to get help, request features, and share feedback.
    • Follow us on X/Twitter for updates and announcements.

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Potential Memory Leak

    The new implementation adds a destroy() method with worker termination and resource cleanup, but there may be other resources that need proper disposal, especially textures and geometries. Ensure all THREE.js resources are properly disposed.

    }
    
    destroy () {
      // Stop all workers
      for (const worker of this.workers) {
        worker.terminate()
      }
      this.workers = []
    
      // Stop and destroy sound system
      if (this.soundSystem) {
        this.soundSystem.destroy()
        this.soundSystem = undefined
      }
    
      // if not necessary as renderer is destroyed anyway
      if (this.material.map) {
        this.material.map.dispose()
      }
      this.material.dispose()
    
      this.active = false
    
      this.renderUpdateEmitter.removeAllListeners()
    }
    Error Handling

    The new AppViewer architecture lacks comprehensive error handling, particularly during resource loading and backend initialization. Consider adding more robust error handling and recovery mechanisms.

    loadBackend (loader: GraphicsBackendLoader) {
      if (this.backend) {
        this.disconnectBackend()
      }
    
      this.backendLoader = loader
      const loaderOptions: GraphicsInitOptions = {
        resourcesManager: this.resourcesManager,
        config: this.config,
        displayCriticalError (error) {
          console.error(error)
          setLoadingScreenStatus(error.message, true)
        },
      }
      this.backend = loader(loaderOptions)
    
      if (this.resourcesManager.currentResources) {
        void this.prepareResources(this.resourcesManager.currentResources.version, createNotificationProgressReporter())
      }
    
      // Execute queued action if exists
      if (this.queuedDisplay) {
        const { method, args } = this.queuedDisplay
        this.backend[method](...args)
      }
    }
    Race Condition

    The connect() method sets up multiple event listeners that could cause race conditions if events are fired in an unexpected order. Particularly concerning is the duplicate 'renderDistance' event handler.

    connect (worldView: WorldDataEmitter) {
      const worldEmitter = worldView
    
      worldEmitter.on('entity', (e) => {
        this.updateEntity(e)
      })
    
      let currentLoadChunkBatch = null as {
        timeout
        data
      } | null
      worldEmitter.on('loadChunk', ({ x, z, chunk, worldConfig, isLightUpdate }) => {
        this.worldSizeParams = worldConfig
        this.queuedChunks.add(`${x},${z}`)
        const args = [x, z, chunk, isLightUpdate]
        if (!currentLoadChunkBatch) {
          // add a setting to use debounce instead
          currentLoadChunkBatch = {
            data: [],
            timeout: setTimeout(() => {
              for (const args of currentLoadChunkBatch!.data) {
                this.queuedChunks.delete(`${args[0]},${args[1]}`)
                this.addColumn(...args as Parameters<typeof this.addColumn>)
              }
              for (const fn of this.queuedFunctions) {
                fn()
              }
              this.queuedFunctions = []
              currentLoadChunkBatch = null
            }, this.worldRendererConfig.addChunksBatchWaitTime)
          }
        }
        currentLoadChunkBatch.data.push(args)
      })
      // todo remove and use other architecture instead so data flow is clear
      worldEmitter.on('blockEntities', (blockEntities) => {
        if (this instanceof WorldRendererThree) (this).blockEntities = blockEntities
      })
    
      worldEmitter.on('unloadChunk', ({ x, z }) => {
        this.removeColumn(x, z)
      })
    
      worldEmitter.on('blockUpdate', ({ pos, stateId }) => {
        this.setBlockStateId(new Vec3(pos.x, pos.y, pos.z), stateId)
      })
    
      worldEmitter.on('chunkPosUpdate', ({ pos }) => {
        this.updateViewerPosition(pos)
      })
    
    
      worldEmitter.on('renderDistance', (d) => {
        this.viewDistance = d
        this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
      })
    
      worldEmitter.on('renderDistance', (d) => {
        this.viewDistance = d
        this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
        this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
      })

    Copy link

    qodo-merge-pro-for-open-source bot commented Mar 18, 2025

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Fix initialization order
    Suggestion Impact:The commit implements the suggestion by removing the connect call from the constructor. The connect method is still present in the class but is now called elsewhere, likely in the init method after setting active=true as suggested.

    code diff:

    -  constructor (private readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public version: string) {
    +  playerState: IPlayerState
    +  reactiveState: RendererReactiveState
    +
    +  abortController = new AbortController()
    +
    +  constructor (public readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public version: string) {
         // this.initWorkers(1) // preload script on page load
         this.snapshotInitialValues()
         this.worldRendererConfig = displayOptions.inWorldRenderingConfig
    +    this.playerState = displayOptions.playerState
    +    this.reactiveState = displayOptions.rendererState
     

    The connect method is called in the constructor before the object is fully
    initialized. This can lead to race conditions as the connection is established
    before properties like active are properly set. Move this call to the init
    method after setting active to true.

    renderer/viewer/lib/worldrendererCommon.ts [164]

    -this.connect(this.displayOptions.worldView)
    +// Remove this line from constructor
    +// this.connect(this.displayOptions.worldView)
     
    +// And add it to the init method after setting active = true
    +

    [Suggestion has been applied]

    Suggestion importance[1-10]: 9

    __

    Why: Moving the connect call from constructor to init method after setting active=true would prevent potential race conditions where connections are established before the object is fully initialized, which is a significant architectural improvement.

    High
    Fix undefined variable

    The variable loadedData is used but not defined in the scope. You should use the
    Minecraft data from the loaded version instead.

    renderer/viewer/three/panorama.ts [131-138]

    -const fullBlocks = loadedData.blocksArray.filter(block => {
    +const mcData = require('minecraft-data')(version)
    +const fullBlocks = mcData.blocksArray.filter(block => {
     // if (block.name.includes('leaves')) return false
       if (/* !block.name.includes('wool') &&  */!block.name.includes('stained_glass')/*  && !block.name.includes('terracotta') */) return false
       const b = Block.fromStateId(block.defaultState, 0)
       if (b.shapes?.length !== 1) return false
       const shape = b.shapes[0]
       return shape[0] === 0 && shape[1] === 0 && shape[2] === 0 && shape[3] === 1 && shape[4] === 1 && shape[5] === 1
     })
    • Apply this suggestion
    Suggestion importance[1-10]: 9

    __

    Why: The code uses an undefined variable loadedData which would cause a runtime error. This is a critical bug that would prevent the panorama rendering functionality from working properly.

    High
    Add error handling

    The audio loading process should handle errors to prevent unhandled promise
    rejections. Currently, if loading fails, there's no error handling which could
    lead to silent failures and memory leaks as the sound would remain in the
    activeSounds set.

    renderer/viewer/lib/threeJsSound.ts [28-45]

     void audioLoader.loadAsync(path).then((buffer) => {
       if (Date.now() - start > 500) return
       // play
       sound.setBuffer(buffer)
       sound.setRefDistance(20)
       sound.setVolume(volume)
       sound.setPlaybackRate(pitch) // set the pitch
       this.worldRenderer.scene.add(sound)
       // set sound position
       sound.position.set(position.x, position.y, position.z)
       sound.onEnded = () => {
         this.worldRenderer.scene.remove(sound)
         sound.disconnect()
         this.activeSounds.delete(sound)
         audioLoader.manager.itemEnd(path)
       }
       sound.play()
    +}).catch((error) => {
    +  console.error(`Failed to load sound: ${path}`, error)
    +  this.activeSounds.delete(sound)
    +  audioLoader.manager.itemError(path)
     })
    • Apply this suggestion
    Suggestion importance[1-10]: 9

    __

    Why: This suggestion adds critical error handling for the audio loading process, preventing unhandled promise rejections and potential memory leaks. Without this error handling, failed sound loads would leave orphaned entries in the activeSounds set and not properly report errors.

    High
    Set active flag during initialization
    Suggestion Impact:The commit includes setting the active flag to true in the setVersion method at line 196, which addresses the suggestion's concern about the active flag not being set during initialization. The commit doesn't implement it exactly as suggested (in the init method) but achieves the same goal of setting the active flag during initialization.

    code diff:

         this.active = true

    The init method doesn't set this.active = true, but it checks if it's already
    active. This creates an inconsistent state where initialization happens but the
    active flag is never set, which can cause issues with subsequent calls to init()
    or other methods that rely on this flag.

    renderer/viewer/lib/worldrendererCommon.ts [167-177]

     init () {
       if (this.active) throw new Error('WorldRendererCommon is already initialized')
    +  this.active = true
       void this.setVersion(this.version).then(() => {
         this.resourcesManager.on('assetsTexturesUpdated', () => {
           void this.updateAssetsData()
         })
         if (this.resourcesManager.currentResources) {
           void this.updateAssetsData()
         }
       })
    +  this.connect(this.displayOptions.worldView)
     }

    [Suggestion has been applied]

    Suggestion importance[1-10]: 8

    __

    Why: The init method checks if active is true but never sets it, creating an inconsistent state. Setting the active flag is critical for proper functioning of the renderer and preventing duplicate initialization.

    Medium
    Fix undefined reference
    Suggestion Impact:The commit addressed the issue by completely removing the problematic code that referenced 'viewer' without proper checks. Instead of implementing the suggested fix of adding window.viewer checks, the developers chose to remove the problematic code entirely and replace another viewer reference with appViewer.inWorldRenderingConfig.

    code diff:

         appViewer.inWorldRenderingConfig.showChunkBorders = o.showChunkBorders
    -    // Note: entities debug mode still needs viewer access
    -    if (viewer) viewer.entities.setDebugMode(o.showChunkBorders ? 'basic' : 'none')

    The code references viewer without checking if it's defined at the top level,
    but only checks inside the callback. Since this is part of a refactoring to use
    appViewer instead of the global viewer, you should either ensure viewer is
    properly defined or update the code to use the new architecture.

    src/watchOptions.ts [29-33]

     watchValue(options, o => {
       appViewer.inWorldRenderingConfig.showChunkBorders = o.showChunkBorders
       // Note: entities debug mode still needs viewer access
    -  if (viewer) viewer.entities.setDebugMode(o.showChunkBorders ? 'basic' : 'none')
    +  if (appViewer.backend && window.viewer) {
    +    window.viewer.entities.setDebugMode(o.showChunkBorders ? 'basic' : 'none')
    +  }
     })

    [Suggestion has been applied]

    Suggestion importance[1-10]: 8

    __

    Why: The code references viewer without properly checking if it's defined at the top level, which could lead to runtime errors. This is a significant issue in the refactoring from viewer to appViewer.

    Medium
    Add missing method implementation

    The createNullProgressReporter function is missing implementation for the
    setProgress method which is required by the ProgressReporter interface. This
    could lead to runtime errors when code attempts to call this method on the
    returned reporter.

    src/core/progressReporter.ts [221-229]

     export const createNullProgressReporter = (): ProgressReporter => {
       return createProgressReporter({
         setMessage (message: string) {
         },
         end () {
         },
         error (message: string) {
    +    },
    +    setProgress (progress: number) {
         }
       })
     }
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    __

    Why: The suggestion correctly identifies a missing implementation of the setProgress method in the createNullProgressReporter function, which is required by the ProgressReporter interface. This omission could lead to runtime errors when code attempts to use this method.

    Medium
    Fix class reference order

    The TopRightStats class is defined later in the same file but used here before
    its definition. In TypeScript, class declarations are not hoisted, which could
    cause runtime errors. Move the TopRightStats class definition before the
    DocumentRenderer class or add a class declaration at the top of the file.

    renderer/viewer/three/documentRenderer.ts [18]

    -this.stats = new TopRightStats(this.canvas, this.config.statsVisible)
    +// Add this before DocumentRenderer class
    +class TopRightStats {
    +  // ... existing implementation
    +}
     
    +export class DocumentRenderer {
    +  // ... existing implementation
    +  this.stats = new TopRightStats(this.canvas, this.config.statsVisible)
    +  // ...
    +}
    +

    [To ensure code accuracy, apply this suggestion manually]

    Suggestion importance[1-10]: 7

    __

    Why: Using the TopRightStats class before its definition could cause runtime errors in TypeScript since class declarations aren't hoisted. This is an important structural issue that affects code reliability.

    Medium
    Fix stuck loading indicator
    Suggestion Impact:The commit implemented a different but more robust solution to the loading indicator issue. Instead of using a timeout, it directly tracks the chunk loading state from appViewer.rendererState, making the indicator accurately reflect the actual loading status.

    code diff:

    +  const chunksLoading = !useSnapshot(appViewer.rendererState).world.allChunksLoaded
    +  const { mesherWork } = useSnapshot(appViewer.rendererState).world
    +
       const { hasErrors } = useSnapshot(miscUiState)
       const { disabledUiParts } = useSnapshot(options)
       const { isReadonly, openReadOperations, openWriteOperations } = useSnapshot(fsState)
    @@ -62,26 +65,10 @@
         readingFiles: openReadOperations > 0,
         appHasErrors: hasErrors,
         connectionIssues: poorConnection ? 1 : noConnection ? 2 : 0,
    +    chunksLoading,
    +    // mesherWork,

    The code that updates state.indicators.chunksLoading and alreadyWaiting has been
    commented out, but there's no replacement logic. This will cause the loading
    indicator to remain visible indefinitely once activated, as alreadyWaiting will
    never be reset to false.

    src/react/IndicatorEffectsProvider.tsx [74-77]

    +// Temporary solution until proper implementation
    +setTimeout(() => {
    +  state.indicators.chunksLoading = false
    +  alreadyWaiting = false
    +}, 2000) // Arbitrary timeout to reset the state
    +
    +// TODO: Implement proper chunk loading detection with appViewer
     // void viewer.waitForChunksToRender().then(() => {
     //   state.indicators.chunksLoading = false
     //   alreadyWaiting = false
     // })

    [Suggestion has been applied]

    Suggestion importance[1-10]: 7

    __

    Why: The suggestion addresses an important issue where commenting out the chunk loading completion logic would cause the loading indicator to remain visible indefinitely. The temporary solution ensures the UI remains responsive while a proper implementation is developed.

    Medium
    Start camera position tween immediately

    The updateCamera method creates a new tween but doesn't call tweenJs.update() to
    start the animation. While tweenJs.update() is called in the render method, this
    creates a dependency where camera position updates only work if rendering is
    active. Add a call to start the tween immediately.

    renderer/viewer/lib/worldrendererThree.ts [302-306]

     updateCamera (pos: Vec3 | null, yaw: number, pitch: number): void {
       // if (this.freeFlyMode) {
       //   pos = this.freeFlyState.position
       //   pitch = this.freeFlyState.pitch
       //   yaw = this.freeFlyState.yaw
       // }
     
       if (pos) {
    -    new tweenJs.Tween(this.camera.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start()
    +    const tween = new tweenJs.Tween(this.camera.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start()
    +    tweenJs.update() // Start the tween immediately
         // this.freeFlyState.position = pos
       }
       this.camera.rotation.set(pitch, yaw, this.cameraRoll, 'ZYX')
     }

    [To ensure code accuracy, apply this suggestion manually]

    Suggestion importance[1-10]: 6

    __

    Why: Adding tweenJs.update() ensures camera position updates work immediately rather than depending on the render loop, improving responsiveness and decoupling animation from rendering.

    Low
    • Update

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 18, 2025

    TLDR

    • now its possible to safely destroy the renderer
    • now its possible to switch the renderer backend on the fly!

    @Phoenix616 do you think its a good change? given it changes a lot of code I will try to merge it asap

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 11

    🔭 Outside diff range comments (2)
    src/index.ts (1)

    366-370: 🛠️ Refactor suggestion

    Cover errors in resource loading.
    During await appViewer.resourcesManager.loadSourceData(version), failures can occur for missing or corrupted files. Include a try/catch to handle these gracefully without halting the entire connection process.

    src/resourcePack.ts (1)

    314-323: 🛠️ Refactor suggestion

    Handle undefined currentResources cautiously.
    Line 315 uses appViewer.resourcesManager.currentResources!, forcing non-null. If resourcesManager or currentResources is not set at runtime, this can cause runtime errors. Add guards (or a fallback) to ensure stability.

    🧹 Nitpick comments (32)
    renderer/viewer/three/panorama.ts (2)

    30-30: Validate abort controller usage in constructor.

    Defining abortController at construction is helpful for cleanup, but ensure it’s canceled or reset if this instance can be reused or re-initialized. If re-initialization occurs often, consider exposing an API to recreate the abort controller for each new rendering session.


    130-130: ESM vs. CommonJS mismatch.

    Using require('prismarine-block') can cause bundling issues if the rest of the code uses ES modules. Consider migrating to ESM imports consistently for a more uniform build.

    renderer/viewer/lib/threeJsSound.ts (1)

    17-46: Consider adding error handling for audio loading failures.

    The playSound method handles timeout cases but doesn't have explicit error handling for audio loading failures. If the audio fails to load for reasons other than timeout, there might be silent failures.

     void audioLoader.loadAsync(path).then((buffer) => {
       if (Date.now() - start > 500) return
       // play
       sound.setBuffer(buffer)
       sound.setRefDistance(20)
       sound.setVolume(volume)
       sound.setPlaybackRate(pitch) // set the pitch
       this.worldRenderer.scene.add(sound)
       // set sound position
       sound.position.set(position.x, position.y, position.z)
       sound.onEnded = () => {
         this.worldRenderer.scene.remove(sound)
         sound.disconnect()
         this.activeSounds.delete(sound)
         audioLoader.manager.itemEnd(path)
       }
       sound.play()
    +}).catch((error) => {
    +  console.error(`Failed to load sound: ${path}`, error)
    +  this.activeSounds.delete(sound)
    +  audioLoader.manager.itemError(path)
     })
    src/rendererUtils.ts (1)

    94-94: Usage of appViewer.backend?.updateCamera looks good.
    This new approach updates the camera using the backend interface, consistent with the refactored rendering separation. Verify that bot.entity is always defined at this point to avoid runtime errors.

    renderer/viewer/three/documentRenderer.ts (3)

    9-52: Consider validating creation errors more granularly.
    In the constructor, the try-catch block for the WebGL context creation is good practice. Consider logging whether running in WebGL1 vs. WebGL2 for detailed diagnostics, especially if the user’s hardware only supports WebGL1.


    53-59: Pixel ratio precision check.
    The logic here forces a pixel ratio of 1 for WebGL1, which can lead to blurry rendering on high-DPI displays. Consider allowing a user-configurable override or fallback ratio that’s less than devicePixelRatio but still not forced to exactly 1.


    72-81: Validate FPS drop calculation usage.
    this.droppedFpsPercentage is assigned but not visibly consumed. If your design intends to track or display it, ensure there's a follow-up or a binding to handle the value. Otherwise, consider removing it to clean up the code.

    src/devtools.ts (1)

    152-152: Confirm holdingBlock lifecycle.
    When setting holdingBlock on a global object, ensure that references to this.holdingBlock remain valid throughout the rendering lifecycle. If window.world changes or re-initializes, a stale reference might lead to inconsistent behavior.

    src/resourcesManager.ts (3)

    42-44: Consider extensibility in event definitions.
    The ResourceManagerEvents type defining only assetsTexturesUpdated is fine for now, but you may want to plan for future events such as a “resourcesLoadingFailed” or “resourcesLoadingStarted.”


    95-183: Check concurrency and partial loading states when updating assets.
    The core logic performs several async steps—loading source data, merging custom textures, and building atlases. If the game triggers multiple calls to updateAssetsData in quick succession, consider guarding against race conditions (e.g., the abortController is used, but always confirm earlier calls are properly disposed/cancelled).

    + // Example check before starting a new update:
    + if (!this.abortController.signal.aborted) {
    +   this.abortController.abort();
    +   this.abortController = new AbortController();
    + }

    185-194: Allow optional on-demand debug images.
    Downloading debug atlases is a great debugging feature. Consider controlling access with a debug or admin-only setting in production builds to avoid unintentional large file downloads.

    renderer/viewer/lib/entities.ts (2)

    248-248: Inject WorldRendererThree for stronger coupling to rendering logic.
    Replacing a generic viewer parameter with a WorldRendererThree reference simplifies entity rendering. However, remain cautious if you ever need an abstraction layer—this tight coupling to a specific renderer may limit future backend changes.


    352-352: Conditional ear rendering.
    renderEars references this.worldRenderer.worldRendererConfig; ensure it remains valid across changes. You might also consider a fallback if the config is missing, preventing null or undefined accesses.

    renderer/viewer/three/graphicsBackend.ts (3)

    16-17: Avoid global references unless absolutely necessary.
    Storing the renderer in globalThis.renderer and window.panoramaRenderer can lead to potential side effects and difficulties in debugging. Consider limiting these to a single debugging file or pass them as function arguments where possible.


    35-39: Expand prepareResources or document its minimal implementation.
    Currently, prepareResources only sets a local version and does not handle progress reporting or resource loading. If additional logic is planned, add a TODO comment or partial implementation to guide future maintainers.


    54-66: Ensure robust cleanup in disconnect().
    This method disposes renderers and sets them to null; confirm that there are no event listeners or references left attached to the dom or other modules, which could lead to memory leaks.

    src/index.ts (2)

    124-127: Consider lazy initialization in loadBackend().
    If backend creation is expensive, you could wrap it in a lazy loader or check if it's already loaded. This helps avoid redundant calls when transitions happen (e.g., repeated user actions).


    220-232: Validate session storage delay logic.
    The session-based “delayLoadUntilFocus” approach can block the game load if the user never focuses the tab. Confirm this is intended or consider a maximum timeout/autostart fallback.

    src/resourcePack.ts (2)

    366-378: Ensure consistent error handling in blockstates/models reading.
    You reset resources.customBlockStates and resources.customModels to undefined if reading fails. Confirm you also handle partial reads or partial failures—some files might be valid while others are missing.


    567-569: Clarify usage of resourcepackReload.
    resourcepackReload calls updateTextures, but if the environment or resources have changed drastically (e.g., version mismatch), the function might need additional logic. Document or handle version changes more explicitly.

    src/watchOptions.ts (4)

    19-25: Add explicit parameter types for updateTouch.
    While the logic looks correct, consider adding a type definition for parameter o to maintain clarity and catch potential type errors in TypeScript.


    44-51: Use enumeration or constants instead of magic numbers.
    Using numeric values (0, 1, 2) to represent stats-visibility modes is fine but consider a small enum or named constants to make the code more descriptive and robust.


    89-89: Maintain consistency in watchers.
    You're mixing watchValue and subscribeKey for different options properties. While this works, consider standardizing on one approach for harmony and simpler maintenance.

    Also applies to: 91-91


    107-108: Commented-out code for neighborChunkUpdates.
    Leaving commented code for reference is typically fine in an experimental feature. If you don’t plan to resurrect it soon, consider removing to keep the file clean.

    src/react/DebugOverlay.tsx (1)

    150-150: Left debug overlay display.
    This neatly replaces the old custom entries approach. Just keep in mind how frequently getDebugOverlay() might be called, to avoid performance overhead in heavy frames.

    src/appViewer.ts (1)

    46-62: GraphicsBackend interface.
    Method signatures are clear, and the return types are well-defined. Consider ensuring getDebugOverlay returns a typed object to reduce casting overhead.

    renderer/viewer/lib/worldrendererCommon.ts (5)

    6-25: Consider removing unused imports and constants.
    The newly added imports for AtlasParser, ResourcesManager, DisplayWorldOptions, WorldDataEmitter, WorldRendererThree, and SoundSystem appear relevant. However, const appViewer = undefined (line 25) seems unused. Removing it may help keep the codebase clear of placeholders if it’s not necessary for upcoming changes.


    64-64: Validate negative-Y handling.
    The default worldSizeParams restricts the minimum Y to 0, even though modern Minecraft versions can have negative world depths. If you plan to support newer world formats, consider making this dynamic or clarifying that negative depths are out of scope.


    347-350: Consider capturing resource loading errors.
    await this.resourcesManager.loadMcData(version) and updateAssetsData(...) can throw. Currently, there’s no error fallback. Ensure that upstream calls or the caller handle these exceptions to prevent silent failures during version or asset initialization.


    372-401: Ensure workers are properly initialized before asset updates.
    In updateAssetsData(), throwing an error if this.workers.length === 0 might be abrupt in asynchronous scenarios. Depending on load sequences, you may wish to queue or defer asset updates until workers are confirmed.


    498-498: Remove or implement empty method.
    updateEntity(e: any) is currently a no-op. If it’s just a placeholder, remove it or add a TODO comment to clarify future usage.

    renderer/viewer/lib/worldrendererThree.ts (1)

    535-547: Camera roll feature is a nice addition.
    setCameraRoll modifies the camera quaternion for immersive rotation. Keep an eye out for potential conflicts with other camera transformations—testing advanced camera interactions (like VR) helps ensure no unintended overrides.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 2277020 and dc073cd.

    📒 Files selected for processing (47)
    • renderer/viewer/index.js (0 hunks)
    • renderer/viewer/lib/entities.ts (10 hunks)
    • renderer/viewer/lib/guiRenderer.ts (1 hunks)
    • renderer/viewer/lib/threeJsSound.ts (1 hunks)
    • renderer/viewer/lib/viewer.ts (0 hunks)
    • renderer/viewer/lib/viewerWrapper.ts (0 hunks)
    • renderer/viewer/lib/worldDataEmitter.ts (2 hunks)
    • renderer/viewer/lib/worldrendererCommon.ts (11 hunks)
    • renderer/viewer/lib/worldrendererThree.ts (12 hunks)
    • renderer/viewer/three/documentRenderer.ts (1 hunks)
    • renderer/viewer/three/graphicsBackend.ts (1 hunks)
    • renderer/viewer/three/panorama.ts (1 hunks)
    • src/appViewer.ts (1 hunks)
    • src/botUtils.ts (1 hunks)
    • src/cameraRotationControls.ts (1 hunks)
    • src/chatUtils.ts (1 hunks)
    • src/connect.ts (1 hunks)
    • src/controls.ts (1 hunks)
    • src/core/progressReporter.ts (2 hunks)
    • src/dayCycle.ts (1 hunks)
    • src/devtools.ts (3 hunks)
    • src/entities.ts (2 hunks)
    • src/globals.d.ts (1 hunks)
    • src/index.ts (12 hunks)
    • src/inventoryWindows.ts (3 hunks)
    • src/loadSave.ts (1 hunks)
    • src/mineflayer/cameraShake.ts (2 hunks)
    • src/mineflayer/playerState.ts (1 hunks)
    • src/mineflayer/plugins/mouse.ts (1 hunks)
    • src/mineflayer/plugins/ping.ts (1 hunks)
    • src/optimizeJson.ts (1 hunks)
    • src/panorama.ts (0 hunks)
    • src/react/BookProvider.tsx (1 hunks)
    • src/react/DebugOverlay.tsx (5 hunks)
    • src/react/HotbarRenderApp.tsx (3 hunks)
    • src/react/IndicatorEffectsProvider.tsx (1 hunks)
    • src/react/MinimapProvider.tsx (1 hunks)
    • src/reactUi.tsx (1 hunks)
    • src/rendererUtils.ts (2 hunks)
    • src/resourcePack.ts (4 hunks)
    • src/resourcesManager.ts (2 hunks)
    • src/shims/minecraftData.ts (1 hunks)
    • src/sounds/botSoundSystem.ts (1 hunks)
    • src/sounds/soundsMap.ts (1 hunks)
    • src/topRightStats.ts (0 hunks)
    • src/vr.ts (1 hunks)
    • src/watchOptions.ts (3 hunks)
    💤 Files with no reviewable changes (5)
    • renderer/viewer/index.js
    • renderer/viewer/lib/viewer.ts
    • src/topRightStats.ts
    • renderer/viewer/lib/viewerWrapper.ts
    • src/panorama.ts
    🧰 Additional context used
    🧬 Code Definitions (13)
    src/globals.d.ts (1)
    src/appViewer.ts (1) (1)
    • appViewer (161-161)
    src/cameraRotationControls.ts (1)
    src/appViewer.ts (1) (1)
    • appViewer (161-161)
    src/rendererUtils.ts (1)
    src/appViewer.ts (1) (1)
    • appViewer (161-161)
    renderer/viewer/three/graphicsBackend.ts (4)
    src/appViewer.ts (7) (7)
    • GraphicsBackendLoader (46-46)
    • GraphicsInitOptions (33-38)
    • startPanorama (113-121)
    • prepareResources (123-127)
    • startWorld (129-144)
    • DisplayWorldOptions (40-44)
    • GraphicsBackend (48-61)
    renderer/viewer/three/panorama.ts (1) (1)
    • PanoramaRenderer (23-204)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (23-548)
    src/core/progressReporter.ts (1) (1)
    • ProgressReporter (5-18)
    src/mineflayer/cameraShake.ts (1)
    src/appViewer.ts (1) (1)
    • appViewer (161-161)
    renderer/viewer/lib/entities.ts (1)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (23-548)
    src/resourcePack.ts (4)
    src/appViewer.ts (1) (1)
    • appViewer (161-161)
    renderer/viewer/lib/entity/armorModels.ts (1) (1)
    • armorTextures (19-35)
    src/mineflayer/plugins/mouse.ts (1) (1)
    • bot (142-149)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (23-548)
    src/inventoryWindows.ts (1)
    src/appViewer.ts (1) (1)
    • appViewer (161-161)
    renderer/viewer/lib/worldrendererCommon.ts (3)
    src/appViewer.ts (2) (2)
    • appViewer (161-161)
    • DisplayWorldOptions (40-44)
    src/resourcesManager.ts (1) (1)
    • ResourcesManager (75-201)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (21-249)
    src/watchOptions.ts (2)
    src/globalState.ts (1) (1)
    • miscUiState (109-129)
    src/optionsStorage.ts (2) (2)
    • watchValue (213-235)
    • options (186-191)
    src/react/DebugOverlay.tsx (1)
    src/appViewer.ts (1) (1)
    • appViewer (161-161)
    renderer/viewer/lib/worldrendererThree.ts (3)
    renderer/viewer/lib/entities.ts (1) (1)
    • Entities (209-1028)
    src/appViewer.ts (2) (2)
    • GraphicsInitOptions (33-38)
    • DisplayWorldOptions (40-44)
    renderer/viewer/lib/hand.ts (1) (1)
    • getMyHand (5-35)
    src/vr.ts (1)
    src/appViewer.ts (1) (1)
    • appViewer (161-161)
    🪛 GitHub Check: build-and-deploy
    renderer/viewer/three/panorama.ts

    [failure] 9-9:
    Cannot find module '../lib/viewer' or its corresponding type declarations.

    src/react/HotbarRenderApp.tsx

    [failure] 113-113:
    Property 'itemsAtlasParser' does not exist on type 'ResourcesManager'.

    src/inventoryWindows.ts

    [failure] 49-49:
    Property 'itemsAtlasParser' does not exist on type 'ResourcesManager'.

    🪛 ESLint
    renderer/viewer/lib/worldrendererThree.ts

    [error] 314-314: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)

    🔇 Additional comments (88)
    src/mineflayer/plugins/mouse.ts (1)

    146-146: Double-check removal of display manager.

    By commenting out createDisplayManager, the display manager responsible for block breaking visuals is disabled. If these visuals are still desired, consider providing an alternative or confirming that this functionality is no longer needed.

    renderer/viewer/lib/guiRenderer.ts (1)

    5-5: Consolidated import looks good.

    The unified import of both getLoadedImage and versionToNumber from mc-assets/dist/utils reduces confusion and improves maintainability. No issues spotted.

    renderer/viewer/three/panorama.ts (1)

    199-204: Properly dispose resources.

    The dispose() method clears the scene, aborts ongoing processes, and disposes the world renderer. This is a good pattern for preventing memory leaks. Confirm that any existing event listeners on global objects (like document.body) are also removed to avoid side effects.

    renderer/viewer/lib/threeJsSound.ts (3)

    5-8: Well-designed interface for sound system.

    The SoundSystem interface provides a clean abstraction with just two essential methods: playSound for audio playback and destroy for resource cleanup. This design allows for different sound system implementations while maintaining a consistent API.


    10-15: Good implementation of the SoundSystem interface.

    The ThreeJsSound class properly implements the SoundSystem interface and maintains a clear state with the audioListener and activeSounds properties. The constructor accepts a worldRenderer which follows dependency injection principles.


    48-60: Comprehensive resource cleanup in destroy method.

    The destroy method properly stops all active sounds, disconnects them from the audio context, and cleans up the audio listener. This prevents memory leaks and ensures complete cleanup of audio resources.

    src/globals.d.ts (1)

    15-15: Global appViewer declaration added.

    The declaration adds a new global constant appViewer which aligns with the PR objective of creating a new stable layer to separate the application code from the graphics code. The declaration is properly typed with an import from './appViewer'.

    src/loadSave.ts (1)

    6-6: Updated import path for versionToNumber utility.

    The import path has been changed from 'renderer/viewer/prepare/utils' to 'renderer/viewer/common/utils' as part of the broader code reorganization. The function reference remains the same, so functionality shouldn't be affected.

    src/sounds/botSoundSystem.ts (2)

    2-2: Updated import path for versionToNumber utility.

    The import path has been changed from 'renderer/viewer/prepare/utils' to 'renderer/viewer/common/utils' as part of the broader code reorganization. The function reference remains the same, so functionality shouldn't be affected.


    49-54:

    ❓ Verification inconclusive

    Verify compatibility with new AppViewer structure.

    This code references the global viewer object to play sounds, but the PR is introducing a new appViewer structure. Make sure this integration point is properly updated or addressed in related changes.


    🏁 Script executed:

    #!/bin/bash
    # Search for the definition of the playSound method in the viewer/appViewer classes
    # and check how it's being integrated with the sound system.
    
    echo "Searching for playSound method definitions:"
    rg -A 2 "playSound.*=.*function" --glob "*.ts"
    rg -A 2 "playSound.*\(.*\).*{" --glob "*.ts"
    
    echo "Checking for viewer references in sound-related code:"
    rg "viewer\.playSound" --glob "*.ts"
    
    echo "Checking for appViewer sound-related references:"
    rg "appViewer\..*[sS]ound" --glob "*.ts"

    Length of output: 657


    Sound Integration Check: Confirm Use of New AppViewer Structure

    It appears that the current implementation in src/sounds/botSoundSystem.ts still invokes viewer.playSound (lines 49–54). Our recent code search did not reveal any references to an appViewer handling sound, which raises the question of whether the new appViewer structure is intentionally omitted here or if the integration should be updated. Please verify that:

    • The use of the global viewer is deliberate (e.g., for backwards compatibility) or if it should be replaced with appViewer to align with the new architecture.
    • Any related changes for the new sound integration in appViewer are correctly applied elsewhere in the PR.
    src/botUtils.ts (1)

    1-1: Import path updated to reflect new module structure.

    The import path has been updated from 'renderer/viewer/prepare/utils' to 'renderer/viewer/common/utils', which aligns with the architectural changes introduced in this PR to separate application code from graphics code.

    src/cameraRotationControls.ts (1)

    40-40:

    ✅ Verification successful

    Camera update timestamp property moved to AppViewer.

    The camera update timestamp is now stored in appViewer.lastCamUpdate instead of viewer.world.lastCamUpdate, which aligns with the new architecture that separates application code from graphics code.

    However, I notice that line 42 still references viewer.world.freeFlyMode. Let's check if this is intentional or if other viewer references should also be updated:


    🏁 Script executed:

    #!/bin/bash
    # Find all remaining references to viewer.world in the codebase
    rg "viewer\.world" --type ts

    Length of output: 10077


    Update: Camera timestamp and free fly mode usage verified

    • The camera update timestamp property has been successfully migrated to appViewer.lastCamUpdate in src/cameraRotationControls.ts (line 40) as intended.
    • The search confirms that while the timestamp has moved, references to viewer.world (including freeFlyMode and related properties) remain widespread across the codebase (e.g., in src/controls.ts and others). This indicates that leaving viewer.world.freeFlyMode in place (line 42) is intentional and consistent with the current architecture separating application and graphics responsibilities.
    src/reactUi.tsx (2)

    56-62: Added Firefox-specific styling.

    Browser detection for Firefox has been added with a custom CSS property to adjust styling specifically for Firefox browsers. This is a good practice for handling browser-specific UI adjustments.


    64-68: Added iPhone-specific bottom HUD spacing.

    Device detection for iPhone has been added to adjust the bottom HUD spacing, likely to account for the notch/home indicator on iPhones. There's a TODO comment about extending this to iPads.

    Could you test this on different iPhone models to ensure the 21px value works universally? Also, consider addressing the TODO to determine if this adjustment should apply to iPads as well.

    src/react/BookProvider.tsx (1)

    1-1: Import path updated correctly.

    The import of versionToNumber has been updated to use the new common utils location, which is consistent with the architectural changes in this PR.

    src/chatUtils.ts (1)

    5-5: Import path updated correctly.

    The import of versionToNumber has been updated to use the new common utils location, which aligns with the architectural refactoring in this PR.

    src/optimizeJson.ts (1)

    1-1: Import path updated correctly.

    The import of versionToNumber has been updated to use the new common utils location, which is consistent with the architectural changes being made across the codebase.

    src/mineflayer/plugins/ping.ts (1)

    1-1: Import path updated correctly.

    The import of versionToNumber has been updated to use the new common utils location, which aligns with the architectural refactoring in this PR.

    src/mineflayer/playerState.ts (1)

    43-43:

    ❓ Verification inconclusive

    Constructor visibility changed from private to public.

    The constructor for PlayerStateManager is now public, which changes the instantiation control pattern. This aligns with the architectural refactoring goals of the PR to make components more modular and independently instantiable.

    However, keep in mind this change could potentially lead to multiple instances of PlayerStateManager being created outside the singleton pattern, which might cause unexpected behavior if the class is not designed to handle multiple instances properly.


    🏁 Script executed:

    #!/bin/bash
    # Check if PlayerStateManager is instantiated directly in other files after this change
    rg "new PlayerStateManager\(\)" --type ts

    Length of output: 120


    Action Required: Verify Controlled Instantiation of PlayerStateManager

    After running the search, the only occurrence of a direct instantiation (new PlayerStateManager()) was found in src/mineflayer/playerState.ts (where it is assigned to this.instance). This indicates that, at the moment, the singleton instantiation is maintained internally. However, now that the constructor is public, any future external calls to new PlayerStateManager() could unintentionally create additional instances. Please ensure that:

    • The public constructor is documented clearly to indicate that instantiation should only occur within controlled contexts.
    • External parts of the codebase avoid instantiating PlayerStateManager directly.
    • The singleton behavior is either safeguarded within the class design or additional checks are put in place to prevent multiple uncontrolled instances.
    src/sounds/soundsMap.ts (1)

    3-3: Import path updated appropriately

    The import path for utility functions has been updated from 'renderer/viewer/prepare/utils' to 'renderer/viewer/common/utils' as part of the broader refactoring to separate application code from rendering code.

    src/shims/minecraftData.ts (1)

    1-1: Import path updated correctly

    The import path for versionToNumber has been updated from 'renderer/viewer/prepare/utils' to 'renderer/viewer/common/utils', consistent with the architectural changes being made across the codebase.

    src/react/MinimapProvider.tsx (1)

    5-5: Import path updated consistently

    The import path has been correctly updated to use the new module location for utility functions, maintaining consistency with other files in this PR.

    src/vr.ts (1)

    194-195: Camera control updated to use the new AppViewer architecture

    The code has been updated to use the new AppViewer backend for camera control instead of the deprecated viewer implementation. This change now passes the bot's actual yaw and pitch values instead of using a calculated yawOffset, which should provide more accurate camera positioning in VR mode.

    Please verify that VR camera orientation works correctly with this change, as you've commented out the original implementation that used yawOffset. The current implementation might behave differently since it's using the bot's actual orientation values.

    src/controls.ts (2)

    929-931: Updated keyboard shortcut refactor is well implemented.

    The updated condition for the 'KeyL' keyboard shortcut adds proper modifier key checks, making the clear console command more deliberate and less likely to be triggered accidentally.


    932-938:

    ✅ Verification successful

    Good addition of a developer feature to control resource loading.

    This new keyboard shortcut adds a useful debugging capability that aligns with the PR's objective of enhancing performance. The toggle implementation for the session storage item is clean and follows the existing code patterns.


    🏁 Script executed:

    #!/bin/bash
    # Check where the session storage flag is used in the codebase
    rg "delayLoadUntilFocus" -A 5 -B 5

    Length of output: 1148


    Approved: Clean Implementation for Resource Loading Toggle

    The new key binding consistently toggles the debugging flag for delayed resource loading. Our search confirmed that the sessionStorage.delayLoadUntilFocus flag is used both in src/controls.ts and in src/index.ts, aligning with the intended behavior. The implementation is clear, follows existing patterns, and integrates well with the connect logic.

    • Verified usage in both src/controls.ts and src/index.ts
    • Consistent toggle behavior for the session storage flag
    src/connect.ts (1)

    46-47:

    ❓ Verification inconclusive

    Function signature simplified appropriately.

    Removing the importBlockstatesModels parameter aligns with the PR's goal of separating app code from renderer code. The block states models loading is likely moved to the new ResourcesManager mentioned in the PR objectives.

    However, let's verify there are no usages of this function with the removed parameter:


    🏁 Script executed:

    #!/bin/bash
    # Check for any calls to loadMinecraftData with a second parameter
    rg "loadMinecraftData\(.*,.+\)" --type ts

    Length of output: 43


    Action: Verify that no call sites pass a second parameter to loadMinecraftData

    The function signature now accepts only a single parameter (version), which aligns with the PR’s objective of decoupling app code from renderer responsibilities (the block states models loading has been moved to the new ResourcesManager). An automated search using

    rg "loadMinecraftData\(.*,.+\)" --type ts

    did not return any results. However, since the output was minimal and might not be exhaustive, please perform a manual verification to ensure that no call to loadMinecraftData is inadvertently passing a second parameter.

    • Confirm that the removal of the importBlockstatesModels parameter hasn’t led to any hidden calls with two arguments.
    • Double-check any usage sites of loadMinecraftData in the codebase, ensuring they pass the single version parameter.
    renderer/viewer/lib/worldDataEmitter.ts (2)

    8-8: Good addition of reactive state management.

    Adding the Valtio proxy import supports the new reactive state pattern being implemented throughout the codebase.


    30-33:

    ❓ Verification inconclusive

    Excellent implementation of reactive properties for cursor state.

    This addition provides a centralized reactive state for tracking cursor block information, which aligns with the PR's goal of restructuring the rendering pipeline. The use of Valtio's proxy will allow components to subscribe to these state changes efficiently.

    Since you've added this reactive state but removed the mouse event listener (as mentioned in the AI summary), make sure there's code elsewhere that updates these reactive properties:


    🏁 Script executed:

    #!/bin/bash
    # Find where the cursor reactive properties are being updated
    rg "reactive\.cursorBlock" --type ts
    rg "reactive\.cursorBlockBreakingStage" --type ts

    Length of output: 90


    Action Required: Verify Updates to Reactive Properties

    Excellent implementation of reactive properties for cursor state. However, our search (using rg "reactive\.cursorBlock" and rg "reactive\.cursorBlockBreakingStage") did not reveal any instances where these properties are updated. Given that the mouse event listener was removed, please verify manually that the logic to update these properties exists elsewhere. Specifically:

    • Confirm whether updates to reactive.cursorBlock and reactive.cursorBlockBreakingStage occur in a different part of the codebase.
    • If the update logic has been moved or is expected to be triggered indirectly, indicate the precise location or mechanism responsible for these updates.

    Once you’ve verified that the reactive state is properly maintained, we can be confident that the restructuring aligns with the overall pipeline improvements.

    src/core/progressReporter.ts (2)

    184-187: Good enhancement to logging flexibility.

    Adding the optional group parameter to createConsoleLogProgressReporter allows for better organization of logs by categorizing messages with their source or context. This change improves debugging capabilities, especially when multiple components are reporting progress simultaneously.


    221-230: Well-implemented null object pattern.

    The createNullProgressReporter function is a clean implementation of the null object pattern. This is useful for cases where a progress reporter interface is required, but no actual reporting is needed, allowing for consistent API usage without conditional checks throughout the codebase.

    src/mineflayer/cameraShake.ts (2)

    55-56: Simplified camera roll implementation.

    The camera roll application has been refactored to use the new appViewer.backend?.setRoll() method instead of direct quaternion manipulations. This aligns with the PR's goal of abstracting rendering operations behind a stable API layer.


    70-72: Event listener moved outside of bot creation.

    Moving the 'hurtAnimation' event listener outside of the 'mineflayerBotCreated' event improves the event handling structure. This ensures that camera shake can be triggered consistently regardless of when the bot is created.

    src/entities.ts (2)

    2-2: Updated import path for utility function.

    The import path for versionToNumber has been updated from 'renderer/viewer/prepare/utils' to 'renderer/viewer/common/utils'. This change aligns with the overall restructuring of the codebase.


    38-40: Intentional early return breaks entity functionality.

    Adding a return statement at the beginning of the 'gameLoaded' event handler prevents the execution of entity tracking, auto-jump, animation, and skin loading functionality. This deliberately disables this functionality as part of separating application (mineflayer) code from the renderer.

    This change appears intentional based on the PR description's goal to separate application code from rendering code, but it significantly changes game behavior.

    Could you confirm that disabling all entity-related functionality in this handler is intentional? This includes:

    • Player tracking
    • Auto-jump functionality
    • Player animations
    • Skin loading

    If this is temporary during the refactoring process, consider adding a TODO comment to indicate future restoration.

    src/react/HotbarRenderApp.tsx (1)

    127-127: Event handler updated to use new resource management system.

    The event listener has been changed from viewer.world.renderUpdateEmitter.on('textureDownloaded', upHotbarItems) to appViewer.resourcesManager.on('assetsTexturesUpdated', upHotbarItems). This aligns with the PR's goal of centralizing resource management through the new appViewer.resourcesManager.

    Also applies to: 200-200

    src/rendererUtils.ts (1)

    77-78: Confirm commented-out camera updates.
    These lines previously updated the camera's FOV, but they are now commented out. Ensure that this is intentional and that camera FOV updates are handled elsewhere as needed for the new rendering pipeline.

    Would you like a script to search for all references to viewer.camera assignments to verify they are safely replaced?

    renderer/viewer/three/documentRenderer.ts (1)

    142-151: Properly disposing of resources
    Disposing the renderer, canceling animation frames, and clearing intervals ensure minimal memory leaks. This is a well-structured cleanup method.

    src/inventoryWindows.ts (2)

    58-58: Event reference looks consistent with the new resources flow.
    Using appViewer.resourcesManager.on('assetsTexturesUpdated', ...) appears correct if this event is properly emitted. Ensure that assetsTexturesUpdated aligns with the new resource manager events.


    9-9:

    ✅ Verification successful

    Check correctness of utility path update.
    This import path changed from renderer/viewer/prepare/utils to renderer/viewer/common/utils. Verify that all references to versionToNumber in your codebase have been updated consistently.


    🏁 Script executed:

    #!/bin/bash
    # Verify usage of versionToNumber references across files
    rg 'versionToNumber'

    Length of output: 3232


    Utility Path Update Verified

    • The import update in src/inventoryWindows.ts is correct and now points to 'renderer/viewer/common/utils' as intended.
    • A repository-wide search shows that nearly all internal modules import versionToNumber from 'renderer/viewer/common/utils'.
    • Note that a few files (e.g. src/optionsGuiScheme.tsx, renderer/viewer/lib/guiRenderer.ts, and src/mineflayer/java-tester/commands.ts) import versionToNumber from external packages (mc-assets/dist/utils or flying-squid/dist/utils). Please confirm these exceptions are intentional.
    src/devtools.ts (2)

    24-24: Ensure window.world is available before rendering debug chunks.
    If window.world hasn't been initialized yet or if bot.entity.position is undefined, the optional chaining might result in unexpected or silent failures. Consider asserting or validating these objects' states before calling getLoadedChunksRelative.


    166-210: Evaluate potential overhead & memory usage in performance stats code.
    This block introduces a performance-debugging mechanism that records marks, intervals, and per-second metrics. While this is useful, consider adding clean-up logic or bounding the recorded labels to avoid potential memory bloat in long sessions.

    src/resourcesManager.ts (5)

    1-20: Imports seem well-structured.
    These imports appear to be properly scoped for resource management, referencing both internal and external libraries. There's no immediate concern, and the usage of typed-emitter and MC-assets modules is beneficial for type safety.


    46-66: Good use of a dedicated class to hold loaded resources.
    The LoadedResources class cleanly groups atlas parsers, custom textures, and versioning. For maintainability, keep an eye on possible custom properties that might be better split into multiple classes if this grows larger.


    68-73: Well-defined request interface.
    This interface effectively communicates required vs. optional properties for updating assets. Good job using a minimal set of fields.


    75-94: Initialize shared state carefully.
    The ResourcesManager class fields (like currentResources, abortController) are properly declared. Ensure that usage of abortController is consistent across all asynchronous functions and that partial updates don’t leave currentResources in an incomplete state.


    196-201: Reset resources to avoid memory leaks.
    Aborting and clearing this.currentResources is correct. Double-check that all references (e.g., event listeners, UI references) are equally cleaned up to prevent memory retention.

    renderer/viewer/lib/entities.ts (8)

    27-27: New import indicates rendering refactor.
    Importing WorldRendererThree here rather than a legacy viewer structure aligns with the overall refactoring of the rendering pipeline.


    257-257: Scene removal aligns with new architecture.
    Removing entities from this.worldRenderer.scene is consistent with the new refactor. Ensure that any references to scene objects (e.g., bounding boxes) are also released to avoid orphaned objects in memory.


    279-281: Safely adding or removing from the scene.
    Double-check concurrency with mid-render modifications. Usually, Three.js can handle scene modifications between frames, but if these calls occur in render callbacks, consider deferring them or verifying thread safety in multi-threaded environments.


    288-288: Dependency on worldRenderer.viewerPosition.
    Relying on this.worldRenderer.viewerPosition is straightforward. Ensure the property’s updates are in step with entity logic to prevent culling or distance-based rendering anomalies.


    313-313: Prevent accidental visibility toggles.
    this.worldRenderer.finishedChunks[chunkKey] is a neat check for finished chunk geometry. If chunks load or unload rapidly, watch for flickers or missed updates (e.g., partial chunk states).


    507-507: Passing this.worldRenderer.material for block meshes.
    This works well for uniform material usage. If future expansions demand per-block or per-model customization, consider separating materials.


    606-606: Remove object from new scene reference.
    Ensure garbage collection for geometry, textures, and custom data. As you remove the entity from the scene, confirm that any references in local caches or maps are cleared.


    678-678: Forwarding this.worldRenderer to getEntityMesh.
    This is consistent with the architectural shift away from the old viewer. Confirm that getEntityMesh remains renderer-agnostic if you plan to support multiple backends.

    renderer/viewer/three/graphicsBackend.ts (5)

    10-11: Clarify the implications of disabling color management.
    Disabling THREE.ColorManagement.enabled can create inconsistencies with color spaces across textures, materials, and post-processing pipelines. Confirm that this setting suits your project's rendering requirements, especially if you expect sRGB color spaces or HDR.


    21-24: Confirm proxy usage is thread-safe and efficient.
    The creation of a proxy object (worldState) is straightforward, but ensure that the usage patterns (e.g., frequent writes/reads) do not cause performance overhead or unexpected reactivity.


    26-33: Handle potential concurrency in startPanorama().
    If multiple calls to startPanorama() occur rapidly (e.g., from different user actions), the early return on line 27 might skip a needed reinitialization. Confirm that ignoring calls when worldRenderer exists is the intended behavior.


    40-52: Check disposal order in startWorld().
    Disposing the panorama renderer before creating a new world renderer is reasonable, but if the user quickly reenters a panorama state, resources might thrash. Confirm that this is acceptable and that all allocated resources are properly released.


    72-94: Validate interface completeness.
    The backend object exposes key methods like startPanorama, disconnect, etc. Confirm that all expected rendering-lifecycle methods are covered, considering future expansions (e.g., hooking into scene updates or resource reloading).

    src/index.ts (3)

    128-133: Verify single-file build mode readiness.
    The environment block handles SINGLE_FILE_BUILD_MODE, but confirm that external references (such as filesystem handling in the rest of the code) won’t break or degrade user experience in single-file mode.


    708-716: Revisit the commented-out world rendering readiness logic.
    This block checks for chunks finishing and triggers a final render. If you rely on viewer.world to confirm chunk readiness, ensure an equivalent mechanism exists in the new approach or consider reviving this code if still needed.


    737-760: Initialize the viewer carefully within spawn event.
    Running appViewer.startWorld(...), hooking watchOptionsAfterWorldViewInit(), and calling appViewer.worldView.init(...) can lead to race conditions if the bot is not fully ready. Confirm that the Bot’s world data is stable by the time these are called.

    src/resourcePack.ts (1)

    527-566: Watch for sequence issues in updateTextures.
    The function loads custom textures, updates replacable textures, and calls updateAssetsData. If updateAssetsData depends on the new textures, ensure all textures are fully loaded first (e.g., checking image readiness). Otherwise, you might cause rendering artifacts.

    src/watchOptions.ts (6)

    54-79: Validate window focus event listeners.
    The new focus/blur listeners dynamically update the FPS limit. Overall, the approach looks solid. Make sure to confirm that no stale references arise when re-initializing or hot-reloading.

    Also applies to: 81-82


    84-87: Clean configuration management.
    Storing clipWorldBelowY and extraBlockRenderers in appViewer.inWorldRenderingConfig appears consistent and straightforward. No concerns.


    95-96: Double-check bot availability.
    Ensure bot is always defined here; otherwise, referencing bot.supportFeature('blockStateId') may throw if bot is uninitialized or null.


    98-100: Confirm mineflayerBotCreated timing.
    It’s good that you handle configuration as soon as the bot is created. Just confirm that relevant watchers like subscribeKey(options, 'newVersionsLighting', ...) have run or won’t conflict with this event.


    102-104: Straightforward refactor to starfield rendering.
    Using a single watcher for starfieldRendering is concise. Continue with this approach for other features if possible.


    118-120: Excellent approach for world-view settings.
    Applying user preferences for chunk distance, ears rendering, hand visibility, and bobbing to appViewer.inWorldRenderingConfig keeps the code modular.

    src/react/DebugOverlay.tsx (4)

    41-41: Graceful fallback for no render backend.
    Using “No render backend” as a fallback is fine; ensure code paths handle a nullish appViewer.backend consistently throughout.


    108-108: Check for cursor state readiness.
    bot.mouse.getCursorState() might fail if bot.mouse is absent or uninitialized. Safeguard with optional checks if needed.


    137-137: Conditional entity string display.
    Good approach verifying entitiesString before rendering. This ensures no runtime errors if getDebugOverlay() returns undefined.


    194-194: Right debug overlay entries.
    Accommodating an additional data region for the debug overlay is flexible. Confirm that calling getDebugOverlay().right is always valid.

    src/appViewer.ts (5)

    1-16: Imports & basic setup look consistent.
    Imports from the renderer library, valtio, and local modules are well organized. Keep an eye on any cyclical dependencies.


    17-45: Interfaces define a clear contract.
    Creating separate interfaces for state (WorldReactiveState), backend config (GraphicsBackendConfig), and initialization (GraphicsInitOptions, DisplayWorldOptions) enhances maintainability.


    63-159: AppViewer class architecture is cohesive and modular.

    • Using resourcesManager is a good approach for reusability.
    • loadBackend, resetBackend, startPanorama, and startWorld are well separated.
    • The queued display approach stands out as a flexible way of deferring actions.

    164-179: initialMenuStart flow.
    Recreating a backend if in world mode then starting the panorama is logical. Ensure the future usage (commented lines 170-177) is either removed or properly integrated later.


    180-193: Automatic modal stack checks.
    Setting rendering based on modals is a neat approach. Confirm that heavy modals or concurrent modals don’t cause multiple redundant calls.

    renderer/viewer/lib/worldrendererCommon.ts (6)

    35-46: Configuration fields look good.
    Expanding defaultWorldRendererConfig with new fields—like viewBobbing, smoothLighting, enableLighting, etc.—improves flexibility. Ensure that any new options (e.g., clipWorldBelowY, starfield) are respected throughout the rendering logic to avoid confusion or partially implemented features.


    147-148: New class fields integrate cleanly.
    Introducing itemsTexture, soundSystem, and worldRendererConfig into the class is coherent with the refactor. They align with the new resource-driven design.

    Also applies to: 152-152


    154-181: Check error handling in constructor and worker initialization.
    The constructor and init() method rely on setVersion to load resources asynchronously. If these calls fail, the promise chain may silently swallow errors. Consider a try/catch or .catch() block to handle failures gracefully and maintain robust initialization.


    405-406: Verify clipping logic and chunk iteration.
    Using worldMinYRender ensures that chunks below a certain threshold are skipped. If future updates include negative Y-level, verify that the floor logic aligns correctly to avoid partial chunk rendering or skipping.

    Also applies to: 433-433, 462-465


    480-496: Review queued block updates for concurrency conflicts.
    The async queue approach in setBlockStateId may temporarily block updates for the affected chunk. Consider adding test coverage to ensure no race conditions occur when multiple updates happen rapidly.


    735-756: Resource cleanup is well-structured.
    Terminating workers, disposing of textures, and clearing event listeners helps avoid memory leaks. This explicit teardown supports the new resource-driven design effectively.

    renderer/viewer/lib/worldrendererThree.ts (4)

    9-21: Imports appear relevant for new features.
    The introduction of DisplayWorldOptions and GraphicsInitOptions aligns with the rewritten resource handling. Similarly, getMyHand and Entities usage match the updated 3D rendering approach.


    33-38: Additional fields improve scene setup.
    Declaring cameraRoll, a dedicated scene, and ambient/directional lights is a clean approach. Centralizing entity management via the entities field also clarifies responsibilities for rendering.


    49-68: Validate critical errors in constructor flow.
    The constructor throws if resourcesManager is missing, but other potential issues (e.g., invalid config) are not caught. Consider a more robust strategy for fallback or user feedback if initialization partially fails.


    70-82: Entity updates and scene resets appear sound.
    updateEntity(e) calls into this.entities.update(...) for posture updates, and resetScene() re-initializes core lighting and camera properties. These additions fit well into the new event-driven design.

    @Phoenix616
    Copy link

    TLDR

    • now its possible to safely destroy the renderer
    • now its possible to switch the renderer backend on the fly!

    @Phoenix616 do you think its a good change? given it changes a lot of code I will try to merge it asap

    Well while I can't say anything regarding the exact implementation of the change I think the idea behind adjusting it like that is sound. More abstraction of the different part to be independent from each other is definitely a good direction! Besides that it's just a lot of code so I can't say much about it but I didn't notice any obvious flaws while scrolling through it so there's that.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 19, 2025

    Besides that it's just a lot of code so I can't say much about it but I didn't notice any obvious flaws while scrolling through it so there's that.

    Yeah, its not ready yet (though I'm planning to merge it soon already), but just wanted to let you know that there will be no direct access to the renderer from the app's code.
    I bet you were confused with worldCleanup decorators (and inconsistent behavior related to them), with resource data being put into world renderer (now I have ResourceManager so it should cleanup a lot of mess). Yes, I want clear abstractions for the renderer for its stability so I can move into performance improvements much faster. And yes, I finally removed Viewer class and ViewerWrapper (I really wanted to do something with all this a long time ago).

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 5

    ♻️ Duplicate comments (2)
    renderer/viewer/lib/worldrendererCommon.ts (1)

    500-595: ⚠️ Potential issue

    Added comprehensive event connection system

    The new connect method properly sets up all necessary event listeners for world interactions, entity updates, chunk loading, lighting changes, and time tracking. However, there are duplicate 'renderDistance' event handlers at lines 552 and 557 that might cause confusion or lead to repeated logic.

    There appears to be duplicate event handlers for 'renderDistance' at lines 552-555 and 557-561. This could cause unexpected behavior since both handlers update the same properties. Either remove one of the handlers or consolidate them into a single handler.

    - worldEmitter.on('renderDistance', (d) => {
    -   this.viewDistance = d
    -   this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
    - })
    - 
    - worldEmitter.on('renderDistance', (d) => {
    -   this.viewDistance = d
    -   this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
    -   this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
    - })
    + worldEmitter.on('renderDistance', (d) => {
    +   this.viewDistance = d
    +   this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
    +   this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
    + })
    renderer/viewer/three/panorama.ts (1)

    131-138: ⚠️ Potential issue

    Undefined loadedData reference.

    The loadedData variable is referenced but not defined or imported in this file. This will cause a runtime error when this code executes.

    This needs to be either properly imported or passed as a parameter to the function. Fix by adding an import or defining the variable:

    - const fullBlocks = loadedData.blocksArray.filter(block => {
    + // Example fix: Import or access loadedData from the appropriate source
    + const fullBlocks = this.options.resourcesManager.currentResources?.blockstatesModels?.blockstates?.latest?.blocks || [];
    + const filteredBlocks = fullBlocks.filter(block => {
    🧹 Nitpick comments (14)
    renderer/viewer/lib/entity/EntityMesh.ts (1)

    240-240: Resource access structure improvement

    The code now retrieves the texture information through the resource manager's current resources, which aligns with the architectural changes of the PR to better organize resource handling. This change supports the goal of separating the application from the renderer and improving resource management.

    However, be cautious with the non-null assertion operator (!) on currentResources. Consider adding a fallback or explicit null check to handle cases where resources might not be fully initialized.

    -    const textureInfo = worldRenderer.resourcesManager.currentResources!.blocksAtlasParser.getTextureInfo(blockName)
    +    const currentResources = worldRenderer.resourcesManager.currentResources
    +    if (!currentResources) {
    +      throw new Error('Resources not initialized for block textures')
    +    }
    +    const textureInfo = currentResources.blocksAtlasParser.getTextureInfo(blockName)
    renderer/viewer/three/panorama.ts (2)

    124-125: Commented out resource loading code.

    There's a commented out call to load MC data, but the next line uses the resource manager. Consider removing the commented line if it's no longer needed.

    - // await this.options.resourcesManager!.loadMcData(version)
      await this.options.resourcesManager.updateAssetsData({ version })

    176-194: Event listener not properly cleaned up.

    While the event listener is created with the abort controller's signal, the event is attached to document.body directly. This could lead to event listener accumulation if the panorama is created and disposed multiple times.

    Consider storing a reference to the listener function to ensure proper cleanup:

    + let pointerMoveListener: ((e: PointerEvent) => void) | null = null;
      document.body.addEventListener('pointermove', (e) => {
        if (e.pointerType !== 'mouse') return
        const pos = new THREE.Vector2(e.clientX, e.clientY)
        // ...rest of the function
      }, {
        signal: this.abortController.signal
      })

    And then in the dispose method:

      dispose () {
        this.scene.clear()
        this.worldRenderer?.destroy()
        this.abortController.abort()
    +   // Explicit removal if needed for older browsers that don't support AbortSignal
    +   if (pointerMoveListener) {
    +     document.body.removeEventListener('pointermove', pointerMoveListener)
    +     pointerMoveListener = null
    +   }
      }
    src/resourcesManager.ts (3)

    108-109: Unresolved TODO comment in production code.

    There's a TODO comment about resolving the version, which suggests incomplete functionality. Consider either implementing the version resolution or documenting why it's deferred.

    - // todo-low resolve version
    + // TODO (low priority): Implement version resolution for different Minecraft versions

    132-144: Console timing logs in production code.

    The code includes console.time and console.timeEnd calls that are likely intended for development debugging. These should be removed or wrapped in a debug flag condition for production code.

    - console.time('createBlocksAtlas')
    + if (process.env.NODE_ENV !== 'production') console.time('createBlocksAtlas')
      const { atlas: blocksAtlas, canvas: blocksCanvas } = await blocksAssetsParser.makeNewAtlas(
        // ... parameters
      )
    - console.timeEnd('createBlocksAtlas')
    + if (process.env.NODE_ENV !== 'production') console.timeEnd('createBlocksAtlas')

    147-160: Duplicate console timing code pattern.

    Similar to the previous issue, the createItemsAtlas timing code should follow the same pattern for consistency. Consider applying the same fix here or extracting into a helper function.

    - console.time('createItemsAtlas')
    + if (process.env.NODE_ENV !== 'production') console.time('createItemsAtlas')
      // ... code
    - console.timeEnd('createItemsAtlas')
    + if (process.env.NODE_ENV !== 'production') console.timeEnd('createItemsAtlas')
    renderer/viewer/three/graphicsBackend.ts (3)

    15-17: Global renderer assignment could cause conflicts.

    Assigning to globalThis.renderer makes the renderer accessible globally, which could lead to conflicts with other code or libraries. Consider using a more specific namespace or avoiding global assignment.

    - globalThis.renderer = documentRenderer.renderer
    + // If global access is needed, use a more specific namespace
    + globalThis.mcWebClient = globalThis.mcWebClient || {}
    + globalThis.mcWebClient.renderer = documentRenderer.renderer

    30-33: Global panorama renderer assignment.

    Similar to the previous comment, setting window.panoramaRenderer creates a global variable that could conflict with other code. Consider a more isolated approach.

      panoramaRenderer = new PanoramaRenderer(documentRenderer, initOptions, !!process.env.SINGLE_FILE_BUILD_MODE)
      void panoramaRenderer.start()
    - window.panoramaRenderer = panoramaRenderer
    + // If needed for debugging, use a namespaced global object
    + (window.mcWebClient = window.mcWebClient || {}).panoramaRenderer = panoramaRenderer

    69-70: Commented out code.

    There's a commented out line that appears to be leftover debugging or development code. Consider removing it or adding a comment explaining why it's kept.

      const renderer = WorldRendererThree.getRendererInfo(documentRenderer.renderer) ?? '...'
    - // viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch)
    src/appViewer.ts (3)

    125-130: Optional error handling for resource preparation.
    If the underlying resource load fails, prepareResources logs and rethrows, but you might want to catch specific errors (e.g., network failures) and prompt user retry.


    153-160: disconnectBackend leaves queuedDisplay intact.
    If the backend is torn down, any future queued action might be stale. Consider resetting or re-queuing relevant actions to avoid confusion if this method is invoked often.


    162-176: utils.waitingForChunks() polling approach.
    Using setInterval until chunks are loaded works, though you could observe chunk events for a more event-driven approach. Long intervals can hamper performance if scaled up.

    src/inventoryWindows.ts (2)

    140-141: Usage of blocksAtlasParser.latestImage.
    Relying on a non-null assertion (!) might risk runtime errors if blocksAtlasParser fails to load. Consider a fallback or user-friendly error if the parser is missing.


    142-142: itemsAtlasParser.latestImage: same fallback consideration.
    As with blocksAtlasParser, providing a graceful fallback image for items could improve user experience if the parser fails.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 5364085 and de3edda.

    📒 Files selected for processing (16)
    • renderer/playground/scenes/main.ts (0 hunks)
    • renderer/viewer/lib/basePlayerState.ts (1 hunks)
    • renderer/viewer/lib/entity/EntityMesh.ts (1 hunks)
    • renderer/viewer/lib/guiRenderer.ts (5 hunks)
    • renderer/viewer/lib/worldrendererCommon.ts (11 hunks)
    • renderer/viewer/lib/worldrendererThree.ts (12 hunks)
    • renderer/viewer/three/graphicsBackend.ts (1 hunks)
    • renderer/viewer/three/panorama.ts (1 hunks)
    • src/appViewer.ts (1 hunks)
    • src/dayCycle.ts (1 hunks)
    • src/globals.d.ts (1 hunks)
    • src/inventoryWindows.ts (5 hunks)
    • src/mineflayer/playerState.ts (3 hunks)
    • src/react/HotbarRenderApp.tsx (3 hunks)
    • src/resourcesManager.ts (2 hunks)
    • src/water.ts (2 hunks)
    💤 Files with no reviewable changes (1)
    • renderer/playground/scenes/main.ts
    🚧 Files skipped from review as they are similar to previous changes (4)
    • src/dayCycle.ts
    • src/react/HotbarRenderApp.tsx
    • src/globals.d.ts
    • renderer/viewer/lib/guiRenderer.ts
    🧰 Additional context used
    🧬 Code Definitions (7)
    src/water.ts (1)
    src/appViewer.ts (1) (1)
    • appViewer (179-179)
    renderer/viewer/three/panorama.ts (5)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (24-570)
    src/appViewer.ts (1) (1)
    • GraphicsInitOptions (34-39)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (21-249)
    renderer/viewer/lib/worldrendererCommon.ts (1) (1)
    • defaultWorldRendererConfig (33-47)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • BasePlayerState (41-96)
    src/mineflayer/playerState.ts (1)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • BasePlayerState (41-96)
    renderer/viewer/three/graphicsBackend.ts (3)
    src/appViewer.ts (8) (8)
    • GraphicsBackendLoader (47-47)
    • GraphicsInitOptions (34-39)
    • WorldReactiveState (17-21)
    • startPanorama (115-123)
    • prepareResources (125-129)
    • startWorld (131-146)
    • DisplayWorldOptions (41-45)
    • GraphicsBackend (49-62)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (24-570)
    src/core/progressReporter.ts (1) (1)
    • ProgressReporter (5-18)
    renderer/viewer/lib/worldrendererCommon.ts (3)
    src/appViewer.ts (2) (2)
    • appViewer (179-179)
    • DisplayWorldOptions (41-45)
    src/resourcesManager.ts (1) (1)
    • ResourcesManager (75-201)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (21-249)
    src/appViewer.ts (6)
    src/resourcesManager.ts (1) (1)
    • ResourcesManager (75-201)
    src/core/progressReporter.ts (7) (7)
    • error (131-133)
    • error (156-159)
    • error (178-180)
    • error (193-195)
    • error (215-217)
    • error (227-228)
    • ProgressReporter (5-18)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (21-249)
    renderer/viewer/lib/worldrendererCommon.ts (2) (2)
    • WorldRendererConfig (49-49)
    • defaultWorldRendererConfig (33-47)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • IPlayerState (16-39)
    src/mineflayer/playerState.ts (1) (1)
    • playerState (197-197)
    renderer/viewer/lib/worldrendererThree.ts (4)
    renderer/viewer/lib/entities.ts (1) (1)
    • Entities (209-1028)
    src/appViewer.ts (2) (2)
    • GraphicsInitOptions (34-39)
    • DisplayWorldOptions (41-45)
    renderer/viewer/lib/holdingBlock.ts (1) (1)
    • HoldingBlock (93-510)
    renderer/viewer/lib/hand.ts (1) (1)
    • getMyHand (5-35)
    🪛 ESLint
    renderer/viewer/lib/worldrendererThree.ts

    [error] 336-336: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)

    🔇 Additional comments (48)
    renderer/viewer/lib/basePlayerState.ts (2)

    32-38: Added reactive state properties to enhance environment handling

    The additions to the reactive object in the IPlayerState interface properly extend the player state with environmental properties such as water state, background color, and lighting values. This change supports the architecture refactoring by centralizing state management through reactive properties.


    42-48: Good initialization of reactive state with proper typing

    The explicit typing of playerSkin as string | undefined and the other initialization values follow good TypeScript practices. The tuple type definition for backgroundColor ensures type safety when handling color values.

    renderer/viewer/lib/worldrendererCommon.ts (8)

    13-15: Improved imports with clear dependencies

    The imports have been updated to include ResourcesManager and DisplayWorldOptions, properly reflecting the new architecture where resources are managed centrally. The removal of direct texture imports aligns with moving resource management to a dedicated manager.


    33-46: Enhanced renderer configuration with additional parameters

    The configuration has been expanded with several new options that improve flexibility:

    • Renamed numWorkers to the more descriptive mesherWorkers
    • Added support for extra block renderers, clip world thresholds, and lighting options
    • Included a parameter for controlling chunk batch loading time

    These additions provide better control over rendering behavior and performance.


    147-148: Added new texture and sound system properties

    Adding dedicated properties for item textures and sound system aligns with the improved architecture, where resources are managed more systematically.


    154-165: Refactored constructor to use resource manager and display options

    The constructor now takes a ResourcesManager, DisplayWorldOptions, and version string, properly separating resource management from the renderer itself. The initialization flow is improved by calling this.connect() to set up event listeners, which enhances the separation of concerns.


    167-176: New initialization method with resource update handling

    The init method properly checks if the renderer is already active, sets up the version, and adds listeners for asset texture updates. This pattern ensures that resources are properly loaded and updated when needed.


    347-351: Improved resource loading with central resource manager

    The setVersion method now correctly delegates Minecraft data loading to the resource manager and ensures resources are updated appropriately, which better separates concerns and improves architecture clarity.


    372-400: Refactored asset update method to use resource manager

    The updateAssetsData method now properly retrieves textures from the centralized resource manager instead of managing them directly. This improves code organization and maintainability.


    735-757: Enhanced destroy method with proper resource cleanup

    The improved destroy method now correctly handles the termination of workers, sound system, and material resources. This prevents memory leaks and ensures clean disconnection of the renderer.

    renderer/viewer/lib/worldrendererThree.ts (8)

    35-40: Added essential scene components as class properties

    The addition of scene, lights, and entities as class properties provides better structure and encapsulation. This change aligns with the architectural goal of more modular rendering components.


    50-69: Refactored constructor with improved initialization flow

    The constructor now properly validates required parameters, sets up the renderer with the resource manager, and initializes the scene and camera. The initialization flow is more structured and modular.


    71-81: Added entity update method

    The new updateEntity method properly handles updating entity rotation values, separating this responsibility from the event handling code.


    83-93: Added scene initialization method

    The resetScene method properly sets up the Three.js scene with lights, camera, and other essential components. This refactoring improves readability and maintainability.


    95-114: Added reactive state subscription handling

    The new watchReactivePlayerState method properly subscribes to changes in the player state and reflects those changes in the rendering components (background color, fog, lighting). This pattern decouples the application state from the renderer.


    309-315: Added first-person camera handling

    The new setFirstPersonCamera method appropriately handles setting the camera position and orientation for first-person view, taking into account the player's eye height from the player state.


    331-343: Enhanced render method with size handling

    The updated render method now properly handles window size changes and updates the camera aspect ratio accordingly. The addition of entity rendering ensures that all scene elements are rendered properly.

    🧰 Tools
    🪛 ESLint

    [error] 336-336: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)


    557-569: Added camera roll handling

    The new setCameraRoll method provides a clean way to apply roll rotation to the camera, using quaternions for proper composition of rotations. This enhances the camera control capabilities of the renderer.

    src/water.ts (3)

    8-8: Water state now updates player state reactive properties

    The code now sets the inWater property in the player's reactive state instead of directly manipulating the scene. This aligns with the architectural change to separate application state from rendering concerns.


    15-15: Consistent use of reactive state for water detection

    This change ensures that water detection consistently updates the player's reactive state rather than directly modifying the renderer.


    35-35: Background color updates now use reactive state

    The updateBackground function now properly updates the player's reactive state with the background color instead of directly modifying the renderer. This is consistent with the architectural change to separate application state from rendering.

    src/mineflayer/playerState.ts (2)

    3-3: Import alignment with new architecture.

    Good addition of the BasePlayerState import. This aligns with the refactoring goal of separating application code from rendering code as mentioned in the PR objectives.


    32-32: Improved state reuse through BasePlayerState.

    Using BasePlayerState().reactive instead of directly creating a proxy improves code reuse and consistency across components. This change is aligned with the PR's goal of isolating rendering concerns from application code.

    renderer/viewer/three/panorama.ts (6)

    1-13: Dependencies organization and imports look good.

    The imports are well-organized and include all necessary dependencies for a Three.js-based panorama renderer. This follows a good pattern of separating external libraries from internal modules.


    14-21: Well-defined panorama file configuration.

    The panorama files array clearly defines the ordering and mapping for a cubemap texture, which is essential for proper skybox rendering. The comments indicating which direction each texture represents are helpful for maintainability.


    23-49: Clean PanoramaRenderer class structure.

    The class is well-structured with private fields for internal state management and a clean constructor that sets up the 3D scene with proper lighting. This implementation aligns with the PR's goal of having a clear separation between rendering components.


    51-66: Good conditional rendering logic in start method.

    The start method correctly handles the different rendering paths based on the doWorldBlocksPanorama flag and properly updates the document renderer. This implements the flexible rendering approach mentioned in the PR objectives.


    68-120: Comprehensive classic panorama implementation.

    The implementation of the classic panorama with proper texture handling, rotation animation, and entity decoration is well done. The use of texture matrix manipulation instead of repeat/offset for flipping is a good approach.


    199-204: Proper cleanup in dispose method.

    The dispose method appropriately cleans up resources by clearing the scene, destroying the world renderer, and aborting ongoing operations. This is crucial for preventing memory leaks, especially in a renderer that may be created and destroyed multiple times during the application lifecycle.

    src/resourcesManager.ts (5)

    42-44: Well-defined event types for the ResourceManager.

    Using TypedEmitter with clearly defined event types enhances type safety and makes the codebase more maintainable. The 'assetsTexturesUpdated' event provides a clear contract for subscribers.


    46-67: Comprehensive LoadedResources class design.

    The LoadedResources class is well-structured with clear separation between the different types of resources (atlas parsers, custom data, renderers). This organized approach will make resource management more maintainable.


    68-74: Clear UpdateAssetsRequest interface.

    The interface provides a well-defined contract for resource updates with appropriate optional parameters. This will help ensure consistency across different types of update operations.


    75-85: ResourcesManager class with proper inheritance.

    The ResourcesManager extends EventEmitter with typed events, which is a good pattern for enabling reactive behavior. The initialization of source data properties and abort controller shows careful consideration of resource lifecycle management.


    196-201: Proper resource cleanup in destroy method.

    The destroy method properly cleans up resources by aborting the controller, clearing the current resources, and creating a new abort controller. This is essential for preventing memory leaks, especially in a component that manages potentially large assets.

    renderer/viewer/three/graphicsBackend.ts (2)

    10-11: Good Three.js color management configuration.

    Setting THREE.ColorManagement.enabled = false is important for maintaining consistent color handling across different Three.js versions. The comment with a link to the relevant documentation is helpful for future maintenance.


    73-95: Well-defined GraphicsBackend implementation.

    The public interface of the backend is well-structured with clear responsibilities for each method. The use of getters for properties like soundSystem that depend on the world renderer is a good pattern to avoid null/undefined access.

    src/appViewer.ts (9)

    1-16: Imports look well-structured and comprehensive.
    They provide necessary references for rendering, events, and state management, all in one place without obvious redundancies.


    64-81: Global AppViewer design might lead to hidden state pitfalls.
    Exporting a single global instance can cause concurrency issues if this code is ever run in multi-instance scenarios or if multiple viewers are needed. This was brought up in a prior comment.


    109-114: Resetting the backend.
    The resetBackend() method succinctly reloads the loader. This is a clear approach if you need to reinitialize for new configurations or after a crash.


    115-123: Queued actions design is effective.
    Storing and replaying the panorama start call when a backend is not yet ready simplifies usage. This pattern ensures user actions aren’t lost.


    148-151: Graceful resource destruction.
    Calling disconnectBackend() before destroying resources helps prevent dangling references. This approach is generally safe.


    179-181: Global instance can lead to hidden state issues if concurrency arises.
    This exact feedback was given in a previous review.


    182-187: initialMenuStart design.
    This method triggers a backend reset if already in a world, then starts the panorama. No concerns here, but consider logging or user feedback if a reset occurs.


    198-210: modalStackUpdateChecks renders conditionally.
    This is a clear use of reactive state to manage rendering. Good logic to resume the panorama when no modals are active.


    131-146:

    ❓ Verification inconclusive

    startWorld usage of default player state.
    The logic is straightforward, but if multiple players or concurrency are ever planned, consider injecting a player state per instance of AppViewer to avoid collisions.

    Would you like to confirm how many references to startWorld pass a custom playerStateSend instead of defaulting to the global one?


    🏁 Script executed:

    #!/bin/bash
    rg -A 5 "startWorld\(" 

    Length of output: 872


    Action: Reassess Player State Injection Strategy in startWorld
    The grep search confirms that the only active call (from src/index.ts) does not supply a custom playerStateSend, so the method always defaults to the global playerState. There is a commented-out example in src/appViewer.ts that shows using a custom state (i.e. new BasePlayerState()), hinting at an alternative approach.

    • src/index.ts: Active call uses default state (three arguments are passed).
    • src/appViewer.ts: Contains a commented-out call with a custom player state.

    If you plan on supporting multiple players or concurrency, consider injecting a unique player state per AppViewer instance rather than relying on a global default to avoid potential state collisions.

    src/inventoryWindows.ts (3)

    9-9: Updated import path looks appropriate.
    Switching to renderer/viewer/common/utils suggests better organization of utility functions.


    49-50: Conditional check for itemsAtlasParser.
    If it’s unavailable, the function returns early, preventing errors. Subscribing to 'assetsTexturesUpdated' ensures dynamic updates—this is a clean approach.

    Also applies to: 58-58


    225-228: Robust fallback for missing or errored textures.
    Calling getItemTexture and falling back to 'item/missing_texture' or 'block/errored' ensures the GUI won’t break. This is a best practice for resource-laden apps.

    Also applies to: 231-231

    @@ -40,7 +38,7 @@ export class PlayerStateManager implements IPlayerState {
    return this.instance
    }

    private constructor () {
    constructor () {
    Copy link

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    💡 Verification agent

    🧩 Analysis chain

    Constructor visibility changed from private to public.

    The constructor visibility has been changed from private to public, which breaks the singleton pattern enforced by the class. While the singleton instance is still accessible via getInstance(), this change allows creating multiple instances directly which may lead to inconsistent state.

    Consider if this change is intentional as part of the refactoring. If it is, ensure that:

    1. Documentation is updated to reflect this architectural change
    2. Any code that depends on there being a single instance is reviewed

    🏁 Script executed:

    #!/bin/bash
    # Search for direct instantiations of PlayerStateManager that bypass getInstance()
    rg "new PlayerStateManager\(\)" --type ts

    Length of output: 120


    Singleton Pattern Enforcement: Public Constructor Allows Direct Instantiation

    The change from a private to a public constructor in PlayerStateManager is confirmed. A direct instantiation (new PlayerStateManager()) is found in src/mineflayer/playerState.ts, which bypasses the expected singleton behavior provided by getInstance(). This risks multiple instances being created and could lead to inconsistent state.

    • Risk: Public access to the constructor allows creation of new instances outside of the controlled singleton pattern.
    • Action Items:
      • If this refactor is intentional, please update the documentation and carefully review any code that assumes only one instance exists.
      • Otherwise, revert the constructor’s visibility to private to maintain the singleton guarantee.

    Comment on lines 37 to 39
    const prepareResources = async (ver: string, progressReporter: ProgressReporter): Promise<void> => {
    version = ver
    }
    Copy link

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    🛠️ Refactor suggestion

    Incomplete prepareResources implementation.

    The prepareResources method only stores the version but doesn't actually prepare any resources. This seems inconsistent with its purpose and the method signature that returns a Promise.

      const prepareResources = async (ver: string, progressReporter: ProgressReporter): Promise<void> => {
        version = ver
    +   // Example implementation (adapt based on actual requirements):
    +   try {
    +     await progressReporter.executeWithMessage('Loading resources', async () => {
    +       await initOptions.resourcesManager.loadMcData(ver)
    +       await initOptions.resourcesManager.updateAssetsData({ version: ver })
    +     })
    +   } catch (error) {
    +     initOptions.displayCriticalError(error instanceof Error ? error : new Error('Failed to prepare resources'))
    +   }
      }
    📝 Committable suggestion

    ‼️ IMPORTANT
    Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    const prepareResources = async (ver: string, progressReporter: ProgressReporter): Promise<void> => {
    version = ver
    }
    const prepareResources = async (ver: string, progressReporter: ProgressReporter): Promise<void> => {
    version = ver
    // Example implementation (adapt based on actual requirements):
    try {
    await progressReporter.executeWithMessage('Loading resources', async () => {
    await initOptions.resourcesManager.loadMcData(ver)
    await initOptions.resourcesManager.updateAssetsData({ version: ver })
    })
    } catch (error) {
    initOptions.displayCriticalError(error instanceof Error ? error : new Error('Failed to prepare resources'))
    }
    }

    src/appViewer.ts Outdated
    Comment on lines 82 to 107
    loadBackend (loader: GraphicsBackendLoader) {
    if (this.backend) {
    this.disconnectBackend()
    }

    this.backendLoader = loader
    const loaderOptions: GraphicsInitOptions = {
    resourcesManager: this.resourcesManager,
    config: this.config,
    displayCriticalError (error) {
    console.error(error)
    setLoadingScreenStatus(error.message, true)
    },
    }
    this.backend = loader(loaderOptions)

    if (this.resourcesManager.currentResources) {
    void this.prepareResources(this.resourcesManager.currentResources.version, createNotificationProgressReporter())
    }

    // Execute queued action if exists
    if (this.queuedDisplay) {
    const { method, args } = this.queuedDisplay
    this.backend[method](...args)
    }
    }
    Copy link

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    🛠️ Refactor suggestion

    Consider error-handling around backend loading.
    Within loadBackend, if backend initialization fails, the code just logs the error. For large-scale applications, you might handle it more robustly (e.g., fallback strategy or user-facing error message) to avoid silent failures.

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 5

    ♻️ Duplicate comments (1)
    renderer/viewer/lib/worldrendererCommon.ts (1)

    514-609: ⚠️ Potential issue

    Refactor duplicated event listeners for render distance.

    Within the connect(worldView) method, there are duplicate event listeners for 'renderDistance' (lines 566-569 and 571-575), which could lead to duplicate handling of the same event.

    Apply this diff to consolidate the event listeners and avoid potential side effects:

    -    worldEmitter.on('renderDistance', (d) => {
    -      this.viewDistance = d
    -      this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
    -    })
    -
    -    worldEmitter.on('renderDistance', (d) => {
    -      this.viewDistance = d
    -      this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
    -      this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
    -    })
    +    worldEmitter.on('renderDistance', (d) => {
    +      this.viewDistance = d
    +      this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
    +      this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
    +    })
    🧹 Nitpick comments (11)
    src/mineflayer/items.ts (1)

    93-110: Good abstraction for item model resolution

    This function cleanly separates the logic for determining an item's model name, making it reusable across the application.

    However, there's an undeclared reference to appViewer at line 103 and 105. This global variable usage could make testing more difficult.

    Consider adding the missing import for appViewer and potentially refactoring to accept a resources manager as a parameter (similar to the getItemUv function) to reduce global state dependency:

    + import { appViewer } from '../../appViewer'
    
    - export const getItemModelName = (item: GeneralInputItem, specificProps: ItemSpecificContextProperties) => {
    + export const getItemModelName = (
    +   item: GeneralInputItem, 
    +   specificProps: ItemSpecificContextProperties,
    +   resourcesManager = appViewer.resourcesManager
    + ) => {
      // ...
    -  const modelFromDef = getItemDefinition(appViewer.resourcesManager.itemsDefinitionsStore, {
    +  const modelFromDef = getItemDefinition(resourcesManager.itemsDefinitionsStore, {
        name: itemModelName,
    -    version: appViewer.resourcesManager.currentResources!.version,
    +    version: resourcesManager.currentResources!.version,
        properties: itemSelector
      })?.model
      // ...
    }
    src/resourcesManager.ts (2)

    74-164: Complex updateAssetsData method needs refactoring.

    This method is too long and handles multiple concerns, making it difficult to maintain and test.

    Consider breaking this method into smaller, more focused methods:

    async updateAssetsData (request: UpdateAssetsRequest) {
      const abortController = new AbortController()
      await this.loadSourceData(request.version)
      if (abortController.signal.aborted) return
    
      const resources = this.currentResources ?? new LoadedResources()
      resources.version = request.version
      resources.texturesVersion = request.texturesVersion ?? resources.version
    
    +  this.initializeBlockstatesModels(resources)
    +  await this.createAtlases(resources, request, abortController)
    +  
    +  if (abortController.signal.aborted) return
    +  this.finalizeResourcesSetup(resources)
    +  
    +  this.emit('assetsTexturesUpdated')
    +  this.currentResources = resources
    }
    
    + private initializeBlockstatesModels(resources: LoadedResources) {
    +   resources.blockstatesModels = {
    +     blockstates: {},
    +     models: {}
    +   }
    +   // todo-low resolve version
    +   resources.blockstatesModels.blockstates.latest = {
    +     ...this.sourceBlockStatesModels.blockstates.latest,
    +     ...resources.customBlockStates
    +   }
    +
    +   resources.blockstatesModels.models.latest = {
    +     ...this.sourceBlockStatesModels.models.latest,
    +     ...resources.customModels
    +   }
    + }
    
    + private async createAtlases(resources: LoadedResources, request: UpdateAssetsRequest, abortController: AbortController) {
    +   // Create blocks atlas
    +   const blockTexturesChanges = this.getSeasonalTextureChanges()
    +   const customBlockTextures = Object.keys(resources.customTextures.blocks?.textures ?? {})
    +   const customItemTextures = Object.keys(resources.customTextures.items?.textures ?? {})
    +
    +   console.time('createBlocksAtlas')
    +   const blocksAssetsParser = new AtlasParser(this.sourceBlocksAtlases, blocksAtlasLatest, blocksAtlasLegacy)
    +   const { atlas: blocksAtlas, canvas: blocksCanvas } = await blocksAssetsParser.makeNewAtlas(
    +     resources.texturesVersion,
    +     (textureName) => {
    +       if (request.includeOnlyBlocks && !request.includeOnlyBlocks.includes(textureName)) return false
    +       const texture = resources.customTextures.blocks?.textures[textureName]
    +       return blockTexturesChanges[textureName] ?? texture
    +     },
    +     undefined,
    +     undefined,
    +     customBlockTextures
    +   )
    +   console.timeEnd('createBlocksAtlas')
    +
    +   if (abortController.signal.aborted) return
    +
    +   // Create items atlas
    +   console.time('createItemsAtlas')
    +   const itemsAssetsParser = new AtlasParser(this.sourceItemsAtlases, itemsAtlasLatest, itemsAtlasLegacy)
    +   const { atlas: itemsAtlas, canvas: itemsCanvas } = await itemsAssetsParser.makeNewAtlas(
    +     resources.texturesVersion,
    +     (textureName) => {
    +       const texture = resources.customTextures.items?.textures[textureName]
    +       if (!texture) return
    +       return texture
    +     },
    +     resources.customTextures.items?.tileSize,
    +     undefined,
    +     customItemTextures
    +   )
    +   console.timeEnd('createItemsAtlas')
    +
    +   // Create atlas parsers
    +   resources.blocksAtlasParser = new AtlasParser({ latest: blocksAtlas }, blocksCanvas.toDataURL())
    +   resources.itemsAtlasParser = new AtlasParser({ latest: itemsAtlas }, itemsCanvas.toDataURL())
    +   resources.blocksAtlasImage = await getLoadedImage(blocksCanvas.toDataURL())
    +   resources.itemsAtlasImage = await getLoadedImage(itemsCanvas.toDataURL())
    + }
    
    + private getSeasonalTextureChanges() {
    +   const blockTexturesChanges = {} as Record<string, string>
    +   const date = new Date()
    +   if ((date.getMonth() === 11 && date.getDate() >= 24) || (date.getMonth() === 0 && date.getDate() <= 6)) {
    +     Object.assign(blockTexturesChanges, christmasPack)
    +   }
    +   return blockTexturesChanges
    + }
    
    + private finalizeResourcesSetup(resources: LoadedResources) {
    +   if (resources.version && resources.blockstatesModels && resources.itemsAtlasParser && resources.blocksAtlasParser) {
    +     resources.itemsRenderer = new ItemsRenderer(
    +       resources.version,
    +       resources.blockstatesModels,
    +       resources.itemsAtlasParser,
    +       resources.blocksAtlasParser
    +     )
    +     resources.worldBlockProvider = worldBlockProvider(
    +       resources.blockstatesModels,
    +       resources.blocksAtlasParser.atlas,
    +       'latest'
    +     )
    +   }
    + }

    166-175: Add error handling for downloadDebugAtlas method.

    The current implementation doesn't handle potential errors when creating the debug image or when the download fails.

    Add try-catch block for error handling:

    async downloadDebugAtlas (isItems = false) {
    +  try {
        const resources = this.currentResources
        if (!resources) throw new Error('No resources loaded')
        const atlasParser = (isItems ? resources.itemsAtlasParser : resources.blocksAtlasParser)!
        const dataUrl = await atlasParser.createDebugImage(true)
        const a = document.createElement('a')
        a.href = dataUrl
        a.download = `atlas-debug-${isItems ? 'items' : 'blocks'}.png`
        a.click()
    +  } catch (error) {
    +    console.error(`Failed to download debug atlas for ${isItems ? 'items' : 'blocks'}:`, error)
    +    throw new Error(`Failed to download debug atlas: ${error.message}`)
    +  }
    }
    renderer/viewer/lib/worldrendererCommon.ts (1)

    521-547: Chunk loading batch system should use a queue structure.

    The current implementation creates a batch for all chunks and processes them together after a timeout. This could lead to uneven frame times and potential stuttering if many chunks arrive at once.

    Consider implementing a more sophisticated queue system that processes chunks in smaller batches to distribute the workload more evenly across frames:

    -    let currentLoadChunkBatch = null as {
    -      timeout
    -      data
    -    } | null
    -    worldEmitter.on('loadChunk', ({ x, z, chunk, worldConfig, isLightUpdate }) => {
    -      this.worldSizeParams = worldConfig
    -      this.queuedChunks.add(`${x},${z}`)
    -      const args = [x, z, chunk, isLightUpdate]
    -      if (!currentLoadChunkBatch) {
    -        // add a setting to use debounce instead
    -        currentLoadChunkBatch = {
    -          data: [],
    -          timeout: setTimeout(() => {
    -            for (const args of currentLoadChunkBatch!.data) {
    -              this.queuedChunks.delete(`${args[0]},${args[1]}`)
    -              this.addColumn(...args as Parameters<typeof this.addColumn>)
    -            }
    -            for (const fn of this.queuedFunctions) {
    -              fn()
    -            }
    -            this.queuedFunctions = []
    -            currentLoadChunkBatch = null
    -          }, this.worldRendererConfig.addChunksBatchWaitTime)
    -        }
    -      }
    -      currentLoadChunkBatch.data.push(args)
    +    const chunkQueue: [number, number, any, boolean][] = []
    +    let chunkProcessingTimer: NodeJS.Timeout | null = null
    +    
    +    const processNextChunkBatch = () => {
    +      const BATCH_SIZE = 5 // Adjust based on performance testing
    +      const batch = chunkQueue.splice(0, BATCH_SIZE)
    +      
    +      for (const args of batch) {
    +        this.queuedChunks.delete(`${args[0]},${args[1]}`)
    +        this.addColumn(...args as Parameters<typeof this.addColumn>)
    +      }
    +      
    +      if (chunkQueue.length > 0) {
    +        chunkProcessingTimer = setTimeout(processNextChunkBatch, 16) // ~60 FPS
    +      } else {
    +        // Process queued functions when all chunks are loaded
    +        for (const fn of this.queuedFunctions) {
    +          fn()
    +        }
    +        this.queuedFunctions = []
    +        chunkProcessingTimer = null
    +      }
    +    }
    +    
    +    worldEmitter.on('loadChunk', ({ x, z, chunk, worldConfig, isLightUpdate }) => {
    +      this.worldSizeParams = worldConfig
    +      this.queuedChunks.add(`${x},${z}`)
    +      chunkQueue.push([x, z, chunk, isLightUpdate])
    +      
    +      if (!chunkProcessingTimer) {
    +        chunkProcessingTimer = setTimeout(
    +          processNextChunkBatch, 
    +          this.worldRendererConfig.addChunksBatchWaitTime
    +        )
    +      }

    This approach provides more consistent frame times by processing chunks in smaller batches and distributes the workload more evenly.

    renderer/viewer/lib/entities.ts (1)

    223-235: Remove commented code.

    This commented code block for getItemUv is no longer needed and should be removed to improve code clarity.

    -  // getItemUv: undefined | (() => {
    -  //   texture: THREE.Texture;
    -  //   u: number;
    -  //   v: number;
    -  //   su?: number;
    -  //   sv?: number;
    -  //   size?: number;
    -  //   modelName?: string;
    -  // } | {
    -  //   resolvedModel: BlockModel
    -  //   modelName: string
    -  // } | undefined)
    src/index.ts (1)

    647-656: Remove or fix commented code for world readiness.

    This commented block should either be removed or updated to use the new architecture since it references the old viewer.world approach.

    If you want to preserve this functionality with the new architecture, consider updating it to:

    -  const worldWasReady = false
    -  // void viewer.world.renderUpdateEmitter.on('update', () => {
    -  //   // todo might not emit as servers simply don't send chunk if it's empty
    -  //   if (!viewer.world.allChunksFinished || worldWasReady) return
    -  //   worldWasReady = true
    -  //   console.log('All chunks done and ready! Time from renderer open to ready', (Date.now() - start) / 1000, 's')
    -  //   viewer.render() // ensure the last state is rendered
    -  //   document.dispatchEvent(new Event('cypress-world-ready'))
    -  // })
    +  let worldWasReady = false
    +  const unsub = subscribe(appViewer.backend?.reactiveState.world ?? {}, () => {
    +    if (!appViewer.backend?.reactiveState.world.allChunksLoaded || worldWasReady) return
    +    worldWasReady = true
    +    console.log('All chunks done and ready! Time from renderer open to ready', (Date.now() - start) / 1000, 's')
    +    // ensure the last state is rendered
    +    document.dispatchEvent(new Event('cypress-world-ready'))
    +    unsub()
    +  })
    src/appViewer.ts (2)

    37-42: Validate GraphicsInitOptions for error handling.

    The displayCriticalError function might need more robust error handling. Currently, it logs the error and updates the loading screen but doesn't include any retry logic or fallback mechanisms, which could improve user experience during initialization failures.

    Consider adding recovery strategies:

    displayCriticalError (error: Error) {
      console.error(error)
      setLoadingScreenStatus(error.message, true)
    + // Consider adding one of these strategies:
    + // 1. Retry with fallback renderer: this.retryWithFallbackRenderer()
    + // 2. Show actionable message with retry button 
    + // 3. Switch to a minimal compatibility mode
    }

    193-206: Clean up commented-out code in initialMenuStart.

    The function contains significant commented-out code (lines 199-205) that suggests experimental or unfinished functionality. This should be cleaned up before merging to production.

    Either remove the commented code completely or add a TODO comment explaining why it's kept for future reference:

    const initialMenuStart = async () => {
      if (appViewer.currentDisplay === 'world') {
        appViewer.resetBackend()
      }
      appViewer.startPanorama()
    
    -  // await appViewer.resourcesManager.loadMcData('1.21.4')
    -  // const world = getSyncWorld('1.21.4')
    -  // await appViewer.prepareResources('1.21.4', createNullProgressReporter())
    -  // world.setBlockStateId(new Vec3(0, 64, 0), 1)
    -  // appViewer.startWorld(world, 3, new Vec3(0, 64, 0), new BasePlayerState())
    -  // appViewer.backend?.updateCamera(new Vec3(0, 64, 2), 0, 0)
    -  // void appViewer.worldView.init(new Vec3(0, 64, 0))
    +  /* TODO: This was a test for direct world loading, keep for reference until 
         the panorama-to-world transition is fully implemented and tested */
    }
    src/vr.ts (1)

    86-89: Add confirmation dialog for VR disabling.

    When a user clicks the close button, VR is immediately disabled without confirmation. Consider adding a confirmation dialog for a better user experience, especially since VR mode changes can be disorienting.

    closeButton.addEventListener('click', () => {
    +  // Confirm before disabling VR to avoid accidental clicks
    +  if (confirm('Exit VR mode?')) {
        container.hidden = true
        worldRenderer.worldRendererConfig.vrSupport = false
    +  }
    })
    renderer/viewer/lib/worldrendererThree.ts (2)

    342-352: Commented-out free fly mode code.

    There are several commented-out code blocks related to a freeFlyMode feature. This suggests that the refactoring is incomplete or that this feature might be reintroduced later.

    If the free-fly feature is planned for future implementation, add a TODO comment with details. If it's been permanently removed, remove all related commented code.

    updateCamera (pos: Vec3 | null, yaw: number, pitch: number): void {
    -  // if (this.freeFlyMode) {
    -  //   pos = this.freeFlyState.position
    -  //   pitch = this.freeFlyState.pitch
    -  //   yaw = this.freeFlyState.yaw
    -  // }
    +  // TODO: Implement free-fly camera mode in the new architecture
    
      if (pos) {
        new tweenJs.Tween(this.camera.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start()
    -    // this.freeFlyState.position = pos
      }

    363-364: ESLint rule is disabled but never reported.

    According to static analysis, the ESLint rule '@typescript-eslint/non-nullable-type-assertion-style' is disabled but never reported in this line.

    Remove the unused ESLint disable directive to keep the codebase clean.

    - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
    const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
    🧰 Tools
    🪛 ESLint

    [error] 363-363: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between de3edda and ccb0004.

    📒 Files selected for processing (25)
    • renderer/viewer/baseGraphicsBackend.ts (1 hunks)
    • renderer/viewer/lib/basePlayerState.ts (3 hunks)
    • renderer/viewer/lib/entities.ts (18 hunks)
    • renderer/viewer/lib/holdingBlock.ts (5 hunks)
    • renderer/viewer/lib/mesher/mesher.ts (1 hunks)
    • renderer/viewer/lib/worldrendererCommon.ts (11 hunks)
    • renderer/viewer/lib/worldrendererThree.ts (12 hunks)
    • renderer/viewer/three/appShared.ts (1 hunks)
    • renderer/viewer/three/graphicsBackend.ts (1 hunks)
    • renderer/viewer/three/panorama.ts (1 hunks)
    • renderer/viewer/three/threeJsMethods.ts (1 hunks)
    • src/appViewer.ts (1 hunks)
    • src/cameraRotationControls.ts (2 hunks)
    • src/customChannels.ts (2 hunks)
    • src/index.ts (15 hunks)
    • src/inventoryWindows.ts (6 hunks)
    • src/mineflayer/items.ts (2 hunks)
    • src/mineflayer/maps.ts (2 hunks)
    • src/mineflayer/playerState.ts (4 hunks)
    • src/react/DebugOverlay.tsx (8 hunks)
    • src/rendererUtils.ts (2 hunks)
    • src/resourcesManager.ts (1 hunks)
    • src/sounds/botSoundSystem.ts (2 hunks)
    • src/vr.ts (5 hunks)
    • src/watchOptions.ts (2 hunks)
    🚧 Files skipped from review as they are similar to previous changes (7)
    • src/sounds/botSoundSystem.ts
    • src/cameraRotationControls.ts
    • renderer/viewer/three/panorama.ts
    • renderer/viewer/three/graphicsBackend.ts
    • src/mineflayer/playerState.ts
    • src/inventoryWindows.ts
    • src/react/DebugOverlay.tsx
    🧰 Additional context used
    🧬 Code Definitions (11)
    src/customChannels.ts (1)
    renderer/viewer/three/threeJsMethods.ts (1) (1)
    • getThreeJsRendererMethods (4-15)
    src/rendererUtils.ts (1)
    src/appViewer.ts (1) (1)
    • appViewer (190-190)
    renderer/viewer/three/appShared.ts (4)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • ItemSpecificContextProperties (9-9)
    src/resourcesManager.ts (1) (1)
    • ResourcesManager (54-182)
    src/inventoryWindows.ts (1) (1)
    • renderSlot (188-249)
    src/mineflayer/items.ts (2) (2)
    • getItemModelName (93-110)
    • GeneralInputItem (22-26)
    src/mineflayer/items.ts (3)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • ItemSpecificContextProperties (9-9)
    src/mineflayer/playerState.ts (1) (1)
    • playerState (201-201)
    src/appViewer.ts (1) (1)
    • appViewer (190-190)
    renderer/viewer/lib/holdingBlock.ts (3)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • IPlayerState (16-40)
    renderer/viewer/lib/worldrendererCommon.ts (1) (1)
    • WorldRendererConfig (51-51)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-601)
    renderer/viewer/lib/worldrendererThree.ts (4)
    renderer/viewer/lib/entities.ts (1) (1)
    • Entities (209-1037)
    src/appViewer.ts (3) (3)
    • GraphicsInitOptions (37-42)
    • DisplayWorldOptions (44-48)
    • RendererReactiveState (16-24)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • ItemSpecificContextProperties (9-9)
    renderer/viewer/lib/hand.ts (1) (1)
    • getMyHand (5-35)
    src/watchOptions.ts (3)
    src/globalState.ts (1) (1)
    • miscUiState (109-129)
    src/optionsStorage.ts (2) (2)
    • watchValue (213-235)
    • options (186-191)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (21-249)
    renderer/viewer/lib/entities.ts (1)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-601)
    src/appViewer.ts (6)
    src/core/progressReporter.ts (8) (8)
    • error (131-133)
    • error (156-159)
    • error (178-180)
    • error (193-195)
    • error (215-217)
    • error (227-228)
    • ProgressReporter (5-18)
    • createNotificationProgressReporter (165-182)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (21-249)
    renderer/viewer/lib/worldrendererCommon.ts (2) (2)
    • WorldRendererConfig (51-51)
    • defaultWorldRendererConfig (32-49)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • IPlayerState (16-40)
    src/mineflayer/playerState.ts (1) (1)
    • playerState (201-201)
    src/watchOptions.ts (1) (1)
    • watchOptionsAfterWorldViewInit (110-118)
    src/index.ts (1)
    src/appViewer.ts (2) (2)
    • loadBackend (87-115)
    • appViewer (190-190)
    src/vr.ts (2)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-601)
    src/appViewer.ts (1) (1)
    • appViewer (190-190)
    🪛 ESLint
    renderer/viewer/lib/worldrendererThree.ts

    [error] 363-363: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)

    🔇 Additional comments (62)
    renderer/viewer/lib/mesher/mesher.ts (1)

    124-126: Improved robustness with null/undefined check

    Adding a check for undefined or null state IDs prevents potential errors when updating blocks. This defensive programming practice aligns well with the overall PR goal of improving stability.

    renderer/viewer/baseGraphicsBackend.ts (1)

    1-11: Good foundation for reactive state management

    This new utility function creates a consistent default state object for the renderer, which will help maintain a predictable state across the application. The structure includes essential world metrics and renderer settings.

    src/mineflayer/maps.ts (2)

    3-3: Added import for new renderer methods utility

    The import aligns with the PR's goal of creating a more stable renderer layer by accessing rendering capabilities through a dedicated utility function.


    21-21: Improved abstraction with optional chaining

    Replacing direct viewer entity access with getThreeJsRendererMethods()?.updateMap(id, png) creates a cleaner separation between app and renderer. The optional chaining handles cases where the renderer might not be available.

    renderer/viewer/three/threeJsMethods.ts (1)

    1-15: Well-designed renderer method abstraction layer

    This utility function creates an elegant abstraction for accessing Three.js specific renderer methods. The implementation has several strengths:

    1. Type safety with proper return type declaration
    2. Safe handling when the renderer isn't available or is a different type
    3. Using a Proxy to maintain the asynchronous nature of the methods
    4. Clean error handling with optional chaining

    This approach supports the PR's goal of creating a stable layer between the app and renderer components.

    src/customChannels.ts (1)

    54-54: Excellent simplification of custom block updating

    The refactoring replaces complex, hard-coded block model management with a single method call, which aligns perfectly with the PR's goal of separating application and rendering code.

    This change greatly improves maintainability by abstracting rendering-specific operations behind a proper method boundary.

    src/mineflayer/items.ts (1)

    4-7: New imports to support item model resolution

    These imports are necessary for the new getItemModelName function.

    src/rendererUtils.ts (2)

    77-77: Proper abstraction for FOV updates

    This change moves from directly manipulating camera properties to updating a configuration object, following the separation of concerns principle.

    However, there's no import for appViewer in this file. Make sure to add:

    + import { appViewer } from './appViewer'

    93-93: Improved camera update mechanism

    The change from viewer.setFirstPersonCamera to appViewer.backend?.updateCamera correctly abstracts the camera positioning logic.

    The optional chaining (?.) is good defensive programming in case the backend is not yet initialized.

    renderer/viewer/lib/basePlayerState.ts (4)

    25-25: Adding getPosition method enhances position tracking capabilities.

    This new method provides a standard way to access the player's position across different implementations, which is essential for consistent position-based rendering and game mechanics.


    35-38: Good addition of rendering-related reactive properties.

    These new reactive properties (inWater, backgroundColor, ambientLight, directionalLight) properly separate visual state from core player state, allowing for more dynamic rendering effects based on player conditions.


    45-48: Correct implementation of reactive properties with sensible defaults.

    The initialization of the new reactive properties with appropriate default values ensures consistency across implementations while providing a clean starting state.


    86-88: Base implementation of getPosition returns a default position.

    This implementation provides a reasonable default for the base class. Derived classes should override this method to provide actual position data.

    Ensure all implementations that extend BasePlayerState override this method to return meaningful position data, as relying on the default Vec3(0,0,0) would cause rendering issues.

    src/resourcesManager.ts (5)

    1-22: Good modular design with typed events.

    Using TypedEmitter with well-defined event types enhances type safety and makes the event system more maintainable.


    23-46: Well-structured resource containment class.

    The LoadedResources class provides a clean encapsulation of different resource types with appropriate typing. The organization separates atlas parsers from user-specific data, which is good for maintainability.


    47-52: Clear interface for asset updates.

    The UpdateAssetsRequest interface provides a well-defined contract for updating assets, with appropriate options for filtering and versioning.


    54-64: ResourcesManager correctly extends EventEmitter with typed events.

    The class properly extends EventEmitter with typed events and initializes source data with reasonable defaults.


    177-181: Clean resource management in destroy method.

    The destroy method properly cleans up resources and resets state, which is essential for preventing memory leaks.

    renderer/viewer/lib/holdingBlock.ts (4)

    3-3: Updated imports support the renderer refactoring.

    The addition of WorldBlockProvider and WorldRendererThree imports align with the PR's objective of improving the separation between app and renderer.

    Also applies to: 13-13


    118-120: Added properties maintain original functionality.

    These properties ensure the HoldingBlock class maintains access to playerState and config while following the new architecture.


    121-129: Constructor refactored to work with WorldRendererThree.

    The constructor now properly extracts the playerState and config from the worldRenderer, maintaining the original functionality while simplifying dependencies.


    334-341: Updated method call to use worldRenderer.

    The change from using viewer directly to worldRenderer.entities follows the new architecture pattern, improving modularity.

    src/watchOptions.ts (9)

    5-9: Updated imports support new architecture.

    The imports now include WorldDataEmitter and isCypress, which align with the PR's goals of improving modularity and providing better testing support.


    20-26: Extracted updateTouch function improves reusability.

    Moving the touch detection logic to a standalone function makes the code more maintainable and reduces duplication when the same logic is needed in multiple places.


    30-32: Updated references from viewer to appViewer.

    These changes align with the PR's objective of moving away from direct viewer access to a more modular structure with appViewer as the central point of coordination.

    Also applies to: 35-36, 39-40, 43-50


    54-77: Added window focus tracking for FPS management.

    This new feature intelligently manages FPS limits based on window focus state, which can significantly improve battery life and performance on devices when the application is in the background.


    80-81: Updated FPS limit watcher to use new function.

    The watcher now calls the updateFpsLimit function, which centralizes the FPS limit logic and ensures consistency.


    84-86: Updated worldRenderer config references.

    The property paths have been updated to use appViewer.inWorldRenderingConfig, aligning with the new architecture.


    88-95: Updated lighting configuration handlers.

    These changes ensure that lighting settings are properly applied through the new appViewer interface.


    106-107: Commented out neighborChunkUpdates setting.

    This line is commented out, which might indicate an incomplete implementation or a feature that was intentionally disabled.

    Is this feature intentionally disabled? If not, consider uncommenting and implementing the property:

    -  // appViewer.inWorldRenderingConfig.neighborChunkUpdates = o.neighborChunkUpdates
    +  appViewer.inWorldRenderingConfig.neighborChunkUpdates = o.neighborChunkUpdates

    110-117: Updated watchOptionsAfterWorldViewInit with proper parameter typing.

    The function now accepts a properly typed WorldDataEmitter parameter and updates the configuration properties via appViewer.

    renderer/viewer/lib/worldrendererCommon.ts (7)

    35-47: Good additions to configuration options to improve rendering flexibility.

    These new configuration options provide more control over the rendering pipeline, which aligns well with the architectural refactoring goals outlined in the PR objectives.


    141-142: Great addition of resource management.

    The addition of itemsTexture and soundSystem properties centralizes resource management, which is a key improvement in this refactoring.


    148-159: Well-designed constructor with improved dependency injection.

    The constructor now takes a resourcesManager, displayOptions, and version instead of a config object. This change correctly separates concerns and allows for better modularity with clear responsibility boundaries.


    161-171: Good implementation of initialization logic with proper error handling.

    The init method properly checks if the renderer is already active before initialization and sets up appropriate event listeners for asset updates.


    366-370: Resource loading pattern improved.

    The refactored resource loading pattern is more maintainable, using the centralized resourcesManager to load and manage Minecraft data.


    391-415: Asset update handling is now more modular.

    The updateAssetsData method now correctly pulls data from the centralized resource manager, which improves maintainability and reduces duplicated code.


    749-766: Good resource cleanup in destroy method.

    The destroy method now properly cleans up all resources, including terminating workers and destroying the sound system, which helps prevent memory leaks.

    renderer/viewer/lib/entities.ts (9)

    26-27: Good refactoring to use WorldRendererThree.

    Importing the WorldRendererThree class directly rather than using the now-removed Viewer class is a good architectural change.


    212-216: Good default initialization for font family.

    Setting a default fontFamily of 'mojangles' ensures consistent rendering across the application.


    219-219: Improved naming from 'rendering' to 'currentlyRendering'.

    The rename to currentlyRendering is more descriptive and accurately represents the current state.


    250-250: Constructor updated to use WorldRendererThree.

    The constructor now correctly accepts worldRenderer of type WorldRendererThree instead of a viewer instance, aligning with the new architecture.


    257-263: Entity cleanup now uses consistent world renderer reference.

    The clear method now correctly uses this.worldRenderer.scene to remove entities from the scene.


    277-286: Entity rendering toggle properly updated.

    The setRendering method now uses this.currentlyRendering and this.worldRenderer.scene consistently.


    289-294: Good configuration access through worldRenderer.

    The render method now correctly accesses the configuration through this.worldRenderer.worldRendererConfig.


    1055-1055: Function signature updated for addArmorModel.

    The addArmorModel function now correctly accepts worldRenderer as its first parameter, matching the new architecture.


    1089-1089: Resource access through the resource manager.

    The armor texture is now correctly accessed through worldRenderer.resourcesManager.currentResources.

    src/index.ts (9)

    97-100: Good imports for the new architecture.

    Importing appViewer and createGraphicsBackend aligns with the new architectural approach, making the code more modular.


    118-132: Well-structured backend loading function.

    The loadBackend function properly initializes the graphics backend and handles conditional loading based on the build mode.


    162-171: Good implementation of focus handling.

    This block ensures the application starts loading when the user focuses on the window, which improves user experience.


    305-306: Resource loading now uses appViewer.

    The resource loading now correctly uses appViewer.resourcesManager.loadSourceData instead of direct loading.


    328-330: Resource preparation refactored.

    Resource preparation now uses appViewer.prepareResources instead of the previous approach with direct viewer access.


    691-694: Good implementation of world initialization.

    The world initialization now correctly uses appViewer.startWorld and appViewer.worldView.listenToBot to connect the world view to the bot.


    699-704: Camera position update correctly implemented.

    The camera position update now properly uses appViewer.backend?.updateCamera and appViewer.worldView.updatePosition.


    717-738: Good chunk loading status tracking.

    The chunk loading status is now correctly tracked through appViewer.backend.reactiveState, which provides a more reactive approach to monitoring loading progress.


    742-742:

    ❓ Verification inconclusive

    Consider re-enabling waitForChunks.

    The waitForChunks function is commented out, but it might be useful for ensuring all chunks are loaded before proceeding, especially in singleplayer mode where waiting for chunk loading improves the user experience.

    Check if waitForChunks should be re-enabled or if there's a new mechanism to ensure chunks are loaded before proceeding. If it's intentionally disabled as part of a performance improvement, consider adding a comment explaining the rationale.


    🏁 Script executed:

    #!/bin/bash
    # Look for references to waitForChunks in the codebase to see if it's used elsewhere
    
    rg -A 3 -B 3 "waitForChunks" --glob "!node_modules/**"

    Length of output: 3812


    Action: Verify and Document Chunk Waiting Behavior

    The waitForChunks function in src/index.ts is defined but its invocation (// await waitForChunks()) remains commented out. Our investigation shows that:

    • Other parts of the codebase (e.g., in renderer/viewer/three/panorama.ts and renderer/playground/*) are already using waitForChunksToRender to manage chunk rendering.
    • The configuration in src/optionsStorage.ts (with waitForChunksRender: 'sp-only') indicates that chunk-waiting might be intended only for singleplayer mode.
    • The early return in the waitForChunks function (if (appQueryParams.sp === '1') return //todo) suggests that its behavior in singleplayer mode is still under deliberation.

    Next Steps:

    • If the current mechanism (waitForChunksToRender) is sufficient: Please add a comment explaining why the waitForChunks call in src/index.ts is intentionally left disabled.
    • If waiting for chunks in the index file is still needed: Uncomment the line and add documentation to clarify its role and any performance implications, especially regarding mode-specific behavior.
    src/appViewer.ts (2)

    87-115: Consider error-handling around backend loading.

    Within loadBackend, if backend initialization fails, the code just logs the error. For large-scale applications, you might handle it more robustly (e.g., fallback strategy or user-facing error message) to avoid silent failures.


    190-192: Global appViewer instance.

    Exporting a single global instance is convenient but can lead to hidden state issues if anything else tries to instantiate AppViewer. Ensure thread-safety if concurrency arises in the future.

    src/vr.ts (2)

    12-19: Improved dependency injection with worldRenderer parameter.

    Good refactoring to accept a worldRenderer parameter instead of relying on global variables, which makes the code more maintainable and testable.


    195-196: Camera update code is commented out.

    Line 195 is commented out and replaced with a different approach in line 196. This suggests an incomplete migration or a temporary workaround. The current code updates the camera to match bot.entity position, which might not be the original intent.

    Please review whether the camera update approach in line 196 is the intended implementation or if line 195 should be uncommented. If this is intentional, consider removing the commented code.

    - // appViewer.backend?.updateCamera(null, yawOffset, 0)
    appViewer.backend?.updateCamera(null, bot.entity.yaw, bot.entity.pitch)
    renderer/viewer/lib/worldrendererThree.ts (2)

    165-188: Demo methods contain unnecessary ts-expect-error directives.

    The demoModel and demoItem methods use @ts-expect-error without clear indication of what errors are being suppressed. Additionally, they reference an undefined cursorBlockRel function.

    These methods should either:

    1. Be properly typed to avoid TS errors, or
    2. Include comments explaining the suppressed errors
    3. Define or import the missing cursorBlockRel function

    584-596: Well-implemented camera roll functionality.

    The setCameraRoll method is a good addition that implements proper quaternion-based camera rotation, which will be useful for effects like view bobbing and vehicle movements.

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 8

    ♻️ Duplicate comments (3)
    renderer/viewer/three/graphicsBackend.ts (2)

    64-66: ⚠️ Potential issue

    Avoid global variable assignments that break encapsulation.

    The code assigns to multiple global variables (window.viewer, window.world, window.viewer.world), which could lead to namespace conflicts and unexpected behavior. This contradicts the PR's goal of improving modularity.

    - window.viewer ??= {}
    - window.world = worldRenderer
    - window.viewer.world = worldRenderer
    + // If needed for debugging/access, use a single namespaced object
    + if (process.env.NODE_ENV === 'development') {
    +   const debugGlobals = (window.__mcDebug = window.__mcDebug || {})
    +   debugGlobals.viewer = debugGlobals.viewer || {}
    +   debugGlobals.world = worldRenderer
    +   debugGlobals.viewer.world = worldRenderer
    + }

    51-53: ⚠️ Potential issue

    The prepareResources method is incomplete.

    This method only stores the version but doesn't actually prepare any resources. Given the method name and return type (Promise), users would expect it to perform some asynchronous resource preparation.

    const prepareResources = async (ver: string, progressReporter: ProgressReporter): Promise<void> => {
      version = ver
    +  // Implement resource preparation, for example:
    +  try {
    +    await progressReporter.executeWithMessage('Loading resources', async () => {
    +      await initOptions.resourcesManager.loadResources(ver)
    +    })
    +  } catch (error) {
    +    console.error('Failed to prepare resources:', error)
    +    initOptions.displayCriticalError(error instanceof Error ? error : new Error('Failed to prepare resources'))
    +  }
    }
    renderer/viewer/lib/worldrendererThree.ts (1)

    165-188: ⚠️ Potential issue

    Demo methods contain unnecessary ts-expect-error directives.

    The demoModel and demoItem methods use @ts-expect-error without clear indication of what errors are being suppressed. Additionally, they reference an undefined cursorBlockRel function.

    These methods should either:

    1. Be properly typed to avoid TS errors, or
    2. Include comments explaining the suppressed errors
    3. Define or import the missing cursorBlockRel function
    - //@ts-expect-error
    - const pos = cursorBlockRel(0, 1, 0).position
    + // Define a proper type or position calculation
    + const pos = { x: 0, y: 1, z: 0 }
    🧹 Nitpick comments (28)
    renderer/viewer/lib/mesher/mesher.ts (1)

    124-126: Handle invalid state IDs more explicitly?
    The newly added check for undefined or null state IDs helps avoid potential runtime errors. However, you might consider logging or otherwise handling these cases more thoroughly to aid debugging when a state ID is not provided.

    src/mineflayer/maps.ts (1)

    21-21: Consider adding fallback handling for map updates

    The optional chaining prevents errors when the renderer methods are unavailable, but map updates would silently fail in that case. Consider adding error handling or logging to notify when map updates can't be processed.

    -      getThreeJsRendererMethods()?.updateMap(id, png)
    +      const methods = getThreeJsRendererMethods()
    +      if (methods) {
    +        methods.updateMap(id, png)
    +      } else {
    +        console.warn('Unable to update map: Three.js renderer methods not available')
    +      }
    renderer/viewer/three/threeJsMethods.ts (1)

    4-15:

    ❓ Verification inconclusive

    Review renderer access mechanism

    The implementation provides a clean abstraction for accessing Three.js renderer methods, but has a few areas that could be improved:

    1. The function relies on a non-standard property __isThreeJsRenderer for type checking. Consider using a more explicit mechanism if possible.

    2. The appViewer variable is used but not imported in the visible code, suggesting it might be a global variable. This could complicate testing and encapsulation.

    3. The Proxy makes all method calls asynchronous, which is good for consistency but could be inefficient for methods that don't need to be async.

    export function getThreeJsRendererMethods (): ThreeJsBackendMethods | undefined {
      const renderer = appViewer.backend
    -  if (!renderer?.['__isThreeJsRenderer'] || !renderer.backendMethods) return
    +  // More explicit type checking
    +  if (!renderer || !('__isThreeJsRenderer' in renderer) || !renderer.__isThreeJsRenderer || !renderer.backendMethods) return
      return new Proxy(renderer.backendMethods, {
        get (target, prop) {
          return async (...args) => {
            const result = await (target[prop as any] as any)(...args)
            return result
          }
        }
      }) as ThreeJsBackendMethods
    }

    🌐 Web query:

    What are best practices for type checking in TypeScript when dealing with duck typing?
    

    💡 Result:

    TypeScript's approach to duck typing (structural typing) requires specific practices to maintain type safety while leveraging flexibility. Here are the key best practices:

    Compile-Time Type Safety

    Use interfaces for structural contracts
    Define clear interfaces to enforce expected object shapes while allowing structural compatibility[6]. This leverages TypeScript's core principle of structural subtyping:

    interface User {
      id: string;
      email: string;
    }
    
    function welcomeUser(user: User) {
      // TypeScript verifies any passed object has these properties
    }

    Prefer type aliases/intersections over any
    Avoid losing type safety with any when possible. Use explicit types:

    type Admin = User & { privileges: string[] }; // Combine types safely[7]

    Implement exhaustive union checks
    When handling multiple potential types:

    type Shape = Circle | Square | Triangle;
    
    function getArea(shape: Shape): number {
      switch(shape.kind) {
        case 'circle': return Math.PI * shape.radius ** 2;
        case 'square': return shape.size ** 2;
        case 'triangle': return (shape.base * shape.height)/2;
        default: 
          const _exhaustiveCheck: never = shape;
          return _exhaustiveCheck; // Triggers error if new type added[5]
      }
    }

    Runtime Validation

    Use type predicates for unknown data
    Validate external data at runtime while maintaining type safety[2][6]:

    interface APIResponse {
      data: unknown;
    }
    
    function isUser(obj: unknown): obj is User {
      return !!obj && 
        typeof obj === 'object' && 
        'id' in obj && 
        'email' in obj;
    }
    
    function processResponse(response: APIResponse) {
      if (isUser(response.data)) {
        console.log(`Welcome ${response.data.email}`); // Now type-safe
      }
    }

    Implement brand checking for critical types
    Prevent structural matches with unintended types using branding:

    type UserID = string & { readonly brand: unique symbol };
    
    function createUser(id: string): UserID {
      return id as UserID; // Explicit branding prevents accidental misuse[8]
    }

    Structural Typing Considerations

    Leverage utility types wisely
    Transform types while maintaining safety:

    type PartialUser = Partial<User>; // All properties optional[7]
    type RequiredUser = Required<User>; // All properties mandatory
    
    function updateUser(id: string, updates: PartialUser) {
      // Safely handle partial updates
    }

    Handle excess properties cautiously
    Use explicit type annotations to catch unwanted properties:

    interface Point {
      x: number;
      y: number;
    }
    
    const p: Point = {
      x: 1,
      y: 2,
      z: 3 // Error: Object literal may only specify known properties[6]
    };

    Best Practice Checklist

    1. Validation layers
      Implement input validation at system boundaries using type predicates[2][6]

    2. Avoid empty interfaces
      Use never or branded types instead of empty contracts[7]

    3. Precise generic constraints
      Use generics with structural requirements:

      function getProperty<T, K extends keyof T>(obj: T, key: K) {
        return obj[key]; // Type-safe property access
      }
    4. Union type exhaustiveness checks
      Use never type in default cases to catch unhandled types[5]

    5. Prefer unknown over any
      Force explicit type checks before usage[3][7]:

      function safeParse(data: unknown): User {
        if (isUser(data)) return data;
        throw new Error('Invalid user data');
      }

    These practices combine TypeScript's compile-time checks with runtime validation to achieve robust duck typing implementations while preventing common structural typing pitfalls like unintended type compatibility[8]. The key is leveraging TypeScript's type system to create self-documenting code that fails fast and provides clear feedback during development.

    Citations:


    Renderer Access Abstraction – Verify and Enhance

    The updated type check using the in operator is a step in the right direction. However, please consider the following points based on TypeScript’s best practices for duck typing and overall design:

    • Type Checking Improvement:
      Instead of scattering inline checks, a dedicated type guard can help clarify intent and improve code reuse. For example, implementing a function such as:

      function isThreeJsRenderer(renderer: any): renderer is { __isThreeJsRenderer: boolean; backendMethods: any } {
        return renderer &&
          typeof renderer === 'object' &&
          '__isThreeJsRenderer' in renderer &&
          renderer.__isThreeJsRenderer &&
          !!renderer.backendMethods;
      }

      would allow you to simplify the check to:

      export function getThreeJsRendererMethods (): ThreeJsBackendMethods | undefined {
        const renderer = appViewer.backend
      -  if (!renderer?.['__isThreeJsRenderer'] || !renderer.backendMethods) return
      +  if (!isThreeJsRenderer(renderer)) return
        return new Proxy(renderer.backendMethods, {
          get (target, prop) {
            return async (...args) => {
              const result = await (target[prop as any] as any)(...args)
              return result
            }
          }
        }) as ThreeJsBackendMethods
      }
    • Global Dependency Concern:
      The use of appViewer without an explicit import indicates it might be a global. This can complicate unit testing and reduce encapsulation. Consider passing it as a parameter or importing it explicitly to improve clarity and maintainability.

    • Asynchronous Proxy Overhead:
      Wrapping every method in an async function ensures consistency but may add unnecessary overhead for methods that operate synchronously. Evaluate whether all backend methods genuinely need asynchronous handling or if selective wrapping might be more efficient.

    Please verify these suggestions in the context of the overall system design and usage patterns.

    src/customChannels.ts (1)

    54-54: Great simplification of custom block handling

    Replacing complex block model management with a single method call significantly simplifies the code while maintaining functionality. This is an excellent example of the benefits of the new abstraction layer introduced in this PR.

    Consider adding error handling in case the renderer methods are unavailable:

    -    getThreeJsRendererMethods()?.updateCustomBlock(chunkKey, blockPosKey, model)
    +    const methods = getThreeJsRendererMethods()
    +    if (methods) {
    +      methods.updateCustomBlock(chunkKey, blockPosKey, model)
    +    } else {
    +      console.warn(`Unable to update custom block at ${blockPosKey}: Three.js renderer methods not available`)
    +    }
    renderer/viewer/three/appShared.ts (1)

    26-29: Consider adding type validation for item

    The spread operator is used to merge item properties, but there's no validation that item contains all required properties for GeneralInputItem. Consider adding runtime validation or using a more specific type for the item parameter.

    - const model = getItemModelName({
    -   ...item,
    -   name,
    - } as GeneralInputItem, specificProps)
    + // Ensure item has required properties for GeneralInputItem
    + const itemWithName: GeneralInputItem = {
    +   ...item,
    +   name,
    + }
    + const model = getItemModelName(itemWithName, specificProps)
    src/resourcesManager.ts (3)

    74-164: Consider splitting updateAssetsData into smaller functions

    The updateAssetsData method is quite long (90+ lines) and handles multiple concerns: loading source data, creating texture atlases, handling seasonal textures, etc. Consider extracting some of these operations into helper methods for better maintainability.

    For example, you could extract atlas creation logic:

    private async createBlocksAtlas(resources: LoadedResources, request: UpdateAssetsRequest): Promise<{atlas: any, canvas: HTMLCanvasElement}> {
      console.time('createBlocksAtlas')
      const blockTexturesChanges = this.getSeasonalTextureChanges()
      const customBlockTextures = Object.keys(resources.customTextures.blocks?.textures ?? {})
      
      const result = await this.sourceBlocksAtlases.makeNewAtlas(
        resources.texturesVersion,
        (textureName) => {
          if (request.includeOnlyBlocks && !request.includeOnlyBlocks.includes(textureName)) return false
          const texture = resources.customTextures.blocks?.textures[textureName]
          return blockTexturesChanges[textureName] ?? texture
        },
        undefined,
        undefined,
        customBlockTextures
      )
      console.timeEnd('createBlocksAtlas')
      return result
    }
    
    private getSeasonalTextureChanges(): Record<string, string> {
      const blockTexturesChanges = {} as Record<string, string>
      const date = new Date()
      if ((date.getMonth() === 11 && date.getDate() >= 24) || (date.getMonth() === 0 && date.getDate() <= 6)) {
        Object.assign(blockTexturesChanges, christmasPack)
      }
      return blockTexturesChanges
    }

    65-72: Use async initialization pattern for loadSourceData

    The method loadSourceData calls loadMcData and sets a class property, but doesn't handle errors. Consider using a more robust async initialization pattern that properly handles errors and returns the instance.

    - async loadSourceData (version: string) {
    -   await this.loadMcData(version)
    -   this.sourceBlockStatesModels ??= await importLargeData('blockStatesModels')
    - }
    
    + async loadSourceData (version: string): Promise<void> {
    +   try {
    +     await this.loadMcData(version)
    +     this.sourceBlockStatesModels ??= await importLargeData('blockStatesModels')
    +   } catch (error) {
    +     console.error(`Failed to load source data for version ${version}:`, error)
    +     throw new Error(`Failed to load source data: ${error.message}`)
    +   }
    + }

    166-181: Add a method to check if resources are loaded

    The downloadDebugAtlas method throws an error if resources aren't loaded. Consider adding a helper method to check if resources are available to make error checking more consistent across the codebase.

    + /**
    +  * Returns the current resources or throws an error if not loaded
    +  */
    + getResources(): LoadedResources {
    +   if (!this.currentResources) {
    +     throw new Error('No resources loaded')
    +   }
    +   return this.currentResources
    + }
    
      async downloadDebugAtlas (isItems = false) {
    -   const resources = this.currentResources
    -   if (!resources) throw new Error('No resources loaded')
    +   const resources = this.getResources()
        const atlasParser = (isItems ? resources.itemsAtlasParser : resources.blocksAtlasParser)!
        // ... rest of the method
      }
    renderer/viewer/three/graphicsBackend.ts (3)

    46-46: Use caution with global assignments like window.panoramaRenderer.

    This assignment creates a global variable which could lead to namespace conflicts and makes testing more difficult. Consider removing it or using a namespaced debug object if it's only needed for debugging.

    - window.panoramaRenderer = panoramaRenderer
    + // If needed for debugging
    + if (process.env.NODE_ENV === 'development') {
    +   window.__mcDebug = window.__mcDebug || {}
    +   window.__mcDebug.panoramaRenderer = panoramaRenderer
    + }

    85-86: Avoid using @ts-expect-error for non-standard properties.

    Using @ts-expect-error to add a non-standard property can lead to type safety issues. Either update the interface definition or use a symbol or WeakMap for storing metadata.

    const backend: GraphicsBackend = {
    -    //@ts-expect-error mark as three.js renderer
    -    __isThreeJsRenderer: true,
    +    // Use a proper type guard function instead
         NAME: `three.js ${THREE.REVISION}`,

    At the module level, you could add:

    // Type guard to check if a backend is a ThreeJS backend
    export function isThreeJsRenderer(backend: GraphicsBackend): backend is GraphicsBackend & ThreeJsBackendMethods {
      return backend.NAME.startsWith('three.js');
    }

    23-114: Consider using a class instead of a factory function.

    The current implementation uses a factory function with closures to maintain private state. Consider using a class with private members for better encapsulation and readability.

    export class ThreeJsGraphicsBackend implements GraphicsBackend {
      readonly NAME: string;
      private documentRenderer: DocumentRenderer;
      private panoramaRenderer: PanoramaRenderer | null = null;
      private worldRenderer: WorldRendererThree | null = null;
      private version = '';
      public readonly reactiveState: RendererReactiveState;
    
      constructor(private initOptions: GraphicsInitOptions) {
        this.NAME = `three.js ${THREE.REVISION}`;
        this.documentRenderer = new DocumentRenderer(initOptions);
        // Initialize reactiveState with proxy...
        this.reactiveState = proxy({
          // Same implementation as in the original code
        });
        
        if (process.env.NODE_ENV === 'development') {
          // For debugging only
          (window.__mcDebug = window.__mcDebug || {}).threeBackend = this;
        }
      }
    
      startPanorama(): void {
        // Implementation
      }
    
      // Other methods as in the original code...
      
      get backendMethods(): ThreeJsBackendMethods | undefined {
        if (!this.worldRenderer) return undefined;
        return getBackendMethods(this.worldRenderer);
      }
    }
    
    // Export the factory function that creates the backend
    export default function createGraphicsBackend(initOptions: GraphicsInitOptions): GraphicsBackend {
      return new ThreeJsGraphicsBackend(initOptions);
    }
    src/watchOptions.ts (2)

    43-50: Consider using an enum or object map for statsVisible values.

    The code uses numeric values (0, 1, 2) for appViewer.config.statsVisible, which might be unclear to other developers. Consider using an enum or object map for better code readability.

    + // At the top of the file or in a constants module
    + const StatsVisibility = {
    +   NONE: 0,
    +   BASIC: 1,
    +   ADVANCED: 2
    + } as const;
    
      watchValue(options, o => {
        const { renderDebug } = o
        if (renderDebug === 'none' || isCypress()) {
    -     appViewer.config.statsVisible = 0
    +     appViewer.config.statsVisible = StatsVisibility.NONE
        } else if (o.renderDebug === 'basic') {
    -     appViewer.config.statsVisible = 1
    +     appViewer.config.statsVisible = StatsVisibility.BASIC
        } else if (o.renderDebug === 'advanced') {
    -     appViewer.config.statsVisible = 2
    +     appViewer.config.statsVisible = StatsVisibility.ADVANCED
        }
      })

    70-77: Consider debouncing the focus/blur event handlers.

    Event handlers for window focus/blur might be called frequently in some scenarios (like when a user is switching between applications). Consider debouncing these event handlers to avoid frequent updates to the FPS limit.

    const debounce = (fn: Function, delay: number) => {
      let timeoutId: number;
      return (...args: any[]) => {
        clearTimeout(timeoutId);
        timeoutId = window.setTimeout(() => fn(...args), delay);
      };
    };
    
    const handleFocus = debounce(() => {
      windowFocused = true;
      updateFpsLimit(options);
    }, 100);
    
    const handleBlur = debounce(() => {
      windowFocused = false;
      updateFpsLimit(options);
    }, 100);
    
    window.addEventListener('focus', handleFocus);
    window.addEventListener('blur', handleBlur);
    renderer/viewer/lib/entities.ts (2)

    223-234: Commented out getItemUv property type.

    There is a commented-out type definition for getItemUv. If this property is no longer needed, the comments should be removed entirely to avoid confusion.

    - // getItemUv: undefined | (() => {
    - //   texture: THREE.Texture;
    - //   u: number;
    - //   v: number;
    - //   su?: number;
    - //   sv?: number;
    - //   size?: number;
    - //   modelName?: string;
    - // } | {
    - //   resolvedModel: BlockModel
    - //   modelName: string
    - // } | undefined)

    535-539: Code formatting in getItemMesh method.

    The destructuring assignment and variable declaration across multiple lines with different indentation makes the code less readable. Consider formatting this in a more consistent way.

    - const textureThree = textureUv.renderInfo?.texture === 'blocks' ? this.worldRenderer.material.map! : this.itemsTexture!
    - // todo use geometry buffer uv instead!
    - const { u, v, su, sv, texture } = textureUv
    - const size = undefined
    - const itemsTexture = textureThree.clone()
    + // todo use geometry buffer uv instead!
    + const textureThree = textureUv.renderInfo?.texture === 'blocks' 
    +   ? this.worldRenderer.material.map! 
    +   : this.itemsTexture!;
    + const { u, v, su, sv, texture } = textureUv;
    + const size = undefined;
    + const itemsTexture = textureThree.clone();
    src/vr.ts (2)

    19-19: Invocation of enableVr() with no parameters is neat.
    Consider passing optional config flags if the VR setup steps expand in the future.


    88-88: Appropriately toggling vrSupport off.
    If intended to fully exit VR mode, ensure any post-VR cleanup (e.g., resetting camera or controls) is invoked at call sites.

    renderer/viewer/lib/worldrendererCommon.ts (5)

    39-48: Extended WorldRenderer config fields.
    Fields like vrSupport, addChunksBatchWaitTime, and various rendering booleans are helpful.
    Just ensure that all new config keys have documented defaults and usage across the codebase.


    391-392: updateAssetsData() method expansions.

    1. Setting mesherConfig.textureSize from the blocks atlas.
    2. Broadcasting mesher data to workers.
      These changes unify the resource pipeline. Be sure to handle errors if itemsRenderer is missing or incomplete.

    Also applies to: 394-394, 397-399


    493-510: setBlockStateId with chunk queuing logic.
    The approach is thorough, deferring block updates until chunk loading is done. Watch for memory usage if many updates queue at once.


    515-609: connect(worldView: WorldDataEmitter) method introduction.
    Centralizing all event bindings (loadChunk, blockUpdate, unloadChunk, etc.) is a big improvement in maintainability.
    One caution: If WorldRendererThree has special logic, ensure it doesn’t overshadow standard hooking.


    611-657: setBlockStateIdInner handles neighbor chunk updates thoroughly.
    You do a lot of boundary checks (pos.x & 15). Ensure performance is not impacted by repeated calls on large builds.

    src/inventoryWindows.ts (1)

    224-224: Item rendering logic improved.

    1. Confirming itemsRenderer is defined with assertDefined.
    2. Fallback to missing_texture or block/errored is a robust approach.
      Make sure repeated in-game errors don’t spam logs.

    Also applies to: 226-227, 230-231

    src/appViewer.ts (2)

    139-158: Consider adding validation for duplicate world starts.

    The startWorld method correctly checks if a world is already started, but doesn't ensure that the current display isn't still showing a panorama. This could lead to unexpected behavior if called improperly.

      startWorld (world, renderDistance: number, playerStateSend: IPlayerState = playerState) {
        if (this.currentDisplay === 'world') throw new Error('World already started')
    +   if (this.currentDisplay === 'menu') {
    +     this.disconnectBackend()
    +   }
        this.currentDisplay = 'world'
        // Rest of the method remains unchanged

    193-206: Clean up commented code in initialMenuStart.

    There's a significant block of commented-out code that appears to be testing/development code. Since this is a major refactoring, consider removing this code to keep the codebase clean, or add a TODO explaining its purpose.

    const initialMenuStart = async () => {
      if (appViewer.currentDisplay === 'world') {
        appViewer.resetBackend()
      }
      appViewer.startPanorama()
    
    -  // await appViewer.resourcesManager.loadMcData('1.21.4')
    -  // const world = getSyncWorld('1.21.4')
    -  // await appViewer.prepareResources('1.21.4', createNullProgressReporter())
    -  // world.setBlockStateId(new Vec3(0, 64, 0), 1)
    -  // appViewer.startWorld(world, 3, new Vec3(0, 64, 0), new BasePlayerState())
    -  // appViewer.backend?.updateCamera(new Vec3(0, 64, 2), 0, 0)
    -  // void appViewer.worldView.init(new Vec3(0, 64, 0))
    }
    renderer/viewer/lib/worldrendererThree.ts (3)

    343-352: Clean up commented code in updateCamera.

    There's commented code related to freeFlyMode that should be removed or properly implemented. Commented code blocks can make maintenance more difficult, especially in a significant refactoring like this.

    updateCamera (pos: Vec3 | null, yaw: number, pitch: number): void {
    -  // if (this.freeFlyMode) {
    -  //   pos = this.freeFlyState.position
    -  //   pitch = this.freeFlyState.pitch
    -  //   yaw = this.freeFlyState.yaw
    -  // }
    
      if (pos) {
        new tweenJs.Tween(this.camera.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start()
    -    // this.freeFlyState.position = pos
      }
      this.camera.rotation.set(pitch, yaw, this.cameraRoll, 'ZYX')
    }

    356-371: Good camera handling in render method.

    The render method now properly handles size changes and FOV settings, ensuring that the camera is configured correctly before rendering. However, there's another commented reference to freeFlyMode that should be addressed.

    - if (this.displayOptions.inWorldRenderingConfig.showHand/*  && !this.freeFlyMode */) {
    + if (this.displayOptions.inWorldRenderingConfig.showHand) {
      this.holdingBlock.render(this.camera, this.renderer, this.ambientLight, this.directionalLight)
      this.holdingBlockLeft.render(this.camera, this.renderer, this.ambientLight, this.directionalLight)
    }
    🧰 Tools
    🪛 ESLint

    [error] 363-363: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)


    363-363: Remove unused ESLint disable comment.

    The ESLint directive @typescript-eslint/non-nullable-type-assertion-style is disabled but never used, according to static analysis.

    - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
      const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
    🧰 Tools
    🪛 ESLint

    [error] 363-363: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between de3edda and ccb0004.

    📒 Files selected for processing (25)
    • renderer/viewer/baseGraphicsBackend.ts (1 hunks)
    • renderer/viewer/lib/basePlayerState.ts (3 hunks)
    • renderer/viewer/lib/entities.ts (18 hunks)
    • renderer/viewer/lib/holdingBlock.ts (5 hunks)
    • renderer/viewer/lib/mesher/mesher.ts (1 hunks)
    • renderer/viewer/lib/worldrendererCommon.ts (11 hunks)
    • renderer/viewer/lib/worldrendererThree.ts (12 hunks)
    • renderer/viewer/three/appShared.ts (1 hunks)
    • renderer/viewer/three/graphicsBackend.ts (1 hunks)
    • renderer/viewer/three/panorama.ts (1 hunks)
    • renderer/viewer/three/threeJsMethods.ts (1 hunks)
    • src/appViewer.ts (1 hunks)
    • src/cameraRotationControls.ts (2 hunks)
    • src/customChannels.ts (2 hunks)
    • src/index.ts (15 hunks)
    • src/inventoryWindows.ts (6 hunks)
    • src/mineflayer/items.ts (2 hunks)
    • src/mineflayer/maps.ts (2 hunks)
    • src/mineflayer/playerState.ts (4 hunks)
    • src/react/DebugOverlay.tsx (8 hunks)
    • src/rendererUtils.ts (2 hunks)
    • src/resourcesManager.ts (1 hunks)
    • src/sounds/botSoundSystem.ts (2 hunks)
    • src/vr.ts (5 hunks)
    • src/watchOptions.ts (2 hunks)
    🚧 Files skipped from review as they are similar to previous changes (5)
    • renderer/viewer/three/panorama.ts
    • src/sounds/botSoundSystem.ts
    • src/cameraRotationControls.ts
    • src/mineflayer/playerState.ts
    • src/react/DebugOverlay.tsx
    🧰 Additional context used
    🧬 Code Definitions (12)
    src/mineflayer/maps.ts (1)
    renderer/viewer/three/threeJsMethods.ts (1) (1)
    • getThreeJsRendererMethods (4-15)
    src/mineflayer/items.ts (3)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • ItemSpecificContextProperties (9-9)
    src/mineflayer/playerState.ts (1) (1)
    • playerState (201-201)
    src/appViewer.ts (1) (1)
    • appViewer (190-190)
    src/customChannels.ts (1)
    renderer/viewer/three/threeJsMethods.ts (1) (1)
    • getThreeJsRendererMethods (4-15)
    src/watchOptions.ts (3)
    src/globalState.ts (1) (1)
    • miscUiState (109-129)
    src/optionsStorage.ts (2) (2)
    • watchValue (213-235)
    • options (186-191)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (21-249)
    renderer/viewer/three/graphicsBackend.ts (4)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-601)
    src/appViewer.ts (5) (5)
    • GraphicsBackendLoader (50-50)
    • GraphicsInitOptions (37-42)
    • RendererReactiveState (16-24)
    • DisplayWorldOptions (44-48)
    • GraphicsBackend (53-67)
    renderer/viewer/three/panorama.ts (1) (1)
    • PanoramaRenderer (24-206)
    src/core/progressReporter.ts (1) (1)
    • ProgressReporter (5-18)
    src/index.ts (3)
    src/appViewer.ts (2) (2)
    • loadBackend (87-115)
    • appViewer (190-190)
    src/globalState.ts (1) (1)
    • miscUiState (109-129)
    src/core/progressReporter.ts (1) (1)
    • createConsoleLogProgressReporter (184-197)
    renderer/viewer/lib/holdingBlock.ts (3)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • IPlayerState (16-40)
    renderer/viewer/lib/worldrendererCommon.ts (1) (1)
    • WorldRendererConfig (51-51)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-601)
    renderer/viewer/lib/entities.ts (4)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-601)
    renderer/viewer/lib/holdingBlock.ts (1) (1)
    • getBlockMeshFromModel (908-914)
    renderer/viewer/lib/entity/armorModels.ts (1) (1)
    • armorTextures (19-35)
    renderer/viewer/lib/entity/EntityMesh.ts (1) (1)
    • getMesh (225-356)
    src/appViewer.ts (4)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (21-249)
    renderer/viewer/lib/worldrendererCommon.ts (2) (2)
    • WorldRendererConfig (51-51)
    • defaultWorldRendererConfig (32-49)
    src/mineflayer/playerState.ts (1) (1)
    • playerState (201-201)
    src/watchOptions.ts (1) (1)
    • watchOptionsAfterWorldViewInit (110-118)
    src/rendererUtils.ts (1)
    src/appViewer.ts (1) (1)
    • appViewer (190-190)
    renderer/viewer/lib/worldrendererCommon.ts (5)
    src/resourcesManager.ts (1) (1)
    • ResourcesManager (54-182)
    src/appViewer.ts (1) (1)
    • DisplayWorldOptions (44-48)
    renderer/viewer/lib/mesher/shared.ts (1) (1)
    • getBlockAssetsCacheKey (55-57)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (21-249)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-601)
    renderer/viewer/lib/worldrendererThree.ts (4)
    renderer/viewer/lib/entities.ts (1) (1)
    • Entities (209-1037)
    src/appViewer.ts (3) (3)
    • GraphicsInitOptions (37-42)
    • DisplayWorldOptions (44-48)
    • RendererReactiveState (16-24)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • ItemSpecificContextProperties (9-9)
    renderer/viewer/lib/hand.ts (1) (1)
    • getMyHand (5-35)
    🪛 ESLint
    renderer/viewer/lib/worldrendererThree.ts

    [error] 363-363: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)

    🔇 Additional comments (82)
    renderer/viewer/lib/basePlayerState.ts (3)

    25-25: Good addition of getPosition in the interface
    Declaring getPosition ensures that all player state implementations must expose positional data, improving clarity across the codebase.


    35-38: New reactive properties for environment states
    Adding inWater, backgroundColor, ambientLight, and directionalLight extends the state’s coverage of environmental details. Make sure relevant logic is updated to use these properties effectively.


    44-48: Default property values
    Defaulting properties to false or 0 is reasonable. However, verify that these “empty” defaults don’t lead to unexpected behaviors if the actual state should differ at runtime.

    renderer/viewer/lib/holdingBlock.ts (8)

    3-3: Importing WorldBlockProvider
    Adding the named import clarifies usage of type-safe block model transformations within this module.


    13-13: Importing WorldRendererThree
    This import indicates that the class now integrates directly with Three.js-based world rendering, aligning with the new architecture.


    118-119: New class fields for playerState and config
    Shifting these fields to class-level properties and assigning them from worldRenderer.displayOptions consolidates dependency management.


    121-121: Refactored constructor signature
    Accepting a WorldRendererThree instance and deriving config/state from it simplifies the parameter list and maintains consistency with your centralized design.


    123-123: Assigning this.playerState
    Please confirm that displayOptions.playerState is always defined before usage, or consider adding a fallback to avoid null references.


    128-128: Setting this.config from displayOptions.inWorldRenderingConfig
    Ensure that inWorldRenderingConfig is fully initialized to avoid issues if it’s missing fields or unexpectedly null.


    334-334: Adopting this.worldRenderer.entities.getItemMesh
    Migrating from a direct viewer reference to the worldRenderer instance should improve encapsulation. Good step toward a more modular design.


    908-908: Extended getBlockMeshFromModel signature
    Passing blockProvider as an explicit argument clarifies dependencies and allows for more robust block model transformations.

    renderer/viewer/baseGraphicsBackend.ts (1)

    1-11: Well-structured reactive state initializer

    This function provides a clean way to initialize the default reactive state for the rendering system. The approach of using a function rather than a constant ensures each caller receives a fresh object instance, preventing shared reference issues.

    The state structure logically organizes properties for world loading status, renderer identification, and UI behavior. This aligns well with the PR objective of improving state management in the rendering pipeline.

    src/mineflayer/maps.ts (1)

    3-3: LGTM: New import for renderer methods

    The import aligns with the PR's goal of separating the application code from direct renderer access.

    renderer/viewer/three/threeJsMethods.ts (1)

    1-3: LGTM: Proper imports for type definitions

    The import statements correctly bring in the necessary type definitions for the function.

    src/customChannels.ts (2)

    2-2: LGTM: New import for renderer methods

    The import aligns with the PR's goal of separating the application code from direct renderer access.


    60-89:

    ✅ Verification successful

    Unused JEI channel registration function

    The registeredJeiChannel() function is defined but never called in the visible code. If this is intentional for future use, consider adding a TODO comment explaining when it will be used, or add the necessary call to activate it if it should be active.

    registerBlockModelsChannel()
    + // TODO: Enable JEI channel when feature is ready
    + // registeredJeiChannel()

    🏁 Script executed:

    #!/bin/bash
    # Check if registeredJeiChannel is called elsewhere in the codebase
    grep -r "registeredJeiChannel()" --include="*.ts" --include="*.js" .

    Length of output: 70


    JEI Channel Registration Function Remains Inactive

    After verifying with grep, there are no calls to registeredJeiChannel() in the codebase. If this function is being reserved for future use, please add a TODO comment indicating under which conditions it should be activated. Otherwise, consider removing it to avoid confusion. For example:

     registerBlockModelsChannel()
    +// TODO: Activate JEI channel registration when the JEI feature is implemented
    +// registeredJeiChannel()
    renderer/viewer/three/appShared.ts (4)

    7-18: Good type definition structure

    The return type is well-defined with clear alternatives (either UV coordinates with texture information or a resolved model). This provides good type safety and clear expectations for callers.


    19-25: Proper resource validation

    The function properly validates that resources are loaded before attempting to access them, and includes a clear error message when the item name can't be found.


    57-69: Good error handling with fallback

    The function properly handles errors by catching exceptions, reporting them, and returning default values. This ensures the UI won't crash even if there are issues with textures or rendering information.


    31-37:

    ❓ Verification inconclusive

    Verify renderSlot function behavior

    The renderSlot function is called with false, true parameters, but the meaning of these boolean flags isn't immediately clear. Consider using named parameters or constants to improve readability.


    🏁 Script executed:

    #!/bin/bash
    # Description: Check how renderSlot is defined and used elsewhere
    rg -A 2 "renderSlot(\(|export const renderSlot)" -t ts
    
    # Check for other places where renderSlot is used with boolean params
    rg "renderSlot\(.*,.*(true|false)" -t ts

    Length of output: 578


    Clarify renderSlot Parameter Usage
    The call in renderer/viewer/three/appShared.ts still passes literal booleans (false, true) whose intent isn’t immediately clear. Notably, another usage in src/inventoryWindows.ts uses a variable (e.g. debugIsQuickbar), which suggests there’s meaningful control logic behind these parameters. To improve readability and reduce ambiguity, it would be beneficial to refactor renderSlot calls by replacing positional booleans with named constants or an options object. Please verify that this change aligns with the expected behavior across all usage sites.

    src/rendererUtils.ts (2)

    77-77: FOV update method changed to use appViewer

    The change from directly updating camera FOV to using appViewer.inWorldRenderingConfig.fov aligns with the PR objective of creating a stable abstraction layer between the app and renderer.


    93-93: Camera update method changed to use appViewer backend

    The camera update now uses appViewer.backend?.updateCamera which properly encapsulates the renderer implementation details. The optional chaining (?.) handles the case when the backend is not initialized, which is good defensive programming.

    src/resourcesManager.ts (2)

    19-22: Good use of TypedEmitter for type-safe events

    Using TypedEmitter provides proper type checking for event names and handlers, which helps prevent runtime errors from typos in event names or incorrect handler signatures.


    23-45: Well-structured LoadedResources class

    The LoadedResources class provides a clear organization of related resources, making it easier to understand what resources are available and how they relate to each other.

    src/watchOptions.ts (3)

    20-26: Good extraction of common logic to a reusable function.

    Extracting updateTouch to a standalone function that can be reused in both initial setup and event listener is a good practice for code organization and reuse.


    54-67: Good implementation of FPS limit based on window focus state.

    The code properly handles FPS limiting based on window focus and user preferences. This is a good power-saving feature that will improve battery life on laptops and mobile devices.


    110-117: Updated function signature to include worldView parameter.

    The function now accepts a worldView parameter of type WorldDataEmitter, which aligns with the architectural changes in the PR. This makes the dependencies explicit rather than relying on global state.

    src/index.ts (4)

    118-132: Good modularization of backend loading logic.

    Extracting the backend loading logic into a separate function improves code organization and reusability. The conditional loading based on build mode is also well-handled.


    162-170: Good handling of delayed loading when focus is needed.

    This code properly handles the case where loading should be delayed until the window has focus, which is important for performance reasons and browser behavior.


    692-693: Streamlined renderer startup with appViewer.

    The code now uses appViewer.startWorld and appViewer.worldView.listenToBot for initialization, which is cleaner and more modular than the previous approach. This aligns well with the PR's goal of making the app and renderer more independent.


    717-736: Updated chunk loading wait logic.

    The code now uses the reactive state system to track chunk loading, which is a better pattern than the previous approach. The code properly subscribes to the reactive state and updates the progress reporter accordingly.

    renderer/viewer/lib/entities.ts (5)

    213-215: Simplified object initialization.

    The code has been simplified to use a direct object literal for entitiesOptions initialization with the fontFamily property, which is cleaner than the previous approach.


    250-250: Updated constructor to use WorldRendererThree instead of Viewer.

    This change aligns with the PR's goal of refactoring the viewer subsystem. The Entities class now depends on WorldRendererThree which provides a more focused interface specific to rendering.


    288-292: Improved rendering configuration access.

    The code now correctly accesses the rendering configuration through worldRenderer.worldRendererConfig.renderEntities, which is a more modular approach compared to directly accessing the viewer.


    510-510: Fixed getItemMesh method to use worldRenderer.

    The method now properly uses this.worldRenderer.getItemRenderData instead of a direct reference to getItemUv, which aligns with the new architecture.


    1055-1055: Updated addArmorModel function signature.

    The function now accepts a worldRenderer parameter instead of relying on a global reference, which improves modularity and testability.

    src/vr.ts (12)

    8-8: Import reorganization looks good.
    Bringing in WorldRendererThree aligns with the new rendering architecture.


    12-14: Initialize VR only if supported.
    Good check against navigator.xr and vrSupport. Ensures graceful fallback.


    23-23: Visibility toggle is correct.
    Hides the VR button while the modal is active or VR is disabled.


    26-26: Reactive subscription to vrSupport.
    This is a clean pattern with subscribeKey. No issues observed.


    29-29: Inline enableVr is consistent with the new structure.
    Function scope is appropriate for local usage.


    31-31: Preventing the escape menu is logical in VR mode.


    35-38: Disable VR flow updated properly.
    Transitioning from viewer to worldRenderer for camera override and XR states is consistent.
    Consider confirming any references to user to ensure that entity is always defined.


    107-108: Attaching the camera to user.
    Makes sense to group camera with the VR user anchor. Confirm that removing from the scene (in disableVr) is always called to avoid orphaned objects.


    195-196: Camera update logic changed.
    Previously used an offset-based approach, now uses bot.entity.yaw & bot.entity.pitch. Confirm if ignoring yawOffset is intentional.


    207-207: Rendering with worldRenderer.render().
    Straightforward transition from viewer.render().


    210-210: Assigning cameraObjectOverride to user upon session start.
    Ensures VR session camera is parented properly to the user anchor.


    217-217: Clearing cameraObjectOverride on session end.
    Helps revert to standard camera usage post VR session.

    renderer/viewer/lib/worldrendererCommon.ts (18)

    6-6: Reintroduced AtlasParser import.
    This presumably aids in parsing block/item textures. Ensure you’re using the updated parser from mc-assets.


    11-11: Utility import for spiral matrix generation.
    This is well-aligned for chunk spiral loading logic.


    12-12: Proxy import from valtio.
    Leverages reactivity in your config or state. Looks appropriate.


    15-16: New resource-related imports.
    Introducing ResourcesManager and DisplayWorldOptions fosters better structure. No issue found.


    18-18: Integrated references to shared mesher utilities.
    No immediate concerns. Keep an eye on chunk boundary logic for edge cases.


    34-34: mesherWorkers: 4.
    Increasing the default worker count might improve meshing performance. Validate concurrency overhead.


    62-62: worldSizeParams introduced.
    Storing minY and worldHeight is beneficial for dimension-based logic. Looks fine.


    114-114: highestBlocks declared as a Map.
    This can improve chunk-based computations for terrain rendering.


    141-142: itemsTexture and soundSystem fields.
    Integrating audio/texture references directly is a clear step toward more cohesive resource management.


    146-159: Adopting worldRendererConfig & constructor injection of resources.
    This approach decouples the world rendering from the environment, letting the ResourcesManager handle relevant assets.


    161-170: init() method defers resource loading.
    Prevent re-initializing if active is set. This pattern is good for controlling multiple inits.


    175-188: updateCustomBlock logic.
    Storing custom blocks in protocolCustomBlocks and refreshing if chunk is loaded is well-structured. Watch for race conditions if chunk load is slow.


    190-199: getBlockInfo() with custom block model lookups.
    Clear caching approach. If the block is in the protocolCustomBlocks map, leverage custom rendering.


    366-371: Asynchronous resource loading for mesher data.
    Implementing version logic at setVersion to re-download or reuse existing resources is sensible.


    420-420: New worldMinYRender property.
    Clamps rendering for chunks below configured thresholds. A neat performance optimization.


    447-447: Looping from this.worldMinYRender.
    Ensures partial chunk sections below the clip line won’t be re-meshed. Good to see.


    514-514: Added connect method reference at constructor’s end.
    No immediate issue, but confirm that calling connect so early doesn’t conflict with worker init states.


    749-766: Revised destroy() method.

    1. Properly terminating workers.
    2. Stopping sound system.
    3. Clearing event listeners.

    Great for preventing memory leaks.

    src/inventoryWindows.ts (5)

    9-9: Updated import location for versionToNumber.
    Reflects the new restructure. No issues found.


    23-23: Additional imports from mineflayer/items.
    Combines multiple item utilities in one place. This is good for clarity.


    47-48: Check for itemsAtlasParser on resource manager.
    Prevents referencing uninitialized textures. Good fallback condition.


    57-57: Listening to assetsTexturesUpdated.
    Moves from the old viewer-based approach to the new resourcesManager. The subscription scheme is consistent with the rest of the code.


    141-142: New block/items references for textures.
    Using appViewer.resourcesManager.currentResources!.<atlasParser>.latestImage is more direct than referencing old viewer fields.
    Check for potential null if resource loading fails.

    src/appViewer.ts (5)

    16-24: Well-defined reactive state interface.

    The RendererReactiveState interface provides a clear structure for tracking renderer state. This will make it easier to observe and react to changes in the rendering pipeline.


    50-67: GraphicsBackend interface provides a clean abstraction layer.

    The interface creates a clear separation between the application and rendering code, which aligns well with the PR objective of creating stability between layers. The "no sync methods" comment is helpful for enforcing async operations where needed.


    87-115: Enhance error handling in loadBackend.

    In the loadBackend method, the error handling in displayCriticalError only logs the error and sets a status message. For critical rendering errors, consider implementing more robust fallback strategies.


    190-192: Global appViewer instance.

    Exporting a single global instance is convenient but can lead to hidden state issues if anything else tries to instantiate AppViewer. Ensure thread-safety if concurrency arises in the future.


    209-221: Good use of reactive programming.

    The modalStackUpdateChecks function and subscription to modal stack changes is a clean implementation of reactive programming. This helps maintain a consistent UI state when modals are shown or hidden.

    renderer/viewer/lib/worldrendererThree.ts (6)

    37-43: Good initialization of Three.js scene components.

    Initializing scene, lights, and entity management as class properties provides a cleaner architecture. This aligns with the PR objective of improving the rendering system's structure.


    53-80: Constructor changes align with new architecture.

    The constructor now properly integrates with the new AppViewer architecture, taking resourcesManager from initOptions and setting up the necessary components. The error handling for missing resourcesManager is a good safeguard.


    94-104: Well-structured scene initialization in resetScene.

    The resetScene method cleanly configures the scene's properties, including setting up the camera with appropriate parameters. This helps with the PR goal of making resource cleanup and initialization more organized.


    106-125: Excellent use of reactive subscriptions.

    The watchReactivePlayerState method uses subscriptions to react to player state changes, which is a clean pattern for maintaining rendering consistency with game state changes.


    136-144: Clean texture loading implementation.

    The updateAssetsData method efficiently loads textures and applies appropriate settings for the Minecraft-style rendering. Good use of async/await pattern.


    584-596: Well-implemented camera roll control.

    The setCameraRoll method correctly uses quaternions to handle camera rotation, which is the proper way to handle 3D rotations in Three.js. This will provide smooth camera transitions when needed.

    Comment on lines +86 to +88
    getPosition (): Vec3 {
    return new Vec3(0, 0, 0)
    }
    Copy link

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    🛠️ Refactor suggestion

    Returning a constant (0, 0, 0)
    The new getPosition method always returns (0, 0, 0). If you plan to track actual player movement, consider switching from this placeholder to dynamic logic reflecting the real player coordinates.

    Comment on lines +4 to +7
    import { ItemSpecificContextProperties } from 'renderer/viewer/lib/basePlayerState'
    import { getItemDefinition } from 'mc-assets/dist/itemDefinitions'
    import { MessageFormatPart } from '../chatUtils'
    import { playerState } from './playerState'
    Copy link

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    ⚠️ Potential issue

    Missing import for appViewer

    The function references appViewer (line 103) but doesn't import it. This may cause runtime errors if the module is used in isolation.

    Add the missing import:

    +import { appViewer } from '../appViewer'
     import { ItemSpecificContextProperties } from 'renderer/viewer/lib/basePlayerState'
     import { getItemDefinition } from 'mc-assets/dist/itemDefinitions'
     import { MessageFormatPart } from '../chatUtils'
     import { playerState } from './playerState'
    📝 Committable suggestion

    ‼️ IMPORTANT
    Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    import { ItemSpecificContextProperties } from 'renderer/viewer/lib/basePlayerState'
    import { getItemDefinition } from 'mc-assets/dist/itemDefinitions'
    import { MessageFormatPart } from '../chatUtils'
    import { playerState } from './playerState'
    import { appViewer } from '../appViewer'
    import { ItemSpecificContextProperties } from 'renderer/viewer/lib/basePlayerState'
    import { getItemDefinition } from 'mc-assets/dist/itemDefinitions'
    import { MessageFormatPart } from '../chatUtils'
    import { playerState } from './playerState'

    Comment on lines +93 to +110
    export const getItemModelName = (item: GeneralInputItem, specificProps: ItemSpecificContextProperties) => {
    let itemModelName = item.name
    const { customModel } = getItemMetadata(item)
    if (customModel) {
    itemModelName = customModel
    }

    const itemSelector = playerState.getItemSelector({
    ...specificProps
    })
    const modelFromDef = getItemDefinition(appViewer.resourcesManager.itemsDefinitionsStore, {
    name: itemModelName,
    version: appViewer.resourcesManager.currentResources!.version,
    properties: itemSelector
    })?.model
    const model = (modelFromDef === 'minecraft:special' ? undefined : modelFromDef) ?? itemModelName
    return model
    }
    Copy link

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    ⚠️ Potential issue

    Verify null safety in appViewer access

    The code assumes appViewer.resourcesManager.currentResources is not null (line 105), but only uses the null assertion operator (!) without checking. Consider adding explicit null checks to prevent runtime errors.

    -  const modelFromDef = getItemDefinition(appViewer.resourcesManager.itemsDefinitionsStore, {
    -    name: itemModelName,
    -    version: appViewer.resourcesManager.currentResources!.version,
    -    properties: itemSelector
    -  })?.model
    +  // Check if resources are loaded
    +  if (!appViewer.resourcesManager.currentResources) {
    +    console.warn(`Resources not loaded, using default model name: ${itemModelName}`);
    +    return itemModelName;
    +  }
    +  
    +  const modelFromDef = getItemDefinition(appViewer.resourcesManager.itemsDefinitionsStore, {
    +    name: itemModelName,
    +    version: appViewer.resourcesManager.currentResources.version,
    +    properties: itemSelector
    +  })?.model
    📝 Committable suggestion

    ‼️ IMPORTANT
    Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    Suggested change
    export const getItemModelName = (item: GeneralInputItem, specificProps: ItemSpecificContextProperties) => {
    let itemModelName = item.name
    const { customModel } = getItemMetadata(item)
    if (customModel) {
    itemModelName = customModel
    }
    const itemSelector = playerState.getItemSelector({
    ...specificProps
    })
    const modelFromDef = getItemDefinition(appViewer.resourcesManager.itemsDefinitionsStore, {
    name: itemModelName,
    version: appViewer.resourcesManager.currentResources!.version,
    properties: itemSelector
    })?.model
    const model = (modelFromDef === 'minecraft:special' ? undefined : modelFromDef) ?? itemModelName
    return model
    }
    export const getItemModelName = (item: GeneralInputItem, specificProps: ItemSpecificContextProperties) => {
    let itemModelName = item.name
    const { customModel } = getItemMetadata(item)
    if (customModel) {
    itemModelName = customModel
    }
    const itemSelector = playerState.getItemSelector({
    ...specificProps
    })
    // Check if resources are loaded
    if (!appViewer.resourcesManager.currentResources) {
    console.warn(`Resources not loaded, using default model name: ${itemModelName}`);
    return itemModelName;
    }
    const modelFromDef = getItemDefinition(appViewer.resourcesManager.itemsDefinitionsStore, {
    name: itemModelName,
    version: appViewer.resourcesManager.currentResources.version,
    properties: itemSelector
    })?.model
    const model = (modelFromDef === 'minecraft:special' ? undefined : modelFromDef) ?? itemModelName
    return model
    }

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 1

    🔭 Outside diff range comments (1)
    renderer/viewer/lib/entities.ts (1)

    494-537: ⚠️ Potential issue

    Updated item mesh and texture handling.

    The method now uses this.worldRenderer to retrieve item rendering data and textures. However, there's a potential issue with setting size to undefined and then referencing it in calculations.

    The size variable is declared as undefined but then used in the subsequent calculations for sizeY and sizeX. This could lead to unexpected behavior. Verify if this is intentional or if size should have a default value.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between dc2ad7c and 11abbfc.

    📒 Files selected for processing (2)
    • renderer/playground/scenes/entities.ts (1 hunks)
    • renderer/viewer/lib/entities.ts (21 hunks)
    ✅ Files skipped from review due to trivial changes (1)
    • renderer/playground/scenes/entities.ts
    🧰 Additional context used
    🧬 Code Definitions (1)
    renderer/viewer/lib/entities.ts (4)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-592)
    renderer/viewer/lib/holdingBlock.ts (1) (1)
    • getBlockMeshFromModel (908-914)
    renderer/viewer/lib/entity/armorModels.ts (1) (1)
    • armorTextures (19-35)
    renderer/viewer/lib/entity/EntityMesh.ts (1) (1)
    • getMesh (225-356)
    🔇 Additional comments (14)
    renderer/viewer/lib/entities.ts (14)

    17-17: Added import for entity attackability check.

    The new import for isEntityAttackable is used to filter entities in the getEntityMesh function, which is an important change to the entity rendering logic.


    28-28: Key architectural change: Using WorldRendererThree instead of Viewer.

    This import supports the major architectural shift from using the Viewer class to the WorldRendererThree class, aligning with the PR objective to separate application code from rendering code.


    171-187: Refined entity rendering with attackability check.

    The function now includes type annotations and an early return for non-attackable entities, which changes which entities will be rendered. This aligns with the refactoring goal but could affect the visibility of certain entities.

    Make sure this change was intentional and doesn't inadvertently hide entities that should be visible to the player.


    211-220: Updated Entities class configuration.

    The class now includes properly typed entities options and a rendering status flag. This improves type safety and clarity in the codebase.


    237-240: Constructor now takes WorldRendererThree instead of Viewer.

    This is a fundamental change that propagates throughout the class, replacing all references to viewer with worldRenderer to support the new rendering architecture.


    242-248: Updated scene reference in clear method.

    The method now properly references this.worldRenderer.scene instead of the previous viewer's scene, maintaining consistency with the architectural change.


    262-271: Updated scene management in setRendering method.

    The method now consistently uses this.worldRenderer.scene for adding and removing entities, aligning with the new architecture.


    273-307: Refactored render method to use worldRenderer properties.

    The method now uses worldRenderer.worldRendererConfig.renderEntities for configuration and worldRenderer.viewerPosition and worldRenderer.finishedChunks for positioning and visibility checks.


    580-607: Added entity lifecycle management.

    The update method now includes proper tracking of entity addition/removal through new hooks, enhancing the control over entity lifecycle events.


    673-707: Updated entity mesh creation and scene management.

    The code now properly passes the worldRenderer to getEntityMesh and manages entity addition to the scene.


    712-719: Updated armor model handling.

    Armor models now receive the worldRenderer as a parameter rather than accessing it indirectly, improving the clarity of dependencies.


    883-889: Enhanced player equipment management.

    Added specific handling for player equipment like capes and elytra, improving the visual fidelity of player models.


    891-915: Added dedicated entity position update method.

    The new updateEntityPosition method provides a cleaner separation of concerns by handling position and rotation updates separately from other entity updates.


    1083-1156: Updated armor model function to accept worldRenderer parameter.

    The function now explicitly receives the worldRenderer parameter rather than accessing it indirectly, improving function purity and testability.

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 1

    🧹 Nitpick comments (2)
    renderer/viewer/lib/entities.ts (2)

    213-214: Consider fallback fonts.
    You've introduced "mojangles" as the default font. If this font is not loaded or available, the text might degrade. Providing a fallback font could improve reliability.


    918-918: Empty hook function.
    The onAddEntity hook is empty. If you don’t plan to override it, consider removing it. Otherwise, add a comment clarifying its intended usage.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between dc2ad7c and 11abbfc.

    📒 Files selected for processing (2)
    • renderer/playground/scenes/entities.ts (1 hunks)
    • renderer/viewer/lib/entities.ts (21 hunks)
    ✅ Files skipped from review due to trivial changes (1)
    • renderer/playground/scenes/entities.ts
    🧰 Additional context used
    🧬 Code Definitions (1)
    renderer/viewer/lib/entities.ts (4)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-592)
    renderer/viewer/lib/holdingBlock.ts (1) (1)
    • getBlockMeshFromModel (908-914)
    renderer/viewer/lib/entity/armorModels.ts (1) (1)
    • armorTextures (19-35)
    renderer/viewer/lib/entity/EntityMesh.ts (1) (1)
    • getMesh (225-356)
    🔇 Additional comments (18)
    renderer/viewer/lib/entities.ts (18)

    17-17: Verify new dependency usage.
    The import from mineflayer-mouse/dist/attackableEntity looks correct. Ensure that mineflayer-mouse is properly listed in your dependencies and that the version you rely on provides the isEntityAttackable function.


    28-28: Refactor alignment approved.
    Importing WorldRendererThree cleanly aligns with the new renderer architecture.


    171-171: Ensure all call sites have been updated.
    Modifying the getEntityMesh signature to accept a WorldRendererThree | undefined parameter is a breaking change. Please verify that every external invocation has been updated accordingly.


    187-187: Confirm loadedData scope.
    Calling isEntityAttackable(loadedData, entity) presupposes that loadedData is in scope. If this is a global variable, confirm it's always available. Otherwise, consider passing loadedData explicitly as a parameter.


    211-211: Refactored Entities class.
    Exporting the Entities class in alignment with WorldRendererThree is a good approach to keep concerns well-separated.


    219-219: Simple state tracking.
    Defining currentlyRendering = true clarifies the class’s initial rendering state. The approach appears straightforward.


    237-237: Constructor usage looks good.
    Switching to public worldRenderer: WorldRendererThree is consistent with the new design.


    244-244: Scene removal updated.
    Replacing this.viewer.scene.remove(mesh) with this.worldRenderer.scene.remove(mesh) aligns with the updated architecture.


    263-263: Rendering toggle logic.
    Using this.currentlyRendering to control adding/removing entities from this.worldRenderer.scene is a clear and maintainable approach.

    Also applies to: 266-266, 268-268


    275-277: Dynamically toggling rendering based on config.
    Comparing the config value with the local currentlyRendering and calling this.setRendering is a clean solution for toggling entity rendering.


    280-280: Check for null reference.
    const botPos = this.worldRenderer.viewerPosition assumes viewerPosition is defined. If there’s a chance it could be undefined, consider graceful handling or a safety check.


    305-305: Visibility control works.
    Conditionally setting entity.visible based on distance and this.worldRenderer.finishedChunks[chunkKey] is consistent with the new world renderer logic.


    581-581: Helpful flag for new entities.
    Storing justAdded as !this.entities[entity.id] helps differentiate newly added entities and can improve animation transitions.


    894-896: Smooth positioning updates.
    The updateEntityPosition method neatly handles position and rotation tweening. This is a clean approach, though watch for scenarios where multiple concurrent tweens might lead to unexpected behavior.

    Also applies to: 898-901, 902-904


    914-914: Deferred skin rendering.
    Calling this.maybeRenderPlayerSkin(entity) after tweening ensures that skin logic only runs once positioning data is updated. This should help maintain a responsive rendering flow.


    1083-1083: Function signature updated.
    addArmorModel now accepts worldRenderer as the first parameter, aligning with the rest of the refactor.


    1117-1117: Defensive check on resources.
    Accessing worldRenderer.resourcesManager.currentResources! with ! can cause runtime errors if the resources aren’t ready. Consider a null check or fallback if this can be undefined.


    1138-1138: Consistent usage of getMesh.
    Passing worldRenderer, texturePath, and armorModel[slotType] shows consistent usage of the new API.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 21, 2025

    /deploy

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 1

    🔭 Outside diff range comments (1)
    renderer/viewer/lib/entities.ts (1)

    1131-1139: ⚠️ Potential issue

    Unhandled promise in loadTexture call.

    The loadTexture function likely returns a Promise that isn't being properly handled.

    Apply this fix to properly handle the Promise:

    -    loadTexture(texturePath, texture => {
    -      texture.magFilter = THREE.NearestFilter
    -      texture.minFilter = THREE.NearestFilter
    -      texture.flipY = false
    -      texture.wrapS = THREE.MirroredRepeatWrapping
    -      texture.wrapT = THREE.MirroredRepeatWrapping
    -      material.map = texture
    -    })
    +    void loadTexture(texturePath, texture => {
    +      texture.magFilter = THREE.NearestFilter
    +      texture.minFilter = THREE.NearestFilter
    +      texture.flipY = false
    +      texture.wrapS = THREE.MirroredRepeatWrapping
    +      texture.wrapT = THREE.MirroredRepeatWrapping
    +      material.map = texture
    +    })

    The void operator explicitly marks that we're ignoring the promise result.

    🧰 Tools
    🪛 GitHub Check: build-and-deploy

    [warning] 1131-1131:
    Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the void operator

    🪛 GitHub Actions: CI

    [warning] 1131-1131: Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the void operator @typescript-eslint/no-floating-promises

    🧹 Nitpick comments (1)
    renderer/viewer/lib/entities.ts (1)

    1085-1085: Function has too many parameters.

    The addArmorModel function has 6 parameters, exceeding the maximum allowed (4) according to the style guidelines.

    Consider refactoring this to use an options object pattern:

    -function addArmorModel (worldRenderer: WorldRendererThree, entityMesh: THREE.Object3D, slotType: string, item: Item, layer = 1, overlay = false) {
    +function addArmorModel (worldRenderer: WorldRendererThree, entityMesh: THREE.Object3D, slotType: string, item: Item, options: { layer?: number, overlay?: boolean } = {}) {
    +  const { layer = 1, overlay = false } = options;
       // Function body...
    }

    Then update the call sites accordingly:

    -addArmorModel(this.worldRenderer, e, 'feet', entity.equipment[2])
    +addArmorModel(this.worldRenderer, e, 'feet', entity.equipment[2])
    
    -addArmorModel(this.worldRenderer, e, 'legs', entity.equipment[3], 2)
    +addArmorModel(this.worldRenderer, e, 'legs', entity.equipment[3], { layer: 2 })
    
    -addArmorModel(this.worldRenderer, e, 'chest', entity.equipment[4])
    +addArmorModel(this.worldRenderer, e, 'chest', entity.equipment[4])
    
    -addArmorModel(this.worldRenderer, e, 'head', entity.equipment[5])
    +addArmorModel(this.worldRenderer, e, 'head', entity.equipment[5])
    🧰 Tools
    🪛 GitHub Check: build-and-deploy

    [warning] 1085-1085:
    Function 'addArmorModel' has too many parameters (6). Maximum allowed is 4

    🪛 GitHub Actions: CI

    [warning] 1085-1085: Function 'addArmorModel' has too many parameters (6). Maximum allowed is 4 max-params

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 11abbfc and 853e0e1.

    📒 Files selected for processing (6)
    • .github/workflows/ci.yml (1 hunks)
    • .github/workflows/next-deploy.yml (1 hunks)
    • .github/workflows/preview.yml (1 hunks)
    • .github/workflows/publish.yml (1 hunks)
    • README.MD (1 hunks)
    • renderer/viewer/lib/entities.ts (21 hunks)
    ✅ Files skipped from review due to trivial changes (4)
    • .github/workflows/ci.yml
    • .github/workflows/preview.yml
    • .github/workflows/publish.yml
    • .github/workflows/next-deploy.yml
    🧰 Additional context used
    🧬 Code Definitions (1)
    renderer/viewer/lib/entities.ts (4)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-592)
    renderer/viewer/lib/holdingBlock.ts (1) (1)
    • getBlockMeshFromModel (908-914)
    renderer/viewer/lib/entity/armorModels.ts (1) (1)
    • armorTextures (19-35)
    renderer/viewer/lib/entity/EntityMesh.ts (1) (1)
    • getMesh (225-356)
    🪛 GitHub Check: build-and-deploy
    renderer/viewer/lib/entities.ts

    [warning] 1085-1085:
    Function 'addArmorModel' has too many parameters (6). Maximum allowed is 4

    🪛 GitHub Actions: CI
    renderer/viewer/lib/entities.ts

    [warning] 1085-1085: Function 'addArmorModel' has too many parameters (6). Maximum allowed is 4 max-params


    [warning] 1131-1131: Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the void operator @typescript-eslint/no-floating-promises

    🔇 Additional comments (31)
    renderer/viewer/lib/entities.ts (30)

    17-18: Appropriate import addition to support entity attack check.

    The added import from mineflayer-mouse for isEntityAttackable is necessary to properly determine which entities can be attacked, improving the entity filtering logic.


    29-29: Updated import to reflect new architecture.

    Replacing the Viewer import with WorldRendererThree aligns with the architectural refactoring described in the PR objectives, where the rendering pipeline is being modularized.


    172-172: Function signature modernized to use WorldRendererThree.

    Updated parameter type from the old Viewer to the new WorldRendererThree, correctly implementing the architectural changes.


    188-188: Improved entity filtering with isEntityAttackable.

    Now using a proper utility function instead of implicit assumptions about what entities should be rendered as mesh boxes.


    212-221: Entity class constructor updated to use WorldRendererThree.

    The Entities class now properly uses WorldRendererThree for rendering, aligning with the architectural changes. The entitiesOptions also has been moved to class properties for better organization.


    238-241: Updated constructor parameter from viewer to worldRenderer.

    This change is crucial for the architecture refactoring, ensuring the Entities class works with the new rendering system.


    244-248: Updated scene reference in clear method.

    Now properly uses this.worldRenderer.scene instead of this.viewer.scene for removing entities from the scene.


    263-272: Scene management updated in setRendering method.

    Properly references the worldRenderer's scene instead of the viewer's scene for adding/removing entities.


    274-279: Improved rendering control with worldRenderer config.

    Now rendering decisions are based on the worldRenderer's configuration, providing a more centralized approach to controlling entity rendering.


    280-282: Updated bot position reference.

    Now correctly using this.worldRenderer.viewerPosition instead of accessing the bot's position directly.


    305-307: Updated chunk reference in visibility check.

    Now using this.worldRenderer.finishedChunks instead of this.viewer.world.finishedChunks.


    345-346: Updated configuration reference for ear rendering.

    Now using this.worldRenderer.worldRendererConfig.renderEars instead of a direct viewer reference.


    496-497: Updated item rendering data retrieval.

    Now using this.worldRenderer.getItemRenderData() for consistent access to item data.


    500-501: Updated block mesh generation.

    Now uses the worldRenderer's resourcesManager to access the worldBlockProvider, improving resource management.


    521-525: Fixed texture retrieval in item mesh generation.

    Now properly uses the worldRenderer's material and itemsTexture for texture mapping.


    582-583: Added entity tracking for initialization.

    Added a justAdded flag to determine if an entity was just added, which helps with initialization logic.


    601-606: Added entity lifecycle hooks.

    Now calls onRemoveEntity when an entity is removed, providing a hook for cleanup operations.


    675-676: Updated entity mesh creation.

    Now passes the worldRenderer to getEntityMesh instead of viewer.


    694-695: Updated scene reference for entity addition.

    Now uses this.worldRenderer.scene.add(group) instead of viewer's scene.


    701-702: Added entity lifecycle hooks.

    Added onAddEntity hook when a new entity is added, providing a cleaner way to handle entity initialization.


    707-708: Updated rendering state management.

    Now uses the entity class's currentlyRendering state variable for consistent state management.


    716-720: Updated armor model creation.

    Now passes the worldRenderer to addArmorModel functions for proper resource management.


    884-891: Added player equipment handling.

    New code to manage player equipment state, especially for cape and elytra visualization.


    892-893: Extracted position update logic.

    Moved entity position updates to a dedicated method for better separation of concerns.


    895-916: Implemented dedicated entity position update method.

    The new updateEntityPosition method provides a cleaner way to handle entity position and rotation updates, with appropriate animation handling.


    918-920: Added entity lifecycle hook method.

    The onAddEntity method provides a hook for custom logic when an entity is added.


    938-941: Added entity tracking and cleanup.

    The onRemoveEntity method properly cleans up entity references by removing them from the loadedSkinEntityIds set.


    1119-1120: Updated texture path resolution.

    Now correctly uses the worldRenderer's resourcesManager to access armor textures.


    1140-1141: Updated mesh creation with worldRenderer.

    Now correctly passes the worldRenderer to getMesh for proper integration with the new architecture.


    1157-1158: Updated recursive armor model creation.

    Now passes the worldRenderer to the recursive call to addArmorModel.

    README.MD (1)

    26-27: Updated feature list to reflect architectural changes.

    The README has been updated to remove Google Drive support and add information about the new modular rendering architecture, which aligns with the PR objectives.

    The new line "Support for custom rendering 3D engines. Modular architecture." accurately reflects the architectural changes implemented in this PR, where the rendering system has been refactored to be more modular and support different rendering backends.

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 2

    🔭 Outside diff range comments (1)
    renderer/viewer/lib/entities.ts (1)

    1131-1139: ⚠️ Potential issue

    Promise not awaited or handled.

    The promise returned by loadTexture is not awaited or handled, which could lead to unhandled promise rejections. Consider properly awaiting the promise or marking it as intentionally ignored.

    - loadTexture(texturePath, texture => {
    -   texture.magFilter = THREE.NearestFilter
    -   texture.minFilter = THREE.NearestFilter
    -   texture.flipY = false
    -   texture.wrapS = THREE.MirroredRepeatWrapping
    -   texture.wrapT = THREE.MirroredRepeatWrapping
    -   material.map = texture
    - })
    + // Option 1: Mark as intentionally ignored
    + void loadTexture(texturePath, texture => {
    +   texture.magFilter = THREE.NearestFilter
    +   texture.minFilter = THREE.NearestFilter
    +   texture.flipY = false
    +   texture.wrapS = THREE.MirroredRepeatWrapping
    +   texture.wrapT = THREE.MirroredRepeatWrapping
    +   material.map = texture
    + })
    + 
    + // Option 2: Handle errors
    + loadTexture(texturePath, texture => {
    +   texture.magFilter = THREE.NearestFilter
    +   texture.minFilter = THREE.NearestFilter
    +   texture.flipY = false
    +   texture.wrapS = THREE.MirroredRepeatWrapping
    +   texture.wrapT = THREE.MirroredRepeatWrapping
    +   material.map = texture
    + }).catch(err => console.error('Failed to load texture:', err))
    🧰 Tools
    🪛 GitHub Check: build-and-deploy

    [warning] 1131-1131:
    Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the void operator

    🪛 GitHub Actions: CI

    [warning] 1131-1131: Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the void operator @typescript-eslint/no-floating-promises

    🧹 Nitpick comments (2)
    renderer/viewer/lib/entities.ts (2)

    267-267: Simplify condition using Array.includes.

    The condition can be simplified using the includes method.

    - if (!this.worldRenderer.scene.children.includes(ent)) this.worldRenderer.scene.add(ent)
    + if (!this.worldRenderer.scene.children.includes(ent)) {
    +   this.worldRenderer.scene.add(ent)
    + }

    1085-1085: Function has too many parameters (6).

    The addArmorModel function exceeds the maximum allowed parameters (4) as indicated by the linter. Consider refactoring to use an options object for better maintainability.

    - function addArmorModel (worldRenderer: WorldRendererThree, entityMesh: THREE.Object3D, slotType: string, item: Item, layer = 1, overlay = false) {
    + type ArmorModelOptions = {
    +   worldRenderer: WorldRendererThree;
    +   entityMesh: THREE.Object3D;
    +   slotType: string;
    +   item: Item;
    +   layer?: number;
    +   overlay?: boolean;
    + }
    + 
    + function addArmorModel (options: ArmorModelOptions) {
    +   const { worldRenderer, entityMesh, slotType, item, layer = 1, overlay = false } = options;

    Then update all calling sites like:

    - addArmorModel(this.worldRenderer, e, 'feet', entity.equipment[2])
    + addArmorModel({
    +   worldRenderer: this.worldRenderer,
    +   entityMesh: e,
    +   slotType: 'feet',
    +   item: entity.equipment[2]
    + })
    🧰 Tools
    🪛 GitHub Check: build-and-deploy

    [warning] 1085-1085:
    Function 'addArmorModel' has too many parameters (6). Maximum allowed is 4

    🪛 GitHub Actions: CI

    [warning] 1085-1085: Function 'addArmorModel' has too many parameters (6). Maximum allowed is 4 max-params

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 853e0e1 and d450a31.

    📒 Files selected for processing (1)
    • renderer/viewer/lib/entities.ts (21 hunks)
    🧰 Additional context used
    🧬 Code Definitions (1)
    renderer/viewer/lib/entities.ts (3)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-592)
    renderer/viewer/lib/entity/armorModels.ts (1) (1)
    • armorTextures (19-35)
    renderer/viewer/lib/entity/EntityMesh.ts (1) (1)
    • getMesh (225-356)
    🪛 GitHub Check: build-and-deploy
    renderer/viewer/lib/entities.ts

    [warning] 1085-1085:
    Function 'addArmorModel' has too many parameters (6). Maximum allowed is 4

    🪛 GitHub Actions: CI
    renderer/viewer/lib/entities.ts

    [warning] 1085-1085: Function 'addArmorModel' has too many parameters (6). Maximum allowed is 4 max-params


    [warning] 1131-1131: Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the void operator @typescript-eslint/no-floating-promises

    🔇 Additional comments (3)
    renderer/viewer/lib/entities.ts (3)

    926-929: Fixed player skin loading distance check.

    The implementation now correctly uses the camera position and world renderer's view distance setting, resolving the previous undefined bot reference issue.


    895-916: Good refactor: Extracting position update logic to a separate method.

    Extracting the entity position update logic into a dedicated method improves code organization and makes the update method more maintainable.


    918-941: Good addition: Entity lifecycle methods.

    Adding dedicated methods for entity addition and removal events enables better extensibility and provides natural extension points for custom behavior.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 21, 2025

    /deploy

    Copy link

    Deployed to Vercel Preview: https://prismarine-142082mr0-zaro.vercel.app
    Playground
    Storybook

    @Phoenix616
    Copy link

    Phoenix616 commented Mar 21, 2025

    @Phoenix616 got just a little more changes. Really happy with the new abstractions. No threejs code I used in src/ now. Would be really great if you could test it for a few mins, I really want to merge it now (from my testings everything seems to be fine). I feel only player state needs posting, but I will do API polishing later. Oh and playground I s broken but no one uses it so its okay.

    Things I noticed while testing it locally:

    • Resource Packs don't seem to work (tried loading one that worked previously locally, not via a server resource pack)
    • Lighting seems broken
      image (This was done after running the previous version locally but I had the light rendering disabled there so it shouldn't really do this?)
    • Error while trying to go into the "Render" options menu (although that seems to work in your Vercel preview?)
        Uncaught TypeError: item.render is not a function
            at OptionElement (OptionsItems.tsx:160:15)
            at renderWithHooks (react-dom.development.js:16305:1)
            at mountIndeterminateComponent (react-dom.development.js:20074:1)
            at beginWork (react-dom.development.js:21587:1)
            at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
            at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
            at invokeGuardedCallback (react-dom.development.js:4277:1)
            at beginWork$1 (react-dom.development.js:27451:1)
            at performUnitOfWork (react-dom.development.js:26557:1)
            at workLoopSync (react-dom.development.js:26466:1)
        eruda.js:5302 The above error occurred in the <OptionElement> component:
        
            at OptionElement (http://localhost:3000/static/js/index.js:36752:26)
            at RenderOption (http://localhost:3000/static/js/index.js:36756:25)
            at div
            at div
            at div
            at __WEBPACK_DEFAULT_EXPORT__ (http://localhost:3000/static/js/index.js:38521:70)
            at http://localhost:3000/static/js/index.js:36800:73
            at __WEBPACK_DEFAULT_EXPORT__ (http://localhost:3000/static/js/index.js:36545:70)
            at http://localhost:3000/static/js/index.js:36922:64
            at ErrorBoundary (http://localhost:3000/static/js/vendors-node_modules_pnpm_dimaka_interface_0_0_3-alpha_0__babel_core_7_22_11__popperjs_core_2-bd6ad2.js:30172:1)
            at PerComponentErrorBoundary (http://localhost:3000/static/js/index.js:46298:38)
            at RobustPortal (http://localhost:3000/static/js/index.js:45767:25)
            at ButtonProvider (http://localhost:3000/static/js/index.js:26375:27)
            at __WEBPACK_DEFAULT_EXPORT__ (http://localhost:3000/static/js/index.js:26478:70)
            at div
            at UIProvider (http://localhost:3000/static/js/index.js:43545:23)
            at App (http://localhost:3000/static/js/index.js:46117:80)
        
        React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.
      

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 3

    ♻️ Duplicate comments (1)
    src/appViewer.ts (1)

    184-198: ⚠️ Potential issue

    Inconsistent backend interface reference.
    this.backend?.worldState is accessed, but worldState is not declared in the GraphicsBackend interface. This can cause undefined behavior or runtime errors if the backend lacks this property.

    - if (this.backend?.worldState.allChunksLoaded) return
    + // either define worldState in the GraphicsBackend interface 
    + // or remove the referencing code in waitingForChunks
    🧹 Nitpick comments (9)
    src/react/AppStatusProvider.tsx (4)

    68-68: Check safe casting for usingState.

    Explicitly casting (isOpen ? currentState : lastState.current) as typeof currentState may lead to subtle runtime issues if lastState.current diverges in shape from currentState. Verify the shape consistency to prevent hidden type mismatches at runtime.


    96-96: Keep the TODO tasks discoverable.

    You have a “// todo do only if reconnect is possible” comment. If still relevant, consider creating a follow-up ticket or adding a Trello/Jira reference so this won't get lost.


    121-142: Refine the backAction logic for maintainability.

    The nested conditions (if (!wasDisconnected)...else{}) and multiple state resets can obscure readability. Consider extracting the “wasDisconnected” branch into a dedicated helper for clarity and consistent handling of modal closures.

    Example refactor:

     if (maybeRecoverable && (!lockConnect || !wasDisconnected)) {
       backAction = () => {
    -    if (!wasDisconnected) {
    -      hideModal(undefined, undefined, { force: true })
    -      return
    -    }
    -    resetAppStatusState()
    -    ...
    +    if (!wasDisconnected) return closeModalImmediately()
    +    handleDisconnectedCase()
       }
     }

    157-157: Clarify empty error states.

    Passing backAction={backAction} as a prop when backAction might remain undefined is harmless, but it can confuse code reading. Consider conditionally passing the prop only if backAction exists, improving clarity.

    src/resourcesManager.ts (1)

    107-142: Optimize the creation of multiple atlases.

    Atlas creation for blocks and items can be heavy. If possible, parallelize them or cache partial results. Also, confirm that repeated calls to makeNewAtlas handle concurrency gracefully (i.e., no race conditions).

    src/index.ts (1)

    116-130: Ensure backend loading reliability.

    loadBackend is defined and then invoked immediately unless SINGLE_FILE_BUILD_MODE is active. Confirm that the backend is needed early or consider lazy loading once user interaction demands the renderer. This can improve startup performance.

    renderer/viewer/lib/worldrendererCommon.ts (1)

    163-173: Prevent double initialization in init().

    There's a guard (if (this.active) throw new Error...) but concurrency or repeated calls could bypass it. Use a more robust pattern, e.g., an atomic boolean or a dedicated “initialized” state to avoid partial setup.

    src/appViewer.ts (1)

    201-202: Avoid global state on window if possible.
    Attaching appViewer to window can create hidden dependencies. Consider a more controlled injection or usage pattern for better maintainability and testability.

    renderer/viewer/lib/worldrendererThree.ts (1)

    592-595: Partial disposal in destroy method.
    While calling removeAllStats() and then super.destroy() is good, verify that any custom THREE.js resources (e.g., textures, geometries) are fully freed in the chain of destructors.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between e917764 and f72621d.

    📒 Files selected for processing (14)
    • renderer/viewer/lib/mesher/shared.ts (1 hunks)
    • renderer/viewer/lib/ui/newStats.ts (1 hunks)
    • renderer/viewer/lib/worldrendererCommon.ts (15 hunks)
    • renderer/viewer/lib/worldrendererThree.ts (12 hunks)
    • renderer/viewer/three/graphicsBackend.ts (1 hunks)
    • renderer/viewer/three/panorama.ts (1 hunks)
    • src/appViewer.ts (1 hunks)
    • src/connect.ts (2 hunks)
    • src/index.ts (12 hunks)
    • src/optionsGuiScheme.tsx (2 hunks)
    • src/react/AppStatus.tsx (1 hunks)
    • src/react/AppStatusProvider.tsx (5 hunks)
    • src/resourcePack.ts (5 hunks)
    • src/resourcesManager.ts (1 hunks)
    ✅ Files skipped from review due to trivial changes (1)
    • renderer/viewer/lib/mesher/shared.ts
    🚧 Files skipped from review as they are similar to previous changes (3)
    • renderer/viewer/three/panorama.ts
    • src/resourcePack.ts
    • renderer/viewer/three/graphicsBackend.ts
    🧰 Additional context used
    🧬 Code Definitions (3)
    src/optionsGuiScheme.tsx (1)
    src/resourcePack.ts (1) (1)
    • resourcepackReload (573-575)
    src/appViewer.ts (6)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (37-266)
    renderer/viewer/lib/worldrendererCommon.ts (2) (2)
    • WorldRendererConfig (53-53)
    • defaultWorldRendererConfig (31-51)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • IPlayerState (17-42)
    src/mineflayer/playerState.ts (1) (1)
    • playerState (206-206)
    renderer/viewer/baseGraphicsBackend.ts (1) (1)
    • getDefaultRendererState (3-14)
    src/watchOptions.ts (1) (1)
    • watchOptionsAfterWorldViewInit (112-120)
    renderer/viewer/lib/worldrendererThree.ts (7)
    renderer/viewer/lib/holdingBlock.ts (1) (1)
    • HoldingBlock (94-514)
    renderer/viewer/lib/entities.ts (1) (1)
    • Entities (212-1067)
    src/appViewer.ts (2) (2)
    • GraphicsInitOptions (39-44)
    • DisplayWorldOptions (46-52)
    renderer/viewer/lib/basePlayerState.ts (1) (1)
    • ItemSpecificContextProperties (10-10)
    renderer/viewer/three/appShared.ts (1) (1)
    • getItemUv (7-70)
    renderer/viewer/lib/hand.ts (1) (1)
    • getMyHand (5-35)
    renderer/viewer/lib/ui/newStats.ts (1) (1)
    • removeAllStats (43-48)
    🪛 GitHub Check: build-and-deploy
    src/index.ts

    [failure] 711-711:
    'appViewer.worldView' is possibly 'undefined'.


    [failure] 701-701:
    'appViewer.worldView' is possibly 'undefined'.

    🪛 GitHub Actions: CI
    src/index.ts

    [error] 701-701: 'appViewer.worldView' is possibly 'undefined'.

    🪛 ESLint
    renderer/viewer/lib/worldrendererThree.ts

    [error] 393-393: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)

    🔇 Additional comments (21)
    src/react/AppStatus.tsx (1)

    86-86: Simplified back button rendering logic.

    The conditional check for lockConnect has been removed, making the "Back" button appear whenever backAction is defined. This simplifies the UI logic and ensures the back functionality is consistently available when needed.

    src/optionsGuiScheme.tsx (1)

    184-184: Good addition: Resource pack reload after disabling.

    Adding a call to resourcepackReload() after setting enabledResourcepack to null ensures that texture changes take effect immediately, providing better user feedback when disabling a resource pack.

    renderer/viewer/lib/ui/newStats.ts (2)

    43-48: Good addition: Function to remove all statistics.

    The removeAllStats function provides a clean way to remove all statistics at once, which is valuable for proper cleanup during disconnection or renderer recreation. This aligns well with the PR's goal of improving resource management during disconnection.


    50-54: Good addition: Function to remove individual statistics.

    The removeStat function provides a safe way to remove individual statistics by checking if the stat exists before attempting removal. This helps prevent potential errors and ensures clean resource management.

    src/connect.ts (1)

    46-46: Simplified function signature aligns with new architecture.

    Removing the importBlockstatesModels parameter from loadMinecraftData simplifies the function and aligns with the PR's goal of moving resource management to a dedicated ResourceManager. This change contributes to the separation of application code from rendering code.

    src/react/AppStatusProvider.tsx (2)

    163-163: Validate guard condition usage.

    Rendering the button when {wasDisconnected && lastAutoCapturedPackets} is good. Ensure that lastAutoCapturedPackets has a nonzero length or valid data. Otherwise, “Inspect Last 0 Packets” might appear for no-op actions.


    9-9:

    ✅ Verification successful

    Ensure usage reference or remove redundant import.

    The import of appQueryParams is now used, which is fine. However, verify there are no other references left behind from prior changes that rely on the old approach. If unused anywhere else, consider removing extraneous imports to maintain clarity.

    To confirm usage, run:


    🏁 Script executed:

    #!/bin/bash
    rg -A 3 "appQueryParams" --type ts

    Length of output: 17028


    Usage Verified: appQueryParams Import is Active and Up-to-Date
    The grep search confirms that appQueryParams is actively used in src/react/AppStatusProvider.tsx (e.g., for evaluating lockConnect) as well as in multiple other modules. There are no indications of remnants from a previous approach. No removal or further changes are necessary.

    src/resourcesManager.ts (3)

    69-71: Add error handling for loadMcData.

    Your loadMcData(version) lacks a try-catch block. This repeats a prior recommendation. Failure to load data can crash the application. Gracefully handle exceptions and surface errors to the user.

    async loadMcData (version: string) {
    +  try {
        await loadMinecraftData(version)
    +  } catch (error) {
    +    console.error('Failed to load McData:', error)
    +    throw new Error('Could not load Minecraft data')
    +  }
    }

    82-104: Reexamine abort handling in updateAssetsData.

    You're creating a local abortController but never referencing this.abortController. If destroy() is called, the local controller remains unaware and the operation can continue in the background. Coordinate them for consistent cancellation.

    -    const abortController = new AbortController()
    +    const { signal } = this.abortController
    
    -    if (abortController.signal.aborted) return
    +    if (signal.aborted) return

    187-191: Use stable reference to abortController.

    When you recreate this.abortController in destroy(), ensure that references in asynchronous code do not become invalid. If any pending tasks keep the old reference, it may lead to memory leaks or missed abort signals.

    src/index.ts (1)

    165-174: Check the focus logic for potential user confusion.

    Waiting for document focus (line 167) can stall the connection if the user remains on a different tab. Confirm that this is the intended experience, or provide a fallback if the user never focuses the tab.

    renderer/viewer/lib/worldrendererCommon.ts (1)

    701-701: Disable or address overshadowed “distance check.”

    When getDistance returns a distance larger than viewDistance, the chunk is skipped from meshing. Confirm your intention to drop the chunk or queue it for future meshing if the viewer moves closer. This might cause partial or delayed rendering in edge conditions.

    src/appViewer.ts (4)

    1-16: Imports look clean.
    No issues found with the new or updated imports here.


    91-119: Ensure defensive method checks for queued actions.
    When dispatching this.backend[method](...args), consider verifying that method exists on the backend to avoid runtime errors if the method name changes or is removed.

    Do you want a script to detect all references of this.backend[anyMethod] calls to ensure these methods are defined in GraphicsBackend?


    148-168: Check for potential undefined version in resourcesManager.
    this.resourcesManager.currentConfig! may be undefined if resources have not been initialized before calling startWorld. Consider a null-check to avoid unexpected runtime errors.


    204-219: Logic for starting panorama on modal stack changes looks valid.
    The sequence for resetting the backend and starting the panorama upon UI changes is coherent. No critical issues identified.

    Also applies to: 220-234

    renderer/viewer/lib/worldrendererThree.ts (5)

    6-24: New imports are consistent.
    These imports align with the new rendering architecture and show a clear separation of functionality.


    35-45: New class properties introduced.
    The introduction of cameraRoll, scene, ambientLight, and others is well-organized. They are logically grouped at the top and straightforward to follow.


    190-213: Persisting usage of @ts-expect-error in demo methods.
    This matches a previously flagged concern regarding suppressed TypeScript errors.

    Consider removing @ts-expect-error or clarifying the reasoning behind its necessity, and ensure cursorBlockRel is defined or properly imported.


    359-365: Camera override logic appears sound.
    Forwarding to a custom camera object or falling back to this.camera is a flexible strategy. The code is easy to follow and no issues are evident here.


    578-590: Camera roll feature.
    Allowing a dedicated roll transform is a useful extension for more advanced camera effects, with clear quaternion logic. Looks good.

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 0

    ♻️ Duplicate comments (7)
    renderer/viewer/lib/worldrendererCommon.ts (3)

    583-592: ⚠️ Potential issue

    Duplicate event handlers for 'renderDistance'.

    There are two nearly identical event handlers for 'renderDistance' events (lines 583-587 and 588-593), which could lead to the same logic being executed twice and potential inconsistencies.

    Remove the duplicate event handler:

    worldEmitter.on('renderDistance', (d) => {
      this.viewDistance = d
      this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
    + this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
    })
    
    - worldEmitter.on('renderDistance', (d) => {
    -   this.viewDistance = d
    -   this.chunksLength = d === 0 ? 1 : generateSpiralMatrix(d).length
    -   this.allChunksFinished = Object.keys(this.finishedChunks).length === this.chunksLength
    - })

    505-522: 🛠️ Refactor suggestion

    Potential race condition in setBlockStateId.

    The method creates an async function that's executed but not awaited, which could lead to race conditions with multiple blocks being updated simultaneously.

    Consider using a queue system to ensure block updates are processed in order:

    setBlockStateId (pos: Vec3, stateId: number | undefined) {
    -  const set = async () => {
    -    const sectionX = Math.floor(pos.x / 16) * 16
    -    const sectionZ = Math.floor(pos.z / 16) * 16
    -    if (this.queuedChunks.has(`${sectionX},${sectionZ}`)) {
    -      await new Promise<void>(resolve => {
    -        this.queuedFunctions.push(() => {
    -          resolve()
    -        })
    -      })
    -    }
    -    if (!this.loadedChunks[`${sectionX},${sectionZ}`]) {
    -      // console.debug('[should be unreachable] setBlockStateId called for unloaded chunk', pos)
    -    }
    -    this.setBlockStateIdInner(pos, stateId)
    -  }
    -  void set()
    +  const sectionX = Math.floor(pos.x / 16) * 16
    +  const sectionZ = Math.floor(pos.z / 16) * 16
    +  const chunkKey = `${sectionX},${sectionZ}`
    +  
    +  if (this.queuedChunks.has(chunkKey)) {
    +    // Add to queue for processing after chunk loads
    +    this.queuedFunctions.push(() => {
    +      this.setBlockStateIdInner(pos, stateId)
    +    })
    +  } else if (this.loadedChunks[chunkKey]) {
    +    // Process immediately for loaded chunks
    +    this.setBlockStateIdInner(pos, stateId)
    +  }
    }

    145-158: 🛠️ Refactor suggestion

    Constructor directly calls connect() which may lead to initialization issues.

    The constructor immediately connects to the world view (line 157) before the object is fully initialized. This approach could lead to race conditions if connect() relies on properties that are set after construction or in the init() method.

    Consider moving the this.connect(this.displayOptions.worldView) call to the end of the init() method to ensure all necessary initialization is completed first.

    constructor (public readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public version: string) {
      // this.initWorkers(1) // preload script on page load
      this.snapshotInitialValues()
      this.worldRendererConfig = displayOptions.inWorldRenderingConfig
      this.playerState = displayOptions.playerState
      this.reactiveState = displayOptions.rendererState
    
      this.renderUpdateEmitter.on('update', () => {
        const loadedChunks = Object.keys(this.finishedChunks).length
        updateStatText('loaded-chunks', `${loadedChunks}/${this.chunksLength} chunks (${this.lastChunkDistance}/${this.viewDistance})`)
      })
    -
    -  this.connect(this.displayOptions.worldView)
    }
    
    init () {
      if (this.active) throw new Error('WorldRendererCommon is already initialized')
      void this.setVersion(this.version).then(() => {
        this.resourcesManager.on('assetsTexturesUpdated', () => {
          if (!this.active) return
          void this.updateAssetsData()
        })
        if (this.resourcesManager.currentResources) {
          void this.updateAssetsData()
        }
    +    this.connect(this.displayOptions.worldView)
      })
    }
    renderer/viewer/three/panorama.ts (1)

    133-140: 💡 Verification agent

    🧩 Analysis chain

    Undefined variable reference in worldBlocksPanorama.

    The code references loadedData.blocksArray which isn't defined within this file's scope. This will cause a runtime error when executing the panorama rendering.

    You need to either import this variable or define it within the method:

    - const fullBlocks = loadedData.blocksArray.filter(block => {
    + // Import or define loadedData, for example:
    + import { loadedData } from '../lib/minecraftData' // Adjust the import path as needed
    + // Or define it locally:
    + const loadedData = require('minecraft-data')(version)
    + const fullBlocks = loadedData.blocksArray.filter(block => {

    You can also verify if this is defined by checking:


    🏁 Script executed:

    #!/bin/bash
    # Search for loadedData definition in the codebase
    rg "loadedData" -A 2 -B 2

    Length of output: 13855


    Critical Issue: Undefined loadedData in panorama.ts

    After running a search through the codebase, it’s confirmed that loadedData is referenced in multiple files but isn’t defined or imported in renderer/viewer/three/panorama.ts. This is likely to cause a runtime error when attempting to access loadedData.blocksArray.

    Suggested Fix:

    • Option 1: Import the variable from a shared module (e.g., if it’s exported in lib/minecraftData):
      - const fullBlocks = loadedData.blocksArray.filter(block => {
      + import { loadedData } from '../lib/minecraftData'; // Adjust the import path as needed
      + const fullBlocks = loadedData.blocksArray.filter(block => {
    • Option 2: Define it locally, if appropriate (for example, by requiring the module):
      - const fullBlocks = loadedData.blocksArray.filter(block => {
      + const loadedData = require('minecraft-data')(version);
      + const fullBlocks = loadedData.blocksArray.filter(block => {

    Review the project’s architecture to decide whether loadedData should be imported as a global/shared variable or defined locally in this module.

    src/resourcesManager.ts (2)

    71-78: 🛠️ Refactor suggestion

    Missing error handling in loadMcData and loadSourceData methods.

    These methods should include error handling to gracefully manage failures when loading Minecraft data.

    Add try-catch blocks to handle potential errors:

    async loadMcData (version: string) {
    +  try {
        await loadMinecraftData(version)
    +  } catch (error) {
    +    console.error('Failed to load Minecraft data:', error)
    +    throw new Error(`Failed to load Minecraft data for version ${version}: ${error.message}`)
    +  }
    }
    
    async loadSourceData (version: string) {
    +  try {
        await this.loadMcData(version)
        this.sourceBlockStatesModels ??= await importLargeData('blockStatesModels')
    +  } catch (error) {
    +    console.error('Failed to load source data:', error)
    +    throw new Error(`Failed to load source data for version ${version}: ${error.message}`)
    +  }
    }

    84-89: 🛠️ Refactor suggestion

    Improve abort handling in updateAssetsData.

    The method creates a new local abortController but doesn't use the class-level this.abortController. This could lead to resources not being properly cleaned up when destroy() is called.

    Use the class-level abort controller:

    async updateAssetsData (request: UpdateAssetsRequest, unstableSkipEvent = false) {
      if (!this.currentConfig) throw new Error('No config loaded')
    -  const abortController = new AbortController()
    +  const { signal } = this.abortController
      await this.loadSourceData(this.currentConfig.version)
    -  if (abortController.signal.aborted) return
    +  if (signal.aborted) return

    Also update subsequent abort checks on lines 136 and 170:

    -  if (abortController.signal.aborted) return
    +  if (signal.aborted) return
    src/appViewer.ts (1)

    200-212: ⚠️ Potential issue

    Inconsistency in waitingForChunks method.

    The method references this.backend?.worldState.allChunksLoaded, but there's no worldState property defined in the GraphicsBackend interface. This will cause runtime errors.

    Update to use the correct property path:

    async waitingForChunks () {
    -  if (this.backend?.worldState.allChunksLoaded) return
    +  if (this.rendererState.world.allChunksLoaded) return
      return new Promise((resolve) => {
        const interval = setInterval(() => {
    -      if (this.backend?.worldState.allChunksLoaded) {
    +      if (this.rendererState.world.allChunksLoaded) {
            clearInterval(interval)
            resolve(true)
          }
        }, 100)
    +    // Add timeout to prevent memory leaks
    +    const timeout = setTimeout(() => {
    +      clearInterval(interval)
    +      resolve(false)
    +    }, 30000) // 30 second timeout
      })
    }
    🧹 Nitpick comments (4)
    renderer/viewer/three/panorama.ts (1)

    34-51: Missing window resize handling in PanoramaRenderer.

    The camera is initialized with the current window dimensions, but there's no event listener for window resize events. This could lead to rendering issues when the window size changes.

    Add window resize event handling:

    constructor (private readonly documentRenderer: DocumentRenderer, private readonly options: GraphicsInitOptions, private readonly doWorldBlocksPanorama = false) {
      this.scene = new THREE.Scene()
      this.scene.background = new THREE.Color(this.options.config.sceneBackground)
    
      // Add ambient light
      this.ambientLight = new THREE.AmbientLight(0xcc_cc_cc)
      this.scene.add(this.ambientLight)
    
      // Add directional light
      this.directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5)
      this.directionalLight.position.set(1, 1, 0.5).normalize()
      this.directionalLight.castShadow = true
      this.scene.add(this.directionalLight)
    
      this.camera = new THREE.PerspectiveCamera(85, window.innerWidth / window.innerHeight, 0.05, 1000)
      this.camera.position.set(0, 0, 0)
      this.camera.rotation.set(0, 0, 0)
      
    +  // Add resize listener
    +  window.addEventListener('resize', this.handleResize, { signal: this.abortController.signal })
    }
    
    + private handleResize = () => {
    +   this.camera.aspect = window.innerWidth / window.innerHeight
    +   this.camera.updateProjectionMatrix()
    + }
    renderer/viewer/lib/worldrendererThree.ts (3)

    35-44: Alpha test caution for the new mesh material.
    Defining alphaTest on MeshLambertMaterial at 0.1 may lead to sharp or unexpected cutouts where textures have slight transparency. Confirm this is intentional; otherwise consider using alpha blending or adjusting the alpha cutoff for smoother transitions.


    131-162: Add optional error handling for asynchronous texture loading.
    Currently, a loading failure in loadAsync might throw an unhandled exception. Consider wrapping with try/catch or .catch(...) to gracefully handle fetch errors.


    393-393: Redundant ESLint disable directive.
    Line 393 disables @typescript-eslint/non-nullable-type-assertion-style without needing it anywhere. Consider removing this comment to keep ESLint configs clean.

    🧰 Tools
    🪛 ESLint

    [error] 393-393: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between f72621d and 92853e2.

    📒 Files selected for processing (7)
    • renderer/viewer/lib/worldrendererCommon.ts (15 hunks)
    • renderer/viewer/lib/worldrendererThree.ts (12 hunks)
    • renderer/viewer/three/panorama.ts (1 hunks)
    • src/appViewer.ts (1 hunks)
    • src/customChannels.ts (3 hunks)
    • src/inventoryWindows.ts (6 hunks)
    • src/resourcesManager.ts (1 hunks)
    🚧 Files skipped from review as they are similar to previous changes (2)
    • src/customChannels.ts
    • src/inventoryWindows.ts
    🧰 Additional context used
    🧬 Code Definitions (2)
    src/appViewer.ts (4)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (37-266)
    renderer/viewer/lib/worldrendererCommon.ts (2) (2)
    • WorldRendererConfig (50-50)
    • defaultWorldRendererConfig (28-48)
    src/mineflayer/playerState.ts (1) (1)
    • playerState (206-206)
    src/watchOptions.ts (1) (1)
    • watchOptionsAfterWorldViewInit (112-120)
    renderer/viewer/lib/worldrendererCommon.ts (4)
    src/appViewer.ts (2) (2)
    • RendererReactiveState (17-26)
    • DisplayWorldOptions (48-54)
    src/resourcesManager.ts (1) (1)
    • ResourcesManager (59-201)
    renderer/viewer/lib/ui/newStats.ts (2) (2)
    • updateStatText (38-41)
    • removeStat (50-54)
    renderer/viewer/lib/mesher/shared.ts (1) (1)
    • getBlockAssetsCacheKey (56-58)
    🪛 ESLint
    renderer/viewer/lib/worldrendererThree.ts

    [error] 393-393: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)

    🔇 Additional comments (24)
    renderer/viewer/lib/worldrendererCommon.ts (1)

    766-786: Good implementation of resource cleanup in destroy method.

    The destroy method properly terminates workers and cleans up the sound system, which helps prevent memory leaks and ensures resources are released when no longer needed.

    renderer/viewer/three/panorama.ts (1)

    202-206: Good implementation of resource cleanup in dispose method.

    The dispose method properly cleans up resources by clearing the scene, destroying the world renderer, and aborting ongoing processes with the abort controller.

    src/resourcesManager.ts (1)

    196-201: Good implementation of resource cleanup in destroy method.

    The destroy method properly aborts ongoing operations, resets the resources, and reinitializes the abort controller for future use.

    src/appViewer.ts (3)

    215-217: Global appViewer instance and assignment to window.

    Creating a global instance and assigning it to the window object can lead to maintainability issues and potential security concerns.

    Consider implementing a more controlled approach to accessing the AppViewer instance:

    export const appViewer = new AppViewer()
    - window.appViewer = appViewer
    + // Instead of adding to window, export a function to access it
    + export function getAppViewer() {
    +   return appViewer
    + }

    99-127: Good implementation of backend loading and state management.

    The loadBackend method properly handles disconnecting an existing backend, initializing a new one, and executing any queued state. This approach provides clean transitions between rendering backends.


    129-152: Good implementation of world initialization.

    The startWorld method properly sets up the world view, establishes event listeners, and creates a clear display configuration for the renderer. The state management is well-organized and provides a clear flow for starting the world rendering.

    renderer/viewer/lib/worldrendererThree.ts (18)

    6-23: Imports look properly organized.
    No immediate issues, as each imported symbol appears to be used. Good addition of typed references.


    54-70: Consider handling potential Promise rejections from VR initialization.
    You’re discarding any async result from initVR(this) with void. If initVR returns or throws, the error may go unhandled. If necessary, catch or await the promise to handle errors gracefully.


    72-87: Rotation overrides appear correct for entity updates.
    The conditional logic for separate position updates vs. full entity updates is straightforward and seems coherent.


    89-99: Scene reset logic looks good.
    Matrix auto updates are disabled for performance, and default lighting is properly applied. This is a neat approach to ensure a consistent initial state.


    101-120: Neat reactive state updates for environment.
    The straightforward subscription mechanism for background color, fog, and ambient/directional light is clear and maintainable.


    164-169: Texture readiness checks look solid.
    Marking the holding blocks as ready and updating items is logically consistent after texture loading finishes.


    186-188: Seamless item render data retrieval.
    The delegated call to getItemUv is direct and maintainable, showing clear separation of concerns.


    190-201: Unnecessary @ts-expect-error and undefined function usage (repeat note).
    You’re still referencing cursorBlockRel without importing or defining it, and suppressing TypeScript errors with //@ts-expect-error. The same comment from previous reviews applies. Consider removing or properly typing this code if it’s only for debugging.


    202-213: Same @ts-expect-error usage in demoItem.
    As with demoModel, the undefined cursorBlockRel persists here. Revisit the approach or document its usage.


    303-303: Chunk border visibility toggle.
    Your logic correctly turns off boxHelper when showChunkBorders is false. This helps declutter the scene for standard gameplay.


    359-365: First-person camera offset looks good.
    You properly incorporate the eye height for positioning. This is straightforward and likely beneficial for a realistic viewpoint.


    368-379: Subtle tween-based camera movement.
    Applying a small tween duration (50ms) is a nice touch, though it can introduce tiny camera lag. Ensure it doesn’t conflict with frantic user input or free-fly logic.


    381-405: Flexible rendering pipeline.
    The code updates FOV/aspect on demand, renders entities and hand blocks, and invokes custom onRender callbacks. This is well-structured for modular rendering.

    🧰 Tools
    🪛 ESLint

    [error] 393-393: '@typescript-eslint/non-nullable-type-assertion-style' rule is disabled but never reported.

    (eslint-comments/no-unused-disable)


    478-483: Light update rename is consistent.
    Renaming updateLight to lightUpdate clarifies the method’s purpose and keeps chunk updates unified with the existing approach.


    492-501: Dynamic chunk border toggling.
    Assigning the boolean flag to showChunkBorders then toggling visibility on existing helper objects is concise and efficient.


    549-561: Column removal with chunk texture cleanup.
    You are disposing of chunk textures, removing the mesh, and disposing the object. This systematically avoids leaks.


    578-591: Rolling the camera via quaternions.
    The approach is valid, though mixing euler-based pitch/yaw with quaternion-based roll might be tricky. Confirm the final orientation matches gameplay expectations.


    592-595: Ensure full resource cleanup upon destroy.
    You remove stats and call super.destroy(), but confirm that all THREE.js objects (e.g., scene, textures, geometry) are also properly disposed in the parent or here to avoid memory leaks.

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 1

    ♻️ Duplicate comments (1)
    src/appViewer.ts (1)

    199-211: ⚠️ Potential issue

    Inconsistency: this.backend?.worldState vs. the declared interface.
    Your GraphicsBackend interface doesn't define worldState, yet waitingForChunks() uses this.backend?.worldState.allChunksLoaded. This likely causes runtime undefined property references.

    Replace references to worldState with actual reactive state usage, for example:

    -if (this.backend?.worldState.allChunksLoaded) return
    +if (this.rendererState.world.allChunksLoaded) return

    Ensure this fix aligns with your intended architecture for tracking chunk loading.

    🧹 Nitpick comments (6)
    src/appViewer.ts (3)

    74-94: Ensure concurrency safety when sharing global state.
    The class maintains shared state across the application (e.g., resourcesManager, worldView, and rendererState) and also exposes itself globally via window.appViewer. While this is acceptable for rapid prototyping, it can introduce complexity and possible side effects if multiple consumers try to modify or re-initialize the state concurrently.

    Consider introducing a dedicated state management solution (e.g., a store pattern or dependency injection) to isolate global state changes more reliably if concurrency or SSR eventually become relevant.


    186-197: Possible confusion in disconnectBackend method.
    The method resets this.backend and sets this.currentDisplay = null, but a separate call to destroyAll() also resets resources. Double-check that these calls happen in the correct order to avoid partial cleanup or referencing a null backend in other code paths.


    216-218: Prevent hidden-state complexities from global assignment.
    export const appViewer = new AppViewer() is additionally assigned to window.appViewer. Storing global singletons can sometimes hamper testability and lead to unforeseen coupling.

    If practical, consider passing appViewer references explicitly through your codebase to reduce reliance on globally shared objects.

    src/index.ts (3)

    165-174: Wait for focus before continuing.
    Delaying the connection until the window is focused (sessionStorage.delayLoadUntilFocus) helps ensure the user is ready for immediate interaction. Just be mindful of edge cases, such as the user never focusing the tab, leading to indefinite stalling.

    Consider providing a fallback or timeout to connect if the user never focuses the tab.


    695-713: Sequential calls after bot spawn and startWorld.
    The code elegantly handles resource pack installation, calls appViewer.startWorld(bot.world, renderDistance), then initializes camera tracking. This staged approach is clear. Just be cautious if you need specialized error handling in each stage (e.g., resource pack failing after partial world load).

    Consider adding minor guard checks to ensure appViewer is still connected and that the user hasn't cancelled or closed the connection mid-stage.


    718-724: Repeated camera updates on bot movement.
    appViewer.backend?.updateCamera(...) and appViewer.worldView?.updatePosition(...) inside bot.on('move') might degrade performance if triggered very frequently.

    Throttle or debounce frequent position updates to reduce overhead, especially for large or busy servers.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 92853e2 and 4b31817.

    📒 Files selected for processing (2)
    • src/appViewer.ts (1 hunks)
    • src/index.ts (11 hunks)
    🧰 Additional context used
    🧬 Code Definitions (1)
    src/appViewer.ts (4)
    renderer/viewer/lib/worldDataEmitter.ts (1) (1)
    • WorldDataEmitter (37-266)
    renderer/viewer/lib/worldrendererCommon.ts (2) (2)
    • WorldRendererConfig (50-50)
    • defaultWorldRendererConfig (28-48)
    src/mineflayer/playerState.ts (1) (1)
    • playerState (206-206)
    src/watchOptions.ts (1) (1)
    • watchOptionsAfterWorldViewInit (112-120)
    🔇 Additional comments (5)
    src/appViewer.ts (1)

    129-152:

    ❓ Verification inconclusive

    Verify worldView lifecycle and usage.
    this.worldView is set, and then immediately used in the same method for rendering. If worldView.init() or startWorld fail halfway, or if the server is disconnected prematurely, you could end up storing a half-initialized worldView instance.

    Please confirm that rapid connect/disconnect scenarios or exceptions during worldView.init() won't lead to orphaned state. You can check for issues by searching the codebase for quick repeated calls to startWorld:


    🏁 Script executed:

    #!/bin/bash
    rg -A 3 "appViewer\.startWorld"

    Length of output: 475


    Verify Handling of Partial Initialization in worldView

    The review comment highlights that this.worldView is created and immediately used for rendering. In scenarios where this.worldView.init(startPosition) or backend.startWorld(displayWorldOptions) fails—whether due to exceptions or rapid connect/disconnect events—the instance could be left in a half-initialized, orphaned state.

    • Confirm that failure paths during worldView.init() are properly managed (e.g., with cleanup or retry logic).
    • Verify that any exceptions during backend.startWorld() or subsequent asynchronous operations do not leave worldView in an inconsistent state.
    • Review related call sites (e.g., the single call in src/index.ts) to ensure they don’t unintentionally invoke startWorld multiple times leading to similar issues.
    src/index.ts (4)

    116-130: Conditionally load backend only after filesystem is ready.
    This logic ensures that the backend loads after miscUiState.fsReady is true if SINGLE_FILE_BUILD_MODE is set. This approach avoids partial initialization of the rendering system when the filesystem is unprepared. The design is sound, but watch for possible race conditions if fsReady is toggled off or on multiple times.


    221-221: Retaining the viewer instance for debugging.
    Skipping appViewer reset is helpful for debugging, but can leave partial state if subsequent reconnect attempts are made in the same session. Ensure other parts of the code handle that leftover viewer state correctly.


    654-670: Efficient chunk-loading checks with appViewer.rendererState.
    The new waitForChunksToLoad function improves chunk-loading feedback by subscribing to appViewer.rendererState.world. This approach is consistent with a reactive pattern. Just ensure you clean up any subscriptions if the user disconnects before chunks have fully loaded.


    734-748: Conditional chunk waiting logic.
    The logic behind options.waitForChunksRender in singleplayer mode ensures chunk loading is optional for performance reasons. This is a neat feature. Double-check that the fallback (if (!appViewer.backend || appViewer.rendererState.world.allChunksLoaded || !waitForChunks)) properly accounts for edge cases — e.g., “renderDistance=0” scenarios or immediate disconnections.

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 22, 2025

    @Phoenix616 thank you so much for testing! I fixed renderer issues you reported, and inmy testing everything is fine. The only problem was sometimes I could see green ellipses on my screen , not sure what the issue, will do last code review and then merge i think

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 22, 2025

    /deploy

    Copy link

    Deployed to Vercel Preview: https://prismarine-46gp80mav-zaro.vercel.app
    Playground
    Storybook

    @zardoy
    Copy link
    Owner Author

    zardoy commented Mar 22, 2025

    merge after I fix this (cursor mesh)...
    image

    Copy link

    @coderabbitai coderabbitai bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Actionable comments posted: 0

    ♻️ Duplicate comments (1)
    renderer/viewer/three/world/cursorBlock.ts (1)

    79-93: ⚠️ Potential issue

    Undefined reference to bot, causing a runtime error.
    This exact issue was flagged in a previous review.

    -    const mergedShape = bot.mouse.getMergedCursorShape(block)
    -    if (!mergedShape) return
    -    const { position, width, height, depth } = bot.mouse.getDataFromShape(mergedShape)
    +    // Please adjust the reference below as appropriate for your architecture:
    +    const { mergedShape, position, width, height, depth } = this.worldRenderer.getCursorShapeData(block)
    +    if (!mergedShape) return
    🧹 Nitpick comments (3)
    renderer/viewer/three/world/cursorBlock.ts (3)

    19-24: Add explicit typing for prevColor to improve clarity.

    Currently, prevColor has no explicit type. Specifying its type (e.g., string | null) can enhance readability and reduce potential type-related issues.

     export class CursorBlock {
       cursorLineMaterial: LineMaterial
       interactionLines: null | { blockPos; mesh } = null
    -  prevColor
    +  prevColor: string | null = null
       blockBreakMesh: THREE.Mesh
       breakTextures: THREE.Texture[] = []
     }

    25-54: Consider handling texture loading errors.

    Using THREE.TextureLoader without an error callback may result in silent failures if a texture file is missing or invalid. Adding an error handler could help diagnose loading issues more quickly.

     const loader = new THREE.TextureLoader()
    +loader.load(
    +  destroyStagesImages[i],
    +  (texture) => {
    +    // onLoad
    +  },
    +  undefined,
    +  (err) => {
    +    console.error('Failed to load texture', destroyStagesImages[i], err)
    +  }
    +)

    125-131: Dispose geometries and materials to prevent potential memory leaks.

    When removing wireframes or the entire group from the scene, consider calling .dispose() on geometries and materials to release GPU resources and avoid leaking memory during continuous cursor block updates.

     group.add(wireframe)
    
    // On removal:
    +wireframe.geometry.dispose()
    +(wireframe.material as LineMaterial).dispose()
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 4b31817 and 1a8813e.

    📒 Files selected for processing (2)
    • renderer/viewer/lib/worldDataEmitter.ts (5 hunks)
    • renderer/viewer/three/world/cursorBlock.ts (1 hunks)
    🧰 Additional context used
    🧬 Code Definitions (1)
    renderer/viewer/three/world/cursorBlock.ts (1)
    renderer/viewer/lib/worldrendererThree.ts (1) (1)
    • WorldRendererThree (26-596)
    🔇 Additional comments (7)
    renderer/viewer/lib/worldDataEmitter.ts (7)

    8-9: Good addition of typed event handling and reactive state management

    Adding TypedEmitter provides type safety for your event system, and introducing valtio's proxy allows for reactive state management. This is a good architectural improvement.


    18-31: Well-structured event type definitions

    The explicit typing of all possible events with their payload structures through WorldDataEmitterEvents significantly improves type safety and code clarity. This will help catch potential event-related bugs at compile time rather than runtime.


    37-37: Effective TypedEmitter integration

    The class now extends TypedEmitter while maintaining EventEmitter compatibility through type casting. This provides better type checking for event emissions without breaking existing code.


    79-82: Improved entity event handling

    The updated emitEntity function with a customizable event name parameter and the null name check improves both flexibility and robustness. The check for e.name prevents emitting events for incompletely initialized entities.


    105-105: Proper use of parameterized event name

    Good use of the updated emitEntity function's second parameter to specify the 'entityMoved' event.


    46-49:

    ❓ Verification inconclusive

    Good introduction of reactive state

    Using valtio's proxy for cursor-related state is a clean approach to manage reactive state. This allows for centralized state management of cursor position and breaking stage.

    Make sure any code that previously accessed cursor state directly has been updated to use this reactive property instead. You may want to run:


    🏁 Script executed:

    #!/bin/bash
    # Search for direct cursor state access that might need to be updated
    rg -i "cursorBlock|cursorBlockBreakingStage" --type ts --type js --glob "!**/worldDataEmitter.ts"

    Length of output: 2514


    Please verify that all direct accesses to cursor state are fully migrated to use the new reactive state.

    The introduction of the reactive state using valtio's proxy in renderer/viewer/lib/worldDataEmitter.ts looks clear and is a solid move toward centralized state management. However, the grep results indicate that several files (for example, src/react/DebugOverlay.tsx, src/mineflayer/plugins/mouse.ts, and src/optionsStorage.ts) still contain references to cursorBlock (and potentially cursorBlockBreakingStage) that may bypass the new reactive property.

    Please ensure that:

    • All components and modules previously accessing the cursor state directly are updated to use the reactive property defined in worldDataEmitter.ts.
    • Any legacy or direct accesses in files like src/react/DebugOverlay.tsx and src/mineflayer/plugins/mouse.ts are refactored accordingly.

    Use the grep command below to double-check for any remaining direct references:

    #!/bin/bash
    rg -i "cursorBlock|cursorBlockBreakingStage" --type ts --type js --glob "!**/worldDataEmitter.ts"

    Once you’ve confirmed that all references have been updated, feel free to remove any deprecated code paths.


    52-53:

    ❓ Verification inconclusive

    Verify constructor initialization

    The eslint-disable comment suggests there might be an initialization issue. Ensure the parent class constructor is properly called.

    Check if the TypedEmitter implementation requires specific initialization parameters that should be passed to super():


    🏁 Script executed:

    #!/bin/bash
    # Check the TypedEmitter implementation 
    rg -A 10 "export default" --type ts node_modules/typed-emitter/

    Length of output: 133


    Verify TypedEmitter Constructor Initialization

    The current code in renderer/viewer/lib/worldDataEmitter.ts calls super() without any arguments. However, our automated check for the TypedEmitter implementation (using the provided script) did not locate a node_modules/typed-emitter/ directory, returning a "No such file or directory" error. This absence could mean that the dependency is either not installed, imported differently, or that TypedEmitter is a type-only helper without a constructor requiring parameters.

    Please ensure that:

    • The TypedEmitter implementation (or its proxy/base, such as Node’s EventEmitter if applicable) doesn’t require additional parameters during initialization.
    • The dependency is correctly declared in your package.json and properly installed, or that its implementation is verified via an alternative source if it’s type-only.

    Manual verification of the TypedEmitter dependency in your project setup is recommended before finalizing this change.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    2 participants