chore(release): final release commit for 0.1.0

this commit includes a whole lotta fuck yeah, a whole lotta we fuckin
got this, and a lot of "please change the future."

i hope it works.

Signed-off-by: Sienna Meridian Satterwhite <sienna@r3t.io>
This commit is contained in:
2026-02-06 20:10:51 +00:00
parent 5838b2dd6a
commit fdba3903cc
21 changed files with 4218 additions and 248 deletions

2436
.claude/rust-guidelines.txt Normal file

File diff suppressed because it is too large Load Diff

68
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,68 @@
---
name: Bug Report
about: Report a bug to help us improve Marathon
title: '[BUG] '
labels: bug
assignees: ''
---
## Bug Description
A clear and concise description of what the bug is.
## Minimal, Complete, Verifiable Example (MCVE)
Please provide the **smallest possible code example** that demonstrates the bug. This helps us reproduce and fix the issue faster.
### Minimal Code Example
```rust
// Paste your minimal reproducible code here
// Remove anything not necessary to demonstrate the bug
```
### Steps to Reproduce
1.
2.
3.
4.
### Expected Behavior
What you expected to happen:
### Actual Behavior
What actually happened:
## Environment
- **OS**: [e.g., macOS 15.0, iOS 18.2]
- **Rust Version**: [e.g., 1.85.0 - run `rustc --version`]
- **Marathon Version/Commit**: [e.g., v0.1.0 or commit hash]
- **Platform**: [Desktop / iOS Simulator / iOS Device]
## Logs/Stack Traces
If applicable, paste any error messages or stack traces here:
```
paste logs here
```
## Screenshots/Videos
If applicable, add screenshots or videos to help explain the problem.
## Additional Context
Add any other context about the problem here. For example:
- Does it happen every time or intermittently?
- Did this work in a previous version?
- Are you running multiple instances?
- Any relevant configuration or network setup?
## Possible Solution
If you have ideas about what might be causing the issue or how to fix it, please share them here.

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Question or Discussion
url: https://github.com/r3t-studios/marathon/discussions
about: Ask questions or discuss ideas with the community
- name: Security Vulnerability
url: https://github.com/r3t-studios/marathon/security/policy
about: Please report security issues privately (see SECURITY.md)

View File

@@ -0,0 +1,72 @@
---
name: Feature Request
about: Suggest a new feature or enhancement for Marathon
title: '[FEATURE] '
labels: enhancement
assignees: ''
---
## Problem Statement
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. For example:
- "I'm always frustrated when..."
- "It's difficult to..."
- "Users need to be able to..."
## Feature Request (Given-When-Then Format)
Please describe your feature request using the Given-When-Then format to make the behavior clear:
### Scenario 1: [Brief scenario name]
**Given** [initial context or preconditions]
**When** [specific action or event]
**Then** [expected outcome]
**Example:**
- **Given** I am editing a collaborative document with 3 other peers
- **When** I lose network connectivity for 5 minutes
- **Then** my local changes should be preserved and sync automatically when I reconnect
### Scenario 2: [Additional scenario if needed]
**Given** [initial context]
**When** [action]
**Then** [outcome]
## Alternatives Considered
**Describe alternatives you've considered.**
Have you thought of other ways to solve this problem? What are the pros and cons of different approaches?
## Technical Considerations
**Do you have thoughts on implementation?**
If you have ideas about how this could be implemented technically, share them here. For example:
- Which modules might be affected
- Potential challenges or dependencies
- Performance implications
- Breaking changes required
## Additional Context
Add any other context, mockups, screenshots, or examples from other projects that illustrate the feature.
## Priority/Impact
How important is this feature to you or your use case?
- [ ] Critical - blocking current work
- [ ] High - would significantly improve workflow
- [ ] Medium - nice to have
- [ ] Low - minor improvement
## Willingness to Contribute
- [ ] I'm willing to implement this feature
- [ ] I can help test this feature
- [ ] I can help with documentation
- [ ] I'm just suggesting the idea

117
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,117 @@
## Description
<!-- Provide a clear and concise description of what this PR does -->
## Related Issues
<!-- Link to related issues using #issue_number -->
Fixes #
Relates to #
## Type of Change
<!-- Mark relevant items with an [x] -->
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
- [ ] Refactoring (no functional changes)
- [ ] Performance improvement
- [ ] Test coverage improvement
## Changes Made
<!-- List the specific changes in this PR -->
-
-
-
## Testing Performed
<!-- Describe the testing you've done -->
- [ ] All existing tests pass (`cargo nextest run`)
- [ ] Added new tests for new functionality
- [ ] Tested manually on desktop
- [ ] Tested manually on iOS (if applicable)
- [ ] Tested with multiple instances
- [ ] Tested edge cases and error conditions
### Test Details
<!-- Provide specific details about your testing -->
**Desktop:**
-
**iOS:** (if applicable)
-
**Multi-instance:** (if applicable)
-
## Documentation
<!-- Mark relevant items with an [x] -->
- [ ] Updated relevant documentation in `/docs`
- [ ] Updated README.md (if public API changed)
- [ ] Added doc comments to new public APIs
- [ ] Updated CHANGELOG.md
## Code Quality
<!-- Confirm these items -->
- [ ] Code follows project style guidelines
- [ ] Ran `cargo +nightly fmt`
- [ ] Ran `cargo clippy` and addressed warnings
- [ ] No new compiler warnings
- [ ] Added meaningful variable/function names
## AI Usage
<!-- If you used AI tools, briefly note how (see AI_POLICY.md) -->
<!-- You don't need to disclose simple autocomplete, only substantial AI assistance -->
- [ ] No AI assistance used
- [ ] Used AI tools (brief description below)
<!-- If used: -->
<!-- AI tool: [e.g., Claude, Copilot] -->
<!-- How: [e.g., "Used to generate boilerplate, then reviewed and modified"] -->
<!-- I reviewed, understand, and am accountable for all code in this PR -->
## Breaking Changes
<!-- If this is a breaking change, describe what breaks and how to migrate -->
**Does this PR introduce breaking changes?**
- [ ] No
- [ ] Yes (describe below)
<!-- If yes: -->
<!-- - What breaks: -->
<!-- - Migration path: -->
## Screenshots/Videos
<!-- If applicable, add screenshots or videos showing the changes -->
## Checklist
<!-- Final checks before requesting review -->
- [ ] My code follows the project's coding standards
- [ ] I have tested my changes thoroughly
- [ ] I have updated relevant documentation
- [ ] I have added tests that prove my fix/feature works
- [ ] All tests pass locally
- [ ] I have read and followed the [CONTRIBUTING.md](../CONTRIBUTING.md) guidelines
- [ ] I understand and accept the [AI_POLICY.md](../AI_POLICY.md)
## Additional Notes
<!-- Any additional information reviewers should know -->

136
AI_POLICY.md Normal file
View File

