issues/11-002b-implement-similarity-filtered-choice-selection.md

Issue 11-002b: Implement Similarity-Filtered Choice Selection

Current Behavior

Issue 11-002a produces 768 dimension-extreme candidates per poem. These candidates are each "opposite" along one embedding dimension, but may vary widely in overall similarity to the source poem.

Intended Behavior

From the 768 dimension-extreme candidates, select the 3-6 that are most similar overall to the source poem. This creates "variations on a theme" - poems that are almost the same, except for ONE thing.

Algorithm

function select_maze_exits(poem_id, dimension_extremes, embeddings, num_exits)
    num_exits = num_exits or 6

    -- Get the 768 dimension-extreme candidates
    local candidates = dimension_extremes[poem_id]
    local source_embedding = embeddings[poem_id]

    -- Compute full cosine similarity for each candidate
    local scored = {}
    local seen = {}  -- Deduplicate (same poem may be extreme for multiple dims)

    for dim, candidate_id in ipairs(candidates) do
        if not seen[candidate_id] then
            seen[candidate_id] = true
            local candidate_embedding = embeddings[candidate_id]
            local similarity = cosine_similarity(source_embedding, candidate_embedding)
            table.insert(scored, {id = candidate_id, similarity = similarity, dim = dim})
        end
    end

    -- Sort by similarity (descending) and take top N
    table.sort(scored, function(a, b) return a.similarity > b.similarity end)

    local exits = {}
    for i = 1, math.min(num_exits, #scored) do
        table.insert(exits, scored[i].id)
    end

    return exits
end

Deduplication Consideration

Multiple dimensions may point to the same extreme poem. For example, if poem Q is extreme for both dimension 47 and dimension 203, it should only appear once in the candidate pool.

Expected unique candidates: ~400-600 (some dimensions will share extremes)

Output Format

{
    "metadata": {
        "algorithm": "dimension-extreme-similarity-filtered",
        "dimensions": 768,
        "exits_per_poem": 6,
        "generated_at": "2025-12-25 12:00:00"
    },
    "exits": {
        "1": [423, 1847, 3291, 892, 5521, 2103],
        "2": [1502, 847, 4421, 3892, 721, 6103],
        ...
    }
}

This is the final cache used by the HTML generator.

Suggested Implementation Steps

Step 1: Load Dimension Extremes

  • [ ] Load output from 11-002a
  • [ ] Load embeddings for similarity computation

Step 2: Implement Cosine Similarity

  • [ ] Reuse existing cosine similarity function from similarity-engine
  • [ ] Or implement standalone for this module

Step 3: Filter and Select Exits

  • [ ] For each poem, deduplicate 768 candidates
  • [ ] Compute similarity for each unique candidate
  • [ ] Sort and select top 6

Step 4: Write Final Cache

  • [ ] Output dimension_maze_cache.json
  • [ ] Include metadata for cache invalidation

Computational Analysis

OperationComplexityTime
Load dimension extremesO(n × 768)~2s
Deduplicate per poemO(768)negligible
Cosine similarity (768 dims)O(768) per candidate
Total similaritiesO(n × ~500 × 768)~2 minutes
Sort and selectO(n × 500 log 500)~5s
Total~3 minutes

Configurable Parameters

ParameterDefaultDescription
NUM_EXITS6Choices per poem
MIN_SIMILARITY0.5Discard candidates below this threshold

Edge Cases

Case 1: Fewer than 6 unique candidates

  • Should not happen (768 dimensions should produce many unique extremes)
  • Fallback: use all available candidates

Case 2: Very similar poem pairs

  • If top candidates are all >0.99 similar, user choices may feel identical
  • Acceptable: the subtle differences are the point

Case 3: Isolated poems

  • Poems with unusual embeddings may have low similarity to all extremes
  • Acceptable: these become "frontier rooms" in the maze

Files to Create

  • src/maze-choice-selector.lua (selection algorithm)
  • Extend scripts/precompute-dimension-extremes to include this step

Dependencies

  • Output from 11-002a (dimension_extremes.json or similar)
  • assets/embeddings/EmbeddingGemma_latest/embeddings.json

Related Issues

  • 11-002a: Provides input (768 dimension extremes per poem)
  • 11-002c: Consumes output (6 exits per poem for HTML generation)

Phase: 11 (Advanced Exploration)

Priority: High (blocks 11-002c)

Created: 2025-12-25

Status: Open

Blocked By: MVP completion (similar/different navigation functional, pipeline stable, site deployable)

Depends On: 11-002a