Skip to content

Integrate native runtime, redesigned world, and canonical humanoids - #15

Open
lyc-aon wants to merge 128 commits into
mainfrom
dev/rust-client
Open

Integrate native runtime, redesigned world, and canonical humanoids#15
lyc-aon wants to merge 128 commits into
mainfrom
dev/rust-client

Conversation

@lyc-aon

@lyc-aon lyc-aon commented Aug 3, 2026

Copy link
Copy Markdown

What changed

  • completes the native Rust client path: streamed/skinned GLB rendering, routed movement, cutaways, authored building interiors, doors, and current world presentation
  • promotes the redesigned 1024-cell desert/forest fixture with sparse population, safe starting region, Dustgate facilities, modular buildings, travel, and combat/runtime authority parity
  • wires canonical male/female PawnForge bodies through durable character appearance, creator selection, runtime rendering, fitted wardrobe assets, CI/LFS, and production packaging
  • makes desktop durability locking and packaged verification portable to macOS, including isolated state and native fullscreen behavior
  • reconciles the concurrent cold-load ticket and Rust WebGL2/runtime-model fixes already on dev/rust-client

Verification

  • pnpm run ci
  • make -C client-rust verify
  • pnpm hygiene:rust (hard gates pass; cargo-machete unavailable and reported as skipped)
  • pnpm desktop:build && pnpm --dir desktop verify:key-ownership
    • packaged source: 2c486e517ac1dac19acfa3d72aba69717bf35ce3
    • connected authority, 5 actors, live WASD movement, Ctrl+W ownership, F11/Alt+Enter fullscreen, empty fresh durable roster
  • production assets: 504 files / 288,824,630 bytes; manifest ecaae3f9705174d70a31eae99c954d9c7ea72644c9d22ca83a14361843b9d523

lyc-aon and others added 30 commits August 4, 2026 21:11
The debug character builder ("blue frog") is now accessible via converse with
GR0K or when opening converse with no target under `--features dev-tools`.
Unlike Core3's single-select SUI, leaves are multi-select checkboxes so a
tester can tick multiple items, packs, or skills and commit them via GRANT
SELECTED in one action.

The catalog (173 items, 126 skills, 7 packs) is generated by
`crates/successor-sim/src/bin/emit_debug_catalog.rs` probing the runtime tables,
embedded via `include_str!`, and checked by `tools/successor/assets/fix_glb_pbr.py`
and the hygiene gate. Added `ClientCommand::DebugGiveCredits { amount }` (kind 98)
across net, sim, bridge, and server protocol schemas. Debug grants are exempted
from session rate-limiting when `GAME_DEBUG_AUTHORITY_COMMANDS=1` is set.

Inventory radial: right-clicks over interactive windows were swallowed by `covers()`
before reaching the radial branch. Now resolved above the swallow, opening the
object radial with UNEQUIP, EXAMINE, DISCARD, SPLICE. Relabelled DROP to DISCARD
to match DiscardStack semantics.

Converse NPC bust: camera facing calculation now dead-ons the speaker's front
(yaw + PI) instead of following pawn yaw (which showed the back of turned NPCs).
Trainer detection range widened to 16 cells with in_range gating preserved.

Viewer seat alpha lowered from 245 to 28 so 3D composite cells blend with
translucent window backgrounds. Added fill light to ItemPreviewRenderer.
A ConnectedScene is constructed before its first framebuffer exists, so
`framebuffer` starts at the (1280, 720) placeholder. `load_persisted` ran
before any real frame and matched the saved layout's viewport stamp against
that placeholder, so a 720p document looked authoritative and its rects were
applied verbatim. The first real frame then set the live size but found no
pending HUD slots, so nothing re-anchored: on a 1600x1000 window the whole
bottom band -- chat console, notifications, ground radar -- stayed pinned to
y=720, floating mid-screen.

The decision cannot be made at restore time. Restore now applies saved rects
unconditionally and records the document's viewport stamp; `frame` compares
that stamp against the real framebuffer and re-anchors the band when they
disagree. That covers both the startup case and a live window resize.

`hud_surfaces_re_anchor_when_the_framebuffer_changes` passed throughout,
because it only ever drove `register_hud_surfaces_at` with an explicit
viewport -- the one path production never takes. It now asserts the stamp
comparison directly and proves the stranded-mid-screen state before the
reconcile pulls the band back to the edge.

Verified live: at a 1600x1000 framebuffer the band bottoms out at 992, and at
1728x1052 (macOS clamped the 1920x1200 request) it bottoms out at 1044 and
right-aligns to 1728 -- the HUD follows the real framebuffer, not the request.
Wave 6 documents the framebuffer anchoring regression and the measured
before/after. It also retracts the in-game verification filed for Wave 5: the
local authority's Rust bridge had exited, so the shard served a frozen world
and closed every join with 1011, and the three screenshots kept as proof are
crops of the PLANETFALL loading screen.

The Wave 5 code and its green gates stand. Its runtime claims do not, and the
surfaces it touched are listed as needing a live pass.
An idle native client was disconnected within the Colyseus liveness window,
every time. It survived only while something was actively driving it, which is
why this kept reading as intermittent: an agent journey sending control input
flushed the socket as a side effect, and a client left alone died.

`ws_poll` answered a server Ping with a bare `WsEvent::None` and the comment
"tungstenite handles responder automatically". Tungstenite queues the Pong; it
only reaches the socket on the next write or flush. A moving player's outbound
commands flushed it incidentally. A standing player sent nothing, the pong
never left, and Colyseus terminated the connection for missed liveness -- an
abrupt reset with no close frame. The Ping arm now flushes, treating WouldBlock
as ordinary backpressure and any other error as a transport failure.