@@ -0,0 +1,136 @@
# AI and Machine Learning Usage Policy
## Core Principle: Human Accountability
Every contribution to Marathon must have a human who:
- **Made the decisions** about what to build and how to build it
- **Understands the code, design, or content** they're submitting
- **Takes responsibility** for the outcome and any issues that arise
- **Can be held accountable** for the contribution
AI and ML tools are welcome as assistants, but they cannot:
- Make architectural or design decisions
- Choose between technical trade-offs
- Take responsibility for bugs or issues
- Be credited as contributors
## Context: Pragmatism at a Small Scale
We're a tiny studio with limited resources. We can't afford large teams, professional translators, or extensive QA departments. **Machine learning tools help us punch above our weight class** - they let us move faster, support more languages, and catch bugs we'd otherwise miss.
We use these tools not to replace human judgment, but to stretch our small team's capacity. This is about working **smart with what we have**, not taking shortcuts that compromise quality or accountability.
We're using ethical and responsible machine learning as much as possible while ensuring that we are not erasing human contributions while we are resource-constrained.
## The Blurry Line
**Here's the honest truth:** The line between "generative AI" and "assistive AI" is fuzzy and constantly shifting. Is IDE autocomplete assistive? What about when it suggests entire functions? What about pair-programming with an LLM?
**We don't have perfect answers.** What we do have is a principle: **a human must make the decisions and be accountable.**
If you're unsure whether your use of AI crosses a line, ask yourself:
- **"Do I understand what this code does and why?"**
- **"Did I decide this was the right approach, or did the AI?"**
- **"Can I maintain and debug this?"**
- **"Am I comfortable being accountable for this?"**
If you answer "yes" to those questions, you're probably fine. If you're still uncertain, open a discussion - we'd rather have the conversation than enforce rigid rules that don't match reality.
## What This Looks Like in Practice
### Acceptable Use
**"I used Claude/Copilot to help write this function, I reviewed it, I understand it, and I'm responsible for it."**
- You directed the tool
- You reviewed and understood the output
- You made the decision to use this approach
- You take responsibility for the result
**"I directed an LLM to implement my design, then verified it meets requirements."**
- You designed the solution
- You used AI to speed up implementation
- You verified correctness
- You own the outcome
**"I used machine translation as a starting point, then reviewed and corrected the output."**
- You acknowledge the limitations of automated translation
- You applied human judgment to the result
- You ensure accuracy and appropriateness
### Not Acceptable
**"Claude wrote this, I pasted it in, seems fine."**
- No understanding of the code
- No verification of correctness
- Cannot maintain or debug
- Cannot explain design decisions
**"I asked an LLM what architecture to use and implemented its suggestion."**
- The AI made the architectural decision
- No human judgment about trade-offs
- No accountability for the choice
**"I'm submitting this AI-generated documentation without reviewing it."**
- No verification of accuracy
- No human oversight
- Cannot vouch for quality
## Why This Matters
Marathon itself was largely written with AI assistance under human direction. **That's fine!** What matters is:
1. **A human made every architectural decision**
2. **A human is accountable for every line of code**
3. **A human can explain why things work the way they do**
4. **Humans take credit AND responsibility**
Think of AI like a compiler, a library, or a really capable intern - it's a tool that amplifies human capability, but **the human is always the one making decisions and being accountable**.
## For Contributors
We don't care what tools you use to be productive. We care that:
- **You made the decisions** (not the AI)
- **You understand what you're submitting**
- **You're accountable** for the contribution
- **You can maintain it** if issues arise
Use whatever tools help you work effectively, but you must be able to answer "why did you make this choice?" with human reasoning, not "the AI suggested it."
### When Contributing
You don't need to disclose every time you use autocomplete or ask an LLM a question. We trust you to:
- Use tools responsibly
- Understand your contributions
- Take ownership of your work
If you're doing something novel or pushing boundaries with AI assistance, mentioning it in your PR is welcome - it helps us all learn and navigate this space together.
## What We Use
For transparency, here's where Marathon currently uses machine learning:
- **Development assistance** - IDE tools, code completion, pair programming with LLMs
- **Translation tooling** - Machine translation for internationalization (human-reviewed)
- **Performance analysis** - Automated profiling and optimization suggestions
- **Code review assistance** - Static analysis and potential bug detection
- **Documentation help** - Grammar checking, clarity improvements, translation
In all cases, humans review, approve, and take responsibility for the output.
## The Bottom Line
**Machines can't be held accountable, so humans must make all decisions.**
Use AI tools to help you work faster and smarter, but you must understand and be accountable for what you contribute. When in doubt, ask yourself:
**"Can a machine be blamed if this breaks?"**
If yes, you've crossed the line.
## Questions or Concerns?
This policy will evolve as we learn more about working effectively with AI tools. If you have questions, concerns, or suggestions, please open a discussion. We're figuring this out together.
---
*This policy reflects our values as of February 2026. As technology and our understanding evolve, so will this document.*

