bend_puzzle/docs/internal-system.md
2026-08-01 16:06:14 +09:00

9.5 KiB
Raw Permalink Blame History

Bend Field: Internal System Reference

Runtime layout

The game is a browser application with no framework dependency.

  • index.html defines the application shell, HUD, dialogs, and script loading order.
  • style.css owns the visual system, responsive layout, zoom presentation, and interaction styling.
  • app.js owns runtime state, rendering, input, generation orchestration, persistence, economy, and UI behavior.
  • app-logic.js contains deterministic shared logic used by both runtime code and tests.
  • puzzle-core.js contains deterministic puzzle generation, solving, and difficulty analysis.
  • puzzle-worker.js runs expensive generation and verification away from the main thread.
  • server.js serves static files and owns the shared-world HTTP API, solution validation, revisions, and clear events. realtime-server.js owns transient WebSocket presence, five-minute board-claim leases, and short-lived reactions. Player purchases are stored separately from the shared world and validated by server.js.

World and puzzle data

The global data object stores:

  • Board metadata in data.metas.
  • Mutable board state in data.states.
  • Local score, time-attack state, purchases, cursor selection, and unfinished routes.
  • Shared solved count, board definitions, cleared state, first-solver identity, expansion state,.
  • Persistence revisions, authors, tombstones, and shared-world synchronization state.

A board metadata record identifies its world position, occupied five-by-five chunks, level, seed, puzzle definition, sealed sides, and revision.

A board state record contains drawn paths, special-cell progress, solved/expanded state, reward data, shop state, and revision.

A path contains its start gate, optional end gate, ordered cells, color information, and detached status. A detached path has two interactive pickups and does not participate in cross-board thickness through its former gate.

Input and connection pipeline

Pointer samples are coalesced and processed once per animation frame.

  1. Screen coordinates are converted to board SVG coordinates without a live layout read.
  2. Traversed cells are resolved in pointer order.
  3. Occupancy, warp, crossing, key/door, and self-rewind rules validate each step.
  4. Pickup snapping searches other open endpoints.
  5. Pickup and gate targets may bridge at most one empty cell in a straight line.
  6. Every bridge cell is checked against puzzle bounds and live occupancy.
  7. A successful join is committed as one board command and pointer capture is released.

Gate hitboxes cover the inward gate area plus a small outward strip equal to 14% of one cell. The geometric snap selector remains shared by direct gate input, gate-cell input, and drag completion.

Puzzle generation

World expansion is gate-driven. Solving a board opens unresolved gate frontiers, then generation:

  1. Chooses region-appropriate connected chunk shapes.
  2. Collects required facing-gate connections.
  3. Generates and validates a complete puzzle candidate.
  4. Applies obstacles and scheduled special cells.
  5. Verifies connection requirements, interaction burden, difficulty, and uniqueness where required.
  6. Commits metadata and state only after all validation succeeds.

Enclosed one-chunk world holes are treated as terminal fills. Existing saves are scanned for both gate-backed and ungated enclosed holes. Ungated terminal puzzles are sealed on all four sides; gate-backed fills keep only required connection sides open.

Obstacle policy

Obstacle placement operates by removing safe detours from generated solution paths.

  • The baseline selection rate is 2%.
  • Puzzle area gradually adds up to four percentage points for large puzzles.
  • Adjacent candidates receive a clustering preference.
  • The final obstacle count is capped at floor(totalCells × 0.20).
  • Detours larger than the remaining allowance are rejected.
  • Crossing conversion and fallback crossing templates use the same 20% cap.

After obstacle mutation, number clues and turn totals are rebuilt from the modified solution.

Difficulty and rewards

The base score uses board level and rendered connected-line thickness. Two effort multipliers are then applied:

  • A hard-puzzle multiplier begins above level 4 and increases through level 10.
  • A large-puzzle multiplier uses the number of occupied chunks and grows faster at high levels.

The result is passed through the deterministic board coefficient, active Score Lens multiplier, and time-attack modifier. SCORE_VERSION identifies the active reward formula for stored results.

Rendering and performance