Two diagnostics that hid this are fixed alongside, because both discarded the
cause and left only "reconnect 1/5" on stderr:

- a server Close frame's code and reason are printed, which is how
  `1011 durable character entry failed` becomes visible at all;
- the tungstenite error behind `WsEvent::Error` is printed.

With those the failure reads directly as `Broken pipe` followed by
`Connection reset without closing handshake`.

Verified: an idle client held `game_connection: Connected` against
`sessionCount: 1` across 89 seconds and ~2,250 ticks, having previously dropped
within seconds of going idle. 428 client tests, clippy -D warnings, and the
rust hygiene gate stay green.
d57ff3f moved item GLB loads onto AssetStreamer, but changed_path latched
lane identity on the first frame, so a lane whose fetch returned Pending
never re-requested: inventory cards stayed dark while examine (requesting
after the bytes had landed) still worked. Lanes now carry the unsettled
path and keep asking until assign_model records the result; a miss whose
UNKNOWN_MODEL placeholder is itself still in flight stays unsettled too.
Steady-state frames stay allocation-free once a lane settles.

Proof: control-server journey on the local authority — inventory opens
with all 19 held stacks rendering 3D previews (was: every card dark).
Ground-up rewrite of every screen. The design system ports the game
client's window grammar — translucent panels under a bright accent
perimeter, caption bands, corner survey ticks, evidence plaques,
underline verbs, tracked micro-caps — with oklab color-mix lines and
glass throughout, and cuts the copy to the bone.

Five keyed themes: DAWN (default) is the site's own daylight glass;
SIGNAL/PHOSPHOR/AMBER/OXIDE are byte-exact ports of hud.rs THEMES,
selectable from a deck in the topbar and persisted per visitor. The
theme-contract suite now reads hud.rs directly, so client palette
drift fails the site build.

Home rebuilds the intro as an instrument sequence: live cinematic reel
under a theme-keyed veil, field-record plates (including two native
Rust client captures with provenance in the media manifest), the real
80x24 TUI frame with a staggered opening crawl, and a chrome demo
window. The terminal fixture's off-world resource name is corrected to
this world's Daxmere iron.

Tests: 178 passing, all budgets met (home HTML+CSS 9.0KB gz, cold
118KB). CSP inline-style ban kept; storage hygiene extended with a
ruled theme-preference exception.
…ab/1/2 keys

Combat FX went dark on the live wire: the server ships combat events as
GameCompactCombatEvent tuples (shard.ts sends events: [] beside
compactEvents since the size-reduction wave), but CombatEvent::from_json
only read the verbose object shape, so every muzzle flash, tracer,
outcome burst, floating hit text, and combat SFX cue was silently
dropped while receipts kept ammo and vitals honest. from_json now
recognizes the tuple layout (indices mirrored from protocol.ts) and both
wire shapes feed one shared presentation derivation; four new tests pin
tuple/object equivalence, kill boosts, shield precedence, and id-less
drops.

Looting: F only consulted store.player_corpses, so an authority-marked
rogue corpse (lootable, rights held) never opened the loot window. The
key handler now falls back to the nearest lootable actor corpse within
reach, and the loot projection builds the corpse:<actor-id> container
view from the actor snapshot with rights checked against
lootRightsActorId.

Input: Tab (cycle live targets by distance, wrapping from the current
selection) and Digit1/Digit2 (basic/aimed shot through the shared
shot_action body) join CONNECTED_INPUT_KEYS, making the README's
documented bindings true on both hosts and reachable from the control
server.

Proof: 439 unit tests pass; live Bunker re-verification follows.
…ll bind sampling

The toolbar owns every number-row key (press_code_result runs before the
key match), so the documented 1/2 combat bindings could never fire: a
fresh install shipped all-empty slots, and worse, only Digit1/Digit2 of
the twelve bind keys were even sampled by the connected host — toolbar
keybinds worked from clicks alone. CONNECTED_INPUT_KEYS now samples the
whole shipped bind row (Digit1..Digit0; Minus/Equal have no Key variant
yet and stay click-only), fresh installs get the original's number-row
grammar via ToolbarDoc::default_loadout() — ATTACK on 1, the new AIMED
SHOT verb on 2 — and both combat verbs route through shot_action, so
they inherit nearest-hostile fallback targeting instead of demanding a
prior selection. Persisted docs are untouched: from_json still overlays
a blank() base, so a deliberately emptied slot stays empty.

439 unit tests pass.
ToolbarDoc::load treated a missing persisted section as blank(), so the
default_loadout only survived until the preferences load pass replaced
the constructor's toolbar — a fresh profile still showed empty 1/2
slots. A missing section now loads default_loadout(); a present doc
(including a deliberately emptied bar) is untouched. 439 tests pass.
Authority image 47abcfe6 (movement ingress budgets, room messages,
pong flush), Rust beta successor-rust-beta@e1e0e44caa38b1d1 (compact
combat-event decode, NPC corpse looting, inventory preview retry, Tab
targeting, number-row toolbar with fresh-install combat loadout), and
the rewritten five-theme site. State untouched; generation unchanged.
Evidence seal f9d837dec8faed775e1fc7c9c02a8af4b2489d994b413d2e22738fb
70a863b10. Records the farm host-availability waiver and the operational
drift found during the promotion.
frame() cleared hud_actions before build_hud, so RunVerb/ToggleWindow
actions pushed by handle_key's toolbar branch (host loop, pre-frame)
were discarded every frame — number-row keys played the ToolbarUse cue
and did nothing, while pointer clicks (pushed during build_hud, after
the clear) worked. The drain already leaves the vec empty; drop the
early clear. 439 tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants