본문으로 건너뛰기
cccskills
무료GitHub에 공개

hex-grid-lattice-mc-refinement

Design guide + benchmarked pitfalls for building Monte-Carlo / Simulated Annealing refinement layers on top of greedy hex-grid layouts for code visualization (packages/gui/src/store/hexLayout.ts, sandbox/hex-sandbox, future Rust ports). Use when: (1) planning to add MC/SA on top of an existing hex-grid pack, (2) deciding whether a simpler SA would match a hierarchical MC with force-directed + chain drag, (3) budgeting wall-clock for million-node cold-start, (4) debugging "blob turns Swiss-cheese" or "folders stop nucleating" during MC refinement, (5) choosing between folder cohesion and raw Σ-link-length as cost function. Contains four hard-won, quantified lessons and anti-patterns that would take a full day of experiments to rediscover.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md12.5 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Hex-Grid Lattice MC Refinement: Design Principles

Problem

You already have a greedy hex-grid packer (pack.js / hexLayout.ts / future Rust port) that produces reasonable file-tree layouts. You want to push closer to optimal via Monte Carlo / Simulated Annealing. Four non-obvious design decisions determine whether the refinement actually helps or wastes compute; this skill documents them with benchmark numbers from a full multi-hour experiment session so you don't rediscover them the hard way.

Context / Trigger Conditions

  • Hex-grid hierarchical layout for code visualisation (file → folder → pkg → root), hundreds to millions of nodes
  • Greedy pack already exists and is "good enough" structurally
  • Considering MC/SA refinement to reduce Σ link length further
  • Or planning a from-scratch MC cold-start because "why not"
  • Or seeing surprising symptoms (Swiss-cheese holes, folders refusing to nucleate, MC never improving on the last 20 minutes)
  • Or choosing move set: random adjacency swap vs force-directed vs chain drag vs folder drift

Lesson 1 — Hole-guard > smart proposals

Claim: For dense-pack hex lattice MC, preventing isolated-hole formation gives more improvement than adding force-directed proposal generation. Geometric invariants beat smart directions.

Benchmark (1497 grafema nodes, warm-start from greedy, 15s wall budget):

Move setΣ-link reduction over greedy
Random swaps + Metropolis (naive)5.0%
+ hole-guard (reject hops that create interior holes)6.9% (+1.9 p.p.)
+ force-directed top-3 flanking proposal7.6% (+0.7 p.p.)
+ hierarchical force (intra before snap, external after)7.5% (noise)
+ BFS bent chain drag7.5% (noise)

Why hole-guard matters so much: Single-atom hop moves create vacancies when an atom jumps from a boundary cell into empty space. In Metropolis, hop-OUT is typically ΔE > 0 (stretches links, rarely accepted) while hop-BACK is ΔE < 0 (shortens links, almost always accepted). This breaks detailed balance — holes form at the boundary and migrate inward, turning the blob Swiss-cheese over thousands of iterations even though the energy metric keeps improving.

Fix: before allowing an atom to hop into an empty neighbour, verify the atom's old cell has ≥2 empty neighbours so it remains connected to the outer empty region after the hop. One-line check, 40% relative improvement:

const oldEmptyNeighbours = countEmptyHexNeighbours(a.coord);
if (oldEmptyNeighbours < 2) return null; // would create isolated hole

Non-obvious corollary: after adding hole-guard, force-directed proposals give only +0.7 p.p. more. If you're on a refinement budget, spend it on invariants, not smart proposals.

Lesson 2 — MC cold-start is infeasible vs structured construction

Claim: Lattice MC with local moves (swap, hop, drift, chain) cannot close the gap with a structured greedy pack for cold-start initialisation, even with hours of compute.

Benchmark (1497 grafema nodes, cold-start from alphabetic ring):

BudgetΣ-link reductionvs greedy's 13026
Greedy (3.6s)n/a1.00×
MC 60s31.4%14.0× worse
MC 180s38.3%12.6× worse
MC 300s42.9%11.7× worse
MC 900s (15 min)54.9%9.2× worse