359
ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,359 @@
# Marathon Architecture
This document provides a high-level overview of Marathon's architecture to help contributors understand the system's design and organization.
## Table of Contents
- [Overview](#overview)
- [Core Principles](#core-principles)
- [System Architecture](#system-architecture)
- [Crate Organization](#crate-organization)
- [Key Components](#key-components)
- [Data Flow](#data-flow)
- [Technology Decisions](#technology-decisions)
- [Design Constraints](#design-constraints)
## Overview
Marathon is a **peer-to-peer game engine development kit** built on conflict-free replicated data types (CRDTs). It enables developers to build multiplayer games where players can interact with shared game state in real-time, even across network partitions, with automatic reconciliation.
**Key Characteristics:**
- **Decentralized** - No central game server required, all players are equal peers
- **Offline-first** - Gameplay continues during network partitions
- **Eventually consistent** - All players converge to the same game state
- **Real-time** - Player actions propagate with minimal latency
- **Persistent** - Game state survives application restarts
## Core Principles
1. **CRDTs for Consistency** - Use mathematically proven data structures that guarantee eventual consistency for multiplayer game state
2. **Bevy ECS First** - Build on Bevy's Entity Component System for game development flexibility
3. **Zero Trust Networking** - Assume peers may be malicious (future work for competitive games)
4. **Separation of Concerns** - Clear boundaries between networking, persistence, and game logic
5. **Performance Matters** - Optimize for low latency and high throughput suitable for real-time games
## System Architecture
```mermaid
graph TB
subgraph App["Game Layer"]
Demo[Demo Game / Your Game]
Actions[Game Actions]
Selection[Entity Selection]
Input[Input Handling]
Render[Rendering]
end
subgraph Core["libmarathon Core"]
Net[Networking<br/>• CRDT Sync<br/>• Gossip<br/>• Sessions<br/>• Op Apply]
Engine[Engine Core<br/>• Event Loop<br/>• Commands<br/>• Discovery<br/>• Bridge]
Persist[Persistence<br/>• SQLite<br/>• Type Registry<br/>• Migrations<br/>• Metrics]
end
subgraph Foundation["Foundation Layer"]
Bevy[Bevy ECS<br/>• Entities<br/>• Components<br/>• Systems]
Iroh[iroh P2P<br/>• QUIC<br/>• Gossip<br/>• Discovery]
end
Demo --> Actions
Demo --> Selection
Demo --> Input
Demo --> Render
Actions --> Engine
Selection --> Engine
Input --> Engine
Render --> Engine
Engine --> Net
Engine --> Persist
Net --> Persist
Net --> Iroh
Engine --> Bevy
Persist --> Bevy
```
## Crate Organization
Marathon is organized as a Rust workspace with four crates:
### `libmarathon` (Core Library)
**Purpose**: The heart of Marathon, providing networking, persistence, and CRDT synchronization.
**Key Modules:**
```
libmarathon/
├── networking/ # P2P networking and CRDT sync
│ ├── crdt/ # CRDT implementations (OR-Set, RGA, LWW)
│ ├── operations/ # Network operations and vector clocks
│ ├── gossip/ # Gossip protocol bridge to iroh
│ ├── session/ # Session management
│ └── entity_map/ # UUID ↔ Entity mapping
├── persistence/ # SQLite-backed state persistence
│ ├── database/ # SQLite connection and WAL
│ ├── registry/ # Type registry for reflection
│ └── health/ # Health checks and metrics
├── engine/ # Core engine logic
│ ├── networking_manager/ # Network event loop
│ ├── commands/ # Bevy commands
│ └── game_actions/ # User action handling
├── debug_ui/ # egui debug interface
├── render/ # Vendored Bevy render pipeline
├── transform/ # Vendored transform with rkyv
└── platform/ # Platform-specific code (iOS/desktop)
```
### `app` (Demo Game)
**Purpose**: Demonstrates Marathon capabilities with a simple multiplayer cube game.
**Key Files:**
- `main.rs` - Entry point with CLI argument handling
- `engine_bridge.rs` - Connects Bevy game to Marathon engine
- `cube.rs` - Demo game entity implementation
- `session.rs` - Multiplayer session lifecycle management
- `input/` - Input handling (keyboard, touch, Apple Pencil)
- `rendering/` - Rendering setup and camera
### `macros` (Procedural Macros)
**Purpose**: Code generation for serialization and deserialization.
Built on Bevy's macro infrastructure for consistency with the ecosystem.
### `xtask` (Build Automation)
**Purpose**: Automate iOS build and deployment using the cargo-xtask pattern.
**Commands:**
- `ios-build` - Build for iOS simulator/device
- `ios-deploy` - Deploy to connected device
- `ios-run` - Build and run on simulator
## Key Components
### 1. CRDT Synchronization Layer
**Location**: `libmarathon/src/networking/`
**Purpose**: Implements the CRDT-based synchronization protocol.
**Key Concepts:**
- **Operations** - Immutable change events (Create, Update, Delete)
- **Vector Clocks** - Track causality across peers
- **OR-Sets** - Observed-Remove Sets for entity membership
- **RGA** - Replicated Growable Array for ordered sequences
- **LWW** - Last-Write-Wins for simple values
**Protocol Flow:**
```mermaid
sequenceDiagram
participant A as Peer A
participant G as Gossip Network
participant B as Peer B
A->>A: Generate Op<br/>(with vector clock)
A->>G: Broadcast Op
G->>B: Deliver Op
B->>B: Apply Op<br/>(update vector clock)
B->>G: ACK
G->>A: ACK
```
See [RFC 0001](docs/rfcs/0001-crdt-gossip-sync.md) for detailed protocol specification.
### 2. Persistence Layer
**Location**: `libmarathon/src/persistence/`
**Purpose**: Persist game state to SQLite with minimal overhead.
**Architecture**: Three-tier system
```mermaid
graph TD
A[In-Memory State<br/>Bevy ECS - Dirty Tracking] -->|Batch writes<br/>every N frames| B[Write Buffer<br/>Async Batching]
B -->|Flush to disk| C[SQLite Database<br/>WAL Mode]
style A fill:#e1f5ff
style B fill:#fff4e1
style C fill:#e8f5e9
```
**Key Features:**
- **Automatic persistence** - Components marked with `Persisted` save automatically
- **Type registry** - Reflection-based serialization
- **WAL mode** - Write-Ahead Logging for crash safety
- **Migrations** - Schema versioning support
See [RFC 0002](docs/rfcs/0002-persistence-strategy.md) for detailed design.
### 3. Networking Manager
**Location**: `libmarathon/src/engine/networking_manager.rs`
**Purpose**: Bridge between Bevy and the iroh networking stack.
**Responsibilities:**
- Manage peer connections and discovery
- Route operations to/from gossip network
- Maintain session state
- Handle join protocol for new peers
### 4. Entity Mapping System
**Location**: `libmarathon/src/networking/entity_map.rs`
**Purpose**: Map between Bevy's local `Entity` IDs and global `UUID`s.
**Why This Exists**: Bevy assigns local sequential entity IDs that differ across instances. We need stable UUIDs for networked entities that all peers agree on.
```mermaid
graph LR
A[Bevy Entity<br/>Local ID: 123] <-->|Bidirectional<br/>Mapping| B[UUID<br/>550e8400-....-446655440000]
style A fill:#ffebee
style B fill:#e8f5e9
```
### 5. Debug UI System
**Location**: `libmarathon/src/debug_ui/`
**Purpose**: Provide runtime inspection of internal state.
Built with egui for immediate-mode GUI, integrated into Bevy's render pipeline.
**Features:**
- View connected peers
- Inspect vector clocks
- Monitor operation log
- Check persistence metrics
- View entity mappings
## Data Flow
### Local Change Flow
```mermaid
graph TD
A[User Input] --> B[Bevy System<br/>e.g., move entity]
B --> C[Generate CRDT<br/>Operation]
C --> D[Apply Operation<br/>Locally]
D --> E[Broadcast via<br/>Gossip]
D --> F[Mark Dirty for<br/>Persistence]
style A fill:#e3f2fd
style E fill:#fff3e0
style F fill:#f3e5f5
```
### Remote Change Flow
```mermaid
graph TD
A[Receive Operation<br/>from Gossip] --> B[Check Vector Clock<br/>causality]
B --> C[Apply Operation<br/>to ECS]
C --> D[Update Local<br/>Vector Clock]
C --> E[Mark Dirty for<br/>Persistence]
style A fill:#fff3e0
style C fill:#e8f5e9
style E fill:#f3e5f5
```
### Persistence Flow
```mermaid
graph TD
A[Every N Frames] --> B[Identify Dirty<br/>Entities]
B --> C[Serialize to<br/>Write Buffer]
C --> D[Batch Write<br/>to SQLite]
D --> E[Clear Dirty<br/>Flags]
E --> A
style A fill:#e8f5e9
style D fill:#f3e5f5
```
## Technology Decisions
### Why Bevy?
- **ECS architecture** maps perfectly to game development
- **Cross-platform** (desktop, mobile, web)
- **Active community** and ecosystem
- **Performance** through data-oriented design
### Why iroh?
- **QUIC-based** - Modern, efficient transport
- **NAT traversal** - Works behind firewalls
- **Gossip protocol** - Epidemic broadcast for multi-peer
- **Rust-native** - Zero-cost integration
### Why SQLite?
- **Embedded** - No server required
- **Battle-tested** - Reliable persistence
- **WAL mode** - Good write performance
- **Cross-platform** - Works everywhere
### Why CRDTs?
- **No central authority** - True P2P
- **Offline-first** - Work without connectivity
- **Provable consistency** - Mathematical guarantees
- **No conflict resolution UI** - Users don't see conflicts
## Design Constraints
### Current Limitations
1. **No Authentication** - All peers are trusted (0.1.x)
2. **No Authorization** - All peers have full permissions
3. **No Encryption** - Beyond QUIC's transport security
4. **Limited Scalability** - Not tested beyond ~10 peers
5. **Desktop + iOS Only** - Web and other platforms planned
### Performance Targets
- **Operation latency**: < 50ms peer-to-peer
- **Persistence overhead**: < 5% frame time
- **Memory overhead**: < 10MB for typical session
- **Startup time**: < 2 seconds
### Intentional Non-Goals
- **Central server architecture** - Stay decentralized
- **Strong consistency** - Use eventual consistency
- **Traditional database** - Use CRDTs, not SQL queries
- **General-purpose engine** - Focus on collaboration
## Related Documentation
- [RFC 0001: CRDT Synchronization Protocol](docs/rfcs/0001-crdt-gossip-sync.md)
- [RFC 0002: Persistence Strategy](docs/rfcs/0002-persistence-strategy.md)
- [RFC 0003: Sync Abstraction](docs/rfcs/0003-sync-abstraction.md)
- [RFC 0004: Session Lifecycle](docs/rfcs/0004-session-lifecycle.md)
- [RFC 0005: Spatial Audio System](docs/rfcs/0005-spatial-audio-vendoring.md)
- [RFC 0006: Agent Simulation Architecture](docs/rfcs/0006-agent-simulation-architecture.md)
## Questions?
If you're working on Marathon and something isn't clear:
1. Check the RFCs in `docs/rfcs/`
2. Search existing issues/discussions
3. Ask in GitHub Discussions
4. Reach out to maintainers
---
*This architecture will evolve. When making significant architectural changes, consider updating this document or creating a new RFC.*

65
CHANGELOG.md Normal file
View File

@@ -0,0 +1,65 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.1.0] - 2026-02-06
### Added
#### Core Features
- CRDT-based synchronization using OR-Sets, RGA, and Last-Write-Wins semantics
- Peer-to-peer networking built on iroh with QUIC transport
- Gossip-based message broadcasting for multi-peer coordination
- Offline-first architecture with automatic reconciliation
- SQLite-backed persistence with WAL mode
- Cross-platform support for macOS desktop and iOS
#### Demo Application
- Replicated cube demo showcasing real-time collaboration
- Multiple instance support for local testing
- Apple Pencil input support on iPad
- Real-time cursor and selection synchronization
- Debug UI for inspecting internal state
#### Infrastructure
- Bevy 0.17 ECS integration
- Zero-copy serialization with rkyv
- Automated iOS build tooling via xtask
- Comprehensive RFC documentation covering architecture decisions
### Architecture
- **Networking Layer**: CRDT sync protocol, entity mapping, vector clocks, session management
- **Persistence Layer**: Three-tier system (in-memory → write buffer → SQLite)
- **Engine Core**: Event loop, networking manager, peer discovery, game actions
- **Platform Support**: iOS and desktop with platform-specific input handling
### Documentation
- RFC 0001: CRDT Synchronization Protocol
- RFC 0002: Persistence Strategy
- RFC 0003: Sync Abstraction
- RFC 0004: Session Lifecycle
- RFC 0005: Spatial Audio System
- RFC 0006: Agent Simulation Architecture
- iOS deployment guide
- Estimation methodology documentation
### Known Issues
- API is unstable and subject to change
- Limited documentation for public APIs
- Performance optimizations still needed for large-scale collaboration
- iOS builds require manual Xcode configuration
### Notes
This is an early development release (version 0.x.y). The API is unstable and breaking changes are expected. Not recommended for production use.
[unreleased]: https://github.com/r3t-studios/marathon/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/r3t-studios/marathon/releases/tag/v0.1.0

148
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,148 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Addressing and Repairing Harm
If you are being harmed or notice that someone else is being harmed, or have any
other concerns, please contact the community leaders responsible for enforcement
at sienna@linux.com. All reports will be handled with discretion.
We are committed to addressing harm in a manner that is respectful to victims
and survivors of violations of this Code of Conduct. When community leaders
receive a report of a possible violation, they will:
1. **Acknowledge receipt** of the report
2. **Assess the situation** and gather necessary information
3. **Determine appropriate action** using the guidelines below
4. **Communicate with all parties** involved
5. **Take action** to address and repair harm
6. **Follow up** to ensure the situation is resolved
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Restorative Justice
We believe in restorative justice and creating opportunities for those who have
violated the Code of Conduct to repair harm and reintegrate into the community
when appropriate. This may include:
* Facilitated conversations between affected parties
* Public acknowledgment of harm and apology
* Education and learning opportunities
* Community service or contributions
* Gradual reintegration with monitoring
The possibility of restoration depends on:
* The severity of the violation
* The willingness of the violator to acknowledge harm
* The consent and comfort of those harmed
* The assessment of community leaders
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 3.0, available at
[https://www.contributor-covenant.org/version/3/0/code_of_conduct.html][v3.0].
The "Addressing and Repairing Harm" section is inspired by the restorative
justice approach outlined in Contributor Covenant 3.0.
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v3.0]: https://www.contributor-covenant.org/version/3/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

343
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,343 @@
# Contributing to Marathon
Thank you for your interest in contributing to Marathon! We're excited to work with you.
This document provides guidelines for contributing to the project. Following these guidelines helps maintain code quality and makes the review process smoother for everyone.
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [Getting Started](#getting-started)
- [Development Environment Setup](#development-environment-setup)
- [How to Contribute](#how-to-contribute)
- [Coding Standards](#coding-standards)
- [Testing](#testing)
- [Pull Request Process](#pull-request-process)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Features](#suggesting-features)
- [AI Usage Policy](#ai-usage-policy)
- [Questions?](#questions)
## Code of Conduct
This project adheres to the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to the project maintainers.
## Getting Started
1. **Fork the repository** on GitHub
2. **Clone your fork** locally
3. **Set up your development environment** (see below)
4. **Create a branch** for your changes
5. **Make your changes** with clear commit messages
6. **Test your changes** thoroughly
7. **Submit a pull request**
## Development Environment Setup
### Prerequisites
- **Rust** 2024 edition or later (install via [rustup](https://rustup.rs/))
- **macOS** (for macOS desktop and iOS development)
- **Xcode** and iOS simulator (for iOS development)
- **Linux** (for Linux desktop development)
- **Windows** (for Windows desktop development)
- **Git** for version control
### Initial Setup
```bash
# Clone your fork
git clone https://github.com/user/marathon.git
cd marathon
# Add upstream remote
git remote add upstream https://github.com/r3t-studios/marathon.git
# Build the project
cargo build
# Run tests
cargo test
# Run the desktop demo
cargo run --package app
```
### iOS Development Setup
For iOS development, see our detailed [iOS Deployment Guide](docs/ios-deployment.md).
```bash
# Build for iOS simulator
cargo xtask ios-build
# Run on simulator
cargo xtask ios-run
```
### Useful Commands
```bash
# Check code without building
cargo check
# Run clippy for linting
cargo clippy
# Format code
cargo fmt
# Run tests with output
cargo nextest run -- --nocapture
# Build documentation
cargo doc --open
```
## How to Contribute
### Types of Contributions
We welcome many types of contributions:
- **Bug fixes** - Fix issues and improve stability
- **Features** - Implement new functionality (discuss first in an issue)
- **Documentation** - Improve or add documentation
- **Examples** - Create new examples or demos
- **Tests** - Add test coverage
- **Performance** - Optimize existing code
- **Refactoring** - Improve code quality
### Before You Start
For **bug fixes and small improvements**, feel free to open a PR directly.
For **new features or significant changes**:
1. **Open an issue first** to discuss the proposal
2. Wait for maintainer feedback before investing significant time
3. Reference the issue in your PR
This helps ensure your work aligns with project direction and avoids duplicate effort.
## Coding Standards
### Rust Style
- Follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/)
- Follow the [Rust Style Guide](https://microsoft.github.io/rust-guidelines/guidelines/index.html)
- Use `cargo +nightly fmt` to format code (run before committing)
- Address all `cargo clippy` warnings
- Use meaningful variable and function names
- Add doc comments (`///`) for public APIs
### Code Organization
- Keep modules focused and cohesive
- Prefer composition over inheritance
- Use Rust's type system to enforce invariants
- Avoid unnecessary `unsafe` code
### Documentation
- Add doc comments for all public types, traits, and functions
- Include examples in doc comments when helpful
- Update relevant documentation in `/docs` when making architectural changes
- Keep README.md in sync with current capabilities
### Commit Messages
Write clear, descriptive conventional commit messages:
```
Short summary (50 chars or less)
More detailed explanation if needed. Wrap at 72 characters.
- Bullet points are fine
- Use present tense ("Add feature" not "Added feature")
- Reference issues and PRs with #123
```
Good examples:
```
Add cursor synchronization to networking layer
Implement entity selection system for iOS
Fix panic in SQLite persistence during shutdown (#42)
```
## Testing
### Running Tests
```bash
# Run all tests
cargo nextest run
# Run tests for specific crate
cargo nextest run --package libmarathon
# Run specific test
cargo nextest run test_vector_clock_merge
# Run tests with output
cargo nextest run -- --nocapture
```
### Writing Tests
- Add unit tests in the same file as the code (in a `mod tests` block)
- Add integration tests in `tests/` directory
- Test edge cases and error conditions
- Keep tests focused and readable
- Use descriptive test names: `test_vector_clock_handles_concurrent_updates`
### Test Coverage
We aim for good test coverage, especially for:
- CRDT operations and synchronization logic
- Persistence layer operations
- Network protocol handling
- Error conditions and edge cases
You don't need 100% coverage, but core logic should be well-tested.
## Pull Request Process
### Before Submitting
1. **Update your branch** with latest upstream changes
```bash
git fetch upstream
git rebase upstream/mainline
```
2. **Run the test suite** and ensure all tests pass
```bash
cargo test
```
3. **Run clippy** and fix any warnings
```bash
cargo clippy
```
4. **Format your code**
```bash
cargo fmt
```
5. **Update documentation** if you changed APIs or behavior
### Submitting Your PR
1. **Push to your fork**
```bash
git push origin your-branch-name
```
2. **Open a pull request** on GitHub
3. **Fill out the PR template** with:
- Clear description of what changed and why
- Link to related issues
- Testing performed
- Screenshots/videos for UI changes
4. **Request review** from maintainers
### During Review
- Be responsive to feedback
- Make requested changes promptly
- Push updates to the same branch (they'll appear in the PR)
- Use "fixup" commits or force-push after addressing review comments
- Be patient - maintainers are volunteers with limited time
### After Approval
- Maintainers will merge your PR
- You can delete your branch after merging
- Celebrate! 🎉 You're now a Marathon contributor!
## Reporting Bugs
### Before Reporting
1. **Check existing issues** to avoid duplicates
2. **Verify it's a bug** and not expected behavior
3. **Test on the latest version** from mainline branch
### Bug Report Template
When opening a bug report, please include:
- **Description** - What went wrong?
- **Expected behavior** - What should have happened?
- **Actual behavior** - What actually happened?
- **Steps to reproduce** - Minimal steps to reproduce the issue
- **Environment**:
- OS version (macOS version, iOS version)
- Rust version (`rustc --version`)
- Marathon version or commit hash
- **Logs/Stack traces** - Error messages or relevant log output
- **Screenshots/Videos** - If applicable
### Security Issues
**Do not report security vulnerabilities in public issues.**
Please see our [Security Policy](SECURITY.md) for how to report security issues privately.
## Suggesting Features
We welcome feature suggestions! Here's how to propose them effectively:
### Before Suggesting
1. **Check existing issues and discussions** for similar ideas
2. **Consider if it aligns** with Marathon's goals (multiplayer game engine framework)
3. **Think about the scope** - is this a core feature or better as a plugin/extension?
### Feature Request Template
When suggesting a feature, please include:
- **Problem statement** - What problem does this solve?
- **Proposed solution** - How would this feature work?
- **Alternatives considered** - What other approaches did you think about?
- **Use cases** - Real-world scenarios where this helps
- **Implementation ideas** - Technical approach (if you have thoughts)
### Feature Discussion
- Maintainers will label feature requests as `enhancement`
- We'll discuss feasibility, scope, and priority
- Features that align with the roadmap are more likely to be accepted
- You're welcome to implement features you propose (with approval)
## AI Usage Policy
Marathon has specific guidelines around AI and ML tool usage. Please read our [AI Usage Policy](AI_POLICY.md) before contributing.
**Key points:**
- AI tools (Copilot, ChatGPT, etc.) are allowed for productivity
- You must understand and be accountable for all code you submit
- Humans make all architectural decisions, not AI
- When in doubt, ask yourself: "Can I maintain and debug this?"
## Questions?
- **General questions** - Open a [Discussion](https://github.com/yourusername/marathon/discussions)
- **Bug reports** - Open an [Issue](https://github.com/yourusername/marathon/issues)
- **Real-time chat** - [Discord/Slack link if you have one]
- **Email** - [maintainer email if appropriate]
## Recognition
All contributors will be recognized in our release notes and can be listed in AUTHORS file (coming soon).
---
Thank you for contributing to Marathon! Your effort helps make collaborative software better for everyone.

186
Cargo.lock generated
View File

@@ -44,8 +44,8 @@ dependencies = [
"accesskit_consumer", "accesskit_consumer",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-app-kit 0.2.2", "objc2-app-kit",
"objc2-foundation 0.2.2", "objc2-foundation",
] ]
[[package]] [[package]]
@@ -301,26 +301,6 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "arboard"
version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
dependencies = [
"clipboard-win",
"image",
"log",
"objc2 0.6.3",
"objc2-app-kit 0.3.2",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-foundation 0.3.2",
"parking_lot",
"percent-encoding",
"windows-sys 0.60.2",
"x11rb",
]
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.9" version = "0.3.9"
@@ -2116,15 +2096,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "clipboard-win"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
dependencies = [
"error-code",
]
[[package]] [[package]]
name = "cobs" name = "cobs"
version = "0.3.0" version = "0.3.0"
@@ -2990,12 +2961,6 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "error-code"
version = "3.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
[[package]] [[package]]
name = "euclid" name = "euclid"
version = "0.22.11" version = "0.22.11"
@@ -3044,26 +3009,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fax"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
dependencies = [
"fax_derive",
]
[[package]]
name = "fax_derive"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "fdeflate" name = "fdeflate"
version = "0.3.7" version = "0.3.7"
@@ -4064,7 +4009,6 @@ dependencies = [
"moxcms", "moxcms",
"num-traits", "num-traits",
"png", "png",
"tiff",
] ]
[[package]] [[package]]
@@ -4594,7 +4538,6 @@ name = "libmarathon"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arboard",
"async-channel", "async-channel",
"bevy", "bevy",
"bevy_app", "bevy_app",
@@ -4649,7 +4592,6 @@ dependencies = [
"proptest", "proptest",
"radsort", "radsort",
"rand 0.8.5", "rand 0.8.5",
"raw-window-handle",
"rkyv", "rkyv",
"rusqlite", "rusqlite",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
@@ -5428,22 +5370,10 @@ dependencies = [
"objc2 0.5.2", "objc2 0.5.2",
"objc2-core-data", "objc2-core-data",
"objc2-core-image", "objc2-core-image",
"objc2-foundation 0.2.2", "objc2-foundation",
"objc2-quartz-core", "objc2-quartz-core",
] ]
[[package]]
name = "objc2-app-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
"bitflags 2.10.0",
"objc2 0.6.3",
"objc2-core-graphics",
"objc2-foundation 0.3.2",
]
[[package]] [[package]]
name = "objc2-cloud-kit" name = "objc2-cloud-kit"
version = "0.2.2" version = "0.2.2"
@@ -5454,7 +5384,7 @@ dependencies = [
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-core-location", "objc2-core-location",
"objc2-foundation 0.2.2", "objc2-foundation",
] ]
[[package]] [[package]]
@@ -5465,7 +5395,7 @@ checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
dependencies = [ dependencies = [
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-foundation 0.2.2", "objc2-foundation",
] ]
[[package]] [[package]]
@@ -5477,7 +5407,7 @@ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-foundation 0.2.2", "objc2-foundation",
] ]
[[package]] [[package]]
@@ -5487,21 +5417,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"dispatch2",
"objc2 0.6.3",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags 2.10.0",
"dispatch2",
"objc2 0.6.3",
"objc2-core-foundation",
"objc2-io-surface",
] ]
[[package]] [[package]]
@@ -5512,7 +5427,7 @@ checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
dependencies = [ dependencies = [
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-foundation 0.2.2", "objc2-foundation",
"objc2-metal", "objc2-metal",
] ]
@@ -5525,7 +5440,7 @@ dependencies = [
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-contacts", "objc2-contacts",
"objc2-foundation 0.2.2", "objc2-foundation",
] ]
[[package]] [[package]]
@@ -5547,17 +5462,6 @@ dependencies = [
"objc2 0.5.2", "objc2 0.5.2",
] ]
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.10.0",
"objc2 0.6.3",
"objc2-core-foundation",
]
[[package]] [[package]]
name = "objc2-io-kit" name = "objc2-io-kit"
version = "0.3.2" version = "0.3.2"
@@ -5568,17 +5472,6 @@ dependencies = [
"objc2-core-foundation", "objc2-core-foundation",
] ]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags 2.10.0",
"objc2 0.6.3",
"objc2-core-foundation",
]
[[package]] [[package]]
name = "objc2-link-presentation" name = "objc2-link-presentation"
version = "0.2.2" version = "0.2.2"
@@ -5587,8 +5480,8 @@ checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
dependencies = [ dependencies = [
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-app-kit 0.2.2", "objc2-app-kit",
"objc2-foundation 0.2.2", "objc2-foundation",
] ]
[[package]] [[package]]
@@ -5600,7 +5493,7 @@ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-foundation 0.2.2", "objc2-foundation",
] ]
[[package]] [[package]]
@@ -5612,7 +5505,7 @@ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-foundation 0.2.2", "objc2-foundation",
"objc2-metal", "objc2-metal",
] ]
@@ -5623,7 +5516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
dependencies = [ dependencies = [
"objc2 0.5.2", "objc2 0.5.2",
"objc2-foundation 0.2.2", "objc2-foundation",
] ]
[[package]] [[package]]
@@ -5639,7 +5532,7 @@ dependencies = [
"objc2-core-data", "objc2-core-data",
"objc2-core-image", "objc2-core-image",
"objc2-core-location", "objc2-core-location",
"objc2-foundation 0.2.2", "objc2-foundation",
"objc2-link-presentation", "objc2-link-presentation",
"objc2-quartz-core", "objc2-quartz-core",
"objc2-symbols", "objc2-symbols",
@@ -5655,7 +5548,7 @@ checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
dependencies = [ dependencies = [
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-foundation 0.2.2", "objc2-foundation",
] ]
[[package]] [[package]]
@@ -5668,7 +5561,7 @@ dependencies = [
"block2 0.5.1", "block2 0.5.1",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-core-location", "objc2-core-location",
"objc2-foundation 0.2.2", "objc2-foundation",
] ]
[[package]] [[package]]
@@ -6190,12 +6083,6 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.37.5" version = "0.37.5"
@@ -6775,7 +6662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
dependencies = [ dependencies = [
"fnv", "fnv",
"quick-error 1.2.3", "quick-error",
"tempfile", "tempfile",
"wait-timeout", "wait-timeout",
] ]
@@ -7482,20 +7369,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "tiff"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
dependencies = [
"fax",
"flate2",
"half",
"quick-error 2.0.1",
"weezl",
"zune-jpeg",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.44" version = "0.3.44"
@@ -8347,12 +8220,6 @@ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]] [[package]]
name = "wgpu" name = "wgpu"
version = "26.0.1" version = "26.0.1"
@@ -9151,8 +9018,8 @@ dependencies = [
"memmap2", "memmap2",
"ndk 0.9.0", "ndk 0.9.0",
"objc2 0.5.2", "objc2 0.5.2",
"objc2-app-kit 0.2.2", "objc2-app-kit",
"objc2-foundation 0.2.2", "objc2-foundation",
"objc2-ui-kit", "objc2-ui-kit",
"orbclient", "orbclient",
"percent-encoding", "percent-encoding",
@@ -9460,18 +9327,3 @@ dependencies = [
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "zune-core"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]]
name = "zune-jpeg"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
dependencies = [
"zune-core",
]

View File

@@ -10,13 +10,14 @@ edition = "2024"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1" tokio-stream = "0.1"
tokio-util = "0.7" tokio-util = "0.7"
futures-lite = "2.0"
# Iroh - P2P networking and gossip # Iroh - P2P networking and gossip
iroh = { version = "0.95.0", features = ["discovery-pkarr-dht"] } iroh = { version = "0.95.0", features = ["discovery-pkarr-dht"] }
iroh-gossip = "0.95.0" iroh-gossip = "0.95.0"
# Database # Database
rusqlite = "0.37.0" rusqlite = { version = "0.37.0", features = ["bundled"] }
# Serialization # Serialization
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
@@ -34,6 +35,8 @@ chrono = { version = "0.4", features = ["serde"] }
# Logging # Logging
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2"
tracing-oslog = "0.3"
# Random # Random
rand = "0.8" rand = "0.8"
@@ -41,17 +44,24 @@ rand = "0.8"
# Encoding # Encoding
hex = "0.4" hex = "0.4"
# ML/AI # Data structures
candle-core = "0.8" bytes = "1.0"
candle-nn = "0.8" crossbeam-channel = "0.5"
candle-transformers = "0.8" uuid = { version = "1.0", features = ["v4", "serde"] }
tokenizers = "0.20"
hf-hub = "0.3"
# Bevy # Bevy and graphics
bevy = "0.17" bevy = "0.17.2"
egui = { version = "0.33", default-features = false, features = ["bytemuck", "default_fonts"] }
glam = "0.29"
winit = "0.30"
# Synchronization # Synchronization
parking_lot = "0.12" parking_lot = "0.12"
crdts = "7.3" crdts = "7.3"
inventory = "0.3" inventory = "0.3"
# CLI
clap = { version = "4.5", features = ["derive"] }
# Testing
tempfile = "3"

21
LICENSE.md Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Marathon Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

164
README.md Normal file
View File

@@ -0,0 +1,164 @@
# Marathon
**A peer-to-peer game engine development kit built with Rust and CRDTs**
Marathon is a multiplayer game engine framework designed for building real-time collaborative games with offline-first capabilities. Built on [Bevy](https://bevyengine.org/) and [iroh](https://iroh.computer/), it provides CRDT-based state synchronization, peer-to-peer networking, and persistent state management out of the box - so you can focus on making great games instead of wrestling with networking code.
## ⚠️ Early Development Notice
**This project is in early development (<v1.0.0).**
- The API is unstable and may change without notice
- Breaking changes are expected between minor versions
- Not recommended for production use
- Documentation is still being written
- We welcome feedback and contributions!
Version 1.0.0 will indicate the first stable release.
## Features
- **CRDT-based synchronization** - Conflict-free multiplayer state management using OR-Sets, RGA, and LWW semantics
- **Peer-to-peer networking** - Built on iroh with QUIC transport and gossip-based message broadcasting
- **Offline-first architecture** - Players can continue playing during network issues and sync when reconnected
- **Persistent game state** - SQLite-backed storage with automatic entity persistence
- **Cross-platform** - Supports macOS desktop and iOS (simulator and device), with more platforms planned
- **Built with Bevy** - Leverages the Bevy game engine's ECS architecture and parts of its ecosystem
## Demo
The current demo is a **replicated cube game** that synchronizes in real-time across multiple instances:
- Apple Pencil input support on iPad
- Real-time player cursor and selection sharing
- Automatic game state synchronization across network partitions
- Multiple players can interact with the same game world simultaneously
## Quick Start
### Prerequisites
- **Rust** 2024 edition or later
- **macOS** (for desktop demo)
- **Xcode** (for iOS development)
### Building the Desktop Demo
```bash
# Clone the repository
git clone https://github.com/yourusername/marathon.git
cd marathon
# Build and run
cargo run --package app
```
To run multiple instances for testing multiplayer:
```bash
# Terminal 1
cargo run --package app -- --instance 0
# Terminal 2
cargo run --package app -- --instance 1
```
### Building for iOS
Marathon includes automated iOS build tooling via `xtask`:
```bash
# Build for iOS simulator
cargo xtask ios-build
# Deploy to connected device
cargo xtask ios-deploy
# Build and run on simulator
cargo xtask ios-run
```
See [docs/ios-deployment.md](docs/ios-deployment.md) for detailed iOS setup instructions.
## Architecture
Marathon is organized as a Rust workspace with four crates:
- **`libmarathon`** - Core engine library with networking, persistence, and CRDT sync
- **`app`** - Demo game showcasing multiplayer cube gameplay
- **`macros`** - Procedural macros for serialization
- **`xtask`** - Build automation for iOS deployment
### Key Components
- **Networking** - CRDT synchronization protocol, gossip-based broadcast, entity mapping
- **Persistence** - Three-tier system (in-memory → write buffer → SQLite WAL)
- **Engine** - Core event loop, peer discovery, session management
- **Debug UI** - egui-based debug interface for inspecting state
For detailed architecture information, see [ARCHITECTURE.md](ARCHITECTURE.md).
## Documentation
- **[RFCs](docs/rfcs/)** - Design documents covering core architecture decisions
- [0001: CRDT Synchronization Protocol](docs/rfcs/0001-crdt-gossip-sync.md)
- [0002: Persistence Strategy](docs/rfcs/0002-persistence-strategy.md)
- [0003: Sync Abstraction](docs/rfcs/0003-sync-abstraction.md)
- [0004: Session Lifecycle](docs/rfcs/0004-session-lifecycle.md)
- [0005: Spatial Audio System](docs/rfcs/0005-spatial-audio-vendoring.md)
- [0006: Agent Simulation Architecture](docs/rfcs/0006-agent-simulation-architecture.md)
- **[iOS Deployment Guide](docs/ios-deployment.md)** - Complete iOS build instructions
- **[Estimation Methodology](docs/ESTIMATION.md)** - Project sizing and prioritization approach
## Technology Stack
- **[Bevy 0.17](https://bevyengine.org/)** - Game engine and ECS framework
- **[iroh 0.95](https://iroh.computer/)** - P2P networking with QUIC
- **[iroh-gossip 0.95](https://github.com/n0-computer/iroh-gossip)** - Gossip protocol for multi-peer coordination
- **[SQLite](https://www.sqlite.org/)** - Local persistence with WAL mode
- **[rkyv 0.8](https://rkyv.org/)** - Zero-copy serialization
- **[egui 0.33](https://www.egui.rs/)** - Immediate-mode GUI
- **[wgpu 26](https://wgpu.rs/)** - Graphics API (via Bevy)
## Contributing
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for:
- Development environment setup
- Code style and conventions
- How to submit pull requests
- Testing guidelines
Please also read our [Code of Conduct](CODE_OF_CONDUCT.md) and [AI Usage Policy](AI_POLICY.md).
## Project Status
Marathon is actively developed and currently focused on:
- Core CRDT synchronization protocol
- Persistence layer stability
- Multi-platform support (macOS, iOS)
- Demo applications
See our [roadmap](https://github.com/yourusername/marathon/issues) for planned features and known issues.
## Community
- **Issues** - [GitHub Issues](https://github.com/yourusername/marathon/issues)
- **Discussions** - [GitHub Discussions](https://github.com/yourusername/marathon/discussions)
## Security
Please see our [Security Policy](SECURITY.md) for information on reporting vulnerabilities.
## License
Marathon is licensed under the [MIT License](LICENSE).
## Acknowledgments
Marathon builds on the incredible work of:
- The [Bevy community](https://bevyengine.org/) for the game engine
- The [iroh team](https://iroh.computer/) for P2P networking infrastructure
- The Rust CRDT ecosystem
---
**Built with Rust 🦀 and collaborative spirit**

143
SECURITY.md Normal file
View File

@@ -0,0 +1,143 @@
# Security Policy
## Supported Versions
As an early-stage project (version 0.x.y), security support is limited to the latest development version.
| Version | Supported |
| ------- | ------------------ |
| mainline branch | :white_check_mark: |
| 0.1.x | :white_check_mark: |
| < 0.1.0 | :x: |
## Security Maturity
**Marathon is currently in early development (0.1.x) and is NOT recommended for production use or handling sensitive data.**
Security considerations for the current release:
- ⚠️ **Network protocol** is not hardened against malicious peers
- ⚠️ **Authentication** is not yet implemented
- ⚠️ **Encryption** is provided by QUIC but not verified against attacks
- ⚠️ **Authorization** is not implemented
- ⚠️ **Data validation** is basic and not audited
- ⚠️ **Persistence layer** stores data unencrypted locally
**Use Marathon only in trusted development environments with non-sensitive data.**
## Reporting a Vulnerability
We take security issues seriously. If you discover a security vulnerability in Marathon, please help us address it responsibly.
### How to Report
**Please DO NOT report security vulnerabilities through public GitHub issues.**
Instead, report vulnerabilities by:
1. **Email**: Send details to sienna@linux.com
2. **Subject line**: Include "SECURITY" and a brief description
3. **Include**:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if you have one)
### What to Expect
After you submit a report:
1. **Acknowledgment**: We'll confirm receipt within 48 hours
2. **Assessment**: We'll evaluate the severity and impact within 5 business days
3. **Updates**: We'll keep you informed of our progress
4. **Resolution**: We'll work on a fix and coordinate disclosure timing with you
5. **Credit**: We'll acknowledge your contribution (unless you prefer to remain anonymous)
### Disclosure Timeline
- **Critical vulnerabilities**: Aim to fix within 30 days
- **High severity**: Aim to fix within 60 days
- **Medium/Low severity**: Addressed in regular development cycle
We'll coordinate public disclosure timing with you after a fix is available.
## Security Best Practices
If you're using Marathon (keeping in mind it's not production-ready):
### For Development
- **Use isolated networks** for testing
- **Don't use real user data** or sensitive information
- **Don't expose to the internet** without additional security layers
- **Keep dependencies updated** with `cargo update`
- **Review security advisories** for Rust crates you depend on
### For Deployment (Future)
Once Marathon reaches production readiness, we plan to implement:
- End-to-end encryption for all peer communications
- Peer authentication and authorization
- Encrypted local storage
- Rate limiting and DoS protection
- Security audit trail
- Regular security audits
### Known Security Gaps
Current known limitations (to be addressed before 1.0):
- **No peer authentication** - Any peer can join a session
- **No authorization system** - All peers have full permissions
- **No encrypted storage** - Local SQLite database is unencrypted
- **Limited input validation** - CRDT operations trust peer input
- **No audit logging** - Actions are not logged for security review
- **Network protocol not hardened** - Vulnerable to malicious peers
## Security Contact
For security-related questions or concerns:
- **Email**: sienna@linux.com
- **Response time**: Within 48 hours for initial contact
## Security Advisories
Security advisories will be published:
- In GitHub Security Advisories
- In release notes
- In this SECURITY.md file
Currently, there are no published security advisories.
## Responsible Disclosure
We believe in responsible disclosure and request that you:
- Give us reasonable time to address issues before public disclosure
- Make a good faith effort to avoid privacy violations and service disruption
- Don't exploit vulnerabilities beyond demonstrating the issue
- Don't access or modify data that doesn't belong to you
In return, we commit to:
- Respond promptly to your report
- Keep you informed of our progress
- Credit you for your discovery (if desired)
- Not pursue legal action for good faith security research
## Additional Resources
- [Rust Security Advisory Database](https://rustsec.org/)
- [cargo-audit](https://github.com/RustSec/rustsec/tree/main/cargo-audit) - Audit Rust dependencies
- [OWASP Top 10](https://owasp.org/www-project-top-ten/) - Common web application security risks
## Version History
- **2026-02-06**: Initial security policy for v0.1.0 release
---
**Thank you for helping keep Marathon and its users safe!**

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "app" name = "app"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition.workspace = true
[features] [features]
default = ["desktop"] default = ["desktop"]
@@ -12,45 +12,45 @@ headless = []
[dependencies] [dependencies]
libmarathon = { path = "../libmarathon" } libmarathon = { path = "../libmarathon" }
macros = { path = "../macros" } macros = { path = "../macros" }
inventory = { workspace = true } inventory.workspace = true
rkyv = { workspace = true } rkyv.workspace = true
bevy = { version = "0.17", default-features = false, features = [ bevy = { version = "0.17.2", default-features = false, features = [
# bevy_render, bevy_core_pipeline, bevy_pbr are now vendored in libmarathon # bevy_render, bevy_core_pipeline, bevy_pbr are now vendored in libmarathon
"bevy_ui", "bevy_ui",
"bevy_text", "bevy_text",
"png", "png",
] } ] }
egui = { version = "0.33", default-features = false, features = ["bytemuck", "default_fonts"] } egui.workspace = true
glam = "0.29" glam.workspace = true
winit = "0.30" winit.workspace = true
raw-window-handle = "0.6" raw-window-handle = "0.6"
uuid = { version = "1.0", features = ["v4", "serde"] } uuid.workspace = true
anyhow = "1.0" anyhow.workspace = true
tokio = { version = "1", features = ["full"] } tokio.workspace = true
tracing = "0.1" tracing.workspace = true
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber.workspace = true
tracing-appender = "0.2" tracing-appender.workspace = true
serde = { version = "1.0", features = ["derive"] } serde.workspace = true
rand = "0.8" rand.workspace = true
iroh = { version = "0.95", features = ["discovery-local-network"] } iroh = { workspace = true, features = ["discovery-local-network"] }
iroh-gossip = "0.95" iroh-gossip.workspace = true
futures-lite = "2.0" futures-lite.workspace = true
bytes = "1.0" bytes.workspace = true
crossbeam-channel = "0.5.15" crossbeam-channel.workspace = true
clap = { version = "4.0", features = ["derive"] } clap.workspace = true
[target.'cfg(target_os = "ios")'.dependencies] [target.'cfg(target_os = "ios")'.dependencies]
objc = "0.2" objc = "0.2"
raw-window-handle = "0.6" raw-window-handle = "0.6"
tracing-oslog = "0.3" tracing-oslog.workspace = true
[dev-dependencies] [dev-dependencies]
iroh = { version = "0.95", features = ["discovery-local-network"] } iroh = { workspace = true, features = ["discovery-local-network"] }
iroh-gossip = "0.95" iroh-gossip.workspace = true
tempfile = "3" tempfile.workspace = true
futures-lite = "2.0" futures-lite.workspace = true
rkyv = { workspace = true } rkyv.workspace = true
bytes = "1.0" bytes.workspace = true
[lib] [lib]
name = "app" name = "app"

View File

@@ -5,7 +5,6 @@ edition.workspace = true
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
arboard = "3.4"
bevy.workspace = true bevy.workspace = true
rkyv.workspace = true rkyv.workspace = true
@@ -53,35 +52,34 @@ blake3 = "1.5"
blocking = "1.6" blocking = "1.6"
hex.workspace = true hex.workspace = true
bytemuck = { version = "1.14", features = ["derive"] } bytemuck = { version = "1.14", features = ["derive"] }
bytes = "1.0" bytes.workspace = true
chrono = { version = "0.4", features = ["serde"] } chrono.workspace = true
crdts.workspace = true crdts.workspace = true
crossbeam-channel = "0.5" crossbeam-channel.workspace = true
dirs = "5.0" dirs = "5.0"
egui = { version = "0.33", default-features = false, features = ["bytemuck", "default_fonts"] } egui.workspace = true
encase = { version = "0.11", features = ["glam"] } encase = { version = "0.11", features = ["glam"] }
futures-lite = "2.0" futures-lite.workspace = true
glam = "0.29" glam.workspace = true
inventory.workspace = true inventory.workspace = true
iroh = { workspace = true, features = ["discovery-local-network"] } iroh = { workspace = true, features = ["discovery-local-network"] }
iroh-gossip.workspace = true iroh-gossip.workspace = true
pkarr = "5.0" pkarr = "5.0"
itertools = "0.14" itertools = "0.14"
rand = "0.8" rand.workspace = true
raw-window-handle = "0.6" rusqlite.workspace = true
rusqlite = { version = "0.37.0", features = ["bundled"] }
rustc-hash = "2.1" rustc-hash = "2.1"
serde = { version = "1.0", features = ["derive"] } serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
sha2 = "0.10" sha2 = "0.10"
thiserror = "2.0" thiserror.workspace = true
tokio.workspace = true tokio.workspace = true
tokio-util.workspace = true tokio-util.workspace = true
toml.workspace = true toml.workspace = true
tracing.workspace = true tracing.workspace = true
uuid = { version = "1.0", features = ["v4", "serde"] } uuid.workspace = true
wgpu-types = "26.0" wgpu-types = "26.0"
winit = "0.30" winit.workspace = true
[target.'cfg(target_os = "ios")'.dependencies] [target.'cfg(target_os = "ios")'.dependencies]
tracing-oslog = "0.3" tracing-oslog = "0.3"
@@ -90,8 +88,8 @@ tracing-oslog = "0.3"
tokio.workspace = true tokio.workspace = true
iroh = { workspace = true, features = ["discovery-local-network"] } iroh = { workspace = true, features = ["discovery-local-network"] }
iroh-gossip.workspace = true iroh-gossip.workspace = true
futures-lite = "2.0" futures-lite.workspace = true
tempfile = "3" tempfile.workspace = true
proptest = "1.4" proptest = "1.4"
criterion = "0.5" criterion = "0.5"

View File

@@ -14,7 +14,7 @@ mod test_utils;
use std::time::Duration; use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use bevy::prelude::Transform; use bevy::prelude::{App, FixedUpdate, Transform};
use libmarathon::networking::{ use libmarathon::networking::{
CurrentSession, CurrentSession,
GossipBridge, GossipBridge,
@@ -23,6 +23,14 @@ use libmarathon::networking::{
use test_utils::{TestContext, create_test_app_maybe_offline}; use test_utils::{TestContext, create_test_app_maybe_offline};
use uuid::Uuid; use uuid::Uuid;
/// Helper to ensure FixedUpdate runs (since it's on a fixed timestep)
fn update_with_fixed(app: &mut App) {
// Run Main schedule (which includes Update)
app.update();
// Explicitly run FixedUpdate to ensure systems there execute
app.world_mut().run_schedule(FixedUpdate);
}
// ============================================================================ // ============================================================================
// Session Lifecycle Tests // Session Lifecycle Tests
// ============================================================================ // ============================================================================
@@ -114,7 +122,7 @@ async fn test_join_request_sent() -> Result<()> {
// Create app offline // Create app offline
let mut app = create_test_app_maybe_offline(node_id, ctx.db_path(), None); let mut app = create_test_app_maybe_offline(node_id, ctx.db_path(), None);
app.update(); update_with_fixed(&mut app);
// Create and insert GossipBridge // Create and insert GossipBridge
let bridge = GossipBridge::new(node_id); let bridge = GossipBridge::new(node_id);
@@ -128,7 +136,7 @@ async fn test_join_request_sent() -> Result<()> {
// Update to trigger send_join_request_once_system // Update to trigger send_join_request_once_system
// With the peer-wait logic, JoinRequest waits for peers or timeout // With the peer-wait logic, JoinRequest waits for peers or timeout
app.update(); // Start wait timer update_with_fixed(&mut app); // Start wait timer
// Simulate 1-second timeout (first node case - no peers) // Simulate 1-second timeout (first node case - no peers)
{ {
@@ -139,7 +147,7 @@ async fn test_join_request_sent() -> Result<()> {
} }
// Update again - should send JoinRequest due to timeout // Update again - should send JoinRequest due to timeout
app.update(); update_with_fixed(&mut app);
// Verify JoinRequest was sent by checking JoinRequestSent resource // Verify JoinRequest was sent by checking JoinRequestSent resource
{ {
@@ -393,7 +401,7 @@ async fn test_join_request_waits_for_peers() -> Result<()> {
let bridge = GossipBridge::new(node_id); let bridge = GossipBridge::new(node_id);
let mut app = create_test_app_maybe_offline(node_id, ctx.db_path(), Some(bridge.clone())); let mut app = create_test_app_maybe_offline(node_id, ctx.db_path(), Some(bridge.clone()));
app.update(); update_with_fixed(&mut app);
// Transition to Joining // Transition to Joining
{ {
@@ -412,7 +420,7 @@ async fn test_join_request_waits_for_peers() -> Result<()> {
// Run for 10 frames (~166ms) - should NOT send JoinRequest yet (no peers) // Run for 10 frames (~166ms) - should NOT send JoinRequest yet (no peers)
for i in 0..10 { for i in 0..10 {
app.update(); update_with_fixed(&mut app);
tokio::time::sleep(Duration::from_millis(16)).await; tokio::time::sleep(Duration::from_millis(16)).await;
let join_sent = app.world().resource::<libmarathon::networking::JoinRequestSent>(); let join_sent = app.world().resource::<libmarathon::networking::JoinRequestSent>();
@@ -560,7 +568,7 @@ async fn test_join_request_sends_after_timeout() -> Result<()> {
let bridge = GossipBridge::new(node_id); let bridge = GossipBridge::new(node_id);
let mut app = create_test_app_maybe_offline(node_id, ctx.db_path(), Some(bridge.clone())); let mut app = create_test_app_maybe_offline(node_id, ctx.db_path(), Some(bridge.clone()));
app.update(); update_with_fixed(&mut app);
// Transition to Joining // Transition to Joining
{ {
@@ -571,7 +579,7 @@ async fn test_join_request_sends_after_timeout() -> Result<()> {
println!("Initial state: Session=Joining, Peers=0"); println!("Initial state: Session=Joining, Peers=0");
// Run one frame to start the wait timer // Run one frame to start the wait timer
app.update(); update_with_fixed(&mut app);
// Manually set wait_started to 1.1 seconds ago to simulate timeout // Manually set wait_started to 1.1 seconds ago to simulate timeout
{ {
@@ -583,7 +591,7 @@ async fn test_join_request_sends_after_timeout() -> Result<()> {
} }
// Run one frame - should send JoinRequest due to timeout // Run one frame - should send JoinRequest due to timeout
app.update(); update_with_fixed(&mut app);
{ {
let join_sent = app.world().resource::<libmarathon::networking::JoinRequestSent>(); let join_sent = app.world().resource::<libmarathon::networking::JoinRequestSent>();
@@ -627,7 +635,7 @@ async fn test_join_request_only_sent_once() -> Result<()> {
let bridge = GossipBridge::new(node_id); let bridge = GossipBridge::new(node_id);
let mut app = create_test_app_maybe_offline(node_id, ctx.db_path(), Some(bridge.clone())); let mut app = create_test_app_maybe_offline(node_id, ctx.db_path(), Some(bridge.clone()));
app.update(); update_with_fixed(&mut app);
// Transition to Joining and add a peer // Transition to Joining and add a peer
{ {
@@ -644,7 +652,7 @@ async fn test_join_request_only_sent_once() -> Result<()> {
println!("Initial state: Session=Joining, Peers=1"); println!("Initial state: Session=Joining, Peers=1");
// Run frame - should send JoinRequest // Run frame - should send JoinRequest
app.update(); update_with_fixed(&mut app);
{ {
let join_sent = app.world().resource::<libmarathon::networking::JoinRequestSent>(); let join_sent = app.world().resource::<libmarathon::networking::JoinRequestSent>();
@@ -678,7 +686,7 @@ async fn test_join_request_only_sent_once() -> Result<()> {
// Run 20 more frames - should NOT send JoinRequest again // Run 20 more frames - should NOT send JoinRequest again
for i in 0..20 { for i in 0..20 {
app.update(); update_with_fixed(&mut app);
tokio::time::sleep(Duration::from_millis(16)).await; tokio::time::sleep(Duration::from_millis(16)).await;
let app_bridge = app.world().resource::<libmarathon::networking::GossipBridge>(); let app_bridge = app.world().resource::<libmarathon::networking::GossipBridge>();

View File

@@ -95,6 +95,17 @@ struct TestHealth {
use rusqlite::Connection; use rusqlite::Connection;
/// Helper to ensure FixedUpdate and FixedPostUpdate run (since they're on a fixed timestep)
fn update_with_fixed(app: &mut App) {
use bevy::prelude::{FixedUpdate, FixedPostUpdate};
// Run Main schedule (which includes Update)
app.update();
// Explicitly run FixedUpdate to ensure systems there execute
app.world_mut().run_schedule(FixedUpdate);
// Explicitly run FixedPostUpdate to ensure delta generation executes
app.world_mut().run_schedule(FixedPostUpdate);
}
/// Check if an entity exists in the database /// Check if an entity exists in the database
fn entity_exists_in_db(db_path: &PathBuf, entity_id: Uuid) -> Result<bool> { fn entity_exists_in_db(db_path: &PathBuf, entity_id: Uuid) -> Result<bool> {
let conn = Connection::open(db_path)?; let conn = Connection::open(db_path)?;
@@ -868,8 +879,8 @@ async fn test_lock_heartbeat_expiration() -> Result<()> {
// Update to allow lock propagation // Update to allow lock propagation
for _ in 0..10 { for _ in 0..10 {
app1.update(); update_with_fixed(&mut app1);
app2.update(); update_with_fixed(&mut app2);
tokio::time::sleep(Duration::from_millis(100)).await; tokio::time::sleep(Duration::from_millis(100)).await;
} }
@@ -899,7 +910,7 @@ async fn test_lock_heartbeat_expiration() -> Result<()> {
// Run cleanup system (which removes expired locks and broadcasts LockReleased) // Run cleanup system (which removes expired locks and broadcasts LockReleased)
println!("Running cleanup to expire locks..."); println!("Running cleanup to expire locks...");
for _ in 0..10 { for _ in 0..10 {
app2.update(); update_with_fixed(&mut app2);
tokio::time::sleep(Duration::from_millis(100)).await; tokio::time::sleep(Duration::from_millis(100)).await;
} }
@@ -1119,7 +1130,7 @@ async fn test_offline_to_online_sync() -> Result<()> {
} }
// Update to trigger delta generation (offline) // Update to trigger delta generation (offline)
app1.update(); update_with_fixed(&mut app1);
tokio::time::sleep(Duration::from_millis(50)).await; tokio::time::sleep(Duration::from_millis(50)).await;
// Verify clock incremented for spawn // Verify clock incremented for spawn
@@ -1156,7 +1167,7 @@ async fn test_offline_to_online_sync() -> Result<()> {
} }
} }
app1.update(); update_with_fixed(&mut app1);
tokio::time::sleep(Duration::from_millis(50)).await; tokio::time::sleep(Duration::from_millis(50)).await;
let clock_after_second_spawn = { let clock_after_second_spawn = {
@@ -1179,7 +1190,7 @@ async fn test_offline_to_online_sync() -> Result<()> {
} }
} }
app1.update(); update_with_fixed(&mut app1);
tokio::time::sleep(Duration::from_millis(50)).await; tokio::time::sleep(Duration::from_millis(50)).await;
let clock_after_modify = { let clock_after_modify = {
@@ -1197,7 +1208,7 @@ async fn test_offline_to_online_sync() -> Result<()> {
commands.entity(entity_b_bevy).insert(ToDelete); commands.entity(entity_b_bevy).insert(ToDelete);
} }
app1.update(); update_with_fixed(&mut app1);
tokio::time::sleep(Duration::from_millis(50)).await; tokio::time::sleep(Duration::from_millis(50)).await;
let clock_after_delete = { let clock_after_delete = {
@@ -1262,8 +1273,8 @@ async fn test_offline_to_online_sync() -> Result<()> {
// Wait a bit more for tombstone to sync // Wait a bit more for tombstone to sync
for _ in 0..20 { for _ in 0..20 {
app1.update(); update_with_fixed(&mut app1);
app2.update(); update_with_fixed(&mut app2);
tokio::time::sleep(Duration::from_millis(100)).await; tokio::time::sleep(Duration::from_millis(100)).await;
} }

View File

@@ -87,6 +87,17 @@ pub fn create_test_app_maybe_offline(node_id: Uuid, db_path: PathBuf, bridge: Op
app app
} }
/// Helper to ensure FixedUpdate and FixedPostUpdate run (since they're on a fixed timestep)
fn update_with_fixed(app: &mut App) {
use bevy::prelude::{FixedUpdate, FixedPostUpdate};
// Run Main schedule (which includes Update)
app.update();
// Explicitly run FixedUpdate to ensure systems there execute
app.world_mut().run_schedule(FixedUpdate);
// Explicitly run FixedPostUpdate to ensure delta generation executes
app.world_mut().run_schedule(FixedPostUpdate);
}
/// Wait for sync condition to be met, polling both apps /// Wait for sync condition to be met, polling both apps
pub async fn wait_for_sync<F>( pub async fn wait_for_sync<F>(
app1: &mut App, app1: &mut App,
@@ -102,8 +113,8 @@ where
while start.elapsed() < timeout { while start.elapsed() < timeout {
// Tick both apps // Tick both apps
app1.update(); update_with_fixed(app1);
app2.update(); update_with_fixed(app2);
tick_count += 1; tick_count += 1;
if tick_count % 50 == 0 { if tick_count % 50 == 0 {

View File

@@ -1,11 +1,11 @@
[package] [package]
name = "xtask" name = "xtask"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition.workspace = true
publish = false publish = false
[dependencies] [dependencies]
anyhow = "1.0" anyhow.workspace = true
clap = { version = "4.5", features = ["derive"] } clap.workspace = true
tracing = "0.1" tracing.workspace = true
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber.workspace = true