Skip to content

Add crafting recipes and pickup drops#14

Merged
MichaelFisher1997 merged 3 commits into
masterfrom
opencode/sunny-panda
Jun 18, 2026
Merged

Add crafting recipes and pickup drops#14
MichaelFisher1997 merged 3 commits into
masterfrom
opencode/sunny-panda

Conversation

@MichaelFisher1997

Copy link
Copy Markdown
Contributor

Summary

  • add pure crafting recipe registry and matcher for 2x2/3x3 grids
  • wire interactive inventory crafting UI with live output and persisted crafting grid
  • add starter recipes, planks/sticks, crafting table block, and recipe tests
  • isolate creative/survival inventories and improve mouse input dedupe/hold handling
  • spawn survival block drops as floating pickups instead of direct inventory insertion

Closes #8

Verification

  • bunx tsc --noEmit
  • bun run test
  • bun run sync:game (website)

@railway-app

railway-app Bot commented Jun 18, 2026

Copy link
Copy Markdown

🚅 Deployed to the voxl-pr-14 environment in voxl

Service Status Web Updated (UTC)
website ✅ Success (View Logs) Web Jun 18, 2026 at 3:00 am
game ✅ Success (View Logs) Web Jun 18, 2026 at 3:00 am

@railway-app railway-app Bot temporarily deployed to voxl / voxl-pr-14 June 18, 2026 02:29 Destroyed
@github-actions

Copy link
Copy Markdown

📋 Summary

  • Linked issue: Crafting system (2x2 + 3x3 grid, recipe matching) #8 (crafting recipes and pickup drops).
  • The PR implements a pure recipe registry/matcher with unit tests, wires a 2x2 inventory crafting UI, adds a crafting table block, and converts survival block drops into floating pickups. It also isolates creative/survival inventories and improves pointer-event reliability. The implementation aligns with the issue scope.

This PR adds substantial, well-factored survival-crafting functionality. The pure Recipes.ts matcher is testable, the new recipe tests pass, and both the game and website build cleanly. However, a mode-switching path can overwrite saved survival progress, which needs fixing before merge.

📌 Review Metadata

🔴 Critical Issues (Must Fix - Blocks Merge)

[CRITICAL] src/game/Game.ts:528-542 — Switching from creative back to survival overwrites saved survival progress
Confidence: High
Description: setMode("survival") always calls seedInventoryForMode("survival") and this.stats.reset(), which replaces the player's saved backpack and vitals with the starter kit. The saveState() call that follows then persists that starter kit, destroying the survival progress that was preserved while the player was in creative. This directly contradicts the intent stated in saveState() ("Preserve the last survival backpack so returning to survival still restores progress").
Impact: Data loss of player inventory and vitals when returning to survival from creative.
Suggested Fix: When switching to survival, load the saved progress if it exists; only seed the starter kit for a fresh survival start:

private setMode(mode: GameMode): void {
  const prev = this.settings.mode;
  this.applySettings({ mode });
  if (prev !== mode) {
    if (mode === "creative") {
      this.seedInventoryForMode("creative");
      this.stats.reset();
    } else {
      const save = loadSave(this.settings.seed);
      if (save) {
        this.inventory.clear();
        this.inventory.load(save.inventory);
        this.inventory.loadCrafting(save.crafting);
        this.stats.load(save.stats);
      } else {
        this.seedInventoryForMode("survival");
        this.stats.reset();
      }
    }
    this.invUI.refresh();
    this.refreshHud();
  }
  this.hud.showToast(mode === "creative" ? "Creative mode" : "Survival mode");
  this.saveState();
}

⚠️ High Priority Issues (Should Fix)

None identified.

💡 Medium Priority Issues (Nice to Fix)

[MEDIUM] src/game/Game.ts:841-882 — Grounded drops never fall again if the block beneath them is removed
Confidence: Medium
Description: Once a drop sets grounded = true, it only hovers at baseY. If the player mines the supporting block, the drop floats in mid-air indefinitely instead of falling.
Impact: Minor gameplay/visual inconsistency; drops can be stranded.
Suggested Fix: Remember the supporting block coordinates when grounding, and in each updateDrops tick re-verify that the block is still solid; if not, set grounded = false and let gravity resume.

ℹ️ Low Priority Suggestions (Optional)

[LOW] src/ui/InventoryUI.ts:327-333renderCraft() iterates a sparse array by index
Confidence: Low
Description: for (let i = 0; i < this.craftEls.length; i++) walks undefined slots for the hidden 3x3 cells. Using for (const node of this.craftEls) if (node) is clearer and avoids relying on the array's maximum set index.