The field uses several levels of detail:

  • Full interactive SVG boards near the active viewport.
  • Static board summaries outside the immediate interaction area.
  • A single canvas-based distant overview that reuses the minimap cell and long-line renderer.

Camera updates, board drag frames, the minimap, distant overview, presence cursors, reactions, and static noise are scheduled and instrumented independently. Difficulty-field overlays do not exist in the current runtime.

Effects and cosmetics

Reaction rendering keeps the complete classic, giant, laser, orbit, firework, and comet artwork at the existing 30 FPS cap. Accepted local reactions are sent to nearby realtime subscribers; reactions created while the socket is reconnecting are queued until their visual lifetime expires. Each accepted reaction receives one deterministic prepared model containing its immutable geometry and trigonometry. Frames reuse that model, draw every valid visible reaction in original layer order, and derive lifetime from authoritative timestamps. Overload is measured but never changes style, duration, particle count, glow, or compositing.

The reaction scheduler uses absolute deadlines, one pending timer, and one pending animation-frame callback. It stops while the page is hidden and resumes from current time. The renderer retains full-canvas clearing because cropped clears and cached transformed glyphs did not satisfy the visual-equivalence gate. A bounded emoji cache is used only for transform-neutral draws; rotated or scaled glyphs use direct browser text rendering. A separate bounded LRU Path2D cache reuses fixed Orbit and Firework geometry, with direct path construction as the exact fallback.

Aurora is a shop-only line-color contract. Its curated palette advances once every two seconds and colors newly drawn Aurora paths, connectors, endpoints, and usable gates. Updates are scoped to the world element and run only while tracked Aurora presentation nodes are visible. Completion flashes, completion bursts, and gem particles use bounded reusable node pools with overflow cleanup; gem particles share immutable keyframe and option templates while per-particle values live on the reused nodes. The cosmetic inventory keeps keyed category/item nodes, skips unchanged view signatures, patches only the old and new equipped cards, preserves focus and scroll position, lazy-decodes flag images, and applies render containment to offscreen cards.

BEND_PERF reports reaction preparation and publication, style, compositing, frame cadence, scheduler callbacks, visible/active counts, glyph and path-cache entries/hits/misses/evictions, Aurora ticks and writes, pooled-node setup/activity, and inventory patch/render timings.

The item UI contains no debug toggle. Opening /debug-items enables the debug purchase economy for that URL only: normal shop stock and purchase records are retained, while affordability checks settle at zero cost.

Persistence

IndexedDB is the durable local source of truth. A compact local mirror supports recovery when IndexedDB startup or writes fail.

Persistence uses:

  • Dirty metadata and state ID sets.
  • Chunked transactions.
  • Monotonic revisions and revision authors.
  • Deleted-board tombstones.
  • A shared-world outbox for retryable, server-validated mutations.

Unfinished routes are deliberately excluded from the shared outbox. Shared pulls replace board definitions and accepted clear state while preserving personal economy, cursor state, and compatible local unfinished routes.

The server stores one shared world and immutable versioned board shards. All mutations are serialized through one world queue. Clients poll revision deltas; successful clears are validated by the server before neighboring board batches are accepted.

The world-generation identifier is separate from the application version. Ordinary behavior and balance updates do not reset an existing field.

Verification

test/run-all.js runs source, gameplay, economy, performance, storage, concurrency, generation, interaction, reset, special-cell, and server regressions.

test/browser-performance-benchmark.js verifies real Edge startup and measures pointer work, camera work, minimap conversion, level-of-detail processing, persistence, overview rendering, DOM size, main-thread responsiveness, every reaction style, four/eight-effect overlap, deterministic checkpoint visual equivalence, reaction/Gem/completion setup cost, Aurora active/inactive/hidden behavior, localized cosmetic inventory updates with focus and scroll retention, effect-plus-interaction scenarios, and 100-cycle cleanup. It writes the complete report to test-results/browser-performance-benchmark.json unless BEND_FIELD_BENCHMARK_OUTPUT overrides the path.

Documentation policy

Documentation describes the current product and system only. Do not add changelog, release-note, update-history, migration-diary, or version-specific change-summary documents. When behavior changes, update the relevant current specification in place and remove obsolete historical notes.