Builds the browser game around the existing Rust runtime: a window into the universe, not a second simulation. Pure std, no external crates. New crates: - protocol: versioned, hashable client/server messages + hand-rolled JSON value and total parser (malformed packet -> Err, never panic). - game_runtime: authoritative match state. Resolves turns through the INDEPENDENT interpreter (runtime_under_test::native_resolve), not the reference engine; filters visibility/knowledge; records and regenerates replays. A match is a pure function of (seed, roster, ordered inputs). - web_assets/web_client: embedded browser client (arena, rune editor, knowledge panels, replay viewer) + static HTTP delivery. - server: std::net HTTP + WebSocket (hand-rolled SHA-1/base64/RFC-6455 framing), turn timer, disconnect handling, panic-proof dispatch, poison-tolerant lock. - web_tests: dependency-free WebSocket test client + Phase H gates. Trust hardening per review: - game_runtime no longer delegates to reference_runtime::execute; it runs the independent interpreter that the runtime-equivalence gate proves correct. - Protocol/socket/replay/visibility/resilience gates are merge-blocking (added to the merge_group-required job in merge-gates.yml): 1k matches/0 drift, 10k fuzz/0 panics, 100 headless socket E2E, 0 hidden-state leaks. - Rendered-browser E2E is marked EXTERNAL-BLOCKED: Playwright runs advisory-only (continue-on-error, artifacts) until CI infrastructure with a browser exists; it is treated as unsatisfied, not green. The headless 100-match gate is labeled protocol-level coverage, not rendered-browser coverage. - README documents the hand-rolled crypto/parser audit risk explicitly. Fixes an integer-overflow panic in observed-volatility inference (i64 sum / abs near i64::MIN) that could poison the server mutex. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
45 lines
1.6 KiB
Rust
45 lines
1.6 KiB
Rust
//! `web_assets` — the browser client's static files, embedded at compile time
|
|
//! so the server ships as a single binary with no runtime filesystem
|
|
//! dependency. The actual HTML/CSS/JS live under `assets/`.
|
|
|
|
pub const INDEX_HTML: &str = include_str!("../assets/index.html");
|
|
pub const STYLE_CSS: &str = include_str!("../assets/style.css");
|
|
pub const APP_JS: &str = include_str!("../assets/app.js");
|
|
|
|
/// A served asset: its bytes and MIME type.
|
|
pub struct Asset {
|
|
pub body: &'static str,
|
|
pub content_type: &'static str,
|
|
}
|
|
|
|
/// Resolve a request path to a static asset. `/` maps to the client shell.
|
|
pub fn resolve(path: &str) -> Option<Asset> {
|
|
match path {
|
|
"/" | "/index.html" => Some(Asset { body: INDEX_HTML, content_type: "text/html; charset=utf-8" }),
|
|
"/style.css" => Some(Asset { body: STYLE_CSS, content_type: "text/css; charset=utf-8" }),
|
|
"/app.js" => Some(Asset { body: APP_JS, content_type: "application/javascript; charset=utf-8" }),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn shell_and_assets_resolve() {
|
|
assert!(resolve("/").is_some());
|
|
assert!(resolve("/app.js").is_some());
|
|
assert!(resolve("/style.css").is_some());
|
|
assert!(resolve("/nope").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn client_only_sends_intent() {
|
|
// Guard against the client ever embedding a second simulation: the
|
|
// browser code must not reference the reference engine internals.
|
|
assert!(!APP_JS.contains("EngineConfig"));
|
|
assert!(APP_JS.contains("only ever sends INTENT"));
|
|
}
|
|
}
|