[LOW] src/game/Inventory.ts:126-128isEmpty() ignores the crafting grid
Confidence: Low
Description: The method only checks slots. This is currently harmless, but if future code uses it to decide whether a player has any items, it will miss items stranded in the crafting grid.

📊 SOLID Principles Score

Principle Score Notes
Single Responsibility 8 Recipes.ts is a pure matcher; Game remains the orchestrator but grows slightly.
Open/Closed 7 Recipes are append-only, but mode-switch logic is hardcoded rather than extensible.
Liskov Substitution 10 No inheritance hierarchy to evaluate.
Interface Segregation 8 Small, focused interfaces (Recipe, MatchResult, DroppedItem).
Dependency Inversion 7 match() is dependency-free; Game still depends on concrete systems.
Average 8.0

🎯 Final Assessment

Overall Confidence Score: 70%

How to interpret: 61-80%: Minor concerns, mostly ready with some fixes.

Confidence Breakdown:

  • Code Quality: 80% (clean, typed, follows existing patterns)
  • Completeness: 85% (recipes, UI, drops, tests, builds all present)
  • Risk Level: 55% (the creative→survival data-loss bug is a real regression)
  • Test Coverage: 75% (recipe matcher has tests; mode switching and drops have no integration tests)

Merge Readiness:

  • All critical issues resolved
  • SOLID average score >= 6.0
  • Overall confidence >= 60% (blocked until critical fix)
  • No security concerns
  • Tests present and passing (if applicable)

Verdict:

MERGE WITH FIXES — address the creative→survival progress-loss bug; remaining issues are minor.

New%20session%20-%202026-06-18T02%3A29%3A10.363Z
opencode session  |  github run

@railway-app railway-app Bot temporarily deployed to voxl / voxl-pr-14 June 18, 2026 02:39 Destroyed
@github-actions

Copy link
Copy Markdown

📋 Summary

  • Linked issue: Crafting system (2x2 + 3x3 grid, recipe matching) #8 — the PR implements crafting recipes, an interactive crafting UI, and survival block drops as requested.
  • The PR adds a pure Recipes.ts matcher with unit tests, wires a 2×2 inventory crafting grid, introduces a crafting table block, and converts survival block drops into floating pickups. It also isolates creative/survival inventories and improves mouse/pointer input reliability.

📌 Review Metadata

The implementation is well-factored and the recipe matcher is cleanly testable. Both the game and website build cleanly, and all tests pass. Two previously reported issues have been fixed, but a few new interaction bugs remain.

🔴 Critical Issues (Must Fix - Blocks Merge)

✅ All previously reported critical issues have been resolved.

No new critical issues identified.

⚠️ High Priority Issues (Should Fix)

[HIGH] src/game/Game.ts:896-903 — "Inventory full" toast spams every frame while standing near a drop that cannot be picked up
Confidence: High
Description: When the player's inventory is full, updateDrops() shows this.hud.showToast("Inventory full") on every tick the drop remains within pickup radius. showToast() resets its timeout each call, so the toast flickers continuously and never fades.
Impact: Severe UI noise; makes the game annoying/unplayable whenever a drop is near a full inventory.
Suggested Fix: Only show the toast when the drop actually fails to add any items this frame, and throttle it:

const leftover = this.inventory.add(d.id, d.count);
if (leftover <= 0) {
  d.mesh.dispose();
  this.drops.splice(i, 1);
  this.refreshHud();
} else if (leftover < d.count) {
  d.count = leftover;
  this.refreshHud();
} else if (!this.recentDropFullToast()) {
  this.hud.showToast("Inventory full");
}

[HIGH] src/game/Game.ts:531-558 — Creative held/crafting items leak into survival on mode switch
Confidence: High
Description: setMode("survival") loads the saved survival backpack but leaves the cursor-held stack and any items in the crafting grid untouched. When the inventory is closed, InventoryUI.close() returns those items to the current (now survival) inventory, persisting creative-palette items in survival.
Impact: Breaks the creative/survival inventory isolation the PR explicitly aims to enforce.
Suggested Fix: Return the held stack and craft grid to the old mode's inventory (or discard them) before loading the new mode's save. The simplest fix is to close the inventory silently first:

private setMode(mode: GameMode): void {
  const prev = this.settings.mode;
  if (prev !== mode) {
    this.closeInventorySilent(); // returns held/craft to prev inventory
    this.applySettings({ mode });
    if (mode === "creative") {
      this.seedInventoryForMode("creative");
      this.stats.reset();
    } else {
      const save = loadSave(this.settings.seed);
      if (save) {
        this.inventory.clear();
        this.inventory.load(save.inventory);
        this.inventory.loadCrafting(save.crafting);
        this.stats.load(save.stats);
      } else {
        this.seedInventoryForMode("survival");
        this.stats.reset();
      }
    }
    this.invUI.refresh();
    this.refreshHud();
  }
  this.hud.showToast(mode === "creative" ? "Creative mode" : "Survival mode");
  this.saveState();
}

