Skip to content

Greddysnake#3

Open
tiye wants to merge 4 commits intomainfrom
greddysnake
Open

Greddysnake#3
tiye wants to merge 4 commits intomainfrom
greddysnake

Conversation

@tiye
Copy link
Copy Markdown
Contributor

@tiye tiye commented Mar 28, 2026

Summary by CodeRabbit

  • New Features

    • Added 3D Snake game demo featuring world rotation controls where player inputs rotate the game world while the snake remains anchored to the screen.
    • Improved controller stick responsiveness with refined dead zone handling to reduce unwanted residual movement.
  • Chores

    • Updated default demo selection to 3D Snake.
    • Updated version control configuration.

@tiye tiye requested review from a team and Copilot March 28, 2026 03:47
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

Introduces a new 3D Snake game demo for VisionOS with complete game logic, Metal rendering pipeline, and integrated controller input, while refining game controller dead-zone behavior and improving frame lifecycle management in the main renderer.

Changes

Cohort / File(s) Summary
Snake3D Demo Architecture
vr-dive/Demos/Snake3D/PLAN.md, vr-dive/Demos/Snake3D/Snake3DTypes.swift, vr-dive/Demos/Snake3D/Snake3DGameLogic.swift
Core architecture, type definitions, and game state management for 3D Snake. Types define direction enum, game state, and GPU/scene data structures. GameLogic handles movement, collision detection, food spawning, speed scaling, and segment interpolation for smooth animation.
Snake3D Rendering
vr-dive/Demos/Snake3D/Snake3DRenderer.swift, vr-dive/Demos/Snake3D/Snake3DShaders.metal
Metal-based renderer implementing amplification-based multi-view rendering, world rotation quaternion with smooth easing, and instanced cube rendering. Shaders define body segments, food animation with pulsing scale, and border/guide wireframes with directional lighting and specular highlights.
Game Pattern Integration
vr-dive/Renderer/PatternSelection.swift, vr-dive/Renderer/Renderer.swift
Adds .snake3D pattern case to enumeration with localized display name. Renderer creates Snake3DRenderer instance during initialization and registers it in the controller registry. Introduces stricter frame lifecycle tracking to conditionally invoke endUpdate() and endSubmission() only when corresponding start stages succeeded.
Input & Controller Tuning
vr-dive/Game/GameManager.swift
Increases analog stick dead zone from 0.12 to 0.2 and adds residual stick clamping to suppress small residual input after dead-zone threshold, reducing subtle movement noise.
Project Maintenance
.gitignore, vr-dive/Demos/PongWar/PongWarShaders.metal
Updates gitignore to ignore buildServer.json and maintains newline at EOF. Comments out unused nearness variable assignment in PongWar vertex shader; remaining edge mask calculations remain functional.

Sequence Diagram

sequenceDiagram
    participant Input as Player Input
    participant GameMgr as GameManager
    participant Logic as Snake3DGameLogic
    participant Renderer as Snake3DRenderer
    participant Metal as Metal GPU
    
    Input->>GameMgr: requestTurn(direction)
    GameMgr->>Logic: requestTurn(newDirection)
    Logic->>Logic: Validate & queue direction
    
    Renderer->>Renderer: updateSimulation(currentTime)
    Renderer->>Logic: update(currentTime)
    Logic->>Logic: Check moveInterval timer
    Logic->>Logic: step() - Move head, check collision
    Logic->>Logic: Process food consumption
    Logic->>Logic: Spawn food if needed
    
    Renderer->>Logic: getInterpolatedSegmentPositions()
    Logic-->>Renderer: Smoothed positions with α
    Renderer->>Renderer: Upload instance buffers
    
    Renderer->>Metal: encodeFrame()
    Metal->>Metal: Draw border geometry
    Metal->>Metal: Draw snake body (instanced cubes)
    Metal->>Metal: Draw food (pulsing animation)
    Metal-->>Renderer: Rendered frame
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

  • Pagoda demo #2 — Modifies the same .gitignore entries and updates PongWarShaders.metal, indicating overlapping changes to identical configuration and shader files.

Poem

