jigsawR / docs /fusion_implementation_plan.md
pjt222's picture
Upload folder using huggingface_hub
e232e39 verified

Implementation Plan: Fusion for Hexagonal and Concentric Puzzles

Executive Summary

This plan outlines the steps to enable fusion (meta-pieces) for hexagonal and concentric puzzle types, following the pattern established for rectangular puzzles.

Current State:

  • ✅ Rectangular: Full fusion support
  • ❌ Hexagonal: Infrastructure ready, fusion generation not implemented
  • ❌ Concentric: Infrastructure ready, fusion generation not implemented

Recommendation: Implement concentric first (simpler 4-edge topology), then hexagonal (6-edge topology).


Phase 1: Concentric Fusion (Estimated: 3 components)

1.1 Edge Naming Convention

Existing convention (from concentric_geometry.R:169-173):

Edge Index Name Description
1 INNER Connects to inner ring or center
2 RIGHT Next piece in same ring (clockwise)
3 OUTER Connects to outer ring or boundary
4 LEFT Previous piece in same ring

Center piece (hexagon type): 6 edges numbered 1-6, each connecting to ring 1 pieces.

Edge key format: "{piece_id}-{edge_name}" (e.g., "5-INNER", "1-3" for center)

1.2 Adjacency Functions

File: R/adjacency_api.R

Functions to add:

#' Get concentric piece neighbors
#' @param piece_id Piece ID (1-based)
#' @param rings Number of rings in puzzle
#' @param center_shape "hexagon" or "circle"
#' @return Data frame with direction, neighbor_id, is_boundary
get_concentric_neighbors <- function(piece_id, rings, center_shape = "hexagon") {
  # Use existing get_concentric_neighbor() from concentric_geometry.R
  # Returns neighbors for all 4 edges (or 6 for center)
}

#' Get complementary edge for concentric
#' @param direction Edge direction
#' @return Opposite direction
get_concentric_complementary_direction <- function(direction) {
  switch(direction,
    "INNER" = "OUTER",
    "OUTER" = "INNER",
    "LEFT" = "RIGHT",
    "RIGHT" = "LEFT",
    # Center piece edges (1-6) - opposite is (n + 3) %% 6
    as.character((as.integer(direction) + 2) %% 6 + 1)
  )
}

1.3 Fused Edge Computation

File: R/adjacency_api.R

Function to add:

#' Compute fused edges for concentric puzzles
#' @param fusion_groups List of piece ID vectors
#' @param puzzle_result Puzzle result with pieces and parameters
#' @return List with fused_edges, edge_to_group, piece_to_group
compute_concentric_fused_edges <- function(fusion_groups, puzzle_result) {
  # Pattern: Same as compute_fused_edges() but:
  # - Use get_concentric_neighbors() instead of get_piece_neighbors()
  # - Handle center piece 6-edge case
  # - Use concentric complementary directions
}

1.4 Piece Generation Update

File: R/unified_piece_generation.R

Function: generate_concentric_pieces_internal() (lines 351-464)

Changes:

  1. Remove "not yet implemented" comment
  2. Call compute_concentric_fused_edges() if fusion_groups provided
  3. For each piece, set fused_edges metadata:
    # For trapezoid pieces (ring > 0)
    fused_edges <- list(INNER = FALSE, RIGHT = FALSE, OUTER = FALSE, LEFT = FALSE)
    if (!is.null(fused_edge_data)) {
      fused_edges$INNER <- is_edge_fused(piece_id, "INNER", fused_edge_data)
      fused_edges$RIGHT <- is_edge_fused(piece_id, "RIGHT", fused_edge_data)
      fused_edges$OUTER <- is_edge_fused(piece_id, "OUTER", fused_edge_data)
      fused_edges$LEFT <- is_edge_fused(piece_id, "LEFT", fused_edge_data)
    }
    
    # For center piece (ring 0, hexagon type)
    # fused_edges = list(`1` = FALSE, `2` = FALSE, ..., `6` = FALSE)
    

1.5 Path Splitting

File: R/unified_renderer.R

Function to add:

#' Split concentric piece path into individual edge paths
#' @param path SVG path string
#' @param piece Piece object with ring_pos
#' @return List with INNER, RIGHT, OUTER, LEFT edge paths
split_concentric_path_into_edges <- function(path, piece) {
  # Path construction order: M, INNER, RIGHT, OUTER, LEFT, Z
  # Use vertex positions to identify edge boundaries
  # Similar to split_rect_path_into_edges but with different corner detection
}

1.6 Renderer Integration

File: R/unified_renderer.R

Update render_pieces_with_fusion_styled():

  • Detect piece type from piece$type
  • Call appropriate path splitting function:
    • "rectangular"split_rect_path_into_edges()
    • "concentric"split_concentric_path_into_edges()
    • "hexagonal"split_hex_path_into_edges()

Phase 2: Hexagonal Fusion (Estimated: 3 components)

2.1 Edge Naming Convention

Existing convention (from hexagonal_neighbors.R:10-21):

Side Direction Angle
0 Right
1 Upper-right 60°
2 Upper-left 120°
3 Left 180°
4 Lower-left 240°
5 Lower-right 300°

Edge key format: "{piece_id}-{side}" (e.g., "7-3" for piece 7, side 3)