Log-linear scaling: ~20 p.p. of reduction per log-unit of wall-clock. Extrapolating to match greedy's 13026 requires +40 p.p. more = 2 more log units = 100× the 900s budget = 25 hours per run. Infeasible.

Why: glassy regime — the move set is too local. Acceptance rate stays at 16-22% even after 15 min (never drops below 1.5% which would indicate equilibrium). Convergence detector never fires. "Run until frozen" SA doctrine doesn't apply.

Practical rule: Always seed MC with a structured algorithm. For hex hierarchical layouts, use the greedy pack (pack.js / future Rust port). Reserve MC as a refinement layer for the final ~7% polish on warm starts, not as a primary algorithm.

Lesson 3 — Hierarchical nucleation-aware force trades raw energy for

folder cohesion

Claim: Gating force-direction on folder nucleation state (pre-snap intra-folder, post-snap external) improves folder cohesion by ~36% at the cost of ~13% raw Σ-link energy. This is the right tradeoff for code-map visualisation.

Design:

for each atom a:
  find the deepest ancestor folder F that is NOT yet in `connected` set
  AND whose size ≤ connCheckMaxSize
  if F exists:
    # pre-snap: crystallise F
    force(a) = Σ (sibling.pos - a.pos) × weight   over same-F links
    # fallback if no intra-F links: pull to F's centroid
  else:
    # post-snap of all ancestors: place the now-coherent unit
    force(a) = Σ (other.pos - a.pos) × weight  over external-to-leaf links

Benchmark (1497 nodes, cold-start 60s):

Force typeΣlinksconn foldersadj invariants
Plain force (always external)162873273/518186
Hierarchical (intra→external)182678379/518159

Plain gives best raw energy. Hierarchical gives +38% more connected folders — the ones where a leaf actually condensed into a coherent blob. For a visualisation where folders should read as regions on a map, structure beats raw length.

Required fix — size-cap trap: folders above connCheckMaxSize never enter the connected set (BFS check too expensive). Without explicit skip in the ancestor walk, atoms below them are trapped forever in "pre-snap, pull toward huge-folder centroid" mode, producing weak scattered forces. Fix:

for (const fp of ancestors) {
  if (connected.has(fp)) continue;
  const size = folderAtoms.get(fp)?.size ?? 0;
  if (size > connCheckMaxSize) continue; // virtually nucleated
  activeFolder = fp;
  break;
}

This one-liner was missed in the first implementation and caused conn counts to plateau at 372/518. After the fix: 379/518 at 60s, 391/518 at 180s.

Lesson 4 — Bent chain drag via BFS vacancy routing

Claim: Row-shift chain drag (all atoms in a column shift by one hex) frequently breaks folder connectedness. Replacing it with BFS vacancy routing (the chain bends around obstacles) preserves invariants more often.

Design:

