From 08b8f1245bfc52ea00d3dfd3c432f402f4bd2840 Mon Sep 17 00:00:00 2001 From: "david.yapfest.club" Date: Fri, 11 Sep 2026 00:41:07 -0400 Subject: [PATCH] Document the Fluid player correction contract and legacy checkout path docs/fluid-player-design.md records the approved target: single fixed-point actor authority, exact 10 Hz / 3-substep geometry, the three pacing policies with Action-paced default, explicit action durations, pending-effect and resumable-Inn ordering, lit-integer-LOS terrain-only observation memory, and the future v5 schema break. It also preserves the prior first-person acceptance failure and states plainly which parts are target rather than delivered behaviour. README points at the contract and documents the safe legacy-grid-final worktree checkout for operating on original run/replay files without conversion. --- README.md | 40 ++++ docs/fluid-player-design.md | 390 ++++++++++++++++++++++++++++++++++++ 2 files changed, 430 insertions(+) create mode 100644 docs/fluid-player-design.md diff --git a/README.md b/README.md index b934697..7d2cbeb 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,46 @@ requires PyYAML 6.0.2. These are development checks, not game-runtime dependenci Configured CI checks stable and 1.75 separately; configured platforms are not a claim of an unexecuted platform certification. No network calls at runtime, ever. +## Fluid player correction status + +The approved Fluid HEXWORK correction contract is documented in +[`docs/fluid-player-design.md`](docs/fluid-player-design.md). It freezes one +signed-`i64` millimetre actor authority, checked `i128` projection into a +bounded `i32` voxel margin, the `10 Hz`/three-substep simulation, player +geometry and integer heading, action durations and pacing/death separation, +pending-effect ordering, resumable Inn work, and bounded lit observations. + +This is an **approved target, not implemented behavior**. The current +executable remains the legacy grid executable until implementation advances; +`cargo build --locked --release` does not deliver Fluid. The future v5 schema +break is not emitted and no migration is authorized. The optional local SDL2 +`0.38`/fontdue `0.9.3` path is intended but not delivered; ordinary terminal +SSH remains the intended client route. + +For a safe baseline build, use a disposable detached worktree for the +annotated local `legacy-grid-final` commit. This does not modify the current +checkout, push anything, or migrate saves: + +```sh +git worktree add --detach ../hexwork-fluid-player-legacy-grid-final \\ + 3f81e656bead175606b31a87cb15c9068f0d1c2b +cd ../hexwork-fluid-player-legacy-grid-final +git rev-parse HEAD # must print the exact commit above +cargo build --locked --release +./target/release/hexwork --help +``` + +Return to the original checkout and remove only that disposable worktree when +finished (`cd -` then `git worktree remove +../hexwork-fluid-player-legacy-grid-final`); do not run `git push`, merge, +rebase, or a save/schema migration as part of this check. + +The previous first-person acceptance failure remains open: the first-session +observation reported that the advertised Contracts key terminated play. The +normal-brightness first-person acceptance route below is still **pending human +execution**; automated tests and SVG/terminal artifacts do not certify font or +monitor readability or full HEXWORK completion. + ## Terminal support matrix | Capability | Full | Degraded | Floor | diff --git a/docs/fluid-player-design.md b/docs/fluid-player-design.md new file mode 100644 index 0000000..b732b7f --- /dev/null +++ b/docs/fluid-player-design.md @@ -0,0 +1,390 @@ +# Fluid player correction contract — approved target, not implemented + +> **Status:** This is an approved design target for the Fluid HEXWORK player +> correction. It is not an implementation report, a release note, or an +> acceptance result. No Fluid runtime, dependency, save migration, or schema +> change is delivered by this document. +> +> **Annotated baseline:** local `legacy-grid-final` at the exact commit +> `3f81e656bead175606b31a87cb15c9068f0d1c2b`. +> +> **Current executable:** until implementation advances, `cargo build` still +> produces the legacy executable. It does not implement the Fluid contracts +> below. + +This document freezes the correction boundary so implementation can proceed in +small, reviewable slices. “Approved target” means the contract is the intended +behavior. “Implemented” means the current source and executable demonstrably +provide that behavior; this document does not turn one status into the other. + +## 1. Target versus actual status + +| Area | Approved Fluid target | Current local status | +|---|---|---| +| Actor authority | `State::actors` is the single authority for **all** actor transforms, in signed `i64` millimetres | Legacy grid authority remains in the executable | +| Simulation | `10 Hz`, exactly `3` physics substeps per authoritative tick | Legacy timing/runtime path remains active | +| Player geometry | `3000 mm/s`; half-width/depth `300 mm`, height `1800 mm`; **FULL** AABB `600 × 600 × 1800 mm`; `1 mm` skin; `250 mm` step | Fluid geometry is not delivered | +| Heading | Integer heading with `4096` units per full turn | Fluid heading contract is not delivered | +| Pacing | Action-paced default; realtime Tactical and realtime Continuous are independent policies, orthogonal to death | Existing executable is not a Fluid implementation | +| Effects and Inn | Explicit durations; pending descriptor final-tick ordering; resumable Inn with exact debt rules | Not delivered | +| Observations | Lit integer LOS terrain faces only, stale remembered terrain, unknown/blank presentation, and a north-up compact map | Not delivered | +| Persistence | Intentional future v5 schema break; no silent migration | Current formats and readers remain legacy | +| Local presentation | Optional local SDL2 `0.38`/fontdue `0.9.3` path is intended, not required for SSH | Dependencies and local client are not delivered | + +A build, unit test, screenshot, or successful legacy play session must not be +reported as Fluid acceptance. The target remains open until the implementation +and its human/runtime checks actually land. + +## 2. Coordinate and actor authority contract + +### 2.1 One authority: `State::actors` + +`State::actors` is the one authoritative collection for **all** actors: the +player, NPCs, monsters, and every other actor kind. It owns each actor's +canonical transform and liveness. The player is not a special second authority. +Collision, movement, fall resolution, action effects, save/replay state, and +observations read `State::actors` or a checked snapshot of it. No player-only +position, NPC shadow transform, floating-point transform, or independently +mutable grid location may silently become authoritative. + +The implementation may derive voxel candidates, render coordinates, +projection DTOs, and broad-phase bounds, but those are projections. A +projection must never write back a rounded or truncated position without an +explicit checked operation. There is no wraparound, saturation, or “nearest +valid cell” recovery for an invalid authoritative position. + +### 2.2 Signed millimetres and Euclidean voxel floor + +Authoritative actor feet positions are signed `i64` millimetres. One voxel is +`1000 mm`. Converting millimetres to a voxel index uses **Euclidean floor**, +never language integer truncation toward zero: + +```text +voxel(mm) = floor(mm / 1000) +``` + +Thus `-1 mm` maps to voxel `-1`, `-1000 mm` maps to `-1`, `-1001 mm` maps to +`-2`, `0 mm` maps to `0`, and `999 mm` maps to `0`. The implementation must +use an explicitly Euclidean operation such as `div_euclid` and test negative +boundaries. + +The voxel index and its permitted margin are signed `i32` with a one-cell guard +margin at each end: the usable canonical index domain is + +```text +[i32::MIN + 1, i32::MAX - 1] +``` + +The reserved edge cells are not valid occupancy/query targets. This margin is +part of the v5 domain, not an implementation convenience. All actors and all +voxel projections obey it, including negative coordinates and chunk edges. + +### 2.3 Checked projection ordering + +A movement, collision, fall, or observation query computes its complete body, +skin, step, and one-cell-margin envelope in checked `i128` intermediates before +narrowing to voxel indices: + +1. Start with the signed `i64` millimetre authority in `State::actors`. +2. Add/subtract the full AABB, `1 mm` skin, movement/fall step envelope, and + required one-cell voxel margin in `i128`. +3. Reject arithmetic overflow/underflow and every envelope that could project + outside the usable `i32` domain **before** indexing, allocation, or a world + read. +4. Apply Euclidean floor and perform deterministic voxel queries/collision + checks. Never wrap, truncate toward zero, or clamp an invalid coordinate. + +A valid voxel projection is a checked read/validation result, never a second +actor authority. The same rule applies at the minimum and maximum world edges, +across negative chunks, during three-substep movement, and for LOS targets. + +## 3. Fixed simulation and player geometry + +The Fluid simulation uses these exact constants: + +| Contract | Value | +|---|---:| +| Authoritative simulation rate | `10 Hz` (`100 ms` per tick) | +| Physics substeps | Exactly `3` per authoritative tick | +| Player speed | `3000 mm/s` | +| Player body half-width | `300 mm` | +| Player body half-depth | `300 mm` | +| Player body height | `1800 mm` | +| Player full AABB | `600 × 600 × 1800 mm` (full width × full depth × height) | +| Collision skin | `1 mm` | +| Step height | `250 mm` | +| Heading resolution | `4096` integer units per full turn | + +At the nominal speed, one authoritative tick permits `300 mm` of travel and +its three equal substeps permit `100 mm` each before collision constraints. +Movement preserves the speed cap and makes collision/step decisions at +substep boundaries. Render cadence, terminal latency, and input-arrival timing +must not alter authoritative displacement. + +Heading is a canonical integer with `4096` units per full turn. It is not a +floating-point render angle and is not inferred exclusively from movement. +Presentation yaw/compass state is separately defined in the map section below. + +## 4. Pacing, actions, and death + +### 4.1 The three pacing policies + +The following table is normative. A policy changes when simulation work runs; it +does not select or rewrite a death policy. + +| Policy | Idle, look, and browse | Held/blocked movement and debt | Other exact behavior | +|---|---|---|---| +| **Action** (default) | Idle is frozen. Look and browse are free observational work. | A held or blocked move is still a `100 ms` move attempt in all phases. Pending-action and fall debt continue their cadence. | Accepted actions run according to their declared duration. | +| **Tactical** | Idle is live. An explicit pause is deferred until existing debt is settled. Browsing does not autopause. | A held or blocked move is still a `100 ms` move attempt **in all phases**; pending-action and fall debt continue their cadence. | `Step` is one wait (`1` tick), while a chosen action runs for its whole declared duration. | +| **Continuous** | Idle is live. Menus request a system pause; noninteractive presentation does not pause the simulation. | A held or blocked move is still a `100 ms` move attempt **in all phases**; pending-action and fall debt continue their cadence outside a system-paused menu. | Realtime menu/system pause is a pacing behavior, not a death-policy choice. | + +Look and browse never mutate the authoritative world merely because a view is +open. In Tactical, “pause” is an explicit request with the stated debt barrier; +opening a browser is not an implicit pause. In Continuous, only the explicit +system pause for an interactive menu stops noninteractive work; ordinary +noninteractive presentation remains live. + +Death is orthogonal to all three policies. Story, Classic, and any later death +policy retain their own lifecycle and recovery rules in every pacing policy. +Death is an authoritative boundary: queued player work stops while the +applicable death/lifecycle transition is resolved. A pacing mode must not hide, +postpone, or resume a dead-branch action. + +### 4.2 Exact action durations + +Durations are authoritative simulation ticks (`100 ms` each), not wall-clock +animation lengths, terminal frames, key-repeat intervals, or guessed defaults. +The approved table is: + +| Action | Exact duration | Timing class | +|---|---:|---| +| Walk / move | `1` tick | World | +| Wait | `1` tick | World | +| Pickup | `10` ticks | World | +| Drop | `10` ticks | World | +| Wield | `10` ticks | World | +| Accept contract | `10` ticks | World | +| Turn in | `10` ticks | World | +| Service (every service other than Inn) | `10` ticks | World | +| Transition | `10` ticks | World | +| Attack | `10` ticks | World | +| Identify | `20` ticks | World | +| Inn/rest | authored current `8640` ticks | Unattended | +| InspectBoard | observational; no committed action duration | Observation | + +`InspectBoard` is an observational request, not a committed action. It records +no action event, consumes no simulation duration, changes no canonical hash, +and does not enter pending-action history. It is not a zero-duration committed +action. There is **no zero-duration default**: every committed action must +match an explicit positive row in the table (or a separately approved future +row), and an unknown/missing/negative/overflowing duration is invalid rather +than zero. + +The v5 implementation must make action timing exhaustive: no wildcard timing +arm, implicit fallback, or service-wide default may accidentally assign a +new action a duration. Save/replay records carry enough explicit timing data +to reproduce the same tick boundaries. + +### 4.3 Pending descriptors and final-tick ordering + +A pending effect stores a small immutable **descriptor**: action/effect kind, +source identity, exact target identity/coordinates/instance ID as applicable, +start/final tick, and the approved parameters. It does **not** clone `State`, a +world image, or a target's mutable object. The descriptor is an intent to +revalidate, not permission to apply a stale effect. + +At the descriptor's final tick, process the final current state in this order: + +1. Advance the world and all required Fluid substeps for that tick. +2. Resolve world hazards, actor liveness, and death at the world boundary. +3. If the actor/lifecycle is still permitted to continue, revalidate the + pending descriptor against the **final current State** and the exact target + identity/coordinates/instance ID. +4. Apply the effect exactly once only after that revalidation, then commit the + state/history/observation boundary. + +A missing, moved, changed, dead, or otherwise stale exact target rejects or +aborts the pending effect; it never retargets to another actor, item, cell, or +service. Revalidation must not use a cloned State. If the world/death step +reaches a death boundary, the dead branch's pending effect is aborted and is +never applied or resumed; lifecycle handling cannot be bypassed by leaving a +descriptor around. + +### 4.4 Inn admission, unattended work, and debt + +Inn/rest is the authored-current `8640`-tick unattended action and is +resumable, but it is not an unbounded fast-forward: + +- Debit the Inn cost exactly once at successful action admission. A rejected + admission does not debit; a later interruption, death, save failure, or + reload does not refund or debit it again. +- Each unattended-work iteration advances at most **5 unattended ticks**. +- The unattended whitelist is exactly: NPC intent clocks, canonical NPC + locomotion, item clocks, corpse clocks, and gossip clocks. +- Normal hazards are excluded from unattended Inn work. Do not run normal + hazard simulation as an accidental side effect of resting. +- Apply the rest effect exactly once, only when the full admitted action + completes. Partial iterations do not rest repeatedly. +- Persist the remaining Inn debt/pending descriptor at the normal save + boundary. Save/reload preserves that debt exactly: reload performs no refund, + re-debit, repayment, or debt clearing, and resumes only the remaining work. + +The final Inn completion follows the same world-then-death-then-final-effect +ordering. If a death boundary aborts the dead branch, its pending Inn effect +does not resume on resurrection; only pending state that was already part of +the accepted Story checkpoint can be restored by Story rollback. + +## 5. Terrain observation and lighting contract + +Fluid observation is a bounded, deterministic projection of **terrain only**. +The observation memory contains no remembered actors, items, services, hazards, +actor identities, item identities, service availability, or other gameplay +facts. Such information must not be smuggled into a terrain observation cache. + +### 5.1 Positive integer light and first-hit LOS + +A current terrain face is observable only through positive local light represented +as an integer and a first-hit integer LOS test. The LOS range is at most +`48 m` (`48,000 mm`). The canonical ray origin at an observation boundary is +the authoritative actor feet position plus `1600 mm` on the vertical axis: + +```text +origin = actor_feet + [0 mm, 0 mm, 1600 mm] +``` + +At every canonical observation boundary, including spawn and arrival, perform +this observation omnidirectionally and independently of the camera. Camera +orientation cannot hide a required boundary sample or make unlit geometry +current. A first terrain-face hit within `48 m` is the only terrain geometry +eligible for that sample. + +Face targets are sampled `1 mm` into the air from the terrain face. This +one-millimetre air offset makes the target side of a face unambiguous and is +part of the integer boundary/LOS contract. All range, boundary, light, and LOS +arithmetic is checked; floating-point camera/render values are not authority. + +There is no live-geometry handle crossing into the projection. The renderer and +map receive detached, bounded observation values; they cannot retain a pointer, +reference, mutable handle, or lazy query into live geometry. + +### 5.2 Observation states and stale memory + +| State | Required meaning and presentation | +|---|---| +| **Lit** | A current terrain face passed positive integer local light and first-hit LOS. It may be rendered as current terrain. | +| **Remembered / stale** | A terrain face observed earlier but not currently visible. It remains explicitly stale and is not refreshed by hidden world changes. | +| **Unknown** | No valid terrain observation has ever been obtained. Do not infer terrain, occupancy, light, safety, actors, items, or services. | +| **Blank** | No terrain face is returned for this presentation cell. Render blank; do not turn blank into guessed floor or an omniscient dump. | + +Hidden changes stay stale. If terrain, light, occupancy, or a boundary changes +while a face is hidden, its remembered value remains the old stale value until +a new valid observation replaces it. Stale/unknown/blank values cannot +authorize movement, collision, targeting, service use, or a current-world claim. +The observation cache is bounded and is cleared or restamped only at its +explicit lifecycle boundary. + +## 6. North-up map and compass presentation + +The tactical map is **NORTH-UP**. North remains at the top; the map does not +rotate with player heading, camera yaw, or compass yaw. The approved map sizes +are: + +| Terminal/layout | Map viewport | +|---|---:| +| Normal `80 × 24` terminal | `15 × 9` | +| Compact layout | `9 × 5` | + +The map shows only permitted current/stale/unknown/blank terrain observations; +it is not an omniscient geometry view and never becomes movement authority. + +The compass's presentation yaw is separate from canonical actor heading. It may +be changed freely and immediately for presentation, independently of any +captured canonical heading. Canonical heading is not derived from movement +exclusively, and a presentation compass change does not mutate actor authority, +world direction, map north-up orientation, or replay state. + +## 7. Story checkpoint rollback and epochs + +A Story checkpoint is a complete accepted rollback boundary. Story death rolls +back **all pacing state, all knowledge, and all pending state** to the +checkpoint, not merely player position and HP. This includes the selected +pacing policy, cadence/debt/accumulator state, learned observation/knowledge +state, and any pending descriptors that were part of the accepted checkpoint. +After rollback, restamp a new epoch so a descriptor, target, observation, or +queued input from the dead branch cannot pass as current. + +Every action/effect admitted after that checkpoint on the now-dead branch is +aborted and does not resume. In particular, a pending effect that was in flight +on the dead branch is not replayed after resurrection. Only pending state that +was already present in the checkpoint may be restored, and it must still obey +final-current-State exact-target revalidation before any effect can apply. + +## 8. Persistence, schema, and optional local presentation + +### 8.1 Future v5 schema break + +The Fluid authority, timing, pending descriptor, Inn debt, pacing, observation, +and epoch contracts require a deliberate future **v5 schema break**. v5 is not +emitted by the current executable, and no v4-to-v5 migration is authorized by +this document. There must be no silent reinterpretation of legacy positions, +actions, ticks, pending state, observations, hashes, or map orientation. + +When implementation advances, incompatible prior artifacts must receive a +clear version/compatibility result before their bodies are interpreted. Exact +v5 fields, action tags, timing data, canonical ordering, bounds, epoch rules, +limits, and old-format behavior must be reviewed and frozen together. A +placeholder version bump is not a migration plan. + +### 8.2 Optional local SDL2/fontdue path + +An optional **local** presentation path using SDL2 `0.38` and fontdue `0.9.3` +is intended for a later local development/client slice. It is not delivered, +not a dependency change in this documentation-only correction, and not a +requirement for the ordinary terminal/SSH path. + +Ordinary terminal SSH remains the intended constrained-client route. It must +not depend on SDL2, fontdue, a GPU, or a local graphical session. Conversely, +terminal/SSH evidence does not prove that the optional local path exists or +that Fluid implementation is complete. + +## 9. Safe isolated baseline checkout/build + +Use a separate detached worktree. Do not replace the current checkout, alter +its branch, migrate saves, push a branch, or treat a build as Fluid delivery. +The annotated local baseline is the exact commit shown below, not a moving +branch name: + +```sh +# Run from the repository root; choose a disposable sibling path. +git worktree add --detach ../hexwork-fluid-player-legacy-grid-final \ + 3f81e656bead175606b31a87cb15c9068f0d1c2b +cd ../hexwork-fluid-player-legacy-grid-final +git rev-parse HEAD # must print 3f81e656bead175606b31a87cb15c9068f0d1c2b +cargo build --locked --release +./target/release/hexwork --help +``` + +The resulting binary is still the legacy executable until Fluid implementation +advances. Keep the worktree isolated and disposable; do not run `git push`, +`git merge`, `git rebase`, or a save/schema migration as part of this checkout +or build check. Remove only the disposable worktree when finished: + +```sh +cd - +git worktree remove ../hexwork-fluid-player-legacy-grid-final +``` + +## 10. Acceptance boundary and open human check + +The earlier first-person acceptance failure remains preserved: the first-session +observation reported that the advertised Contracts key terminated play. It is +not reclassified as a pass by the Fluid target. The README normal-brightness +first-person route is still **pending human execution**; screenshots, terminal +identity/dimensions, color mode, and an explicit human pass/fail remain +required. Automated tests and SVG/terminal artifacts cannot certify font or +monitor readability or the complete HEXWORK experience. + +No Fluid acceptance, v5 compatibility result, SDL2/fontdue delivery, ordinary +SSH certification, or HEXWORK completion claim is made here. A pending effect +that encounters death is aborted on the dead branch; it is never applied or +resumed, including after Story resurrection or a later load. -- 2.51.2