🐰 A serpent dances in three dimensions grand,
Twisting through grids with a controller's hand,
Food pulses brightly as the head turns true,
Metal shaders paint the frame anew—
Snake3D hops into the VisionOS zoo! 🎮✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Greddysnake' appears to be a misspelling or typo; the changeset implements a '3D Snake' game demo, not something called 'Greddysnake'. Rename the PR title to something like 'Add Snake3D demo with game logic, renderer, and shaders' to accurately reflect the main feature being introduced.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch greddysnake

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements a 3D Snake game demo for VisionOS, including its core logic, Metal shaders, and a renderer that uses world rotation to keep the snake head centered. It also refines input filtering in the GameManager and improves frame lifecycle management in the main Renderer to prevent crashes during dismissal. Feedback focuses on resolving hardcoded absolute paths in the build configuration, correcting inconsistencies in the development plan, and optimizing performance-critical logic like collision detection and head interpolation. Additionally, suggestions were made to enhance code safety by replacing magic numbers and handling Metal pipeline initialization more robustly.

I am having trouble creating individual review comments. Click here to see my feedback.

buildServer.json (15-16)

critical

The workspace and build_root paths are hardcoded with absolute paths specific to a user's machine. This makes the project configuration non-portable and will cause it to fail for any other developer or on a CI/CD system.

These paths should be made relative to the project directory or use environment variables (e.g., ${PROJECT_DIR}) to ensure the setup is machine-independent.

	"workspace": "${PROJECT_DIR}/vr-dive.xcodeproj/project.xcworkspace",
	"build_root": "${PROJECT_DIR}/DerivedData",

vr-dive/Demos/Snake3D/Snake3DRenderer.swift (53)

high

The rotationDuration is set to 3.0 seconds, which is likely too slow for a responsive game experience and may induce motion sickness. This also contradicts the PLAN.md file, which specifies a rotation animation time of 0.3 seconds. Please adjust this value to match the design document and improve gameplay feel.

  private let rotationDuration: Float = 0.3

vr-dive/Demos/Snake3D/PLAN.md (188)

medium

The description for 加速规则 (acceleration rule) is "每 5 分提速 5%". The term "分" is ambiguous in this context and could be interpreted as either "points" or "minutes".

The implementation in Snake3DGameLogic.swift clarifies this is based on score (speedUpPerScore = 5). To improve clarity in the plan, please specify the unit explicitly, for example: "每得 5 分提速 5%" (speed up 5% for every 5 points scored).

vr-dive/Demos/Snake3D/PLAN.md (202)

medium

There is a calculation error in the estimated game space size. The table on line 183 states the 格子步长 (grid step) is 0.1m (10cm). For a 20x20x20 grid, the side length would be 20 * 0.1m = 2m, not 80cm as written here. Please correct this value to maintain consistency in the plan.

vr-dive/Demos/Snake3D/Snake3DGameLogic.swift (80)

medium

Using state.segments.contains(newHead) for self-collision detection is inefficient, with a time complexity of O(N) where N is the length of the snake. As the snake grows, this check can become a performance bottleneck.

A more performant approach would be to maintain a Set<SIMD3<Int>> of the snake's body segments for O(1) average time complexity lookups. You would need to update this set whenever the snake moves or grows.

This optimization also applies to the food spawning logic in spawnFoodIfNeeded().

vr-dive/Demos/Snake3D/Snake3DGameLogic.swift (112)

medium