💡 Medium Priority Issues (Nice to Fix)

[MEDIUM] src/game/Game.ts:896-903 — Partial drop pickups do not refresh the HUD
Confidence: High
Description: If a drop is partially picked up (e.g., only 32 of 64 items fit), d.count is updated to the leftover but this.refreshHud() is only called when the drop is fully consumed.
Impact: The hotbar/item count does not update until the next periodic/throttled refresh, making pickups feel unresponsive.
Suggested Fix: Call this.refreshHud() whenever leftover < d.count, and only show the "Inventory full" toast when leftover === d.count.

[MEDIUM] src/ui/InventoryUI.ts:427-433 — Held cursor items are silently lost if the backpack is full on close
Confidence: High
Description: close() calls this.inventory.add(this.held.id, this.held.count) and then unconditionally sets this.held = null. If add() returns a leftover (backpack full), those items are destroyed.
Impact: Item loss when closing the inventory with a full backpack and a held stack.
Suggested Fix: Preserve the leftover, mirroring the craft-grid handling added in this PR:

if (this.held) {
  const leftover = this.inventory.add(this.held.id, this.held.count);
  this.held = leftover > 0 ? { id: this.held.id, count: leftover } : null;
  if (leftover > 0) this.onRefresh?.();
}

ℹ️ Low Priority Suggestions (Optional)

None identified.

📊 SOLID Principles Score

Principle Score Notes
Single Responsibility 8 Recipes.ts is a pure matcher; Game remains the orchestrator but grows slightly.
Open/Closed 7 Recipes are append-only, but mode-switch logic is hardcoded rather than extensible.
Liskov Substitution 10 No inheritance hierarchy to evaluate.
Interface Segregation 8 Small, focused interfaces (Recipe, MatchResult, DroppedItem).
Dependency Inversion 7 match() is dependency-free; Game still depends on concrete systems.
Average 8.0

🎯 Final Assessment

Overall Confidence Score: 75%

How to interpret: 61-80%: Minor concerns, mostly ready with some fixes.

Confidence Breakdown:

  • Code Quality: 85% (clean, typed, follows existing patterns)
  • Completeness: 90% (recipes, UI, drops, tests, builds all present)
  • Risk Level: 60% (two real interaction bugs: toast spam and creative→survival leak)
  • Test Coverage: 80% (recipe matcher has tests; UI/drop edge cases lack integration tests)

Merge Readiness:

  • All critical issues resolved
  • SOLID average score >= 6.0
  • Overall confidence >= 60% (blocked until high-priority fixes land)
  • No security concerns
  • Tests present and passing (if applicable)

Verdict:

MERGE WITH FIXES — address the drop-pickup toast spam/HUD refresh and the creative→survival held-item leak before merging.

New%20session%20-%202026-06-18T02%3A40%3A11.099Z
opencode session  |  github run

@MichaelFisher1997

Copy link
Copy Markdown
Contributor Author

Addressed review feedback in 183a997:

  • Fixed survival mode switch to restore saved survival backpack/crafting grid/stats when available instead of overwriting progress with the starter kit.
  • Added support-block tracking for drops so grounded drops resume falling if the block beneath them is removed.
  • Cleaned up craft-slot rendering iteration and made Inventory.isEmpty include the crafting grid.

Verification:

  • bunx tsc --noEmit
  • bun run test
  • bun run sync:game

@railway-app railway-app Bot temporarily deployed to voxl / voxl-pr-14 June 18, 2026 02:59 Destroyed
@MichaelFisher1997

Copy link
Copy Markdown
Contributor Author

Addressed latest review feedback in c7b2efa:

  • Throttled full-inventory drop pickup toasts so they do not spam every frame.
  • Refresh HUD on partial drop pickup and keep the leftover drop count accurate.
  • Prevent creative held/crafting items from leaking into survival by resolving/clearing cursor state before mode inventory swap.
  • Preserve held cursor leftovers on normal inventory close when the backpack is full.

Verification:

  • bunx tsc --noEmit
  • bun run test
  • bun run sync:game

@github-actions

Copy link
Copy Markdown

