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:**
```r
#' 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:**
```r
#' 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:
```r
# 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:**
```r
#' 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 | 0° |
| 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:**
```r
#' 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:**
```r
#' 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:
```r
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:**
```r
#' 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`
```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`
```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