The value 1000 used as the maximum number of attempts to spawn food is a magic number. It should be defined as a named constant to improve code readability and make it easier to configure.

    while state.foods.count < Self.maxFoodCount, attempts < Snake3DState.maxSpawnAttempts {

vr-dive/Demos/Snake3D/Snake3DGameLogic.swift (163-170)

medium

This function is inefficient because it calls getInterpolatedSegmentPositions, which processes the entire snake's body, just to extract the head's position. This can be optimized by creating a dedicated method that only calculates the interpolated position for the head segment.

  func getInterpolatedHeadPosition(currentTime: TimeInterval) -> SIMD3<Float> {
    guard let head = state.segments.first else {
      // Default to center of the grid if snake has no body (should not happen in normal gameplay)
      let mid = Float(Snake3DState.gridSize / 2)
      return SIMD3<Float>(mid, mid, mid)
    }

    let alpha = interpolationAlpha(currentTime: currentTime)
    let nextHead = head &+ state.direction.delta

    let start = SIMD3<Float>(Float(head.x), Float(head.y), Float(head.z))
    let target = SIMD3<Float>(Float(nextHead.x), Float(nextHead.y), Float(nextHead.z))

    return simd_mix(start, target, SIMD3<Float>(repeating: alpha))
  }

vr-dive/Demos/Snake3D/Snake3DRenderer.swift (94-99)

medium

Using try! to create Metal pipeline states can lead to an unrecoverable crash if an error occurs (e.g., a typo in a shader function name). It's safer to handle this potential failure gracefully during initialization. Using a do-catch block to wrap the creation and calling fatalError with a descriptive message will make debugging much easier.

    do {
      bodyPipelineState = try Snake3DRenderer.makeBodyPipeline(
        device: device, library: library, maxViewCount: max(1, maxViewCount))
      foodPipelineState = try Snake3DRenderer.makeFoodPipeline(
        device: device, library: library, maxViewCount: max(1, maxViewCount))
      borderPipelineState = try Snake3DRenderer.makeBorderPipeline(
        device: device, library: library, maxViewCount: max(1, maxViewCount))
    } catch {
      fatalError("Failed to create Snake3D pipeline states: \(error)")
    }

vr-dive/Demos/Snake3D/Snake3DRenderer.swift (213-215)

medium

This input handling block for buttonTriangle appears to be incomplete. The comment suggests it's for a reset, but the implementation only updates lastOptionsTime without actually resetting the game state. If this button is intended to reset the game, you should call resetToInitialState() here.

    if input.buttonTriangle && currentTime - lastOptionsTime > optionsDelay {
      resetToInitialState()
      lastOptionsTime = currentTime
    }

vr-dive/Demos/Snake3D/Snake3DRenderer.swift (414-416)

medium

Using the nil-coalescing operator ?? to provide a default head position can mask underlying logic errors. The snake should always have at least one segment during gameplay. If gameLogic.state.segments.first ever evaluates to nil, it signifies an unexpected and potentially erroneous state. Using guard let would be safer, as it would allow you to handle this unexpected case explicitly, perhaps by returning early or logging an error.

    guard let head = gameLogic.state.segments.first else {
      guideInstanceCount = 0
      return
    }

vr-dive/Demos/Snake3D/Snake3DShaders.metal (152)

medium

The blockSize is hardcoded as 0.192 in the shader. This value is already defined as a constant on the Swift side (Snake3DState.blockSize). Hardcoding values in shaders makes them difficult to maintain and tune. This value should be passed from the CPU to the shader, for instance, by adding it to the FoodInstance struct or the Snake3DSceneUniforms.

  float blockSize = inst.scale;

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new Snake3D visual pattern/demo to the VisionOS immersive renderer, alongside some render-loop lifecycle handling tweaks and controller input filtering adjustments.

Changes:

  • Register and integrate a new snake3D pattern (selection enum + renderer controller wiring).
  • Add full Snake3D implementation (game logic, renderer, Metal shaders, and a design plan doc).
  • Adjust render-loop update/submission bracketing and refine controller dead-zone filtering.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
vr-dive/Renderer/Renderer.swift Registers Snake3D and refines frame update/submission lifecycle handling.
vr-dive/Renderer/PatternSelection.swift Adds snake3D to VisualPatternKind and sets it as the default pattern.
vr-dive/Game/GameManager.swift Updates stick dead-zone and adds residual stick clamping.
vr-dive/Demos/Snake3D/Snake3DTypes.swift Introduces Snake3D data model and GPU-facing structs.
vr-dive/Demos/Snake3D/Snake3DShaders.metal Adds Metal shaders for Snake3D body/food/border rendering.
vr-dive/Demos/Snake3D/Snake3DRenderer.swift Implements the Snake3D VisualPatternController renderer and input handling.
vr-dive/Demos/Snake3D/Snake3DGameLogic.swift Implements Snake3D movement/food/spawn/update logic.
vr-dive/Demos/Snake3D/PLAN.md Adds a design/implementation plan for Snake3D.
vr-dive/Demos/PongWar/PongWarShaders.metal Comments out an unused shader variable.
buildServer.json Adds an Xcode build server config file.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +94 to +96
- **网格**:20×20×20,坐标范围 [0, 19]³
- **蛇体存储**:`[SIMD3<Int>]`,每次前进 prepend 新头,pop 尾(若未吃食物)
- **食物**:随机生成,最多 3 个同时存在,不与蛇身重叠
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

PLAN.md describes a 20×20×20 grid with up to 3 foods and ~200 segments, but the current code uses Snake3DState.gridSize = 40, Snake3DGameLogic.maxFoodCount = 16, and maxSegments = 512 / maxFoods = 8. Please update the plan (or constants) to keep the design doc consistent with the implementation.

Suggested change
- **网格**20×20×20,坐标范围 [0, 19]³
- **蛇体存储**`[SIMD3<Int>]`,每次前进 prepend 新头,pop 尾(若未吃食物)
- **食物**:随机生成,最多 3 个同时存在,不与蛇身重叠
- **网格**40×40×40,坐标范围 [0, 39]³(与 `Snake3DState.gridSize` 一致)
- **蛇体存储**`[SIMD3<Int>]`,每次前进 prepend 新头,pop 尾(若未吃食物),当前实现最大段数约 512(与 GPU 缓冲区一致)
- **食物**:随机生成,最多 16 个同时存在,不与蛇身重叠(与 `Snake3DGameLogic.maxFoodCount` 一致)

Copilot uses AI. Check for mistakes.
Comment on lines +440 to +444
case .posX:
return Array(stride(from: head.x, to: Snake3DState.gridSize, by: 2))
case .negX:
return Array(stride(from: head.x, through: 0, by: -2))
case .posY:
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

forwardAlignedIndices uses stride(from: head.<axis>, ...) directly. Because the snake position is not bounded to [0, gridSize), head can become very negative/positive over time, producing very large index arrays and amplifying the risk of overflowing guideBuffer. Consider clamping head (and/or the generated indices) to the grid range before building guide instances, or enforce wall/wrap rules in game logic.

Copilot uses AI. Check for mistakes.
guard let manager = gameManager else { return }
let input = manager.getTetrisInput()

// Options button = reset
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

The comment says “Options button = reset”, but this branch only updates lastOptionsTime and never calls resetToInitialState() (or triggers a reset flag). Either implement the reset action here or rename/comment it to reflect the actual behavior.

Suggested change
// Options button = reset
// Triangle is wired as a placeholder for the Options button here and only updates the
// options debounce timer; actual reset behavior is implemented below using buttonCross.

Copilot uses AI. Check for mistakes.
}

