117 lines
6.6 KiB
Markdown
117 lines
6.6 KiB
Markdown
|
|
# 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.
|
|||
|
|
|
|||
|
|
## 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, and main-thread responsiveness.
|
|||
|
|
|
|||
|
|
|
|||
|
|
## 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.
|