📋 Summary

  • Linked issue: Crafting system (2x2 + 3x3 grid, recipe matching) #8 — the PR implements crafting recipes, an interactive crafting UI, and survival block drops as requested.
  • The PR adds a pure Recipes.ts matcher with unit tests, wires a 2×2 inventory crafting grid, introduces a crafting table block, and converts survival block drops into floating pickups. It also isolates creative/survival inventories and improves pointer-event reliability.
  • All verification commands pass: bun run typecheck, bun run test, and bun run build / website bun run build.

📌 Review Metadata

The implementation is well-factored, builds cleanly, and the recipe matcher is cleanly testable. All previously reported issues from prior automated reviews have been addressed.

🔴 Critical Issues (Must Fix - Blocks Merge)

✅ All previously reported critical issues have been resolved. No new critical issues identified.

⚠️ High Priority Issues (Should Fix)

✅ All previously reported high-priority issues have been resolved. No new high-priority issues identified.

💡 Medium Priority Issues (Nice to Fix)

[FIXED] Previous issues: grounded drops now resume falling when their support block is removed; partial drop pickups refresh the HUD; held cursor leftovers are preserved on inventory close.

[MEDIUM] src/game/Game.ts:545-559,1074-1080 — Switching to creative overwrites saved survival vitals
Confidence: Medium
Description: setMode("creative") calls seedInventoryForMode("creative"), which invokes this.stats.reset(). The final saveState() then persists those reset vitals into the per-seed save. When the player later returns to survival, loadSave() restores the full-health stats from the creative save, overwriting the survival health/hunger progress that existed before the creative visit.
Impact: Survival vitals can be silently refilled by briefly switching to creative, undermining survival progression and contradicting the PR's inventory-isolation goal.
Suggested Fix: When saving in creative mode, preserve the previous survival stats instead of overwriting them with the reset creative stats:

// Creative: never persist inventory/craft-grid changes.
// Preserve the last survival backpack AND vitals.
const prev = loadSave(this.settings.seed);
writeSave(this.settings.seed, {
  inventory: prev?.inventory ?? [],
  crafting: prev?.crafting ?? [],
  stats: prev?.stats ?? this.stats.serialize(),
  mode: this.settings.mode,
});

ℹ️ Low Priority Suggestions (Optional)

[FIXED] Previous issues: renderCraft() no longer iterates a sparse array by index; Inventory.isEmpty() now includes the crafting grid.

[LOW] src/game/Game.ts:858-864 — Drops never merge, so mass mining creates many individual meshes
Confidence: Low
Description: spawnDrop() always creates a new Mesh even when an identical drop is already nearby. Breaking many blocks in quick succession produces one mesh per drop, which can become a rendering/perf concern before pickups occur.
Impact: Minor; only noticeable during large-scale mining without pickup.
Suggested Fix: When spawning a drop, search nearby drops for one with the same id and a count under maxStack; if found, increment its count and dispose the new mesh instead of adding a second entity.

📊 SOLID Principles Score

Principle Score Notes
Single Responsibility 8 Recipes.ts is a pure matcher; Inventory now manages slots + crafting grid; Game remains the orchestrator.
Open/Closed 7 Recipes are append-only, but mode-switch logic is hardcoded rather than extensible.
Liskov Substitution 10 No inheritance hierarchy to evaluate.
Interface Segregation 8 Small, focused interfaces (Recipe, MatchResult, DroppedItem).
Dependency Inversion 7 match() is dependency-free; Game still depends on concrete systems.
Average 8.0

🎯 Final Assessment

Overall Confidence Score: 85%

How to interpret: 81-100%: High confidence, ready to merge or with trivial fixes.

Confidence Breakdown:

  • Code Quality: 85% (clean, typed, follows existing patterns)
  • Completeness: 90% (recipes, UI, drops, tests, builds all present)
  • Risk Level: 75% (creative→survival vital reset is a real exploit; drops could merge for perf)
  • Test Coverage: 80% (recipe matcher has tests; mode/vitals edge cases lack integration tests)

Merge Readiness:

  • All critical issues resolved
  • SOLID average score >= 6.0
  • Overall confidence >= 60%
  • No security concerns
  • Tests present and passing (if applicable)

Verdict:

MERGE WITH FIXES — address the creative→survival vital-reset exploit; the drops-merging suggestion is optional.

New%20session%20-%202026-06-18T02%3A59%3A55.762Z
opencode session  |  github run

@MichaelFisher1997 MichaelFisher1997 merged commit 8ac2317 into master Jun 18, 2026
3 checks passed
@MichaelFisher1997 MichaelFisher1997 deleted the opencode/sunny-panda branch June 18, 2026 03:48
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.

Crafting system (2x2 + 3x3 grid, recipe matching)

1 participant