// Reset
let optionsPressed = input.buttonCross // □ for now; Options not exposed in TetrisInput
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

optionsPressed is bound to input.buttonCross but the inline comment says “□ for now”. In GameManager.TetrisInput, buttonCross maps to PS5 × (buttonA) and □ is buttonSquare. This mismatch will make it hard to reason about controls; please align the mapping and comments (and ideally use a dedicated input mapping for Snake3D).

Suggested change
let optionsPressed = input.buttonCross // for now; Options not exposed in TetrisInput
let optionsPressed = input.buttonCross // × for now; Options not exposed in TetrisInput

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +87
length: MemoryLayout<SnakeSegmentInstance>.stride * 512,
options: .storageModeShared)!

foodBuffer = device.makeBuffer(
length: MemoryLayout<FoodInstance>.stride * 8,
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

The buffer sizes here hardcode 512 (and 8 below) even though maxSegments / maxFoods constants exist. Using the constants avoids accidental divergence if limits change later.

Suggested change
length: MemoryLayout<SnakeSegmentInstance>.stride * 512,
options: .storageModeShared)!
foodBuffer = device.makeBuffer(
length: MemoryLayout<FoodInstance>.stride * 8,
length: MemoryLayout<SnakeSegmentInstance>.stride * maxSegments,
options: .storageModeShared)!
foodBuffer = device.makeBuffer(
length: MemoryLayout<FoodInstance>.stride * maxFoods,

Copilot uses AI. Check for mistakes.
Comment on lines 189 to 193
frame.startUpdate()

shouldEndUpdate = true

// Check state immediately after startUpdate - if transitioning, end gracefully
guard layerRenderer.state == .running else {
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

frame.startUpdate() happens before predictTiming() / queryDrawables(). If queryDrawables() returns empty the frame is canceled/invalid (so you can't safely call endUpdate()), leaving an unmatched startUpdate() on a canceled frame. Consider querying timing/drawables first and only calling startUpdate() once you know the frame is valid and you will do update work, so the update bracket stays well-formed.

Copilot uses AI. Check for mistakes.
Comment on lines +425 to +429
(Float(cell.z) - g * 0.5 + 0.5) * Snake3DState.cellSize
)
ptr[instanceIndex] = SnakeGuideInstance(position: position, scale: scale, color: color)
instanceIndex += 1
}
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

addDash writes to ptr[instanceIndex] without checking instanceIndex < maxGuideInstances. Since head can drift outside [0, gridSize) over time, the computed index sequences can grow and instanceIndex can exceed the allocated guide buffer, causing out-of-bounds writes into shared memory. Add a bounds check/cap (or clamp indices / keep the snake within the grid) before writing/incrementing.

Copilot uses AI. Check for mistakes.
private var rotationStartQuat: simd_quatf = simd_quatf(ix: 0, iy: 0, iz: 0, r: 1)
private var targetWorldQuat: simd_quatf = simd_quatf(ix: 0, iy: 0, iz: 0, r: 1)
private var rotationProgress: Float = 1.0 // 1.0 = done
private let rotationDuration: Float = 3.0
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

rotationDuration is set to 3.0, which is an order of magnitude slower than the 0.31s input debounce and the 0.4s move interval. If this was intended to be ~0.3s (as suggested by the PLAN.md), consider correcting the value; otherwise the world rotation may feel disconnected from input and the debounce timing should be revisited.

Suggested change
private let rotationDuration: Float = 3.0
private let rotationDuration: Float = 0.3

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +80
// Collision: self
if state.segments.contains(newHead) {
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

Self-collision currently checks state.segments.contains(newHead) before removing the tail. This will incorrectly end the game when the head moves into the cell currently occupied by the tail on a non-growing step (a move that is usually legal because the tail vacates). Consider excluding the tail segment from the collision set when pendingGrow == 0 (and the move doesn’t eat food).

Suggested change
// Collision: self
if state.segments.contains(newHead) {
// Determine whether the snake will grow on this move (before removing tail).
let willGrow = state.pendingGrow > 0 || state.foods.contains(newHead)
// Collision: self (ignore current tail if it will move away this step)
let bodyToCheck: ArraySlice = willGrow ? state.segments[0...] : state.segments[..<max(state.segments.count - 1, 0)]
if bodyToCheck.contains(newHead) {

Copilot uses AI. Check for mistakes.
- 蛇在逻辑 3D 网格中记录真实方向,但渲染时蛇头坐标系始终对齐摄像机前方(-Z 轴)
- 每次转向,维护一个**累积世界旋转四元数** `worldOrientation: simd_quatf`
- 渲染所有几何体(蛇身、食物、边界框)时,先用 `worldOrientation` 旋转世界坐标,再做 ViewProjection 变换
- 转向时,用 `simd_slerp` 做平滑插值,动画时长约 0.3 秒
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

The plan states the slerp rotation animation duration is ~0.3s, but the implementation uses rotationDuration = 3.0 seconds. Please update PLAN.md or adjust the code so the documentation matches the shipped behavior.

Suggested change
- 转向时,用 `simd_slerp` 做平滑插值,动画时长约 0.3
- 转向时,用 `simd_slerp` 做平滑插值,动画时长约 3.0

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@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

🧹 Nitpick comments (5)
vr-dive/Demos/PongWar/PongWarShaders.metal (1)

66-66: Remove unused CPU-side computation of nearness for cubes.

The shader no longer reads edgeData.x (nearness) in the cube branch (line 66 is now commented out), but the CPU still computes and uploads this value every frame in updateOuterBoundaryVisibility() (lines 193-195 of PongWarRenderer.swift).

Since this value is only used for spheres (wobble calculation at line 124), remove the cube-specific computation:

In PongWarRenderer.swift, lines 193-195:
// Remove these lines for cubes:
let nearness: Float = minDist < nearThreshold ? 1.0 : 0.0
cubeStates[idx].edgeData.x = nearness

Note: edgeData.x remains properly set for spheres and continues to drive the wobble effect, so only the cube-related computation should be removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vr-dive/Demos/PongWar/PongWarShaders.metal` at line 66, Remove the unused
cube-specific computation of nearness in updateOuterBoundaryVisibility(): stop
computing and writing the temporary `nearness` value into
`cubeStates[idx].edgeData.x` for cubes (the CPU-side lines setting `let
nearness: Float = ...` and `cubeStates[idx].edgeData.x = nearness`); leave
`edgeData.x` assignments for spheres intact so the wobble (sphere) logic still
receives its value. Locate the logic inside the updateOuterBoundaryVisibility()
function in PongWarRenderer.swift and delete only the cube-related nearness
calculation and assignment, ensuring no other uses of `nearness` or `edgeData.x`
for cubes remain.
vr-dive/Demos/Snake3D/Snake3DRenderer.swift (1)

82-88: Use defined constants for buffer allocation sizes.

The buffer allocations use hardcoded 512 and 8 instead of maxSegments and maxFoods constants defined on lines 31-32.

♻️ Suggested refactor
     bodyBuffer = device.makeBuffer(
-      length: MemoryLayout<SnakeSegmentInstance>.stride * 512,
+      length: MemoryLayout<SnakeSegmentInstance>.stride * maxSegments,
       options: .storageModeShared)!

     foodBuffer = device.makeBuffer(
-      length: MemoryLayout<FoodInstance>.stride * 8,
+      length: MemoryLayout<FoodInstance>.stride * maxFoods,
       options: .storageModeShared)!
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vr-dive/Demos/Snake3D/Snake3DRenderer.swift` around lines 82 - 88, Replace
the hardcoded sizes when allocating bodyBuffer and foodBuffer with the defined
constants maxSegments and maxFoods: update the calls to device.makeBuffer that
use MemoryLayout<SnakeSegmentInstance>.stride * 512 and
MemoryLayout<FoodInstance>.stride * 8 to instead multiply by maxSegments and
maxFoods respectively so allocations reflect the declared capacities.
vr-dive/Demos/Snake3D/PLAN.md (1)

41-48: Add language specifier to fenced code block.

The file structure block lacks a language specifier. While plaintext or text would work, you could also leave it as-is since directory trees don't have a standard syntax highlighter.

📝 Optional fix
-```
+```text
 vr-dive/Demos/Snake3D/
 ├── PLAN.md                  ← 本文件
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vr-dive/Demos/Snake3D/PLAN.md` around lines 41 - 48, The fenced code block in
PLAN.md that shows the directory tree (starting with "vr-dive/Demos/Snake3D/")
needs a language specifier to avoid linter/highlighter warnings; update the
opening triple backticks to include a language such as text or plaintext (e.g.,
change ``` to ```text) so the directory tree block is explicitly marked as plain
text.
vr-dive/Demos/Snake3D/Snake3DGameLogic.swift (1)

38-44: Consider capping the catch-up loop iterations.

If currentTime jumps significantly (e.g., after app backgrounding/resuming), this while loop could execute many iterations in a single frame, causing a lag spike or frame drop.

♻️ Suggested fix with iteration cap
+    var iterations = 0
+    let maxIterations = 10  // Prevent runaway catch-up
     while currentTime - lastMoveTime >= moveInterval {
+      iterations += 1
+      if iterations > maxIterations {
+        lastMoveTime = currentTime  // Skip ahead instead of catching up
+        break
+      }
       lastMoveTime += moveInterval
       step()
       if state.isGameOver {
         break
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vr-dive/Demos/Snake3D/Snake3DGameLogic.swift` around lines 38 - 44, The
catch-up loop using currentTime, lastMoveTime, moveInterval and calling step()
can run unbounded after a large time jump; add a small max iteration cap (e.g.,
maxCatchUpSteps) and track an iterations counter in the loop so it exits after
that many steps, and if the cap is hit advance lastMoveTime to currentTime (or
to lastMoveTime + moveInterval * remainingSteps) to avoid replaying an excessive
backlog; keep checking state.isGameOver inside the loop as before.
vr-dive/Demos/Snake3D/Snake3DShaders.metal (1)

152-152: Hardcoded block size duplicates Swift constant.

blockSize = 0.192 is hardcoded here but also defined as Snake3DState.blockSize in Swift. If the Swift constant changes, this shader value won't automatically update.

Consider passing block size via uniforms or accepting this as a known trade-off for shader simplicity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vr-dive/Demos/Snake3D/Snake3DShaders.metal` at line 152, The shader currently
hardcodes float blockSize = 0.192 in Snake3DShaders.metal which duplicates the
Swift constant Snake3DState.blockSize; instead expose blockSize as a uniform
(e.g., add a float field to the shader uniform/constant buffer used by your
vertex/fragment functions) and update the Swift side where you encode the render
pipeline to set that uniform from Snake3DState.blockSize so the shader uses the
authoritative value; alternatively, if you intentionally accept the duplication,
add a comment in Snake3DShaders.metal referencing Snake3DState.blockSize to mark
it as a deliberate trade-off.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@vr-dive/Demos/Snake3D/Snake3DRenderer.swift`:
- Line 53: The rotationDuration constant is set to 3.0s but should be 0.3s per
PLAN.md; update the private let rotationDuration: Float from 3.0 to 0.3 and
verify any code using rotationDuration (animation/timing logic) expects seconds
(Float) so no unit mismatch; also update any inline comments or docs referencing
the old value to keep them consistent.
- Around line 212-215: The handling of input.buttonTriangle currently only
updates lastOptionsTime and does nothing else (dead code); either remove this
empty block or implement the intended "Options button = reset" behavior by
invoking the same reset logic used for input.buttonCross (the reset routine that
resets game state and timers) and ensuring you respect optionsDelay when
checking lastOptionsTime/ optionsDelay and updating lastOptionsTime; modify the
block with input.buttonTriangle to call the existing reset routine (or
consolidate both button checks to a single reset handler) or delete the block if
Triangle should not trigger reset.

---

Nitpick comments:
In `@vr-dive/Demos/PongWar/PongWarShaders.metal`:
- Line 66: Remove the unused cube-specific computation of nearness in
updateOuterBoundaryVisibility(): stop computing and writing the temporary
`nearness` value into `cubeStates[idx].edgeData.x` for cubes (the CPU-side lines
setting `let nearness: Float = ...` and `cubeStates[idx].edgeData.x =
nearness`); leave `edgeData.x` assignments for spheres intact so the wobble
(sphere) logic still receives its value. Locate the logic inside the
updateOuterBoundaryVisibility() function in PongWarRenderer.swift and delete
only the cube-related nearness calculation and assignment, ensuring no other
uses of `nearness` or `edgeData.x` for cubes remain.

In `@vr-dive/Demos/Snake3D/PLAN.md`:
- Around line 41-48: The fenced code block in PLAN.md that shows the directory
tree (starting with "vr-dive/Demos/Snake3D/") needs a language specifier to
avoid linter/highlighter warnings; update the opening triple backticks to
include a language such as text or plaintext (e.g., change ``` to ```text) so
the directory tree block is explicitly marked as plain text.

In `@vr-dive/Demos/Snake3D/Snake3DGameLogic.swift`:
- Around line 38-44: The catch-up loop using currentTime, lastMoveTime,
moveInterval and calling step() can run unbounded after a large time jump; add a
small max iteration cap (e.g., maxCatchUpSteps) and track an iterations counter
in the loop so it exits after that many steps, and if the cap is hit advance
lastMoveTime to currentTime (or to lastMoveTime + moveInterval * remainingSteps)
to avoid replaying an excessive backlog; keep checking state.isGameOver inside
the loop as before.

In `@vr-dive/Demos/Snake3D/Snake3DRenderer.swift`:
- Around line 82-88: Replace the hardcoded sizes when allocating bodyBuffer and
foodBuffer with the defined constants maxSegments and maxFoods: update the calls
to device.makeBuffer that use MemoryLayout<SnakeSegmentInstance>.stride * 512
and MemoryLayout<FoodInstance>.stride * 8 to instead multiply by maxSegments and
maxFoods respectively so allocations reflect the declared capacities.

In `@vr-dive/Demos/Snake3D/Snake3DShaders.metal`:
- Line 152: The shader currently hardcodes float blockSize = 0.192 in
Snake3DShaders.metal which duplicates the Swift constant Snake3DState.blockSize;
instead expose blockSize as a uniform (e.g., add a float field to the shader
uniform/constant buffer used by your vertex/fragment functions) and update the
Swift side where you encode the render pipeline to set that uniform from
Snake3DState.blockSize so the shader uses the authoritative value;
alternatively, if you intentionally accept the duplication, add a comment in
Snake3DShaders.metal referencing Snake3DState.blockSize to mark it as a
deliberate trade-off.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5263dc3-4e2b-4c50-9000-75e7e68df5dc

📥 Commits

Reviewing files that changed from the base of the PR and between 36d8654 and 3df1c20.

📒 Files selected for processing (10)
  • .gitignore
  • vr-dive/Demos/PongWar/PongWarShaders.metal
  • vr-dive/Demos/Snake3D/PLAN.md
  • vr-dive/Demos/Snake3D/Snake3DGameLogic.swift
  • vr-dive/Demos/Snake3D/Snake3DRenderer.swift
  • vr-dive/Demos/Snake3D/Snake3DShaders.metal
  • vr-dive/Demos/Snake3D/Snake3DTypes.swift
  • vr-dive/Game/GameManager.swift
  • vr-dive/Renderer/PatternSelection.swift
  • vr-dive/Renderer/Renderer.swift

private var rotationStartQuat: simd_quatf = simd_quatf(ix: 0, iy: 0, iz: 0, r: 1)
private var targetWorldQuat: simd_quatf = simd_quatf(ix: 0, iy: 0, iz: 0, r: 1)
private var rotationProgress: Float = 1.0 // 1.0 = done
private let rotationDuration: Float = 3.0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Rotation duration mismatch with documentation.

rotationDuration is set to 3.0 seconds, but PLAN.md specifies 0.3s for rotation animation. A 3-second rotation may feel sluggish for a responsive snake game.

🔧 Suggested fix
-  private let rotationDuration: Float = 3.0
+  private let rotationDuration: Float = 0.3
📝 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
private let rotationDuration: Float = 3.0
private let rotationDuration: Float = 0.3
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vr-dive/Demos/Snake3D/Snake3DRenderer.swift` at line 53, The rotationDuration
constant is set to 3.0s but should be 0.3s per PLAN.md; update the private let
rotationDuration: Float from 3.0 to 0.3 and verify any code using
rotationDuration (animation/timing logic) expects seconds (Float) so no unit
mismatch; also update any inline comments or docs referencing the old value to
keep them consistent.

Comment on lines +212 to +215
// Options button = reset
if input.buttonTriangle && currentTime - lastOptionsTime > optionsDelay {
lastOptionsTime = currentTime
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dead code: buttonTriangle handler has no effect.

This block updates lastOptionsTime but performs no action. The comment says "Options button = reset" but the actual reset logic (lines 250-256) uses buttonCross. This appears to be leftover code or an incomplete implementation.

🔧 Remove dead code or implement intended behavior
-    // Options button = reset
-    if input.buttonTriangle && currentTime - lastOptionsTime > optionsDelay {
-      lastOptionsTime = currentTime
-    }
📝 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
// Options button = reset
if input.buttonTriangle && currentTime - lastOptionsTime > optionsDelay {
lastOptionsTime = currentTime
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@vr-dive/Demos/Snake3D/Snake3DRenderer.swift` around lines 212 - 215, The
handling of input.buttonTriangle currently only updates lastOptionsTime and does
nothing else (dead code); either remove this empty block or implement the
intended "Options button = reset" behavior by invoking the same reset logic used
for input.buttonCross (the reset routine that resets game state and timers) and
ensuring you respect optionsDelay when checking lastOptionsTime/ optionsDelay
and updating lastOptionsTime; modify the block with input.buttonTriangle to call
the existing reset routine (or consolidate both button checks to a single reset
handler) or delete the block if Triangle should not trigger reset.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants