Conversation
📝 WalkthroughWalkthroughIntroduces 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
Sequence DiagramsequenceDiagram
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
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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;
There was a problem hiding this comment.
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
snake3Dpattern (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.
| - **网格**:20×20×20,坐标范围 [0, 19]³ | ||
| - **蛇体存储**:`[SIMD3<Int>]`,每次前进 prepend 新头,pop 尾(若未吃食物) | ||
| - **食物**:随机生成,最多 3 个同时存在,不与蛇身重叠 |
There was a problem hiding this comment.
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.
| - **网格**: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` 一致) |
| 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: |
There was a problem hiding this comment.
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.
| guard let manager = gameManager else { return } | ||
| let input = manager.getTetrisInput() | ||
|
|
||
| // Options button = reset |
There was a problem hiding this comment.
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.
| // 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. |
| } | ||
|
|
||
| // Reset | ||
| let optionsPressed = input.buttonCross // □ for now; Options not exposed in TetrisInput |
There was a problem hiding this comment.
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).
| let optionsPressed = input.buttonCross // □ for now; Options not exposed in TetrisInput | |
| let optionsPressed = input.buttonCross // × for now; Options not exposed in TetrisInput |
| length: MemoryLayout<SnakeSegmentInstance>.stride * 512, | ||
| options: .storageModeShared)! | ||
|
|
||
| foodBuffer = device.makeBuffer( | ||
| length: MemoryLayout<FoodInstance>.stride * 8, |
There was a problem hiding this comment.
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.
| 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, |
| frame.startUpdate() | ||
|
|
||
| shouldEndUpdate = true | ||
|
|
||
| // Check state immediately after startUpdate - if transitioning, end gracefully | ||
| guard layerRenderer.state == .running else { |
There was a problem hiding this comment.
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.
| (Float(cell.z) - g * 0.5 + 0.5) * Snake3DState.cellSize | ||
| ) | ||
| ptr[instanceIndex] = SnakeGuideInstance(position: position, scale: scale, color: color) | ||
| instanceIndex += 1 | ||
| } |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| private let rotationDuration: Float = 3.0 | |
| private let rotationDuration: Float = 0.3 |
| // Collision: self | ||
| if state.segments.contains(newHead) { |
There was a problem hiding this comment.
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).
| // 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) { |
| - 蛇在逻辑 3D 网格中记录真实方向,但渲染时蛇头坐标系始终对齐摄像机前方(-Z 轴) | ||
| - 每次转向,维护一个**累积世界旋转四元数** `worldOrientation: simd_quatf` | ||
| - 渲染所有几何体(蛇身、食物、边界框)时,先用 `worldOrientation` 旋转世界坐标,再做 ViewProjection 变换 | ||
| - 转向时,用 `simd_slerp` 做平滑插值,动画时长约 0.3 秒 |
There was a problem hiding this comment.
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.
| - 转向时,用 `simd_slerp` 做平滑插值,动画时长约 0.3 秒 | |
| - 转向时,用 `simd_slerp` 做平滑插值,动画时长约 3.0 秒 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
vr-dive/Demos/PongWar/PongWarShaders.metal (1)
66-66: Remove unused CPU-side computation ofnearnessfor 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 inupdateOuterBoundaryVisibility()(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 = nearnessNote:
edgeData.xremains 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
512and8instead ofmaxSegmentsandmaxFoodsconstants 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
plaintextortextwould 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
currentTimejumps 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.192is hardcoded here but also defined asSnake3DState.blockSizein 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
📒 Files selected for processing (10)
.gitignorevr-dive/Demos/PongWar/PongWarShaders.metalvr-dive/Demos/Snake3D/PLAN.mdvr-dive/Demos/Snake3D/Snake3DGameLogic.swiftvr-dive/Demos/Snake3D/Snake3DRenderer.swiftvr-dive/Demos/Snake3D/Snake3DShaders.metalvr-dive/Demos/Snake3D/Snake3DTypes.swiftvr-dive/Game/GameManager.swiftvr-dive/Renderer/PatternSelection.swiftvr-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 |
There was a problem hiding this comment.
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.
| 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.
| // Options button = reset | ||
| if input.buttonTriangle && currentTime - lastOptionsTime > optionsDelay { | ||
| lastOptionsTime = currentTime | ||
| } |
There was a problem hiding this comment.
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.
| // 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.
Summary by CodeRabbit
New Features
Chores