MANDRIS / DESIGN REFERENCE
Technical design
How the current game is built and what new systems need to account for.
Snapshot: · Source ca26d81327a1
The game may have changed since this snapshot. Routine deployments do not update this reference.
Runtime architecture #
The active project uses TypeScript, Three.js, Vite, and plain DOM screens. Supabase supplies private co-op transport, room coordination, and optional accounts. The preserved Phaser/Vue project is a separate legacy reference and is not imported by the active game.
Input becomes player commands. A fixed-step deterministic simulation advances rules, returns snapshots, and queues semantic events. Rendering interpolates snapshots, while audio and effects consume events. Presentation must not alter simulation state or consume its randomness.
- src/content/: stable definitions, balance, models, and presentation catalogs.
- src/simulation/: combat, state, seeded randomness, collision, pickups, economy, checkpoints.
- src/ThreeCombatRenderer.ts and presentation modules: 3D scene, interpolation, effects, lighting.
- src/network/: transport, private rooms, ordered commands, checkpoints, and recovery.
- src/ui/: DOM screens and UI lifecycle. src/main.ts coordinates the application.
Source reference: ARCHITECTURE.md · src/main.ts · src/simulation/MissionSimulation.ts
Determinism and compatibility #
Content, simulation, and network identifiers are stable strings rather than display names. Seeded simulation and independent scenery generation allow reproducible scenarios. Network changes must preserve command ordering and ownership, and deterministic state must survive checkpoint restoration.
The host applies store commands using per-pilot sequence tracking and bounded receipts so delayed or duplicated purchases cannot charge twice. Run identity, stage identity, mission preparation, and readiness are tracked through transitions. New deterministic systems need explicit state, snapshot, event, and restoration behavior.
Protocol and checkpoint versions are separate. A feature that changes message compatibility or restored state must update the relevant version and account for co-op clients on older builds.
Source reference: src/simulation/types.ts · src/network/contracts.ts · src/simulation/RunState.ts · ARCHITECTURE.md
Blender models and presentation #
Editable Blender projects and reproducible Python builders live in art/. Runtime assets live in public/assets/. Models use glTF/GLB, meters, Y-up coordinates, and forward -Z. Named mounts, collision objects, and animation clips form an asset contract.
Player hulls expose muzzle, thruster, and collision nodes; enemies expose Muzzle, Thruster, Collision, and role-specific nodes such as launch bays. The unit reference names the runtime model for each craft. Thumbnails are generated from the actual models and copied into this snapshot so future replacements do not alter this reference.
Silhouette, readable attack windups, visible weapon aim, hit reactions, damage panels, explosions, and debris communicate game state. The browser renderer uses bounded effect pools and reduced-motion alternatives. A large visual blast is not automatically evidence of a matching damage volume.
Source reference: src/content/actorModels.ts · src/content/crucible.ts · src/ThreeCombatRenderer.ts · art/ · ARCHITECTURE.md
Audio and cinematic presentation #
Audio is routed through event definitions, category controls, and per-mission sound sets that combine recorded, generated, and synthesized sounds. Source recipes and third-party credits are maintained alongside the asset pipeline.
Mission introductions and promotional media are presentation layers. They remain skippable or optional and must not delay reliable control.
Source reference: src/AudioDirector.ts · src/audio/catalog.ts · src/audio/foundryCues.ts
Constraints for new ideas #
- Keep combat rules independent of Three.js, DOM, audio, and transport.
- Budget active enemies, projectiles, summons, explosions, lights, and transparent effects. More simultaneous effects can hurt readability before they exhaust hardware.
- Treat player count, overlapping abilities, boss phases, ground/air restrictions, and energy depletion as core design cases.
- Telegraph enemy attacks on the unit, not with floor lanes or sweep fans that predict movement.
- Provide visual cues beyond color alone, keyboard focus, readable contrast, and reduced-motion behavior.
- New hulls and enemy families need model mounts, animations, tuning, collision, UI, audio, VFX, and co-op behavior.
- The current main application and simulation modules are large. Prefer bounded modules and clear ownership when adding new systems.
Source reference: PRODUCT.md · ARCHITECTURE.md · CONTEXT_MAP.md
Development labs and tuning #
The admin library contains unit comparisons and model inspection, audio review, the Graphics, HUD, Orb, and Garmo labs. These tools make visual and balance review possible without repeatedly editing presentation code by hand.
This knowledge base is a separate, manual reference snapshot. It has no connection to live lab saves, player accounts, or gameplay state.
Source reference: src/ui/DevHub.ts · src/ui/OrbLab.ts · src/ui/HudLab.ts
Versions and simulation dimensions #
| Constant | Value |
|---|---|
| Fixed simulation step | 16.667 ms (60 Hz) |
| Network protocol | 122 |
| Deterministic checkpoint | 115 |
| Combat content | 1 |
| Upgrade catalog | 28 |
| Pilot flight altitude | 3.8 m |
| Field half width | 30 m |
Source reference: src/simulation/types.ts · src/content/combat.ts · src/content/upgrades.ts
Specialized ability parameters #
The following are named configuration fields for implementation discussion. Ms suffixes mean milliseconds; distances and speeds use meters and meters/second, angles use radians, and fractions are dimensionless. Capacity and pool fields are bounds, not damage values. Only ability families used by ships in rotation are included.
Healing
| Field | Value |
|---|---|
| cooldownMs | 0 |
| shieldPerSecond | 20 |
| energyPerSecond | 34 |
| startupEnergy | 10 |
| range | 20 |
| selfTargetDistanceRatio | 0.5 |
Salvo
| Field | Value |
|---|---|
| energy | 60 |
| count | 3 |
| intervalMs | 0 |
| damage | 23.94 |
| range | 320 |
| speed | 38 |
| lifeMs | 8000 |
| guidanceDelayMs | 180 |
| acquisitionRange | 32 |
| retentionRange | 48 |
| turnRate | 3.142 |
| maxActive | 64 |
| groundY | 0.9 |
| groundDescentMs | 250 |
| radius | 6 |
| fullRadius | 2 |
| edgeDamageFraction | 0.5 |
Burst
| Field | Value |
|---|---|
| energy | 10 |
| crossingCooldownReduction | 0.3 |
| durationMs | 400 |
| cooldownMs | 4000 |
| movement | 2.8 |
| missileCount | 4 |
| missileDamage | 18 |
| acquisitionRange | 32 |
| missileSpeed | 22 |
| missileRange | 54 |
| missileLifeMs | 3000 |
| steering | 0.18 |
| bobAmplitude | 0.65 |
| bobPeriodMs | 900 |
| bobSettleRange | 6 |
Pods
| Field | Value |
|---|---|
| energy | 50 |
| drainPerSecond | 0 |
| durationMs | 10000 |
| cooldownMs | 6000 |
| damage | 8 |
| rateMs | 125 |
| range | 32 |
| speed | 45 |
Twin Lasers
| Field | Value |
|---|---|
| range | 44 |
| drainPerSecond | 0 |
| damage | 3.84 |
| rateMs | 100 |
Event Horizon
| Field | Value |
|---|---|
| energy | 30 |
| cooldownMs | 25000 |
| durationMs | 5000 |
| speed | 8 |
| minRadius | 2.275 |
| maxRadius | 9.5 |
| absorptionCap | 20 |
| pullRadius | 12 |
| pullSpeed | 24 |
| damage | 40 |
| damagePerShot | 3 |
| blastRadiusMultiplier | 1.8 |
| collapseMs | 350 |
| explosionMs | 800 |
| enemyMass | 4 |
| captureCap | 64 |
| captureDamagePerSecond | 18 |
| captureTickMs | 250 |
| intakeMs | 1000 |
| growthTimeMs | 450 |
| captureRadiusMultiplier | 1.2 |
| ejectSpeed | 32 |
| ejectDurationMs | 900 |
Wrath
| Field | Value |
|---|---|
| cooldownMs | 35000 |
| flightMs | 3000 |
| buildupMs | 1200 |
| lingerMs | 1800 |
| damage | 100 |
| friendlyDamage | 20 |
Garmo
| Field | Value |
|---|---|
| primary.burstCount | 3 |
| primary.spacingMs | 50 |
| primary.muzzleSide | 1.35 |
| primary.muzzleForward | 2.65 |
| primary.pierce | 3 |
| primary.blastRadius | 2.25 |
| primary.blastFraction | 0.4 |
| splinter.count | 3 |
| splinter.acquisitionRange | 7 |
| splinter.damageFraction | 0.35 |
| splinter.speed | 36 |
| splinter.lifeMs | 280 |
| splinter.range | 10 |
| splinter.radius | 0.09 |
| splinter.maxActive | 96 |
| conductive.durationMs | 4000 |
| conductive.maxMarks | 64 |
| conductive.detonationCooldownMs | 1200 |
| conductive.damage | 18 |
| conductive.radius | 3 |
| arc.minimumEnergy | 8 |
| arc.energyPerSecond | 32 |
| arc.minimumChargeMs | 200 |
| arc.chargeMs | 1000 |
| arc.recoveryMs | 300 |
| arc.minDamage | 40 |
| arc.maxDamage | 140 |
| arc.minTargets | 3 |
| arc.maxTargets | 7 |
| arc.range | 42 |
| arc.acquisitionRadius | 4 |
| arc.aimHalfAngle | 0.262 |
| arc.jumpRange | 8 |
| arc.conductiveJumpRange | 13 |
| arc.falloff | 0.8 |
| arc.flashMs | 300 |
| pulse.damage | 12 |
| pulse.radius | 9 |
| pulse.energy | 12 |
| pulse.drainPerSecond | 24 |
| pulse.intervalMs | 400 |
| pulse.cooldownMs | 1500 |
| pulse.visualMs | 400 |
| pulse.enterMs | 250 |
| pulse.exitMs | 180 |
| anchor.damage | 12 |
| anchor.radius | 12 |
| anchor.placementRange | 32 |
| anchor.energy | 25 |
| anchor.cooldownMs | 6000 |
| anchor.durationMs | 15000 |
| anchor.intervalMs | 700 |
| anchor.pickRadius | 3 |
| anchor.targets | 3 |
| anchor.enterMs | 350 |
| anchor.exitMs | 300 |
| fracture.visualMs | 520 |
| fracture.maxActive | 96 |
Michen
| Field | Value |
|---|---|
| scale | 1.25 |
| plating | 0.1 |
| phoenix.countdownMs | 3000 |
| phoenix.hullFraction | 0.5 |
| phoenix.immunityMs | 1000 |
| phoenix.damage | 600 |
| phoenix.radius | 22 |
| phoenix.aftermathMs | 1500 |
| phoenix.wingspan | 24 |
| photon.delayMs | 2000 |
| photon.intervalMs | 4000 |
| photon.damage | 60 |
| photon.blastRadius | 6 |
| photon.fullDamageRadius | 2 |
| photon.edgeDamageFraction | 0.5 |
| photon.speed | 24 |
| photon.minTargetRange | 24 |
| photon.acquisitionRange | 64 |
| photon.range | 96 |
| photon.lifeMs | 5000 |
| photon.radius | 0.45 |
| photon.maxActive | 2 |
| photon.maxHits | 256 |
| photon.steering | 6 |
| photon.weaveAngle | 0.21 |
| photon.weaveHz | 1.25 |
| execution.threshold | 0.15 |
| execution.durationMs | 650 |
| execution.reducedDurationMs | 200 |
| execution.maxRadius | 7 |
| execution.poolSize | 32 |
| flame.range | 16.8 |
| flame.halfAngle | 0.393 |
| flame.damagePerSecond | 63 |
| flame.energyPerSecond | 36 |
| flame.ignitionMs | 2000 |
| flame.burnDamagePerSecond | 15 |
| flame.burnDurationMs | 3000 |
| flame.burnTickMs | 250 |
| armor.energy | 35 |
| armor.durationMs | 4000 |
| armor.cooldownMs | 18000 |
| armor.reduction | 0.8 |
| armor.attractionRange | 10 |
| armor.steeringPerSecond | 8 |
| armor.reserveCap | 400 |
| armor.damageFraction | 0.5 |
| armor.shieldReturnFraction | 0.2 |
| armor.minRadius | 6 |
| armor.maxRadius | 18 |
| warp.energy | 30 |
| warp.cooldownMs | 10000 |
| warp.chargeMs | 300 |
| warp.damage | 40 |
| warp.radius | 7 |
| warp.visualMs | 900 |
| slag.threshold | 60 |
| slag.trailMs | 3000 |
| slag.trailRadius | 1.6 |
| slag.damagePerSecond | 24 |
| slag.tickMs | 250 |
| anvil.stunMs | 600 |
| furnace.holdMs | 1500 |
| furnace.cooldownMs | 4000 |
| furnace.radius | 8 |
| furnace.damage | 45 |
| furnace.knockbackSpeed | 18 |
| furnace.knockbackMs | 400 |
Holy Bolt
| Field | Value |
|---|---|
| energyCost | 30 |
| acquisitionRadius | 8 |
| guidanceDelayMs | 100 |
| steering | 0.24 |
| speed | 34 |
| outwardSpeed | 7 |
| damage | 28 |
| lifetimeMs | 1800 |
| blastRadius | 5 |
| fullDamageRadius | 2 |
| edgeDamageFraction | 0.5 |
| arrivalRadius | 0.6 |
Source reference: src/content/combat.ts: CLASS_ABILITIES, HOLY_BOLT · src/content/garmo.ts · src/content/michen.ts