diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..4a167f0 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,6 @@ +[codespell] +skip = *.json,*.bin,*.lock,./target,./.git,./jemalloc,./node_modules,./pkg,./logs +ignore-words-list = aetheris,bevy,serde,quinn,wasm,paseto,p3,p1,p0,otlp,promql,logql,statics,unsecure,crate,lod,implementors,implementors,implementers,implementer,crate,lod,implementor,implementors,implementer,implementers,lode,ore +quiet-level = 2 +check-filenames = +check-hidden = diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..6d8221f --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,208 @@ + + +--- +Version: 1.0.0 +Last Updated: 2026-04-19 +Phase: P1 specification / P1 implementation in progress +Engine Dependency: aetheris-engine 0.3.0 / aetheris-protocol 0.2.4 +GDD Reference: docs/VOID_RUSH_GDD.md +--- + +# Copilot Instructions — void-rush + +Void Rush is the **flagship validation game** for the Aetheris Engine. It is a top-down +3D browser-based space MMO (mining, combat, trading) that proves every engine subsystem +works end-to-end under real gameplay load. This repository is **documentation-only** in +Phase 1; implementation lives in `aetheris-engine` and `aetheris-client`. + +> **Rule**: Every game feature in this repository must trace back to an engine subsystem +> it exercises. No feature is added unless it validates something in the engine. + +--- + +## Repository Layout + +``` +docs/ + VOID_RUSH_GDD.md # Master Game Design Document — single source of truth + ECS_DESIGN.md # 25+ ECS components, system architecture + PLATFORM_DESIGN.md # Browser targets, WebTransport, WASM constraints + THEME_WORLD_DESIGN.md # Art direction, UI design tokens, world aesthetic +README.md # Project overview and engine validation rationale +``` + +--- + +## Core Gameplay Loop + +``` +Mine asteroids → Collect ore into cargo hold → Travel to Station + ↑ ↓ + Respawn Sell ore for credits + at Station ↓ + ↑ Buy ship upgrades + Die in PvP ← Attack other haulers ← Equip stronger weapons +``` + +**The risk-reward mechanic**: Ore increases ship mass +($a_{eff} = F / (m_{hull} + m_{ore} \times n_{ore})$), +making loaded ships slower and easier to attack. This is the core tension. + +--- + +## Engine Systems Map + +Every gameplay feature is an explicit test of an engine capability: + +| Gameplay System | Engine Feature Validated | Phase | +|---|---|---| +| Ship movement + inertia | 60 Hz tick budget, Newtonian physics | P1 | +| Cargo mass penalty | Variable-mass CSP reconciliation | P1 | +| 3 ship classes | `WorldState::spawn_kind`, component schema | P1 | +| PvP weapons (4 types) | Ballistic event replication (not entity) | P1 | +| Client-side prediction | `InputCommand` pipeline + rollback | P1 | +| Safe Zone enforcement | Server-authoritative spatial rules | P1 | +| Spatial AoI | Spatial hash grid, 4-filter pipeline | P1 | +| Priority channels P0–P5 | `ChannelRegistry`, congestion shedding | P1 | +| Ore → Credits economy | `EconomyService` gRPC, idempotency keys | P1 | +| 1,000 concurrent players | tick p99 ≤ 16.6 ms gate criterion | P1 | +| Merkle chain anti-cheat | `MerkleChainState`, `SuspicionScore` | P1 | +| Sector instancing + jump gates | `RoomAndInstance`, entity budget (6,700) | P1 | +| Player crafting + auction house | Inventory gRPC, event-sourced economy | P2 | +| Multi-crew capital ships | Federation, cross-sector entity ownership | P3+ | + +--- + +## Ship Classes + +Three asymmetric classes create natural counters: + +| Class | Entity Type | HP | Accel | Cargo | Role | +|---|---|---|---|---|---| +| **Interceptor** | `1` | Low | High | None | Scout / Assassin | +| **Dreadnought** | `2` | High | Low | None | Tank / Blocker | +| **Hauler** | `3` | Medium | Medium | Large | Trader (high-value target) | + +**Counter triangle**: Interceptor beats Hauler (speed), Dreadnought beats Interceptor (HP), Hauler beats Dreadnought (cargo value — no reason to fight). + +In ECS terms, each ship is a Bevy entity with: +- `Networked(NetworkId)` — marks it as network-replicated +- `Ownership(ClientId)` — prevents spoofed input from other clients +- `Transform` (`ComponentKind(1)`) — position/rotation, sent every tick +- `ShipStats`, `Velocity`, `CargoHold`, `Wallet`, `Health` — gameplay components + +--- + +## ECS Component Schema (Phase 1) + +```rust +// Core spatial (replicated via ComponentKind(1)) +Transform { x, y, z, rotation, entity_type } +Velocity { dx, dy, dz } + +// Physics +ShipStats { hull_hp: u16, shield_hp: u16, energy: f32, max_accel: f32, mass: f32 } +Loadout { chassis: u8, weapon: u8, engine: u8, shield: u8 } + +// Economy +CargoHold { slots: [OreStack; 8], total_mass: f32 } +Wallet { credits: i64 } // integer cents, NEVER f32 + +// Combat +Health { current: u16, max: u16 } +DamageState { shield_remaining: u16, hull_remaining: u16, invuln_ticks: u8 } + +// Network identity (required on every networked entity) +Networked(NetworkId) +Ownership(ClientId) + +// AI +AiState { patrol_target: Option, aggro_target: Option } +``` + +> **`ComponentKind` registry**: `1` is reserved for `Transform`. All new component +> kinds start from `2`. Each kind requires a registered `BoxedReplicator` in +> `BevyWorldAdapter`. + +--- + +## Weapon Systems + +``` +Pulse Laser — hitscan, instant hit, energy cost per shot +Beam Laser — channeled, continuous energy drain, DPS while held +Mining Laser — hitscan, extracts ore from asteroids (PvE only, 0 PvP damage) +Seeker Missile — homing projectile, tracks target NetworkId, 3s TTL +``` + +**Ballistic replication rule**: Projectiles are **NOT** replicated as ECS entities. +The server broadcasts a `BallisticEvent` (fired, hit, miss). Clients render locally +using the known positions + velocity vectors. This avoids ~200 extra entity slots per +sector in heavy combat. + +``` +Server: fires event → NetworkEvent::ReliableMessage { BallisticEvent::LaserFired { ... } } +Client: receives event → renders beam/missile locally → no entity in SAB +``` + +--- + +## Sector Architecture + +| Zone | Description | Damage | Respawn | +|---|---|---|---| +| **Safe Zone** | Station radius (~500 m) | Nullified | Yes | +| **Contested** | Inner ring | Normal | Nearest station | +| **Lawless** | Outer ring | Normal + NPC drones | Nearest station | + +- Hard cap: **6,700 entities per sector** (SAB limit: 8,192 × 80% safety margin) +- Jump gates transition players between sectors (no seamless travel in P1) +- Each sector is a separate server process; no entity state crosses sector boundary in P1 + +--- + +## Priority Channels + +Under network congestion, the scheduler sheds lower-priority channels first: + +| Channel | Priority | Content | Rule | +|---|---|---|---| +| P0 | Critical | Player's own entity state | Never shed | +| P1 | High | Combat events (damage, death, kills) | Never shed | +| P2 | Medium | AoI neighbors (nearby ships) | Shed at heavy congestion | +| P3 | Low | Distant entities (outer AoI ring) | Shed first | +| P4 | Background | Asteroids, loot, environment | Shed freely | +| P5 | Cosmetic | Emotes, effects | Shed freely | + +--- + +## Performance Contracts (Phase 1 Gate Criteria) + +| Metric | Target | +|---|---| +| Server tick p99 | ≤ 16.6 ms | +| Concurrent players / sector | ≥ 100 (gate), 1,000 (stretch) | +| Client FPS (Chrome/Firefox) | ≥ 30 fps | +| Entity budget / sector | ≤ 6,700 | +| Auth round-trip | ≤ 500 ms | +| Economy transaction | ≤ 200 ms p99 | + +--- + +## Key Design Principles + +- **Server is authoritative.** Clients predict locally; server always wins on conflicts. +- **Integer money.** `Wallet.credits` is always `i64` (integer cents). Never `f32`. +- **Ballistic events, not entities.** Projectiles are events, not ECS entities. +- **Safe Zones are server-enforced.** Client cannot bypass damage nullification. +- **Ownership gates every update.** An entity without `Ownership(ClientId)` rejects all client mutations. +- **No speculative features.** Every game system must map to a row in the engine systems table above. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dbae3ba --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + docs-audit: + name: Documentation Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install codespell + run: sudo apt-get update && sudo apt-get install -y codespell + - name: Run Frontmatter Lint + run: python3 scripts/doc_lint.py + - name: Run Link Check + run: python3 scripts/check_links.py + - name: Run Spelling Check + run: codespell diff --git a/README.md b/README.md index 917ad68..1eb2157 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ A top-down 3D space MMO built on the Aetheris engine — Newtonian physics, cont > **[Read the Game Design Document](docs/VOID_RUSH_GDD.md)** — ship classes, weapons, economy loops, and world architecture. > -> 🚀 **Latest Milestone:** **Wireframe Debug Renderer (M1011) functional!** Replaced placeholder overlays with a high-performance in-canvas debug pass. +> 🚀 **Latest Milestone:** **M10146** — Multirepo Consolidation & Hardening (Completed) -[![CI](https://github.com/garnizeh-labs/void-rush/actions/workflows/ci.yml/badge.svg)](https://github.com/garnizeh-labs/void-rush/actions/workflows/ci.yml) +[![Build Status](https://github.com/garnizeh-labs/void-rush/actions/workflows/ci.yml/badge.svg)](https://github.com/garnizeh-labs/void-rush/actions) [![Rust Version](https://img.shields.io/badge/rust-1.94%2B-blue.svg?logo=rust)](https://www.rust-lang.org/) [![License: CC BY 4.0](https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) diff --git a/docs/ECS_DESIGN.md b/docs/ECS_DESIGN.md index 5f34220..1c285fb 100644 --- a/docs/ECS_DESIGN.md +++ b/docs/ECS_DESIGN.md @@ -59,7 +59,7 @@ This document covers: ## 2. ECS in the Five-Stage Pipeline The ECS participates in three of the five tick stages. Its budget is fixed and enforced by telemetry -(see [LC-0100](../roadmap/archive/phase-0-foundation/specs/LC-0100_tick_pipeline.md) for the full timing contract). +(see LC-0100 (Internal Timing Contract) for the full timing contract). ``` ┌──────────────────────────────────────────────────────────────────┐ @@ -132,7 +132,7 @@ BevyWorldAdapter { The `bimap` is the most critical field. It is a strict bijection between the protocol's `NetworkId` and Bevy's internal `Entity` (which embeds both an index and a generation counter). Invariants B1 -through B4 from [LC-0400](../roadmap/archive/phase-0-foundation/specs/LC-0400_worldstate_ecs_contract.md) govern this +through B4 from LC-0400 (Internal ECS Contract) govern this structure: bijection, atomicity, immutability after insertion, and no ID recycling. ### 3.3 Component Registry — `ComponentReplicator` @@ -309,7 +309,7 @@ still single-threaded and returns when all systems have completed. The migration is designed to be a drop-in swap. The checklist: 1. `aetheris-ecs-custom` implements `WorldState` with the **identical method signatures**. -2. All invariants from [LC-0400](../roadmap/archive/phase-0-foundation/specs/LC-0400_worldstate_ecs_contract.md) remain +2. All invariants from LC-0400 (Internal ECS Contract) remain satisfied: bimap bijection, atomic spawn/despawn, no NetworkId recycling. 3. The `LocalId` wrapping contract changes: P3 uses `row_index: u32 || generation: u32` packed into a `u64` (see §7.2). The `to_bits()` / `from_bits()` round-trip contract is preserved. @@ -324,7 +324,7 @@ The migration is designed to be a drop-in swap. The checklist: The bridge between the boiling-hot simulation and the cold persistence tier is handled via asynchronous, lock-free channels to ensure I/O latency never blocks the tick budget. -See [PERSISTENCE_DESIGN.md](PERSISTENCE_DESIGN.md) for the full architectural specification of the persistence bridge, micro-batching strategy, and the event ledger schema. +See [PERSISTENCE_DESIGN.md](https://github.com/garnizeh-labs/aetheris-engine/blob/main/docs/PERSISTENCE_DESIGN.md) for the full architectural specification of the persistence bridge, micro-batching strategy, and the event ledger schema. ### 5.2 Event Sourcing — Append-Only Model @@ -503,7 +503,7 @@ anomalous patterns are detected (impossible velocities, boundary exploits, stati against peer entities) and decays exponentially during clean ticks. > **Canonical definition:** The authoritative score ranges, increment values, and decay policy -> are defined in [SECURITY_DESIGN.md §8](SECURITY_DESIGN.md#8-suspicionscore-system). +> are defined in [SECURITY_DESIGN.md §8](https://github.com/garnizeh-labs/aetheris-engine/blob/main/docs/SECURITY_DESIGN.md#8-suspicionscore-system). > The table below is a summary; SECURITY_DESIGN is the source of truth. | Suspicion Level | Score Range | Audit Frequency | Behavior | @@ -530,7 +530,7 @@ This is 0.3% of the 16.6ms tick budget — negligible. Integrity verification is performed asynchronously by the Audit Worker. -See [AUDIT_DESIGN.md](https://github.com/garnizeh-labs/nexus/blob/main/docs/AUDIT_DESIGN.md) for the actor-based architecture of the audit system and its operational constraints. +See internal audit documentation for the actor-based architecture of the audit system and its operational constraints. --- diff --git a/docs/THEME_WORLD_DESIGN.md b/docs/THEME_WORLD_DESIGN.md index defde74..2bb61c2 100644 --- a/docs/THEME_WORLD_DESIGN.md +++ b/docs/THEME_WORLD_DESIGN.md @@ -69,7 +69,7 @@ Switching Worlds is analogous to switching between entirely different apps. You |------------|-------------|-----------------| | `aetheris-playground` | Engine sandbox / developer tool | Tech-glass dark | | `void-rush` | Space MMO (Aetheris: Void Rush) | Sci-fi glassmorphic dark | -| `nexus-hub` | (Future) social/hub layer | Minimal frosted light | +| `aetheris-hub` | (Future) social/hub layer | Minimal frosted light | | `forge` | (Future) level / world editor | IDE-inspired dark | ### 1.2 What is a Theme? @@ -105,7 +105,7 @@ Aetheris Engine │ ├── Theme: blueprint (default, current aesthetic) │ └── Theme: blueprint-lite (lower blur, accessibility) │ -└── World: nexus-hub (future) +└── World: aetheris-hub (future) ├── Theme: frost (default, light frosted) └── Theme: night (dark mode) ``` diff --git a/justfile b/justfile new file mode 100644 index 0000000..366090d --- /dev/null +++ b/justfile @@ -0,0 +1,11 @@ +# Void-Rush Validation Scripts + +# Run all checks +check: check-docs + +# Standard documentation validation +check-docs: + python3 scripts/doc_lint.py + python3 scripts/check_links.py + python3 scripts/check_branding.py + codespell --config .codespellrc || true diff --git a/scripts/check_branding.py b/scripts/check_branding.py new file mode 100755 index 0000000..d147557 --- /dev/null +++ b/scripts/check_branding.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +import os +import sys +import argparse + +# Configurações +FORBIDDEN_WORDS = ["nexus"] +IGNORE_DIRS = [".git", "node_modules", "target", "logs", "dist", "pkg"] +IGNORE_FILES = ["check_branding.py"] # Don't check yourself + +def check_branding(root_dir): + found_violations = 0 + + for root, dirs, files in os.walk(root_dir): + # Filtrar diretórios ignorados + dirs[:] = [d for d in dirs if d not in IGNORE_DIRS] + + for file in files: + if file in IGNORE_FILES: + continue + + file_path = os.path.join(root, file) + + # Pular arquivos binários básicos + if file_path.endswith((".wasm", ".png", ".jpg", ".jpeg", ".ico", ".aeb")): + continue + + try: + with open(file_path, "r", encoding="utf-8") as f: + content = f.read().lower() + for word in FORBIDDEN_WORDS: + if word in content: + print(f"❌ Violation found: '{word}' in {file_path}") + found_violations += 1 + except (UnicodeDecodeError, PermissionError): + # Skip files that cannot be read as text + continue + + return found_violations + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Aetheris Branding Guard — Check for forbidden internal terms.") + parser.add_argument("path", nargs="?", default=".", help="Root directory to scan (default: current)") + args = parser.parse_args() + + violations = check_branding(args.path) + + if violations > 0: + print(f"\n🚨 Total branding violations: {violations}") + sys.exit(1) + else: + print("✅ Branding check passed. No internal terms found.") + sys.exit(0) diff --git a/scripts/check_links.py b/scripts/check_links.py new file mode 100755 index 0000000..cb67ab2 --- /dev/null +++ b/scripts/check_links.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +import os +import re +import sys + +def check_links_in_file(filepath, all_files): + errors = [] + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + # Find [text](link) or [text]: link + links = re.findall(r'\[.*?\]\((.*?)\)', content) + links += re.findall(r'^\[.*?\]:\s*(.*?)$', content, re.MULTILINE) + + for link in links: + link = link.strip().split(" ")[0].strip(" <>") + if link.startswith('http') or link.startswith('#') or link.startswith('mailto:'): + continue + + # Remove anchor + link_path = link.split('#')[0] + if not link_path: + continue + + # Resolve relative to current file + current_dir = os.path.dirname(filepath) + target_path = os.path.normpath(os.path.join(current_dir, link_path)) + + if not os.path.exists(target_path): + errors.append(f"Broken link: {link} (Target: {target_path})") + + return errors + +def main(): + docs_root = "." # Check from root + failed = False + + all_files = [] + for root, dirs, files in os.walk(docs_root): + # Prune excluded directories in-place to avoid descending into them + dirs[:] = [d for d in dirs if d not in {'.git', 'target', 'jemalloc', 'node_modules', 'pkg'}] + for f in files: + all_files.append(os.path.normpath(os.path.join(root, f))) + + md_files = [f for f in all_files if f.endswith('.md')] + + for md_file in md_files: + errors = check_links_in_file(md_file, all_files) + if errors: + print(f"❌ {md_file}:") + for err in errors: + print(f" - {err}") + failed = True + else: + # print(f"✅ {md_file}: OK") + pass + + if failed: + sys.exit(1) + else: + print("✅ All internal links are valid.") + +if __name__ == "__main__": + main() diff --git a/scripts/doc_lint.py b/scripts/doc_lint.py new file mode 100755 index 0000000..15fa915 --- /dev/null +++ b/scripts/doc_lint.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import os +import sys +import re + +REQUIRED_FIELDS = ["Version", "Status", "Phase", "Last Updated", "Authors"] +REQUIRED_SECTIONS = ["## Executive Summary", "## Appendix A — Glossary", "## Appendix B — Decision Log"] + +def lint_file(filepath): + errors = [] + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + # Check for frontmatter + frontmatter_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL) + if not frontmatter_match: + errors.append("Missing frontmatter (YAML block starting and ending with ---)") + else: + fm_content = frontmatter_match.group(1) + for field in REQUIRED_FIELDS: + if f"{field}:" not in fm_content: + errors.append(f"Missing required frontmatter field: {field}") + + # Check for required sections (only for design files) + if "design" in filepath.split(os.sep): + for section in REQUIRED_SECTIONS: + if section not in content: + errors.append(f"Missing required section: {section}") + + return errors + +def main(): + docs_root = "docs" + design_dir = os.path.join(docs_root, "design") + failed = False + + files_to_check = [] + for root, _dirs, files in os.walk(docs_root): + # Prune excluded directories in-place to avoid descending into them + _dirs[:] = [d for d in _dirs if d not in {'.git', 'target', 'jemalloc', 'node_modules', 'pkg'}] + for f in files: + if f.endswith(".md") and f != "README.md": + files_to_check.append(os.path.join(root, f)) + + for filepath in files_to_check: + errors = lint_file(filepath) + if errors: + print(f"❌ {filepath}:") + for err in errors: + print(f" - {err}") + failed = True + else: + print(f"✅ {filepath}: OK") + + if failed: + sys.exit(1) + +if __name__ == "__main__": + main()