2.2 Adjacency Functions

File: R/adjacency_api.R

Functions to add:

#' Get hexagonal piece neighbors
#' @param piece_id Piece ID (1-based)
#' @param rings Number of rings
#' @return Data frame with side, neighbor_id, is_boundary
get_hex_neighbors_for_fusion <- function(piece_id, rings) {
  # Use existing get_hex_neighbor() from hexagonal_neighbors.R
  # Return neighbors for all 6 sides
}

#' Get complementary side for hexagonal
#' @param side Side number (0-5)
#' @return Opposite side number
get_hex_complementary_side <- function(side) {
  (side + 3) %% 6  # Opposite side is +3 mod 6
}

2.3 Fused Edge Computation

File: R/adjacency_api.R

Function to add:

#' Compute fused edges for hexagonal puzzles
#' @param fusion_groups List of piece ID vectors
#' @param puzzle_result Puzzle result with pieces and parameters
#' @return List with fused_edges, edge_to_group, piece_to_group
compute_hex_fused_edges <- function(fusion_groups, puzzle_result) {
  # Pattern: Same as compute_fused_edges() but:
  # - Use get_hex_neighbors_for_fusion()
  # - Handle 6 sides instead of 4
  # - Use (side + 3) %% 6 for complementary
}

2.4 Piece Generation Update

File: R/unified_piece_generation.R

Function: generate_hex_pieces_internal() (lines 225-348)

Changes:

  1. Remove "not yet implemented" comment
  2. Call compute_hex_fused_edges() if fusion_groups provided
  3. For each piece, set fused_edges metadata:
    fused_edges <- list(`0` = FALSE, `1` = FALSE, `2` = FALSE,
                        `3` = FALSE, `4` = FALSE, `5` = FALSE)
    if (!is.null(fused_edge_data)) {
      for (side in 0:5) {
        fused_edges[[as.character(side)]] <- is_edge_fused(piece_id, side, fused_edge_data)
      }
    }
    

2.5 Path Splitting

File: R/unified_renderer.R

Function to add:

#' Split hexagonal piece path into individual edge paths
#' @param path SVG path string
#' @param piece Piece object
#' @return List with edges 0-5
split_hex_path_into_edges <- function(path, piece) {
  # Path construction: Counter-clockwise, sides 0→5
  # Each side is one segment (L or C commands)
  # Use vertex positions from edge map if available
}

2.6 Renderer Integration

Same as Phase 1.6 - already covered in the type-based dispatch.


Phase 3: Testing

3.1 Unit Tests

File: tests/testthat/test-fusion-concentric.R

test_that("concentric fusion works with offset=0", { ... })
test_that("concentric fusion works with offset>0", { ... })
test_that("concentric fused pieces move together", { ... })
test_that("concentric center piece fusion works", { ... })

File: tests/testthat/test-fusion-hexagonal.R

test_that("hexagonal fusion works with offset=0", { ... })
test_that("hexagonal fusion works with offset>0", { ... })
test_that("hexagonal fused pieces move together", { ... })
test_that("hexagonal center piece fusion works", { ... })

3.2 Integration Tests

  • Test all three puzzle types with same fusion_groups format
  • Verify Shiny app displays fusion controls for all types
  • Visual verification of edge hiding/styling

3.3 Edge Cases

  • Center piece fusion (piece 1 with ring 1 pieces)
  • Multi-ring fusion (pieces spanning rings)
  • Full puzzle fusion (all pieces in one group)
  • Empty fusion groups
  • Invalid piece IDs in fusion groups

Phase 4: Documentation

4.1 Update Function Documentation

  • Add fusion examples to generate_puzzle() for all types
  • Document edge naming conventions

4.2 Update CLAUDE.md

  • Add fusion support status for each puzzle type
  • Document edge naming conventions

Implementation Order

  1. Concentric adjacency functions (1.2) - builds on existing get_concentric_neighbor()
  2. Concentric fused edge computation (1.3) - copy pattern from rectangular
  3. Concentric piece generation update (1.4) - integrate into generation loop
  4. Concentric path splitting (1.5) - simpler than hexagonal
  5. Renderer integration (1.6) - type-based dispatch
  6. Concentric tests (3.1) - verify implementation
  7. Hexagonal adjacency functions (2.2) - builds on existing get_hex_neighbor()
  8. Hexagonal fused edge computation (2.3) - copy pattern from concentric
  9. Hexagonal piece generation update (2.4) - integrate into generation loop
  10. Hexagonal path splitting (2.5) - 6-edge version
  11. Hexagonal tests (3.1) - verify implementation
  12. Documentation updates (4.1, 4.2)

Risks and Mitigations

Risk Mitigation
Path splitting complexity Use existing edge map data instead of parsing paths
Center piece special cases Handle separately with explicit edge counts
Multi-segment edges (concentric OUTER) May need segment grouping logic
Performance with large puzzles Reuse existing neighbor detection infrastructure

Success Criteria

  1. All existing tests continue to pass (901 tests)
  2. New fusion tests pass for concentric and hexagonal
  3. Shiny app displays fusion correctly for all puzzle types
  4. Edge styling (none/dashed/solid) works for all types
  5. Fused pieces move together when separated