tryChainDrag(atom a, direction d):
  target = a.coord + d
  if target empty: use a plain hop instead
  if a.coord has <2 empty neighbours: abort (hole-guard)

  BFS from `target` through OCCUPIED cells, looking for the nearest empty
  cell. Each step uses any of 6 hex neighbours (chain bends freely).
  Max depth CHAIN_BFS_MAX_DEPTH=6.

  When empty cell found, reconstruct path: [target, c1, c2, ..., empty]
  Each cell path[i] moves to path[i+1]:
    - occupant of path[0] → path[1]
    - occupant of path[1] → path[2]
    - ...
    - occupant of path[k-1] → empty
    Finally `a` moves from a.coord to path[0] (now freed).

  Per-atom step direction can differ (that's the bending).
  ΔE: sum over affected atoms' external links; inter-chain links use
  new positions (both endpoints moved).

Why bending helps: a rigid row-shift only succeeds if the entire row can translate without breaking a registered connected(F) invariant. For a typical mid-nucleation cold-start, at least one atom on the row is usually a cut vertex of some folder — rigid shift fails. A bent chain can route around that atom, pushing only atoms whose folder is safe to re-arrange.

Not implemented (future): proactive constraint-aware BFS that weights candidates by "is this atom a cut vertex of an active invariant folder". Current implementation is reactive — it applies the chain, then rejects on invariant-check failure. Proactive would cost O(folder BFS) per step and wasn't worth it for our benchmarks, but may matter for much larger projects.

Consolidated verdict

For a refinement layer on top of greedy on ~1500-node projects:

  • Minimum viable MC = plain random swaps + Metropolis + hole-guard gets 6.9% over greedy in 15s. ~100 lines of code.
  • Maximum effort MC (force-directed + hierarchical + bent chain + nucleation invariants) gets 7.5% in 15s. ~600 lines of code.
  • Net value of the fancy machinery: +0.6 p.p. (noise-level).
  • Only reason to keep the complexity: folder cohesion guarantees (hierarchical nucleation) and map-like visual structure. If raw Σ-links is the only metric, delete it and use plain SA.

For million-node targets: MC is not the answer at all. Port the greedy pack to Rust (see REG-1102). MC refinement gives ≤7% that you can spend a week of engineering on; greedy port gives 100× throughput for 1-2 weeks of engineering. Cold-start MC gives an extrapolated 25h wall-clock for a result that greedy produces in seconds.

Example

// The minimum-viable refinement loop that captures 90% of the value:
async function refine(map, wallMs = 15000) {
  const rng = Math.random;
  const atoms = [...map.nodes.values()];
  const linksByAtom = indexLinksByAtom(map.links);
  const occupancy = buildOccupancy(map);

  // Temperature auto-calibrate from 300 random adjacency-swap ΔE samples
  const avgDelta = sampleSwapDeltas(300, atoms, linksByAtom, rng);
  const T0 = Math.max(1, avgDelta * 2);
  const T1 = Math.max(0.05, avgDelta * 0.01);

  const tStart = performance.now();
  let energy = computeTotalEnergy(map.links);

  while (performance.now() - tStart < wallMs) {
    const t = (performance.now() - tStart) / wallMs;
    const T = T0 * (T1 / T0) ** t;

    // Propose: 80% adjacency swap, 15% long-distance swap, 5% hole-safe hop
    const roll = rng();
    const move = roll < 0.80 ? adjSwap(atoms, rng, occupancy, map)
               : roll < 0.95 ? randomSwap(atoms, rng)
               : holeSafeHop(atoms, rng, occupancy);          // ← guards against Lesson 1
    if (!move) continue;

    const dE = computeDeltaE(move, linksByAtom);
    if (dE > 0 && rng() >= Math.exp(-dE / T)) continue;

    applyMove(move, occupancy);
    energy += dE;
  }
}

For the project-specific holeSafeHop implementation, look in sandbox/hex-sandbox/src/pack-mc.js (commit 6dcefd09) — it's a 20-line helper with the ≥2-empty-neighbours guard.

Notes

  • All benchmarks used a 1497-node grafema codebase with 4351 real polyglot import edges (scan-links.mjs). Synthetic data with folder-biased random links gives artificially optimistic numbers.
  • The connected.has(fp) check relies on a size-cap (connCheckMaxSize=200) — any folder above that never enters the invariant set; code walking ancestors must skip them explicitly (Lesson 3 size-cap trap).
  • 0 reheats across every cold-start benchmark confirms the glassy regime: acceptance stays above the 1.5% reheat threshold even at 15 min. Don't budget based on "time to convergence" — budget on wall-clock.
  • Chain drag is expensive per-call (BFS + multi-atom apply/undo). Cap its proposal frequency to 5% or less of the move mix, otherwise iteration throughput drops below the point where simpler moves give better ΔE/second.
  • For incremental (warm-start with small changes) use case, MC is ideal: seeded from last layout, low-temperature polish. This is the ONE use case where MC beats everything including fresh greedy.

References

  • Session artifact: sandbox/hex-sandbox/src/pack-mc.js at commit 6dcefd09
  • Related skills:
    • .claude/skills/hex-grid-sa-o1-connectivity/ — O(1) connectivity check for SA migrate moves (orthogonal speedup)
    • .claude/skills/hex-grid-sequential-bfs-layout/ — the greedy algorithm this MC refines (pack.js)
  • Linear: REG-1102 (Rust port of greedy for million-node target), REG-1100 (layout/view split), REG-1101 (npm package)
  • Physics literature: Wolff 1989 (cluster moves), Binder & Heermann "Monte Carlo Simulation in Statistical Physics" (glassy regime)

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Fix Elixir/Erlang AST processing bugs in Grafema beam-analyzer. Use when: (1) Elixir parser returns MODULE node but 0 functions/calls — body nesting issue, (2) Erlang parser crashes with "cannot convert list to string" on OTP 26+ — location format changed from integer to keyword list, (3) pipe operator |> creates spurious CALL nodes instead of desugared function calls — clause ordering bug, (4) multi-module .ex files return only the first module — missing __block__ handler, (5) installing Erlang/Elixir on macOS with outdated Xcode/Clang.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Disentinel/grafema352026년 8월 24일 업데이트

Fires after fixing any non-trivial bug or regression. Asks: could the graph have caught this as a guarantee? Pairs with reflection-in-and-on-action — picks up after "earliest catchable signal" and asks the next question: was that signal expressible in graph? Triggers: (1) after any non-trivial bug fix is verified working, (2) after a regression report (something used to work, broke), (3) during step 6 (knowledge extraction) of the workflow, (4) when reflection-on-action surfaces a "would have been catchable" signal. Outcome is a triage decision (graph-reachable? rule expressible? rule sound?) and either a draft Linear issue + guarantee proposal, or a recorded "graph capability gap" note. Never auto-creates guarantees.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Disentinel/grafema352026년 8월 24일 업데이트

Fix Playwright automation failures against code-server (VS Code in browser). Use when: (1) trust dialog blocks all clicks — "monaco-dialog-modal-block intercepts pointer events", (2) button:has-text() finds wrong buttons behind modal dialog, (3) keyboard shortcuts don't work — Meta vs Control inconsistency, (4) VS Code extension activity bar icon not found by aria-label, (5) panels show placeholder text despite extension being "Connected". Covers trust dialog dismissal, keyboard shortcut hybrid mode, extension panel selectors, and database connection verification for Grafema extension.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Disentinel/grafema352026년 8월 24일 업데이트

Systematic methodology for achieving 100% backward dataflow reachability in a new language. Create gauntlet fixture, write trace, diagnose gaps, fix analyzer/algorithm, iterate to 100%. Language-agnostic process. Use when: (1) adding a new language to Grafema, (2) auditing dataflow coverage for existing language, (3) user says "/dataflow-gauntlet".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Disentinel/grafema352026년 8월 24일 업데이트

Fix docker exec hanging when starting background processes (servers, daemons) inside containers. Use when: (1) docker exec never returns despite using & or nohup, (2) background server started in container causes docker exec to hang indefinitely, (3) env_startup_command in SWE-bench or similar frameworks times out, (4) setsid/disown needed for proper process detachment in Docker. Root cause: docker exec tracks ALL processes in the exec session, not just the top-level PID.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Disentinel/grafema352026년 8월 24일 업데이트

Fix Node.js CLI tools crashing inside Docker containers when host-installed node_modules require a newer Node version than the container provides. Use when: (1) "SyntaxError: Invalid regular expression flags" with /v flag in string-width or similar packages, (2) node_modules installed on host with Node 20+ but container has Node 18, (3) `npm install` with file: protocol creates symlinks that break inside Docker, (4) pnpm workspace packages become broken symlinks in containers. Covers version detection, Node binary mounting, and npm install strategies for cross-version compatibility.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Disentinel/grafema352026년 8월 24일 업데이트

Disentinel의 스킬 모두 보기

이 스킬의 문제 신고