Skip to main content

jugar_web/
lib.rs

1//! # jugar-web
2//!
3//! WASM browser integration for the Jugar game engine.
4//!
5//! This crate provides the web platform layer that bridges Jugar to browsers.
6//! All game logic runs in Rust/WASM with **ABSOLUTE ZERO JavaScript computation**.
7//!
8//! ## Architecture
9//!
10//! ```text
11//! ┌─────────────────────────────────────────────────────────────┐
12//! │                      Browser (JavaScript)                    │
13//! │  - Event listeners (keyboard, mouse, touch)                  │
14//! │  - requestAnimationFrame loop                               │
15//! │  - Canvas2D rendering (drawing only)                        │
16//! └─────────────────────────┬────────────────────────────────────┘
17//!                           │ JSON Events ↓  ↑ JSON Commands
18//! ┌─────────────────────────┴────────────────────────────────────┐
19//! │                      WebPlatform (Rust/WASM)                  │
20//! │  - Input translation (browser events → InputState)          │
21//! │  - Game logic (Pong, etc.)                                  │
22//! │  - Render command generation (Canvas2DCommand)              │
23//! │  - Time management (DOMHighResTimeStamp → seconds)          │
24//! └──────────────────────────────────────────────────────────────┘
25//! ```
26//!
27//! ## Usage
28//!
29//! ```javascript
30//! // JavaScript (minimal event forwarding + Canvas2D execution)
31//! import init, { WebPlatform } from './jugar_web.js';
32//!
33//! const platform = new WebPlatform('{"width":800,"height":600}');
34//! const events = [];
35//!
36//! document.addEventListener('keydown', (e) => {
37//!     events.push({ event_type: 'KeyDown', timestamp: e.timeStamp, data: { key: e.code } });
38//! });
39//!
40//! function frame(timestamp) {
41//!     const commands = JSON.parse(platform.frame(timestamp, JSON.stringify(events)));
42//!     events.length = 0;
43//!
44//!     // Execute Canvas2D commands
45//!     for (const cmd of commands) {
46//!         switch (cmd.type) {
47//!             case 'Clear': ctx.fillStyle = rgba(cmd.color); ctx.fillRect(0, 0, w, h); break;
48//!             case 'FillRect': ctx.fillStyle = rgba(cmd.color); ctx.fillRect(cmd.x, cmd.y, cmd.width, cmd.height); break;
49//!             // ...
50//!         }
51//!     }
52//!
53//!     requestAnimationFrame(frame);
54//! }
55//! ```
56
57#![forbid(unsafe_code)]
58#![warn(missing_docs)]
59#![allow(clippy::doc_markdown)]
60// These clippy lints are too strict for practical use in this crate
61#![allow(clippy::std_instead_of_alloc)] // VecDeque from std is fine
62#![allow(clippy::std_instead_of_core)] // std::cmp::Ordering is fine
63#![allow(clippy::missing_const_for_fn)] // Many functions can't be const yet
64
65pub mod ai;
66pub mod audio;
67pub mod compute;
68pub mod demo;
69pub mod input;
70pub mod juice;
71pub mod loadtest;
72pub mod platform;
73pub mod render;
74pub mod simd;
75pub mod simulation;
76pub mod time;
77pub mod trace;
78
79#[cfg(test)]
80mod simulation_tests;
81
82// Re-export main types for convenience
83pub use ai::{
84    DeterminismConfig, DifficultyProfile, FlowChannel, FlowTheoryConfig, ModelMetadata,
85    PlayerMetrics, PongAI, PongAIModel,
86};
87pub use audio::{AudioEvent, ProceduralAudio};
88pub use compute::{
89    detect_compute_capability, ComputeBenchmarkResult, ComputeCapability, ComputeDemo,
90    ComputeDemoState, ComputeTier, GpuShaderInfo, ShaderType, PARTICLE_PHYSICS_WGSL,
91};
92pub use demo::{Attribution, DemoState, GameMode, PerformanceStats, SpeedMultiplier};
93pub use input::{
94    process_input_events, translate_gamepad_axis, translate_gamepad_button, translate_key,
95    translate_mouse_button, BrowserEventData, BrowserInputEvent, InputTranslationError,
96};
97pub use loadtest::{
98    AnomalyResult, ChaosConfig, ChaosResults, ChaosScenario, DriftDetector, DriftReport,
99    FrameTimeReport, FrameTimeStats, LoadTestConfig, LoadTestResult, LoadTestSummary,
100};
101pub use platform::{
102    DebugInfo, FrameOutput, GameState, PongGame, WebConfig, WebGame, WebPlatform, WebPlatformError,
103};
104pub use render::{
105    convert_render_command, convert_render_queue, Canvas2DCommand, Color, RenderFrame, TextAlign,
106    TextBaseline,
107};
108pub use simd::{
109    batch_distance_squared, batch_particle_update, batch_update_positions, check_paddle_collisions,
110    detect_compute_backend, trueno_backend_to_compute_backend, ComputeBackend, SimdBenchmark,
111    SimdVec2,
112};
113pub use simulation::{
114    check_invariants, FailureReplay, FuzzGenerator, GameStateSnapshot, InvariantViolation,
115    MonteCarloConfig, TestResult, TestTier, TimestampedInput,
116};
117pub use time::{
118    calculate_delta_time, clamp_delta_time, dom_timestamp_to_seconds, seconds_to_dom_timestamp,
119    FrameTimer, DEFAULT_MAX_DELTA_TIME, TARGET_DT_120FPS, TARGET_DT_30FPS, TARGET_DT_60FPS,
120};
121pub use trace::{
122    AdaptiveSnapshotter, BufferPolicy, Fixed32, FrameRecord, GameTracer, InputEvent,
123    InputEventType, QueryResult, SnapshotDecision, TraceBuffer, TraceError, TraceQuery, TraceStats,
124    TracerConfig,
125};
126
127#[cfg(test)]
128#[allow(clippy::assertions_on_constants)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_public_exports() {
134        // Verify all public exports are accessible
135        let _ = WebConfig::default();
136        let _ = Color::BLACK;
137        let _ = TextAlign::default();
138        let _ = TextBaseline::default();
139        let _ = RenderFrame::new();
140        let _ = FrameTimer::new();
141        let _ = dom_timestamp_to_seconds(1000.0);
142    }
143
144    #[test]
145    fn test_input_exports() {
146        let _ = translate_key("Space");
147        let _ = translate_mouse_button(0);
148        let _ = translate_gamepad_button(0);
149        let _ = translate_gamepad_axis(0);
150    }
151
152    #[test]
153    fn test_time_constants() {
154        assert!(DEFAULT_MAX_DELTA_TIME > 0.0);
155        assert!(TARGET_DT_60FPS > 0.0);
156        assert!(TARGET_DT_30FPS > TARGET_DT_60FPS);
157        assert!(TARGET_DT_120FPS < TARGET_DT_60FPS);
158    }
159}