docs(governance): D-248..D-251 — locomotion presentation, facing authority, cutaway decoupling, character-route reconfirmation
D-248 (architecture): per-leg constant-velocity interpolation keyed to the stance throttle; cadence-synced gaits; one smoothing layer per channel; annotates D-054 (its 100-150ms tween scoped to the 2D renderer). D-249 (architecture): 'server feet, client eyes' facing split with the verified octant-to-yaw table and the set_facing() trap note. D-250 (perception): wall cutaway is client camera presentation, decoupled from fog-of-perception; fog-memory-for-geometry nuance parked for Phase 5. D-251 (content): Quaternius rig reconfirmed after full 2026 re-evaluation; Synty intake technically proven then product-rejected; in-house wardrobe routes; UAL paid tiers close the animation gap; revisit tripwires recorded. Annotations: D-148 editorial note (preset list authoritative; stepped camera yaw needs a record), Q-020/Q-063/Q-079/Q-084 updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -151,6 +151,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
- **Source:** Control & Interaction Workshop (2026-02-13)
|
||||
- **Raised by:** Tyre (tile-based, non-negotiable), Dudley (tiles-per-tick model), Nigel (converted in Round 2: "tiles are BETTER for replayability — discrete positions = finite meaningful choices")
|
||||
- **Dissent:** Nigel initially proposed free movement with tile-based collision (Round 1). Converted in Round 2 after demonstrating that tile-based spatial puzzles (doorway decisions, corner peeks, eavesdrop corridors) create replayability.
|
||||
- **Amendment (2026-07-06, T-1088):** the "client-side Tween interpolation (100–150 ms)" sentence is **scoped to the 2D renderer** henceforth. A fixed-duration tween conflicts with the D-053 stance cadences (200–800 ms/step) — dash-then-stand stutter at every stance below Sprint. The 3D presentation layer interpolates per [D-248](#d-248-3d-locomotion-presentation--per-leg-constant-velocity-interpolation-keyed-to-the-stance-throttle) (per-leg constant velocity keyed to the stance throttle). Everything else in this record (server-authoritative discrete tiles, octant-only facing on the wire, TilePresence occupancy) is unchanged and remains binding for both renderers.
|
||||
|
||||
### D-055: Sprint explicitly suppresses interaction buffer
|
||||
- **Date:** 2026-02-13
|
||||
@@ -508,6 +509,7 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
- **Dissent:** None
|
||||
- **Supersedes:** [D-019](perception.md#d-019-top-down-confirmed-as-primary-camera-3d-cutscenes-for-key-moments)
|
||||
- **Cross-reference:** [D-149](#d-149-3d-characters-rendered-live-in-scene--not-pre-rendered-sprites) (3D rendering), [D-151](#d-151-direction-count--8-server-side-facings-4-visual-groups-client-sprint-28) (direction count)
|
||||
- **Editorial note (2026-07-06, T-1088):** the opening "30° tilt (60° from horizontal)" contradicts this record's own preset list — **the preset list is authoritative**: pitch values are measured from horizontal (0° = horizontal), gameplay default **−30° from horizontal**, frontal −5°, overhead −80°. Implemented as such in the T-1088 sandbox camera. Also note "45° rotation" here means the **single static map rotation** (one Transform3D on the tile-grid root); stepped *player* camera rotation was never decided (parked under Q-084) and requires a new record before implementation.
|
||||
|
||||
### D-149: 3D characters rendered live in scene — not pre-rendered sprites
|
||||
- **Date:** 2026-03-17
|
||||
@@ -1933,4 +1935,21 @@ Technical foundation decisions that constrain implementation: engine, client-ser
|
||||
|
||||
---
|
||||
|
||||
*103 decisions (D-001 through D-246, excluding gaps). Last updated: 2026-07-02 (D-246 — intra-class micro-habitat mosaic: within a single terrain class the sub-chunk seed paints a spatially-coherent, relief-conditioned mosaic of micro-habitats by modulating the D-228 axes; a new `voxel_mosaic()` band + `SeedDomain::VoxelMosaic`, palette in `mosaic_constants.toml`, five new D-228 axis values front-loaded for determinism, within-patch believability metric in scope; T-1084 under epic T-1079).*
|
||||
### D-248: 3D locomotion presentation — per-leg constant-velocity interpolation keyed to the stance throttle
|
||||
|
||||
- **Decision:** The 3D client rig turns discrete server tile steps into motion with **per-leg constant velocity**: on each confirmed step, leg speed = `distance / stance interval`, the interval read live from `InputMapper.MOVE_INTERVAL_MS` (Sprint 200 / Walk 400 / Careful 600 / Crouch 800 ms — never copied). Multi-tile deltas (latest-wins snapshots drop intermediate steps) close under a **3× catch-up clamp**; ≥ 2.5 m (5 subtiles) snaps all channels (position, yaw, camera, animation hard-cut). Diagonal legs run 1.41× (no √2 on the wire, [D-053](scope.md#d-053)) — accepted as sim truth so held diagonals arrive on time. Gait clips are **cadence-synced** (`speed_scale = clamp(leg_speed / NATIVE_MPS[clip], 0.6–1.8)` — constant per leg, killing foot-slide); Careful uses `Walk_Formal`; idle enters after 0.18 s hysteresis (snapshot-jitter guard). Exactly **one smoothing layer per channel**: position constant-velocity (never eased), yaw eased under per-stance deg/s budgets, camera exponential (rate 6.0 — converges to a constant offset against constant velocity). Purchased UAL tiers (or `ual_extended`) wire via **explicit AnimationLibrary names + `"lib/Clip"` addressing** (the imported default library name is `""` — a naive second copy collides). Stepped player camera yaw remains **out** (not canon; the T-key tilt cycle stays a dev affordance, [D-148](#d-148-30-low-angle-camera-with-45-map-rotation--supersedes-d-019)/D-158).
|
||||
- **Rationale:** resolves the D-054 ↔ D-053 cadence conflict: a fixed 100–150 ms tween dashes-then-stands at Walk/Careful/Crouch cadences, and exponential position lerp (the 2D renderer's model) produces a per-step velocity sawtooth foot cadence can never sync to. Constant-velocity legs sized to the throttle make held-key motion seamless and make cadence sync possible at all. **Annotates [D-054](#d-054):** its "client-side Tween interpolation (100–150 ms)" sentence is scoped to the 2D renderer henceforth.
|
||||
- **Implementation:** T-1088 (`client/scripts/sandbox/locomotion_rig.gd`, `locomotion_anim.gd`, `sandbox_constants.gd` — constants are initial values pending the live tuning pass). Strictly interpolate-only: a silently rejected move needs zero client handling by construction (Q-020 annotation) — facing updates, position doesn't, gait keys off render velocity ("bump-to-turn").
|
||||
- **Cross-reference:** [D-054](#d-054), [D-053](scope.md#d-053), [D-055](#d-055), [D-066](#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor), [D-149](#d-149-3d-characters-rendered-live-in-scene--not-pre-rendered-sprites), [D-249](#d-249-3d-facing-presentation-authority--server-feet-client-eyes). Tickets: T-1088.
|
||||
- **Dissent:** None
|
||||
|
||||
### D-249: 3D facing presentation authority — server feet, client eyes
|
||||
|
||||
- **Decision:** Which source rotates the 3D character model: **moving** (incl. the idle-hysteresis window) → the snapshot `player_facing` octant (the server overwrites Facing from the move delta in the same tick — it *is* the motion direction); **idle** → the client-local aim octant, i.e. **the same snapped octant that rides the `SetFacing` wire** ([D-054](#d-054): only octants cross the wire, so mouse-responsive idle facing is client-local by construction, and the model never shows an octant the server wasn't told). Facing freezes under input suppression (`dialogue_active`/`free_camera_mode`). The idle source is an **injected provider** — NPCs leave it unset and collapse to pure-wire, one code path. The **verified octant→yaw table** is `yaw = π/2 − θ` (South 0°, East +90°, West −90°, North 180°), applied on ModelRoot in WorldRoot-local space so octant→yaw composes with the D-148 45° map rotation **exactly once** (confirms compositor-api-spec §2). **Trap:** the rig must never call `CharacterVisual.set_facing()` — its internal table is E/W-mirrored relative to this convention (`character_visual.gd:184-193`).
|
||||
- **Rationale:** single-source alternatives are strictly worse — pure-snapshot idle facing is ~250–300 ms of laggy 45° pops (the 2D client already draws its indicator from client-local aim, `entity_renderer.gd:186-193`); pure-client facing breaks server-authoritative display while moving. The residual artifact is bounded: idle, the model leads the server's vision cone by ≤ 1 RTT, and the disagreement is exactly the in-flight SetFacing.
|
||||
- **Cross-reference:** [D-054](#d-054), [D-151](#d-151), [D-248](#d-248-3d-locomotion-presentation--per-leg-constant-velocity-interpolation-keyed-to-the-stance-throttle), Q-084 (presentation half settled here; the walk-vs-aim *animation* split stays open — the purchased 8-direction walk/jog/crouch clip sets now make a direction-matched-clip solution tractable). Tickets: T-1088.
|
||||
- **Dissent:** None
|
||||
|
||||
---
|
||||
|
||||
*105 decisions (D-001 through D-249, excluding gaps). Last updated: 2026-07-06 (D-248/D-249 — 3D locomotion presentation: per-leg constant-velocity interpolation keyed to the stance throttle, cadence-synced gaits, and the "server feet, client eyes" facing authority split; T-1088 locomotion sandbox).*
|
||||
|
||||
@@ -646,4 +646,17 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio
|
||||
|
||||
---
|
||||
|
||||
*48 decisions. Last updated: 2026-07-02 (D-247 — native pre-existing biospheres: habitable Reach worlds were alive before humans; an **authored** per-world chirality bit (~50/50 target) gates whether native life is edible (Compatible) or inedible-but-inert (Mirror) — "safe" and "edible" are decoupled; colonization is ecological war that grows an Earth-crop footprint outward from settlements; grounds the D-246 mosaic register; Miri + Burnelli own development).*
|
||||
### D-251: Character asset route, 2026 reconfirmation — Quaternius rig, in-house wardrobe, purchased animation tiers
|
||||
|
||||
- **Decision:** The character stack stays anchored on the Quaternius 65-bone rig, the 11 in-house body types, and the segmentation/Surface-Deform/compositor architecture ([D-159](scope.md#d-159)–D-164). A full 2026-07-06 re-evaluation (fresh vendor research, alternatives landscape, repo coupling audit, bridge mechanics, adversarial stay/pivot cases, and a hands-on intake spike) found no destination that improves the position: Synty Sidekick is Unity/UE-locked with no animations and EULA terms; KayKit/Kenney modularity is texture-skin level; Mixamo is unmaintained; 2026 AI tooling cannot produce layered garments on a consistent skeleton. The runtime rig-lock is ~21 GDScript lines — pivoting later stays cheap by design.
|
||||
- **Synty garment intake: technically proven, product-rejected.** The free-Starter-Pack spike transplanted a Sidekick garment onto our rig (one reusable rig-pair affine + the existing Data-Transfer weight pipeline; 11-body batch; live Godot QA under the production Walk clip) — a technical PASS. The route was then **rejected on product grounds**: per-pack cost, Sidekick's baked body-segment modularity (clothes fused to body chunks — incompatible with layered clothing-over-body, 11 bodies, skin tones, slot shopping), and style. The spike's salvage is permanent: source-agnostic transplant/batch-fit scripts (`spikes/synty-intake/scripts/`) and the T-1090 discovery (5 of 11 bodies misrender bare — upstream of any clothing route).
|
||||
- **Wardrobe is in-house** (T-1089 three-route portfolio): offset-shell garments derived from our own body meshes (weights inherited by construction), derivation cuts from owned Fantasy-pack parts, and true hand-authoring on the owned UBC Source `.blend`s — with the black suit + colorable uniform mandated through the authoring route as its end-to-end proof. Style: v1 ships cyberpunk/modern only; style is a prevalence distribution long-term (manifest `style` tag is the seam).
|
||||
- **Animation gap closed by purchase** (~$50, CC0, owned): UAL1[Source+Pro] (120 clips) + UAL2[Source] (134 clips) — turn-in-place (`Turn90/180`), stance enter/exits (`Sprint/Crouch_Enter/Exit`), full 8-direction Walk/Jog/Crouch sets, life-sim idles (`Counter_Idle`, `Idle_TalkingPhone`), plus root-motion variants (unused — in-place is correct under [D-248](architecture.md#d-248-3d-locomotion-presentation--per-leg-constant-velocity-interpolation-keyed-to-the-stance-throttle)). Only walk start/stop and stair locomotion exist in no surveyed ecosystem — the sole remaining custom clips. Upstream renamed clips between tiers (`Walk_Loop` → `Walk_Fwd_Loop`), so integration goes through explicit library names, not file swaps.
|
||||
- **Revisit tripwires** (this record does not reopen without one): Quaternius ships a modern/casual outfit pack (→ immediate preference switch, CC0 + rig-native); an upstream breaking rig change; Synty ships real Godot/engine-agnostic support; Q-060-class body-fit failure resists ~2 weeks of pipeline effort (→ evaluate a blend-shape body layer); a credible Godot-native modular-character ecosystem emerges; vendor goes dark > 6 months (files owned — re-cost future-content assumptions only).
|
||||
- **Supersedes** the mid-2025 conclusions of `spikes/quaternius-aesthetic/reviews/quaternius-packs-research.md` (stale). **Cross-reference:** [D-149](architecture.md#d-149-3d-characters-rendered-live-in-scene--not-pre-rendered-sprites)–D-152, [D-159](scope.md#d-159)–D-164, [D-244](architecture.md#d-244-asset-rendering--3d-objects-in-world-2d-limited-to-textures--flat-artwork), [D-248](architecture.md#d-248-3d-locomotion-presentation--per-leg-constant-velocity-interpolation-keyed-to-the-stance-throttle)/[D-249](architecture.md#d-249-3d-facing-presentation-authority--server-feet-client-eyes), Q-060. Tickets: T-1088, T-1089, T-1090.
|
||||
- **Raised by:** Jeroen (route challenge — "is Quaternius the right route?", the cost/modularity/style rejection call, and the create-stuff-yourself scope guard) with Claude (four-track investigation, store verification, intake spike, evidence synthesis), 2026-07-06, during the Fable-5-window character sidequest.
|
||||
- **Dissent:** None.
|
||||
|
||||
---
|
||||
|
||||
*49 decisions. Last updated: 2026-07-06 (D-251 — character asset route reconfirmed: Quaternius rig + 11 in-house bodies stay; Synty intake technically proven then product-rejected; wardrobe in-house via offset-shell/derivation/hand-author with suit+uniform as the authoring proof; UAL1/UAL2 paid tiers close the animation gap except walk start/stop + stairs; tripwires recorded).*
|
||||
|
||||
@@ -484,4 +484,14 @@ How the player observes and interacts with the world: camera, fog, line-of-sight
|
||||
|
||||
---
|
||||
|
||||
*38 decisions. Last updated: 2026-02-24 (D-086: Insert icon system)*
|
||||
### D-250: Wall cutaway is client-side camera presentation, decoupled from fog-of-perception
|
||||
|
||||
- **Decision:** The in-world camera's wall-occlusion treatment (camera-facing walls cut down/ghosted so the locked low-angle view stays readable, Xenonauts-style) is **pure client-side rendering** on wall data the client already holds — it is *not* a perception query and must never couple to the fog/vision system. Two occlusion systems, two different subjects: **perception/fog governs what the CHARACTER knows** (server-enforced information boundary — the client only ever receives observer-filtered geometry); the **cutaway governs what the PLAYER'S CAMERA can see** — a render mode (shader height-cut keyed to the character's interpolated position) applied to walls like tint or LOD. They compose instead of coupling: the cutaway cuts *render height*; the visibility tint / fog overlay still gates *knowledge* of whatever lies behind. No leak is possible because "what the client holds" is already boundary-filtered upstream. Walls stay first-class client data objects (future destruction/explosion interactions).
|
||||
- **Rationale:** with the camera locked to the character ([D-015](#d-015)) there is no pan/rotate escape hatch — when the character walks behind a camera-facing wall, the cutaway is the *only* readability mechanism, which makes it load-bearing for locomotion evaluation and a permanent fixture of the 3D view. Coupling it to perception would be a category error: the character behind a south wall sees that room fine; it is only the viewport that is blocked.
|
||||
- **Phase-5 nuance recorded, not implemented:** when destruction exists, a wall destroyed *while unobserved* should keep rendering in its remembered state — the fog-memory model applied to geometry, not just entities. The T-1088 never-evict tile store (last-observation-wins, remembered-state tint) is the embryo of exactly this.
|
||||
- **Cross-reference:** [D-011](#d-011), [D-015](#d-015), [D-148](architecture.md#d-148-30-low-angle-camera-with-45-map-rotation--supersedes-d-019), [D-244](architecture.md#d-244-asset-rendering--3d-objects-in-world-2d-limited-to-textures--flat-artwork), Q-081 (a 3D fog overlay composes on top of the cutaway, unaffected). Tickets: T-1088 (first implementation: smoothstep half-disc height-cut, `client/shaders/sandbox/greybox_wall_cutaway.gdshader`).
|
||||
- **Dissent:** None.
|
||||
|
||||
---
|
||||
|
||||
*39 decisions. Last updated: 2026-07-06 (D-250 — wall cutaway is client-side camera presentation, decoupled from fog-of-perception; two occlusion systems with different subjects that compose rather than couple; fog-memory-for-geometry nuance recorded for Phase 5; T-1088).*
|
||||
|
||||
@@ -34,6 +34,7 @@ Technical foundation questions: engine, protocols, data structures, performance,
|
||||
- **Context:** D-012 defines tile collision. WalkabilityMap exists (server/src/simulation/movement.rs) but handles single-entity validation. Architecture review identified multi-entity collision as unspecified.
|
||||
- **Assigned to:** Gestalt, Dudley
|
||||
- **Source:** Architecture Review Audit 2026-02-11
|
||||
- **Update (2026-07-06, T-1088):** data point from the 3D locomotion rig — a strictly no-prediction, interpolate-only client ([D-248](../decisions/architecture.md#d-248-3d-locomotion-presentation--per-leg-constant-velocity-interpolation-keyed-to-the-stance-throttle)) needs **zero blocked-move handling by construction**: the rig chases only server-confirmed positions, so a silently rejected move produces "bump-to-turn" (facing updates, position doesn't, gait keys off render velocity). Whatever resolution policy this question picks, the client presentation layer is already policy-agnostic.
|
||||
|
||||
### Q-021: Tick budget overflow policy
|
||||
- **Status:** Open
|
||||
@@ -105,6 +106,7 @@ Technical foundation questions: engine, protocols, data structures, performance,
|
||||
- **Status:** Open
|
||||
- **Question:** Integrate the Footsteps asset (https://godotengine.org/asset-library/asset/4122) into the character visual system. Jeroen wants this in the game. Evaluate: how does it hook into the animation system? Does it work with our toon shader pipeline? Should footstep triggers be animation events or raycast-based?
|
||||
- **Cross-reference:** D-149 (3D characters rendered live), D-160 (body segments)
|
||||
- **Update (2026-07-06, T-1088):** the hook seam now exists — the locomotion gait machine (`client/scripts/sandbox/locomotion_anim.gd`) emits `gait_changed(state)` and exposes clip phase via `CharacterVisual.get_animation_player()`. The animation-events-vs-raycast decision itself remains open.
|
||||
|
||||
---
|
||||
|
||||
@@ -219,6 +221,7 @@ Technical foundation questions: engine, protocols, data structures, performance,
|
||||
- **Status:** Open (high relevance)
|
||||
- **Question:** GridMapLayer (https://github.com/Caaz/grid-map-layer) — manages 3D GridMaps through TileMapLayer patterns. MIT, Godot 4.4+. Directly relevant: our server sends 2D tile grid data, the client renders 3D isometric. This plugin bridges that gap — autotiling for 3D tiles using 2D rules, arbitrary subdivisions (2x2+ gridmap tiles per 2D tile), multiple GridTiles per 2D tile (layered grids for collision variation), programmatic tile setting for procedurally generated areas. This could be the rendering layer for server-generated floor plans — the server sends the tile palette + grid, the client uses GridMapLayer with autotiling to render walls, floors, doors with correct 3D tile selection.
|
||||
- **Cross-reference:** D-096 (chunk loading), tile rendering pipeline, server→client tile data
|
||||
- **Update (2026-07-06, T-1088 — data point, not a resolution):** the locomotion sandbox greybox chose **MultiMesh** over GridMap because it needs per-instance state (four-state visibility tint via instance COLOR) and per-material shader uniforms (wall cutaway) — GridMap has neither per-cell color nor per-cell shader state, so tint changes would mean MeshLibrary item churn on every LOS change. Counter-case recorded: GridMap + a code-built MeshLibrary + diff-only `set_cell_item` painting is legitimately simpler **where per-instance color is not required**, and remains the natural Phase-5 candidate for static real-asset geometry (walls/floors with final materials). GridMapLayer's autotiling value proposition is untouched by the greybox's choice.
|
||||
|
||||
---
|
||||
|
||||
@@ -256,6 +259,7 @@ Technical foundation questions: engine, protocols, data structures, performance,
|
||||
- **Question:** Reference: isometric-2d-point-and-click-movement (https://github.com/Domogo/isometric-2d-point-and-click-movement). This is 2D but the interaction pattern applies to our 3D isometric setup: click ground tile → convert screen position to tile coordinate → server pathfinds (A* on tile grid) → client animates character along path. Key problems to solve: (a) mouse→3D isometric tile coordinate conversion (ray from camera through click point to ground plane, accounting for D-148 45° map rotation). (b) Path visualization (preview line showing where character will walk). (c) Click-to-move vs WASD — do we support both? (d) Server roundtrip latency — does the client predict movement or wait for server confirmation? Study for interaction patterns, implement in our 3D stack.
|
||||
- **Design notes:** Character stays center-screen, map scrolls around them (not character moving on static map). Character always faces toward mouse cursor — this creates a natural looking-direction that drives the D-015 vision cone direction. Unresolved: what happens visually when walking one direction while looking backward (walk animation vs facing direction conflict — later solve). Latency: 200ms feels instant in web interactions (Jeroen's rule from web design) — if server roundtrip is under 200ms, click-to-move without client prediction may be acceptable. Needs user testing to verify.
|
||||
- **Cross-reference:** D-148 (camera angle), D-010 (server authority), D-015 (vision cone facing), navigation/pathfinding
|
||||
- **Update (2026-07-06, T-1088):** partially settled. (a) mouse→tile conversion is implemented: camera ray to the y=0 plane, `WorldRoot.to_local()` undoing the D-148 rotation, `atan2` to sim angle (`client/scripts/sandbox/mouse_aim_provider.gd` — the screen-space angle is NOT a sim angle under 45° rotation + ortho pitch, so the full unproject is mandatory). The **facing presentation source** is now governed by [D-249](../decisions/architecture.md#d-249-3d-facing-presentation-authority--server-feet-client-eyes) ("server feet, client eyes"). (d) is answered by [D-248](../decisions/architecture.md#d-248-3d-locomotion-presentation--per-leg-constant-velocity-interpolation-keyed-to-the-stance-throttle): no client prediction, interpolate-only. **Still open here:** click-to-move + path preview (WASD-only today), stepped player camera rotation (explicitly requires a new record), and the walk-vs-aim *animation* split — note the purchased UAL 8-direction walk/jog/crouch sets now make a direction-matched-clip solution tractable (play `Walk_Fwd_L` etc. relative to facing instead of rotating the body into the path).
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user