Skip to content

Set up project config 5696#68

Merged
DarkModder33 merged 6 commits intomainfrom
set-up-project-config-5696
Feb 17, 2026
Merged

Set up project config 5696#68
DarkModder33 merged 6 commits intomainfrom
set-up-project-config-5696

Conversation

@DarkModder33
Copy link
Copy Markdown
Owner

@DarkModder33 DarkModder33 commented Feb 17, 2026

Summary by Sourcery

Add a prototype Hyperborea endless runner mini-game and supporting Zenflow task configuration for the project setup.

New Features:

  • Introduce a Three.js-based HyperboreaGame endless runner React client component with lane switching, jumping, sliding, collectibles, power-ups, and scoring callbacks.

Enhancements:

  • Add Zenflow task plan and report artifacts documenting the project setup workflow and configuration.
  • Define Zenflow project settings including setup, dev, verification scripts, and environment file handling.

Created [`.zenflow/settings.json`](./.zenflow/settings.json) with empty configuration, as the repository contains no project code requiring setup scripts, dev servers, verification scripts, or configuration files.
Configuration complete! Created [`.zenflow/settings.json`](./.zenflow/settings.json) for the Next.js + Solana project:

- **Setup**: `npm install`
- **Dev Server**: `npm run dev` (runs on http://localhost:3000 with Turbopack)
- **Verification**: `npm run lint && npm run type-check` (fast checks only; CI handles full build)
- **Copy Files**: `.env.local` (from template `.env.example`)
Perfect! I'll create a **Nordic/Celtic Hyperborea** themed endless runner with:

🌲 **Yggdrasil (World Tree)** - Running along branches
⚡ **Rune power-ups** - Ancient symbols with digital glow
🌌 **Aurora Borealis** sky effects
❄️ **Ice/frost obstacles** with digital glitch patterns
🔮 **Celtic knots** as collectibles
⚔️ **Norse gods** references (Odin's ravens, Thor's lightning)
🎨 **Cyberpunk-mythology fusion** - neon runes, glowing ice
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Feb 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
main Error Error Feb 17, 2026 3:01am
tradeai Error Error Feb 17, 2026 3:01am

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Feb 17, 2026

Reviewer's Guide

Adds a new Three.js-powered Hyperborea endless runner React client component used for experiments under .zenflow, plus Zenflow task metadata, report, and settings configuration for the Next.js + Solana project.

Sequence diagram for HyperboreaGame main loop and callbacks

sequenceDiagram
  actor Player
  participant BrowserWindow
  participant HyperboreaGame
  participant ThreeScene
  participant GameLoop
  participant ParentComponent

  Player->>BrowserWindow: keyboard or touch input
  BrowserWindow->>HyperboreaGame: keydown/touch handlers
  HyperboreaGame->>HyperboreaGame: update currentLane/jump/slide state

  HyperboreaGame->>ThreeScene: initialize scene/camera/renderer
  HyperboreaGame->>GameLoop: start requestAnimationFrame(animate)

  loop every_frame
    GameLoop->>HyperboreaGame: read state and collections
    HyperboreaGame->>ThreeScene: move track/obstacles/collectibles/powerUps
    HyperboreaGame->>ThreeScene: update player position and animations
    HyperboreaGame->>HyperboreaGame: detect collisions and collections

    alt rune_collected
      HyperboreaGame->>HyperboreaGame: update score/combo/runesCollected
      HyperboreaGame-->>ParentComponent: onCloverCollect(count)
      HyperboreaGame-->>ParentComponent: onScoreChange(score, combo)
    end

    alt power_up_collected
      HyperboreaGame->>HyperboreaGame: push ActivePowerUp and flags
      HyperboreaGame-->>ParentComponent: onPowerUpChange(activePowerUps)
    end

    alt combo_decay
      HyperboreaGame->>HyperboreaGame: decrement comboTimer/combo
      HyperboreaGame-->>ParentComponent: onScoreChange(score, combo)
    end

    alt player_hits_obstacle_without_shield
      HyperboreaGame->>HyperboreaGame: set isDead true
      GameLoop-->>GameLoop: stop updates (early return)
    end

    HyperboreaGame->>ThreeScene: renderer.render(scene, camera)
  end
Loading

Class diagram for HyperboreaGame endless runner entities

classDiagram
class HyperboreaGame {
  +onEnergyChange(energy number)
  +onCloverCollect(count number)
  +onScoreChange(score number, combo number)
  +onPowerUpChange(powerUps ActivePowerUp[])
  +isPaused boolean
}

class TrackSegment {
  +mesh THREE.Mesh
  +zPosition number
  +decorations THREE.Mesh[]
}

class Obstacle {
  +mesh THREE.Mesh
  +lane number
  +zPosition number
  +type ObstacleType
}

class Collectible {
  +mesh THREE.Mesh
  +lane number
  +zPosition number
  +collected boolean
}

class PowerUp {
  +mesh THREE.Mesh
  +lane number
  +zPosition number
  +type PowerUpType
  +collected boolean
}

class ActivePowerUp {
  +type PowerUpType
  +timeLeft number
}

class ObstacleType {
  <<enumeration>>
  icespike
  frostwall
  lowbarrier
}

class PowerUpType {
  <<enumeration>>
  odins_shield
  thors_magnet
  freyas_double
}

class PlayerState {
  +currentLane number
  +isJumping boolean
  +jumpVelocity number
  +isSliding boolean
  +slideTimer number
  +isDead boolean
}

class GameProgress {
  +gameSpeed number
  +distance number
  +score number
  +runesCollected number
  +combo number
  +comboTimer number
}

class Controls {
  +keys map<string, boolean>
}

HyperboreaGame o-- TrackSegment : manages
HyperboreaGame o-- Obstacle : spawns
HyperboreaGame o-- Collectible : spawns
HyperboreaGame o-- PowerUp : spawns
HyperboreaGame o-- ActivePowerUp : tracks
HyperboreaGame o-- PlayerState : maintains
HyperboreaGame o-- GameProgress : maintains
HyperboreaGame o-- Controls : handles
Obstacle --> ObstacleType
PowerUp --> PowerUpType
ActivePowerUp --> PowerUpType
Loading

File-Level Changes

Change Details Files
Introduce a full Three.js-based Hyperborea endless runner React component with keyboard/touch controls, obstacles, collectibles, and power-up game logic, exposed via callback props and pause support.
  • Initialize Three.js scene, camera, renderer, lighting, fog, and window-size-based canvas mounting using a React useEffect tied to the DOM ref.
  • Create player mesh with aura, Yggdrasil-style track segments with decorations, and maintain arrays for track, rune trail particles, obstacles, collectibles, and power-ups.
  • Implement spawning logic for obstacles, collectibles, and power-ups with randomized patterns and lane selection driven by a forward-moving z-axis track.
  • Handle keyboard and touch input for lane switching, jumping, and sliding, updating lane state and player movement accordingly.
  • Implement main animation loop to update physics, move world objects, perform collision detection and magnet/double-score/shield power-up behavior, update score/combo, and follow the player with the camera.
  • Expose game state outward via optional callbacks for clover (rune) collection, score changes, and active power-up changes, and respect an isPaused prop to stop updates.
  • Ensure proper cleanup of event listeners and renderer resources in the effect cleanup function.
.zenflow/tasks/set-up-project-config-5696/HyperboreaGame-new.tsx
Add Zenflow quick-change task metadata and execution report for setting up project configuration.
  • Document a quick-change workflow plan describing implementation-focused steps and expectations for the agent.
  • Record a report summarizing how the Next.js + Solana project configuration was derived, including setup, dev, verification scripts, and copied env files.
.zenflow/tasks/set-up-project-config-5696/plan.md
.zenflow/tasks/set-up-project-config-5696/report.md
Introduce Zenflow settings for project scripts and environment handling (content implied by accompanying report).
  • Create .zenflow/settings.json to define setup, dev, verification commands, and file copy rules in line with the described configuration.
.zenflow/settings.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 6 issues, and left some high level feedback:

  • The main useEffect depends on props like isPaused and callback references, which will tear down and recreate the entire THREE scene (and re-register event listeners) on any parent re-render; consider moving game initialization into a one-time effect (empty deps) and using refs to track mutable state like pause status and callbacks instead.
  • Arrays such as trailParticles, obstacles, collectibles, and powerUps are mutated with splice() inside forEach loops, which can cause elements to be skipped; iterate these arrays backwards or rebuild them via filter to avoid index shifting issues during removal.
  • Several THREE objects (geometries, materials, meshes, lights, and the scene itself) are not explicitly disposed on cleanup, which can lead to GPU memory leaks during navigation or hot reloads; consider traversing the scene graph on unmount and calling dispose() on geometries/materials and canceling the animation frame loop.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The main useEffect depends on props like isPaused and callback references, which will tear down and recreate the entire THREE scene (and re-register event listeners) on any parent re-render; consider moving game initialization into a one-time effect (empty deps) and using refs to track mutable state like pause status and callbacks instead.
- Arrays such as trailParticles, obstacles, collectibles, and powerUps are mutated with splice() inside forEach loops, which can cause elements to be skipped; iterate these arrays backwards or rebuild them via filter to avoid index shifting issues during removal.
- Several THREE objects (geometries, materials, meshes, lights, and the scene itself) are not explicitly disposed on cleanup, which can lead to GPU memory leaks during navigation or hot reloads; consider traversing the scene graph on unmount and calling dispose() on geometries/materials and canceling the animation frame loop.

## Individual Comments

### Comment 1
<location> `.zenflow/tasks/set-up-project-config-5696/HyperboreaGame-new.tsx:428-429` </location>
<code_context>
+    currentMount.addEventListener("touchend", handleTouchEnd, { passive: true });
+
+    // ANIMATION LOOP
+    let frameCount = 0;
+    const animate = () => {
+      requestAnimationFrame(animate);
+
</code_context>

<issue_to_address>
**issue (bug_risk):** requestAnimationFrame loop is never cancelled, which can continue running after unmount and after renderer disposal

The `animate` function keeps rescheduling itself via `requestAnimationFrame(animate)`, but nothing ever cancels it. When the component unmounts or the effect re-runs (e.g. on `isPaused` changes), the old loop continues, possibly targeting a disposed renderer or removed canvas. Store the frame id from `requestAnimationFrame` (e.g. in a ref) and call `cancelAnimationFrame` in the effect cleanup to stop the loop.
</issue_to_address>

### Comment 2
<location> `.zenflow/tasks/set-up-project-config-5696/HyperboreaGame-new.tsx:482-489` </location>
<code_context>
+      }
+
+      // Update trail particles
+      trailParticles.forEach((particle, index) => {
+        particle.mesh.position.add(particle.velocity);
+        particle.life -= 0.02;
+        (particle.mesh.material as THREE.MeshBasicMaterial).opacity = particle.life;
+        
+        if (particle.life <= 0) {
+          scene.remove(particle.mesh);
+          trailParticles.splice(index, 1);
+        }
+      });
</code_context>

<issue_to_address>
**issue (bug_risk):** Mutating arrays with splice inside forEach risks skipping elements and subtle bugs

This pattern appears in several update loops (`trailParticles`, `obstacles`, `collectibles`, `powerUps`, `activePowerUps`). Since `splice` shifts elements, removing by index while using `forEach` can skip the next element and cause missed updates or leaks. Prefer iterating backwards with a classic `for` loop and splicing by `i`, or rebuilding the array with `filter` after the loop.
</issue_to_address>

### Comment 3
<location> `.zenflow/tasks/set-up-project-config-5696/HyperboreaGame-new.tsx:615-621` </location>
<code_context>
+      });
+
+      // Update power-ups
+      activePowerUps.forEach((powerUp, index) => {
+        powerUp.timeLeft--;
+        if (powerUp.timeLeft <= 0) {
+          if (powerUp.type === "odins_shield") hasShield = false;
+          if (powerUp.type === "thors_magnet") hasMagnet = false;
+          if (powerUp.type === "freyas_double") hasDouble = false;
+          activePowerUps.splice(index, 1);
+          if (onPowerUpChange) onPowerUpChange(activePowerUps);
+        }
</code_context>

<issue_to_address>
**issue (bug_risk):** Mutating activePowerUps while iterating can break power-up expiration logic

`activePowerUps` is modified with `splice` inside a `forEach`, which can cause items to be skipped as indices shift, leading to some power-ups not decrementing `timeLeft` or never expiring. Consider iterating backwards or first collecting expired power-ups and removing them after the loop to keep shield/magnet/double state consistent.
</issue_to_address>

### Comment 4
<location> `.zenflow/tasks/set-up-project-config-5696/HyperboreaGame-new.tsx:26-35` </location>
<code_context>
+  useEffect(() => {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Including isPaused and callback props in the effect dependencies recreates the entire scene frequently and can interact badly with the animation loop

Because the effect depends on `isPaused` and the callbacks, any change to those props tears down and rebuilds the entire THREE scene and game state. With the uncancelled animation loop, this can leave old loops running against disposed resources, and even once fixed, recreating the scene on every pause/parent re-render is costly. Consider running this effect only once on mount (empty dependency array) and using refs for `isPaused`/callbacks, or adding a cheaper pause mechanism that doesn’t rebuild the scene.

Suggested implementation:

```typescript
  const mountRef = useRef<HTMLDivElement>(null);
  const [isLoaded, setIsLoaded] = useState(false);

  // Keep latest control props in refs so the main scene effect can be stable
  const isPausedRef = useRef(isPaused);
  const onEnergyChangeRef = useRef(onEnergyChange);
  const onCloverCollectRef = useRef(onCloverCollect);
  const onScoreChangeRef = useRef(onScoreChange);
  const onPowerUpChangeRef = useRef(onPowerUpChange);

  // Update refs whenever props change; cheaper than rebuilding the scene
  useEffect(() => {
    isPausedRef.current = isPaused;
  }, [isPaused]);

  useEffect(() => {
    onEnergyChangeRef.current = onEnergyChange;
  }, [onEnergyChange]);

  useEffect(() => {
    onCloverCollectRef.current = onCloverCollect;
  }, [onCloverCollect]);

  useEffect(() => {
    onScoreChangeRef.current = onScoreChange;
  }, [onScoreChange]);

  useEffect(() => {
    onPowerUpChangeRef.current = onPowerUpChange;
  }, [onPowerUpChange]);

  useEffect(() => {

```

1. In the long-lived THREE setup effect (the `useEffect` that creates the scene and animation loop), change the dependency array to `[]` so the scene is created only once on mount: `}, []);`.
2. Inside that effect (especially in the animation loop and any event handlers), replace direct uses of `isPaused`, `onEnergyChange`, `onCloverCollect`, `onScoreChange`, and `onPowerUpChange` with their corresponding refs (`isPausedRef.current`, etc.).
3. Ensure the animation loop respects `isPausedRef.current` without tearing down/recreating the scene, e.g., by early-returning logic or skipping updates while paused instead of reinitializing THREE objects.
</issue_to_address>

### Comment 5
<location> `.zenflow/tasks/set-up-project-config-5696/HyperboreaGame-new.tsx:133-142` </location>
<code_context>
+    const trackSegments: TrackSegment[] = [];
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Track recycling logic may assume trackSegments is ordered, but the array order is never updated when segments are reused

`spawnObjects` uses `trackSegments[trackSegments.length - 1].zPosition` as `nearestTrack`, but when segments are recycled (`segment.zPosition = lastZ - 12`), the array order is never updated. Eventually the segment with the largest `zPosition` may no longer be last, so `nearestTrack` becomes incorrect and spawn spacing is distorted. Consider either keeping `trackSegments` sorted by `zPosition` when recycling, or deriving `nearestTrack` via a `Math.max` over all segments instead of relying on the last element.

Suggested implementation:

```typescript
    const trackSegments: TrackSegment[] = [];

    // Utility to get the furthest (max z) track segment, robust to recycling and reordering
    const getNearestTrackZ = (): number => {
      if (trackSegments.length === 0) {
        // Fallback when no segments exist yet; adjust base value if needed
        return 0;
      }

      return trackSegments.reduce(
        (maxZ, segment) => (segment.zPosition > maxZ ? segment.zPosition : maxZ),
        -Infinity
      );
    };

```

```typescript
    const spawnObjects = () => {
      // ...
      const nearestTrack = getNearestTrackZ();
      // ...
    };

```

1. Ensure the `spawnObjects` function signature and body match the `SEARCH` block; if the existing code differs (for example, different variable names or additional logic around `nearestTrack`), adjust the `SEARCH` and `REPLACE` snippets accordingly.
2. If `0` is not the correct baseline when no track segments exist (e.g., it should be the player or camera z position), update the fallback in `getNearestTrackZ` to use the appropriate initial z value (such as `player.position.z` or a predefined starting z constant).
3. If `spawnObjects` is defined in a different scope than shown or there are multiple `spawnObjects` functions, apply the `nearestTrack` change in each relevant location.
</issue_to_address>

### Comment 6
<location> `.zenflow/tasks/set-up-project-config-5696/HyperboreaGame-new.tsx:358-388` </location>
<code_context>
+    };
+
+    // CONTROLS
+    const keys: { [key: string]: boolean } = {};
+
+    const handleKeyDown = (e: KeyboardEvent) => {
+      if (isDead) return;
+      keys[e.key.toLowerCase()] = true;
+
+      // Lane switching
+      if ((e.key === "ArrowLeft" || e.key === "a") && currentLane > 0) {
+        currentLane--;
+      }
+      if ((e.key === "ArrowRight" || e.key === "d") && currentLane < 2) {
+        currentLane++;
+      }
+
+      // Jump
+      if ((e.key === "ArrowUp" || e.key === "w" || e.key === " ") && !isJumping && !isSliding) {
+        isJumping = true;
+        jumpVelocity = 0.3;
+      }
+
+      // Slide
+      if ((e.key === "ArrowDown" || e.key === "s") && !isJumping && !isSliding) {
+        isSliding = true;
+        slideTimer = 25;
+      }
+    };
+
+    const handleKeyUp = (e: KeyboardEvent) => {
+      keys[e.key.toLowerCase()] = false;
+    };
+
</code_context>

<issue_to_address>
**suggestion:** The keys map is written to but never read, adding unnecessary state and complexity

`keys` is updated in both handlers but never read in this module. If you don’t actually need continuous key state (e.g., for held keys), remove it to simplify the code. If you do plan to use it, either integrate it into the movement logic now or leave a clear TODO so it doesn’t look like dead/debug code.

```suggestion
    // CONTROLS

    const handleKeyDown = (e: KeyboardEvent) => {
      if (isDead) return;

      // Lane switching
      if ((e.key === "ArrowLeft" || e.key === "a") && currentLane > 0) {
        currentLane--;
      }
      if ((e.key === "ArrowRight" || e.key === "d") && currentLane < 2) {
        currentLane++;
      }

      // Jump
      if (
        (e.key === "ArrowUp" || e.key === "w" || e.key === " ") &&
        !isJumping &&
        !isSliding
      ) {
        isJumping = true;
        jumpVelocity = 0.3;
      }

      // Slide
      if ((e.key === "ArrowDown" || e.key === "s") && !isJumping && !isSliding) {
        isSliding = true;
        slideTimer = 25;
      }
    };

    const handleKeyUp = (_e: KeyboardEvent) => {
      // No-op for now; kept for potential future use and to match event listener wiring
    };
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
@DarkModder33 DarkModder33 merged commit 1fda4c3 into main Feb 17, 2026
10 of 19 checks passed
@DarkModder33 DarkModder33 deleted the set-up-project-config-5696 branch February 17, 2026 03:08
@DarkModder33 DarkModder33 restored the set-up-project-config-5696 branch February 17, 2026 03:47
@DarkModder33 DarkModder33 deleted the set-up-project-config-5696 branch February 17, 2026 04:20
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.

1 participant