Bevy-based game project.
Player combat stats (max HP/shield, primary weapon damage, pickup power/speed tiers, AbilityModifiers, energy, crit, pickup radii) are written from a single derive pass: cosmicrafts_progression::progression_derive_player_stats, scheduled after pickup collection and before update_shooters (see src/lib.rs).
- Grant XP or AETH from gameplay with
cosmicrafts_core::events::GrantPlayerExperienceandGrantPlayerAethereum(Bevy messages). Avoid mutatingPlayerExperienceorPlayerCurrencydirectly for rewards. - Equipment, skill nodes, wave shop push to
cosmicrafts_progression::PlayerModifierStack(push/remove_by_source);dirtytriggers the next derive. - Mode-specific rules use
cosmicrafts_progression::ProgressionProfile(xp_enabled,level_stat_growth). Replace or insert this resource when starting a mode. - Crate:
crates/cosmicrafts_progression— addProgressionPipelinePluginbeforePickupsPlugin(see rootCargo.toml/src/lib.rs).
You need Xcode and a Mac for iOS.
The ios/ folder is set up: it builds cosmicrafts-bevy from this repo and bundles assets from ../assets. You already ran rustup target add aarch64-apple-ios. Do the rest in Xcode:
1. Open the project
Open ios/bevy_mobile_example.xcodeproj in Xcode (double‑click or File → Open). Keep the target name as-is so the build script keeps working.
2. Signing
Select the bevy_mobile_example target → Signing & Capabilities → check Automatically manage signing → choose your Team. Set Bundle Identifier (e.g. com.yourname.cosmicrafts) and remember it for App Store Connect.
3. Device (not simulator)
Plug in your iPhone. In the scheme bar, pick your iPhone as the run destination. iOS simulator is broken for Bevy 0.16+; use a real device only.
4. Build and run
Press ⌘R. First time on device: on the iPhone go to Settings → General → VPN & Device Management and trust your developer certificate.
5. TestFlight
- In App Store Connect: My Apps → + → New App (iOS). Use the same Bundle ID as in Xcode.
- In Xcode: Product → Archive. When it finishes, Distribute App → App Store Connect → Upload.
- In App Store Connect → TestFlight tab → add Internal (or External) testers and assign the new build.
One-time setup: Install Android Studio. In it: File → Settings → Android SDK → SDK Tools → tick NDK (Side by side) → Apply. Then in a terminal run cargo install cargo-ndk (that’s the cargo-ndk tool, not the ndk crate). If the build says "Could not find any NDK", set ANDROID_NDK_HOME to your NDK folder (e.g. C:\Users\You\AppData\Local\Android\Sdk\ndk\27.1.12297018); the batch script will try to find it under the default SDK path if you don’t.
Every time you want to run on the phone:
- Run
.\build_android.ps1(PowerShell) orbuild_android.batfrom repo root (or run thecargo ndk ...command yourself). - Open the
androidfolder in Android Studio. Plug in your phone (USB debugging on). Click the green Run button.
That's it. For a release APK: Android Studio → Build → Generate Signed Bundle / APK.
To run the game in the browser and test shield impact (and whether it affects FPS on WebGL), shaders and textures must be served with the WASM build. RON files are embedded at compile time; shaders (assets/shaders/*.wgsl) and images (e.g. assets/sprites/shield.webp) are loaded at runtime and must be available from the same origin.
- Install Trunk:
cargo install trunk - Add wasm target:
rustup target add wasm32-unknown-unknown - From the repo root run:
- Dev:
trunk serve— builds, copiesassets/todist/, and serves. Open the URL shown (e.g. http://127.0.0.1:8080). - Release:
trunk build— output indist/; deploy that folder so the server servesdist/assets/(and the rest) from the same origin.
- Dev:
index.html at the repo root is set up so Trunk copies the full assets/ directory (including shaders/ and sprites/) into the build output. That lets the shield impact shader and texture load on WebGL.
If you build WASM another way (e.g. cargo build --target wasm32-unknown-unknown and a custom HTML/JS loader), copy at least assets/shaders/ and assets/sprites/ (or the whole assets/ folder) into the directory you serve so that requests for assets/shaders/shield_impact.wgsl, assets/shaders/vfx_noise.wgsl, and assets/sprites/shield.webp return the files (same path layout as on disk). Then open the game and trigger shield hits to see if the effect runs and how it affects FPS.
Single enemy spawn source: All in-world enemies come from assets/stress_test_ranges.ron only. When config.enabled is true, stress_spawn maintains target_enemies (spawns heroes from hero_pool or taxonomy classes, plus optional followers). When enabled is false, no enemies spawn. Enemies can still summon minions via abilities (e.g. spawn_drone); those are descendants of the stress-spawned pool. The old chunk/pack spawner and enemy_packs.ron have been removed.
Asset RONs in use (loaded by code):
| Path | Purpose |
|---|---|
stress_test_ranges.ron |
Enemy spawn config (count, distance, hero_pool, stats ranges). |
settings.ron |
Game settings, fog, keys, fonts. |
unit_taxonomy.ron, unit_models.ron, ai_profiles.ron |
Units and AI. |
companion_settings.ron |
Companion formation and behavior. |
lighting.ron, parallax_settings.ron |
Lighting and sky. |
physics_settings.ron, shield.ron, aura.ron |
Physics, shield VFX, unit aura. |
adjustment_layer.ron |
Color grading. |
world/zones.ron, world/levels.ron |
Zones and event levels (dialogue/levels; spawn uses only stress_test_ranges). |
event_behavior_overrides.ron |
Loaded but not used for current stress-only spawn. |
ui/*.ron |
Layout, theme, strings, healthbar, playerhud, pickups. |
effects/death_explosion.ron |
Death effect. |
abilities/*.ron |
Ability definitions. |
units/**/*.ron |
Unit definitions (heroes, companions, elite_void, etc.). |
world/npc_weights.ron |
Per-faction weights for sector orbiting NPCs (merchant, scammer, mystic, etc.). Role "patrol" is ignored in code (Vanguards not spawned). |
tutorial/*.ron, maps/*.ron |
Designer map config; each can define an npcs array for mission NPCs (see below). |
manifest.ron is auto-generated by build.rs and lists all .ron paths for embedding (WASM) and discovery.
Everything that is spawned from the moment you leave the menu until you are in play, in order. No logging is added; this is a static reference for profiling and debugging.
1. Startup (once per app boot)
No units or NPCs. Only resources and assets: load_stress_test_ranges, load_companion_settings, load_physics_settings, load_healthbar_config, load_playerhud_config, pickups/popups/minimap/tooltip/inventory config, load_ui_theme, load_item_registry, hero node trees, faction relations, lore registry, thruster settings, bullet/laser/missile/orb/burst assets, projectile pool, camera, load_parallax_settings, effect assets, mission templates, objective waypoint 3D (hidden), etc.
2. OnEnter(SceneLoading)
- Save/load:
teardown_in_game_state,clear_procedural_mission_state,clear_in_game_session_initialized. - Then (chained):
mission_load_tutorial_if_new_game(sets ActiveMap from Tutorial / Adventure / SafeZone / or none for free play),apply_pending_load_world_seed,apply_intro_dialogue_from_map,spawn_mission_npcs. spawn_mission_npcs(mission/triggers.rs): For each entry inActiveMap.config.npcswith aunit_id, spawns one full unit viaspawn_unit(ScriptedNpc) atnpc.position, then insertsMissionNpc,Name, andunit_nameplate_from_def. So: every mission/tutorial/safe_zone NPC from the map RON (e.g. Command Center, Engineering Bay, Armory, Aldrin, etc.) is spawned here.- Scene loading UI:
init_scene_loading,spawn_scene_loading_ui.
3. SceneLoading Update (several frames)
advance_scene_loading,update_scene_loading_label; after N frames, transition to InGame. No extra unit spawns in this loop.
4. OnEnter(InGame)
- Player: player/spawning.rs
spawn_player— one unit fromSelectedHero(e.g.player.ronor hero unit id) at mapplayer_startor default; manual ability bar starts empty until item resolution runs. - Progression:
resolve_abilities::auto_equip_base_items(afterspawn_player, all modes when equipment is empty) — equips default starter items, thenResolveAbilitiesRequestfills the hotbar from equipment. - HUD: Inventory panel, info panel, interaction prompt roots (hud/mod.rs).
- Gameplay UI: Joystick, HP/shield/energy rows, ability buttons, FPS text, etc. (gameplay/mod.rs, fps_counter.rs).
- Environment: environment/mod.rs
spawn_background_layers(star layers, nebula tiles; no units).
5. InGame Update (first frames and ongoing)
- Hero companion: companion/spawning.rs
try_spawn_hero_companion— when no companion is alive and respawn timer is done, spawns one companion unit (e.g. Aurora Carrier, Comet) fromhero_companion_unit_id(selected_hero)or mapcompanion_override. - Stress-test enemies: enemy_spawner/stress.rs
stress_spawn— whenstress_maintainer_spawn_enabled: free play always; Tutorial/Adventure only if F2 → Stress tab Toggle maintainer in Tutorial/Adventure (DesignerStressMaintainerOverride). Maintainstarget_enemiesfrom stress_test_ranges.ron: spawns units fromhero_pool(e.g. aurora, tempest) or taxonomy, atmin_distance..max_distancefrom player;spawn_per_framecaps how many per frame. Thenspawn_stress_followers_systemadds follower formation;apply_stress_enemy_shieldadds Shield from PendingShield. - Ambush waves:
handle_spawn_ambush_request— spawns wave units from event/mission requests. - Parallax sector decorations: environment/parallax/rendering.rs
spawn_sector_decorations— for each new sector marker (player entered range): spawns one station unit per sector (e.g.cosmicons_station,celestials_stationfrom units/structures) and 2–5 orbiting NPCs (from world/npc_weights.ron, e.g. merchant, mechanic, smuggler). So stations and sector NPCs appear as you move into new sectors, not all at game start. - Ability summons: abilities/spawn.rs
spawn_unit_on_activate— when the player (or an enemy) uses a spawn ability (e.g. spawn_drone, spawn_sunfire_comet), spawns one unit viaspawn_unit(Summoned role). - Procedural missions: mission/procedural.rs — when a procedural mission is started, a cluster of ProceduralMissionEnemy units is spawned (count from mission def) via
spawn_unit(Enemy role).
Summary table
| When | What spawns (units / entities) |
|---|---|
| Startup | No units; config and assets only. |
| OnEnter(SceneLoading) | All mission NPCs from the active map RON (npcs array with unit_id). |
| OnEnter(InGame) | Player (1), background layers (meshes/tiles, no units). |
| InGame Update | Hero companion (1 when eligible), stress enemies (up to target_enemies, rate-limited by spawn_per_frame), ambush waves (on request), sector stations + orbiting NPCs (per new sector), ability summons (on cast), procedural mission enemies (when mission starts). |
So at the very start of a run you get: mission NPCs (if any map is active) + player + hero companion (soon after) + stress enemies (if free play) filling up to target_enemies from stress_test_ranges.ron. Sector stations and NPCs appear only when the player enters new sectors.
All non-enemy NPCs are spawned from exactly two pipelines. There is no other procedural NPC spawn.
| Source | What is spawned | Where it’s defined | Where it’s run | How to control it |
|---|---|---|---|---|
| 1. Sector orbiting NPCs | Ships orbiting the faction station in each sector (merchants, mechanics, smugglers, etc.). Get FactionNPC + optional DockingPort and dialogue. |
Weights: assets/world/npc_weights.ron (per-faction map: role → weight, e.g. "merchant": 25, "patrol": 20). Units: assets/units/npc/{faction}_{role}.ron (e.g. cosmicons_merchant.ron, webes_mystic.ron). |
crates/cosmicrafts_graphics/src/environment/parallax/rendering.rs: when a sector’s decorations are spawned, it reads LoreRegistry.npc_weights for that faction, builds a weighted role pool, picks 2–5 roles per sector, and for each spawns the unit {faction_lowercase}_{role} from UnitRegistry. |
Turn off a role everywhere: In npc_weights.ron set that role’s weight to 0 for every faction (or remove the key). Turn off one role in code: In rendering.rs, when building the role pool, skip that role (e.g. "patrol" is already skipped so Vanguard patrols are never spawned). Add a new role: Add weights in npc_weights.ron and add assets/units/npc/{faction}_{role}.ron for each faction that should have it. |
| 2. Mission / designer map NPCs | Scripted NPCs for tutorial or campaign: waypoints, dialogue, objective targets, optional convert-to-enemy. Get MissionNpc + full unit or minimal scene. |
Map RON: Each designer map file (e.g. assets/tutorial/tutorial1.ron, assets/maps/safe_zone.ron) has an npcs array. Each entry: id, display_name, position, optional model, optional unit_id (e.g. "juggernaut", "tempest"). |
crates/cosmicrafts_world/src/mission/triggers.rs: spawn_mission_npcs runs once when ActiveMap is present and no MissionNpc exists yet. For each npc in active.config.npcs: if unit_id is set, spawns via spawn_unit + MissionNpc; else spawns a minimal entity (Transform, SceneRoot, MissionNpc). |
Add/remove/list NPCs: Edit the map RON’s npcs array. Which map is active: Set by game flow (e.g. TUTORIAL → load_tutorial1() → tutorial/tutorial1.ron; safe zone → load_safe_zone() → maps/safe_zone.ron). See crates/cosmicrafts_world/src/mission/mod.rs (load_tutorial1, load_safe_zone) and crates/cosmicrafts_core/src/game_state/mod.rs for when ActiveMap is inserted. |
Stations (docking posts) are also spawned in the same sector-decoration pass in rendering.rs: one per sector from a fixed mapping (e.g. cosmicons_station, archs_station). They are not driven by npc_weights.ron; they come from assets/units/structures/*_station.ron.
Models (GLB): Unit and ability RONs reference models/… paths under assets/models/. GLBs not referenced by any unit or ability RON live in assets/models/unused/ (same subfolder structure, e.g. unused/gundam/, unused/omen/) so they can be restored or repurposed without cluttering the active set.
Text rendering: Plain UI labels, menus, and tooltips use Bevy's built-in Text + TextColor. Stylized text (outline, gradient fill) uses crates/bevy_sdf_text: SDF/MSDF atlases (e.g. from msdf-atlas-gen), one quad per glyph, shader outline and four-corner gradient. Damage popups use SDF when an SDF font is available (fonts/Goldman-Bold.json); config in assets/ui/popups.ron can set damage_fill_crit, damage_fill_normal, etc. to solid or gradient. See crates/bevy_sdf_text/README.md for when to use which.
Abilities and pickups (synergy): Energy fuels abilities (cost per cast; regen over time). Energy pickups grant a temporary regen buff so you can sustain ability use. Power increases weapon damage (including from abilities that use the same damage source) and is capped by PowerLevel. Speed increases ship max speed. Drop chance and pickup-type weights are data-driven from ui/pickups.ron (per-tier weights and drop chance by enemy value). Abilities support hold-to-repeat: keep the key or ability button pressed to fire again at a configurable interval (see hold_repeat_interval_secs in ability RONs) until energy or cooldown blocks.
HUD interaction (action registry): In crates/cosmicrafts_ui_hud, UI clicks and touches are wired through a single pattern. For new HUD buttons: define a marker component on the entity → map in a collector system (e.g. in hud/actions.rs) from Interaction/pointer events to HudUiActionRequest → handle in dispatch_hud_actions (one handler per action variant) → style in a visual updater (hover/active/ready). Collectors run before the dispatcher; visuals run after state updates. Hover: collectors write HudHoverState; one dispatch_hover system updates tooltips and hover state (inventory, trading, minimap, standmark, equipment nameplate, skill nodes).
Run the game, start or continue a run (so you have a world seed and sector), then use these cues to verify each phase.
Phase 1 — Full-screen map and sector borders
- Open map: InGame → press M or click the Map button next to the minimap (top-right). Overlay should open.
- Full-screen cue: The map grid uses most of the screen (not a small 520px panel). There is a thin header (“SECTOR MAP (M to close)”) and a Close button; the grid fills the remaining space and scales with window size.
- Sector borders cue: Every cell has a visible edge (1px border). Current sector has a thicker or accent border. Cells look like a “country map” with clear sector boundaries.
- Close: M or Escape or Close button hides the overlay.
Phase 2 — Map drawn from world data (biome colors)
- Open map (M or HUD Map) while in a sector.
- Biome colors cue: Explored cells are not all the same gray. You see different colors per sector (e.g. dark gray empty, brownish asteroid, purple nebula, reddish pirate, greenish friendly, etc.). Unexplored cells stay pitch black.
- Current sector: One cell is highlighted (accent color/border) — “You” or current. Lead targets (if any) show a Go (Travel) button.
- Determinism: Same seed + same sector coord always yield the same cell color; the map matches the procedural world.
Phase 3 — Subdivision (design only)
- No in-game visuals to test. Verify in docs/RON.md: section “Sector and chunk subdivision (procedural world)” describes chunk key,
chunk_seed, and data contract. assets/world/chunk_content.ron exists as a stub. Code:cosmicrafts_world::ChunkKeyandchunk_seed(world_seed, key)exist for future procedural env.
Phase 4 — Icons / LOD (future)
- Deferred. Map biome colors are from Phase 2; LOD/sprites for astronomical zoom are a later milestone.
Quick checklist
| Phase | Cue |
|---|---|
| 1 | Map is full-screen; every cell has a border; current sector stands out. |
| 2 | Explored cells show different colors by biome; unexplored = black. |
| 3 | Docs + stub RON + ChunkKey / chunk_seed in code. |
| 4 | Not implemented; skip. |
Designer maps (e.g. tutorial/tutorial1.ron) run a separate pipeline from free play: when ActiveMap is present, free-play enemy spawn (stress_test_ranges.ron) is gated off. Map RONs define player_hero (optional; unit id the player controls, default "aurora"), intro dialogue, player_start, companion_override, NPCs, missions, triggers (MissionComplete, Proximity, Timer, PhaseTimer, WhenChoice, WhenChoiceValue, AllEnemiesDefeated, AllEnemiesDefeatedAfterChoice), and actions (ShowDialogue, SpawnWave, ShowUnitDialogue, AddNpcWaypoints, SetEnemyTarget, ShowChoiceDialogue, ConvertNpcToEnemy). See docs/RON.md and crates/cosmicrafts_world/src/mission/ for schema and code.
- Wired: Niri companion unit, map
companion_override, blocking dialogue, popup speech bubbles with companion resolution, choice schema (ShowChoiceDialogue,WhenChoice,WhenChoiceValue,AllEnemiesDefeated,AllEnemiesDefeatedAfterChoice), trigger runner, apply_pending_convert_npc_to_enemy (NPC→enemy), apply_choice_made_to_runtime (ChoiceMade→MapRuntime), choice dialogue UI (overlay + buttons, sends ChoiceMade). - Content: Full tutorial in
tutorial1.ron: Niri (sarcastic barks), report to Aldrin, holdout wave and waypoint follow, Spirat boss at rally with choice (join pirates / destroy pirates), branch (Aldrin converts to enemy if join), final war wave, victory only after all_enemies_defeated_after_choice("spirat_choice") then complete_mission("tutorial_win") and cleanup.
Key files: crates/cosmicrafts_world/src/mission/mod.rs, triggers.rs, cosmicrafts_core::events (CurrentChoiceDialogue, ChoiceMade), cosmicrafts_ui gameplay (choice overlay).
GDD is the canonical design source for the sector-based core loop. This README section is the implementation map: it explains which systems already support that loop, which systems are incomplete, and where future coding should anchor itself.
GDD— authoritative game-direction doc: core fantasy, sector episode loop, risk model, reward model, diplomacy role, persistence promise.README.md— repo-level implementation map for current reusable systems, ownership of core loop foundations, and next milestones.docs/RON.md— technical home for future data/schema authoring around sectors, tribes, shrines, clues, and mission-style contracts.
- Sector/world seed base:
crates/cosmicrafts_world/src/generator.rsand world-zone data already support deterministic space, sector coordinates, naming, and discovered-sector tracking. - Adventure pacing base:
crates/cosmicrafts_world/src/adventure/markov.rsalready provides encounter rhythm, escalation, refill behavior, and light narrative pacing. This should remain the pacing layer inside a larger sector episode system. - Tribe interaction seed:
crates/cosmicrafts_world/src/mission/already supports dialogue, rewards, triggers, branching choices, NPC targeting, and consequence-style scripting. This is the seed for optional diplomacy and procedural tribe encounters. - Progression seed:
crates/cosmicrafts_world/src/progression/pluscrates/cosmicrafts_ui_hud/src/hud/inventory_panel.rsandskill_tree_tab.rsalready provide items, bag/equipment, modifier stacking, hero nodes, and UI. - Pickups/blessing seed:
assets/ui/pickups.ronandcrates/cosmicrafts_world/src/pickups/already provide fight-scale boosts. These should eventually ladder upward into sector-scale shrines and blessings. - Save/load seed:
src/save_load.rsandcrates/cosmicrafts_core/src/save_game.rsalready persist world seed, discovered zones, player stats, XP, and currency.
The bevy_save repo (e.g. sibling bevy_save/ or github.com/hankjordan/bevy_save) is a useful reference for patterns; it targets Bevy 0.16, so we do not depend on it directly (this project uses Bevy 0.18). Concepts worth reusing in our own save system:
- Reflection-based snapshots: Capture/apply full or partial
Worldstate (resources + entities + components) via Bevy’sReflect; we stay with an explicitGameSavestruct and manual read/write for now, but the idea of “snapshot → serialize → deserialize → apply” is the same. - Platform save dirs: Uses something like
platform-dirsfor a standard save location per OS; we use a fixedsaves/path and could later align with platform conventions. - Migrations: A
Migratetrait and versioned snapshots for backward compatibility; we use a singleSAVE_VERSIONand#[serde(default)]for new fields; if we add more versions later, a small migration layer inspired by bevy_save would help. - Pipelines / backends: Pluggable backend (e.g. file vs in-memory) and format (JSON, RON, etc.); we currently have one path (RON to
saves/); we could later abstract backend/format if needed. - Checkpoints / rollback: In-memory checkpoints for rollback; not required for our current loop but useful to remember for future “rewind” or debug tools.
So: use bevy_save for ideas and patterns, not as a dependency; keep our implementation on the existing GameSave + write_save / read_save contract.
Cloud save (optional): A small HTTP backend in server/ (run with cargo run -p cosmicrafts-server) exposes PUT/GET /save, POST /stats, and POST /achievements. The game can upload saves and stats and download the latest save when Continue is used. Build the game with cargo build --features cloud and set env COSMICRAFTS_CLOUD_URL=http://127.0.0.1:8080 to use the local server; otherwise the game uses a no-op backend and local files only.
- Sector State The future source of truth for a sector episode: faction control, local tribes, power web, shrines, anomalies, clues, extraction state, and next-sector leads.
- Tribe Interaction Mission/dialogue systems extended into optional negotiation, tribute, alliance, betrayal, and declaration-of-hostility flows.
- Persistent Progression Inventory, equipment, nodes, faction standing, and extraction outcomes promoted from runtime/session helpers into long-term identity.
- Pickups -> Shrines -> Mutations A reward ladder where pickups stay fight-scale, shrines become sector-scale choices, and mutations add run/build-scale procedural identity.
- Adventure is currently pacing, not a full sector loop.
- World discovery exists, but persistent sector episode state does not.
- Save/load still does not preserve the full progression backbone needed for long-term play.
- There is stale or half-wired behavior worth treating as foundation debt:
- split save paths between root save/load flow and older UI save flow
- progression teardown still clears resources that should eventually become persistent
player.inventorysave data is still tied to ability loadout instead of real inventory/equipment persistence- old placeholder content and docs can misrepresent the current direction
In place now: Phase 5 (discovered leads visible in pause menu, Travel to lead) and the sector map (M key, biome grid, Travel on leads) are implemented. Extract is an explicit pause-menu action: it runs the same save path as SAVE and shows “Sector progress saved. Travel to a lead when ready.” so the player can leave the sector with progress persisted.
- Define the
SectorStatecontract and its persistence boundaries. - Define the persistence contract for progression, extraction, and clues.
- Define the data/schema contract for procedural tribes, shrines, and sector content.
- Build the actual sector episode loop on top of Adventure pacing, mission interactions, and persistent progression.
After switching pickup HP/shield/energy regen to % of max per second with ranges and stacking, the following lingering or optional pieces are left for you to decide:
| Finding | Location | Suggestion |
|---|---|---|
Shield instant_heal_percent |
RegenEffectRon / effects.rs: shield branch uses e.instant_heal_percent for an instant shield heal on collect. |
Not used in assets/ui/pickups.ron (only health has it). Keep for future tuning or remove from RON/config and the shield branch in effects.rs if you want zero dead branches. |
Energy instant_heal_percent |
RegenEffectConfig has the field for energy too; energy pickups never had instant heal. |
Purely redundant; only health (and optionally shield) use it. Safe to leave for symmetry or remove from energy in RegenEffectRon / build_effects if you prefer a minimal config. |
| Save/load and buff components | HealthRegenBuff, ShieldRegenBuff, EnergyRegenBuff are now { stacks: Vec<(f32, f32)> }. |
If save/load ever persisted these (e.g. via Reflect), old saves would deserialize with the wrong shape. Current save flow does not appear to persist these short-lived buffs; if it does, treat this as a breaking save format change and document or version it. |
Progression ModifierStat::HealthRegen / ShieldRegen / EnergyRegen |
progression/types.rs and modifier_stack.rs. |
These affect permanent passives (HealthRegenPassive, ShieldRegenPassive, reactor energy regen), not pickup buffs. No change needed; they are still correct. |
No other dead code was found; the old absolute rate/remaining single-buff logic has been fully replaced by rate_percent and stacks.
All of the following are in-world orbs spawned when an enemy dies. Config: assets/ui/pickups.ron. Equipment (thrusters, cannons, reactor, etc.) drops are separate and use the item registry + progression item-drop system.
Drop flow: On enemy death, DropPickupRequest { position, enemy_value } is sent. spawn_pickups_from_requests (cosmicrafts_world):
- If
enemy_value > 0: spawns one Aeth orb with that value (always; no roll). - Rolls one random orb with probability
drop_chance(enemy_value)(fromdrop_chancein pickups.ron: high/mid/low by enemy_value thresholds). - If the roll passes, the type is chosen by weights and player tier (Critical / Damaged / Healthy). A single random in (0,1] is compared to cumulative weights; first type whose weight ≥ roll wins.
| Pickup type | Dropped? | What it does on collect | Config (effects in pickups.ron) | Customization levers |
|---|---|---|---|---|
| Aeth | Yes (one per kill if enemy has aeth_reward > 0) | Adds enemy_value to player Aethereum; no orb type roll. |
N/A (value from unit def aeth_reward). |
Unit RON aeth_reward; no pickups.ron effect block. |
| Health | Yes (via weight roll) | HoT: adds a regen stack (% of max HP/sec for random 2–5 s). Small instant heal (2–8% of max). Stacks with other Health pickups. | health: rate_percent, duration_secs, tier_modifier, instant_heal_percent. |
Ranges for rate (8–12%), duration (2–5 s), instant (2–8%); tier modifiers; weights per player state. |
| Shield | Yes (via weight roll) | HoT: regen stack (% of max shield/sec, 2–5 s). Small instant (2–8% of max) if configured. Stacks. | shield: same shape as health. |
Same levers as Health; weights and tier modifiers. |
| Energy | Yes (via weight roll) | HoT only: regen stack (% of max energy/sec, 2–5 s). No instant. Only applies if player has Energy component. Stacks. | energy: rate_percent, duration_secs, tier_modifier. |
Rate (15–25%), duration (2–5 s); weights and tier modifiers. |
| Power | Yes (via weight roll) | +1 damage tier (resource PowerLevel). Each tier adds 15% damage; capped at max tier. |
power: max_tier, multiplier_per_tier, tier_modifier. |
Max tier (5), multiplier (0.15); weights. |
| Speed | Yes (via weight roll) | +1 speed tier (resource SpeedTier). Each tier adds 20% max speed; capped at max. |
speed: max_tier, multiplier_per_tier, tier_modifier. |
Max tier (3), multiplier (0.2); weights. |
| Xp | Not dropped as orb | (Orb type exists; collection branch skips.) Not in weight table. | — | Would need to be added to weights and drop logic if desired. |
| Credits | Not dropped as orb | Same as Xp. | — | Same. |
Where to tune:
- Drop chance:
drop_chance(enemy_value_high, enemy_value_mid, chance_high, chance_mid, chance_low). Higher enemy value → higher chance of an extra orb (Aeth is always separate). - Which orb type:
weights.critical,weights.damaged,weights.healthy. Cumulative weights 0..1; lower weight = chosen earlier (e.g. Health 0.28 → roll ≤0.28 gives Health). Duplicate "Health" at end is fallback so roll always wins. - Effect strength: Each
effects.*block (health, shield, energy, power, speed). All use (min, max) ranges and optional tier_modifier for player state.
How new abilities hook in today (short):
- Data: Add
assets/abilities/<id>.ron(cooldown, costs, metadata). Effect payload must match a variant incosmicrafts_units_data/ ability definitions (see existingChainLightning,Laser,Burst, etc.). - Runtime:
crates/cosmicrafts_units/src/abilities/— subscribe toAbilityActivateEvent, readAbilityRegistry, spawn VFX entities, sendDamageEvent, optionalKnockback, reusemissile::Explosionfor flashes. - Meshes/materials:
bevy::math::primitives→Assets<Mesh>once at Startup;EmissiveMaterialCache+Assets<StandardMaterial>for shared glowing materials (same pattern as chain_lightning.rs). - Targeting:
GameplaySpatialGrid+Teamfor nearby enemies (chain lightning already shows cell-radius query). - Juice:
assets/settings.ron—shake_*,hit_stop_*,hurt_flash_*,vfx_enabled/vfx_quality.
Easiest first: #1 Rail burst — Same cylinder + jagged segments as chain lightning, but one straight path (caster → first enemy or aim direction), no bounce loop. Smallest new AbilityEffect variant and one plugin module; fastest way to validate RON → damage → VFX end-to-end.
| # | Name | Do | Systems |
|---|---|---|---|
| 1 | Rail burst | Straight beam built from N jittered cylinder segments along one ray; single target or first hit in range. | Copy chain lightning segment spawn; replace grid bounce with one DamageEvent; EmissiveMaterialCache. |
| 2 | Expanding ring | Flat torus or squashed cylinder at impact; scale XZ over time, despawn. | New mesh in ability init_*_assets; Update system scales Transform; damage once or tick in radius. |
| 3 | Spiral drill | Helix of short cylinders from caster to target. | Loop: position = lerp + offset on orthogonal axes by sin/cos along t; same mesh/material as lightning. |
| 4 | Double-layer glow | Two cylinders per segment (wider dim + narrow bright) or halo sphere + core. | Two mat_cache.get_or_insert keys; spawn two children or two entities per segment. |
| 5 | Impact core + spikes | Sphere pop + 6–12 thin cylinders radiating from hit. | Reuse Explosion + radial dirs from Vec3::Y cross products; thin scale on cylinders. |
| 6 | Orbital stakes | Capsules arranged in a ring, then lerp toward target over ~0.3s. | Components with start/end/timer; Update moves Transform.translation; damage on arrival or overlap. |
| 7 | Pillar column | Stack of vertical cylinder segments at a ground point, staggered despawn timers. | Fixed XZ; vary Y; reuse LightningVfx-style shrink or alpha via scale. |
| # | Name | One-line build note |
|---|---|---|
| 8 | Fan burst | N cylinders in a horizontal arc from caster; cone query on grid, one damage per enemy or per ray. |
| 9 | Backstab arc | Short wide cylinder arc behind target relative to caster facing (flip dir). |
| 10 | Ricochet disc | Flat cylinder projectile; on hit, reflect dir, spawn second trail; reuse bullet/orb motion patterns. |
| 11 | Black hole pull | Torus + inward knockback over time in radius; damage tick; optional dark emissive. |
| 12 | Nova bloom | Nested spheres (3 scales) expanding at different speeds; sphere mesh only. |
| 13 | Laser rake | Several parallel thin cylinders (offset orthogonally); sweep rotation over 0.2s Update. |
| 14 | Mine cluster | Spawn small spheres that sit, then pulse scale + radius damage once. |
| 15 | Booster ram | Capsule stretched along velocity; damage on overlap + self dash (see existing Dash plugin). |
| 16 | Orbiting shields | 3 toruses orbiting player transform; block or reflect one hit (component + timer). |
| 17 | Comet tail | Trail of fading cylinders along recent positions (ring buffer on caster). |
| 18 | Ground slam | Cylinder from sky to ground point; shockwave ring (flat cylinder) + Knockback radial. |
| 19 | Sniper needle | One long thin cylinder, minimal jitter; high single-target damage. |
| 20 | Forked rail | Rail burst but 3 parallel rays with slight Y/Z offset (three damage queries). |
| 21 | Delayed mark | Sphere at target; after delay, cylinder strikes from above (two-phase timer component). |
| 22 | Chain shorter | Chain lightning with max_bounces: 2 but huge damage — tune RON only. |
| 23 | Heal pulse | Expanding ring, green emissive, negative damage or heal event if you add a heal message. |
| 24 | EMP dome | Hemisphere (half-scale trick or many small spheres); silence/debuff if status system wired. |
| 25 | Drone line | Spawn summoned unit in line (reuse spawn ability) + cylinder tether VFX between caster and drone. |
| 26 | Scissor cut | Two cylinders crossing at hit point; quick rotation 90° over lifetime. |
| 27 | Whirlpool | Cylinders arranged in decreasing-radius rings, rotating Y each frame. |
| 28 | Meteor drop | Sphere + motion from sky; Explosion on land; reuse missile gravity pattern if any. |
| 29 | Void tether | Cylinder between caster and enemy; damage per second while line intact (distance check). |
| 30 | Victory beam | Vertical column of stacked capsules; static art; wide radius tick damage. |
- tools/ — See
tools/README.mdfor tool usage (e.g. glb-optimize).