No description
  • C# 48.6%
  • C 46.4%
  • ShaderLab 2.4%
  • GLSL 1.9%
  • C++ 0.2%
  • Other 0.1%
Find a file
Robert Goins ff3f0b7c58 Keep the drones and auras through a Redoubt floor change
AdvanceFloor destroyed the player's companion drones and aura rings with
the rest of the run transients, then called ResyncRunTransientLoadouts to
rebuild them - but Unity defers Destroy to the end of the frame, so the
loadouts' entries still compared non-null, Sync read the set as already
complete and spawned nothing. The drones died at end of frame and the
stale references kept Sync satisfied, so they were gone for the rest of
the run (until another drone pick happened to clean the list).

Discard the old set explicitly before resyncing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-27 19:41:54 -07:00
Assets Keep the drones and auras through a Redoubt floor change 2026-09-27 19:41:54 -07:00
docker/webgl Docker: nginx container to host the WebGL browser build 2026-09-02 19:23:25 -07:00
Packages Pause-menu build summary, Steam Deck defaults, package and account cleanup 2026-09-13 21:22:35 -07:00
pages Sign in automatically with Steam, Game Center, or Google Play Games 2026-09-14 03:51:41 -07:00
ProjectSettings Balance pass: finales that match the build each stage hands you 2026-09-27 16:24:13 -07:00
scripts
steam Steam release prep: display/frame-rate settings, overlay pause, depot cleanup 2026-09-13 18:27:50 -07:00
store_art Export achievement icons as JPG for Steamworks 2026-09-14 08:15:46 -07:00
trailer
.dockerignore Docker: nginx container to host the WebGL browser build 2026-09-02 19:23:25 -07:00
.gitattributes
.gitignore
BUG_AUDIT.md Merge cloud progress instead of letting the newer push overwrite it 2026-09-13 10:37:26 -07:00
BUG_AUDIT_2026-09-27.md Mark the 2026-09-27 audit entries fixed 2026-09-27 18:27:37 -07:00
compose.yaml Docker: nginx container to host the WebGL browser build 2026-09-02 19:23:25 -07:00
DEMO.md Add the demo build: two whole stages behind ECHO_DEMO 2026-09-19 14:11:20 -07:00
GRAPHICS_AUDIT.md
HUB_NPC_ANIMATION_PLAN.md
ITCH_RELEASE.md
PERFORMANCE.md Preload combat audio and warm the level-up crate 2026-09-16 00:02:09 -07:00
PRIVACY_POLICY.md Record the live GitHub Pages site in the docs 2026-09-14 08:51:46 -07:00
README.md Merge mobile-friendliness work; Cold Harbor: continuous timed stages 2026-09-20 14:23:13 -07:00
STEAM_ACHIEVEMENTS.md Export achievement icons as JPG for Steamworks 2026-09-14 08:15:46 -07:00
steam_appid.txt
STEAM_RELEASE.md Record the live GitHub Pages site in the docs 2026-09-14 08:51:46 -07:00
TRAILER.md

Echo Protocol

Echo Protocol is a top-down sci-fi roguelite shooter built in Unity 6 / URP. A run is one of six self-contained stages — a procedural multi-floor dungeon, an endless survivors field, two wave defences, a forward-pressure extraction street, and an escort — each with its own director, arena and win condition. Between runs the player returns to a Hub to spend a persistent currency on permanent upgrades and to raise the per-stage difficulty ladder.

Almost everything the player sees is built at runtime from code: lighting, cameras, the player, enemies, every HUD and menu, and (in the dungeon stages) the level geometry itself. Scenes carry authored art and a bootstrap component; they do not carry authored gameplay hierarchies.


📚 Docs map

Doc Covers
README.md (this file) Architecture, systems, invariants, controls, build targets
STEAM_RELEASE.md Steam runbook — one-time setup, build → sign → upload → publish, troubleshooting
STEAM_ACHIEVEMENTS.md The 25 achievements + kill stat: Steamworks setup table, triggers, testing
DEMO.md The free demo — the slice it ships, ECHO_DEMO and what it gates, save carry-over, pre-ship checklist
PRIVACY_POLICY.md Store-facing privacy policy (published from pages/ via GitHub Pages; re-check when adding analytics / sign-in providers)
ITCH_RELEASE.md WebGL / itch.io pipeline (butler, WebGL hardening)
GRAPHICS_AUDIT.md Rendering audit + remediation plan (2026-08-29)
PERFORMANCE.md Profiling pass (2026-09-13): method, numbers, fixes made, what's left
TRAILER.md Screenshot / trailer capture recipe, CaptureRig hotkeys
HUB_NPC_ANIMATION_PLAN.md Hub background-NPC animation setup
store_art/README.md Final marketing art + the self-contained regenerator

🎯 The loop

StartScene (title)  →  Hub  →  stage briefing + Threat Tier  →  stage  →  run-end screen  →  Hub
                        ↑                                                   (Echoes banked)  │
                        └───────────────────────────────────────────────────────────────────┘

The Hub (Hub.unity) is the between-runs area: a portal that opens the stage briefing (HubStageSelect), a medbay terminal that spends Echoes (HubMetaTerminal), idle NPCs, and a one-time how-to-play card on first arrival (HubFirstRunDialog).

Stages

LevelMode (in GameManager.cs) is the canonical stage id. Its enum order is the Hub stage index and the DifficultyService key — do not reorder it.

# LevelMode Name Scene Bootstrap Director(s) Shape
0 Simulator The Redoubt GameplayScene DungeonBootstrap DungeonGenerator 10 procedural floors; boss arenas on 5 and 10
1 OpenField The Sunken Mire SwampScene OpenFieldBootstrap OpenFieldDirector + WaveDirector Endless survivors field; boss at 12 min
2 BaseAssault Cold Harbor OutpostScene BaseAssaultBootstrap BaseAssaultDirector + SiegeWaveDirector Hand-authored snow arena, 8 timed waves, boss last
3 CityRun Neon Run NeonScene CityRunBootstrap CityRunDirector Procedural street, push through a sustained horde ahead of an advancing kill-wall, hold the extraction pad
4 Emplacement Last Light EmplacementScene EmplacementBootstrap EmplacementDirector + LastStandWaveDirector Defend a reactor core on foot across 8 boarding waves
5 Convoy Convoy ConvoyScene ConvoyRunBootstrap ConvoyRunDirector Escort an auto-driving crawler down a procedural street

Only the Redoubt is mid-run resumable (GameSaveData); the wave/run stages deliberately are not. Only the Redoubt and Last Light use the minimap.


🏗️ Architecture

Every stage scene holds art plus one *Bootstrap component. The bootstrap does the same runtime setup — physics tick, asset validation, lighting, camera, effect managers, HUD — and then wires the stage's own director(s) and calls the matching GameManager.Start*Run().

graph TD
    Boot["*Bootstrap (per scene)"] -->|Time.fixedDeltaTime = 1/60| Phys[Physics tick]
    Boot -->|ValidateRequiredAssets| RAL[RuntimeAssetLibrary]
    Boot --> Light[Lighting + weather/motes]
    Boot --> Cam["RoomCameraController / HubCameraController"]
    Boot --> FX["EffectManager + GameFeel + DamageToasterManager"]
    Boot --> UI["PlayerHUDUI + PauseController + mode HUD + MobileJoystickUI"]
    Boot --> GM[GameManager]
    GM -->|StartGame / Start*Run| Dir["Stage director (DungeonGenerator, WaveDirector, CityRunDirector, ...)"]
    Dir --> Spawn["Enemy spawns + loot + XP orbs"]
    GM -->|CreatePlayer| Player[PlayerController]
    Player --> Meta[MetaProgressApplier]
    Player -->|level up| LevelUp[LevelUpUI]
    GM -->|run end| RunEnd["PlayerHUDUI run-end screen → MetaProgressService.AwardRun"]

📂 Script directory

157 scripts under Assets/Scripts/, root namespace SciFiShooter.

Assets/Scripts/
├── Audio/
│   ├── BackgroundMusicController.cs   # Stage track + boss-theme crossfade (EnemyBase.ActiveBoss)
│   ├── MusicLibrary.cs                # Boss themes by BossEnemyBase.MusicKey (Resources asset)
│   └── WeaponAudioController.cs       # Combat one-shots off one persistent AudioSource
├── Auth/                              # SciFiShooter.Auth asmdef
│   ├── AuthService.cs                 # UGS Authentication; platform-identity/guest sign-in, link/unlink
│   ├── AuthProvider.cs / AuthProviderRegistry.cs / IExternalAuthProvider.cs
│   └── Providers/                     # Steam (live), Game Center + GPGS (need plugins), Unity Player
│                                      #   Accounts; Apple / Google / Facebook are stubs (see "Known gaps")
├── BaseAssault/                       # "Cold Harbor" (OutpostScene)
│   ├── BaseAssaultArena.cs            # Scene-authored arena description (spawn lanes, bounds)
│   ├── BaseAssaultDirector.cs         # Run owner
│   ├── SiegeWaveDirector.cs           # Fixed list of timed assault waves, boss on the last
│   └── SnowWeather.cs                 # Ambient snowfall
├── Camera/
│   └── RoomCameraController.cs        # Follow rig with per-level framing bias + room clamping
├── CityRun/                           # "Neon Run" (NeonScene)
│   ├── CityRunConfig.cs               # ScriptableObject set-dressing + tuning
│   ├── CityRunDirector.cs             # Assembles the street, drives the run
│   ├── PursuitWall.cs / PursuitWallLife.cs  # The advancing lethal kill-wall
│   └── RouteZones.cs                  # Checkpoint / extraction trigger volumes
├── Combat/
│   ├── ElementType.cs                 # Fire / Ice / Acid / Lightning
│   ├── GrenadeProjectile.cs           # Physically thrown, bounces, timed blast
│   └── PlasmaProjectile.cs            # Spherecast bolt; pierce, ricochet, element, lifesteal
├── Convoy/                            # "Convoy" (ConvoyScene) — forked from CityRun
│   ├── ConvoyRunConfig.cs / ConvoyRunDirector.cs
│   ├── ConvoyVehicle.cs               # The auto-driving crawler
│   ├── ConvoyHealth.cs                # Hull HP; implements IEnemyTarget
│   └── ConvoyTurret.cs                # Roof gun, auto-acquires nearest enemy
├── Core/
│   ├── *Bootstrap.cs                  # One per stage scene (see the stage table)
│   ├── GameManager.cs                 # LevelMode, run lifecycle, floor advance, score, threat tier
│   ├── RuntimeAssetLibrary.cs         # THE Resources loader (Resources/SciFiShooter/**)
│   ├── InputHelper.cs                 # Unified KBM + gamepad + touch input
│   ├── SettingsService.cs             # PlayerPrefs-backed options ("sfs." prefix)
│   ├── MetaProgression.cs             # MetaUpgradeId / MetaUpgradeDef / MetaUpgradeCatalog / MetaBonuses
│   ├── MetaProgressService.cs         # Echoes currency, purchased tiers, AwardRun payout
│   ├── DifficultyService.cs           # Threat Tiers (5 per stage), unlock-by-clearing
│   ├── GameSaveData.cs                # Mid-run resume (Redoubt only), via JU SaveLoad
│   ├── PlayerProgress.cs              # "Is there anything worth keeping?" + wipe
│   ├── CloudProgress.cs / CloudSaveSync.cs  # Unity Cloud Save mirror, last-write-wins by rev
│   ├── StreetStreamer.cs              # Shared band streamer for Neon Run / Convoy
│   ├── RunObjectPool.cs               # Pools short-lived prefabs, releases between sectors
│   ├── WebGLGraphics.cs               # Browser build → URP asset with APV off
│   └── CommunityLinks.cs
├── Debug/
│   └── CaptureRig.cs                  # Editor / dev-build only. Screenshot + cinematic hotkeys
├── Dungeon/                           # "The Redoubt" (GameplayScene)
│   ├── DungeonGenerator.cs            # 10-floor generator + runtime NavMesh bake
│   ├── Room.cs                        # RoomType, clear state, door wiring
│   ├── RoomShape.cs / RoomTemplate.cs / RoomTheme.cs  # Template + theming system
│   ├── RoomTransitionZone.cs          # Entry routing for authored footprint regions
│   ├── Door.cs                        # Directional traversal trigger + sealed visuals
│   └── FloorTeleporter.cs             # Powers up on room clear → GameManager.AdvanceFloor
├── Emplacement/                       # "Last Light" (EmplacementScene)
│   ├── EmplacementArena.cs / EmplacementDirector.cs
│   ├── LastStandWaveDirector.cs       # 8 boarding waves, RobotBoss on the last
│   └── ShieldGenerator.cs             # The reactor core; hitting zero loses the run
├── Enemies/
│   ├── EnemyBase.cs                   # Health, statuses (burn/slow/vulnerable/shock), knockback,
│   │                                  #   contact damage, damage toasters, FindNearest
│   ├── BossEnemyBase.cs               # Shared boss plumbing (phases, telegraphs)
│   ├── MechBossEnemy.cs               # Ranged 3-phase finale mech
│   ├── RobotBossEnemy.cs              # War-robot boss
│   ├── CyberNinjaEnemy.cs             # Neon Run elite / mini-boss
│   ├── AlienMeleeEnemy / AlienRangedEnemy / ArmoredBruteEnemy / KamikazeDroneEnemy / RobotMeleeEnemy
│   │                                  #   → Redoubt, Sunken Mire, Cold Harbor
│   ├── BoarderTrooperEnemy / BoarderMarksmanEnemy / BreacherEnemy / SapperDroneEnemy
│   │                                  #   → Last Light
│   ├── CyborgSwarmEnemy / CorpSecShooterEnemy / EnforcerEnemy / HunterDroneEnemy
│   │                                  #   → Neon Run, Convoy
│   ├── EnemyWarpIn / EnemyDissolve / DissolveMaterialSet / EnemyHitFlash / EnemyRagdoll
│   └── IEnemyTarget.cs                # Non-player attack targets (reactor, convoy hull)
├── Hub/
│   ├── HubStageSelect.cs              # Stage briefing panel + Threat Tier selector + launch
│   ├── HubMetaTerminal.cs             # Medbay console — spends Echoes on permanent upgrades
│   ├── HubPortal.cs / HubTriggerRelay.cs / HubBillboard.cs
│   ├── HubPlayerSpawner.cs / HubCameraController.cs
│   ├── HubFirstRunDialog.cs           # One-time how-to-play card
│   └── HubNpcAnimator.cs / HubNpcWanderer.cs
├── OpenField/                         # "The Sunken Mire" (SwampScene)
│   ├── OpenFieldBiome.cs              # ScriptableObject ground identity + set dressing
│   ├── OpenFieldDirector.cs           # Endless ground/prop streaming
│   └── WaveDirector.cs                # Ramping horde count, boss at the 12-minute mark
├── Player/
│   ├── PlayerController.cs            # Movement, dash, aim/fire, reload, grenade, health/shield,
│   │                                  #   XP + level-ups, and every upgrade's applied effect
│   ├── PlayerUpgradeType.cs           # The ~50 level-up cards
│   ├── PlayerUpgradeInfo.cs           # Card category, rarity, offer weight, copy
│   ├── PlayerActiveAbility.cs         # The single ability slot (Blink / Stomp / Turret / Overdrive)
│   ├── AuraType.cs / AuraLoadout.cs / PlayerAura.cs         # Frost / Ember / Venom / Tempest
│   ├── DroneType.cs / DroneLoadout.cs / PlayerDrone.cs      # Attack / Heal / Shock / Bulwark
│   ├── OrbitalLaser.cs / GravityWell.cs / CausticField.cs / DeployedTurret.cs
│   ├── ExperienceOrb.cs / EnemyDrop.cs / LootCrate.cs / LootCrateReward.cs
│   ├── MetaProgressApplier.cs         # Folds MetaBonuses into the player at run start
│   └── DashTrail.cs / PlayerFootsteps.cs
├── Steam/                             # SciFiShooter.Steam asmdef (Standalone/Editor only)
│   └── SteamManager.cs                # Self-booting Steamworks.NET lifetime owner
├── UI/                                # Every screen here is built in code at runtime
│   ├── PlayerHUDUI.cs                 # Health/shield/XP bars, ammo, ability, boss bar, run-end screen
│   ├── LevelUpUI.cs                   # Three weighted cards + reroll / banish
│   ├── PauseController.cs             # Pause layer + on-screen pause button
│   ├── MainMenuView.cs / MenuController.cs / MainMenuController.cs   # Title screen
│   ├── ConsistentSettingsView.cs / SettingsMenuController.cs         # Options screen
│   ├── AccountPanelController.cs / AccountStatusLabel.cs             # Auth UI
│   ├── LoadingScreenController.cs     # THE scene-transition entry point
│   ├── CityRunUI / ConvoyRunUI / SiegeWaveUI / LastStandWaveUI       # Per-mode HUDs
│   ├── MinimapUI.cs                   # Per-room radar (Redoubt, Last Light)
│   ├── MobileJoystickUI.cs / SafeArea.cs / CrosshairCursor.cs
│   └── MenuMotion.cs / UIFeedbackBridge.cs / BuildInfoLabel.cs
└── Visuals/
    ├── GameFeel.cs                    # Trauma camera shake, hitstop, cue bank
    ├── ProceduralSfx.cs               # Cue bank: real .wav if present, synthesised fallback
    ├── EffectManager.cs               # Synty / Polygon Arsenal particle spawner
    ├── DamageToasterManager.cs        # Floating damage + heal numbers
    ├── SciFiMaterialFactory.cs        # Procedural URP emissive materials
    ├── HighlightFx.cs                 # Highlight Plus outline wrapper
    ├── WeatherVolume.cs / AmbientMotes.cs / LootVfx.cs
    ├── ExtractionDropship.cs          # Neon Run / Convoy finale dropship
    └── AccessibilityApplier.cs        # Applies the live-scene accessibility options

Editor-only tooling lives in Assets/Editor/: EchoBuild.cs (build menu), RoomTemplateVariantBuilder.cs, RagdollRigBuilder.cs, WebGLAudioImport.cs.


🔄 Dungeon generation & NavMesh pipeline (The Redoubt)

A Redoubt run is 10 floors (DungeonGenerator.FloorCount). Floors 5 (BossFloor) and 10 (FinalFloor) are single boss arenas; every other floor is a start-to-exit directional layout of 4–8 rooms with 1–2 dead-end branches. Each floor is generated whole — there is no room streaming.

  1. Floor layout — a critical path is walked from the start cell with a PrimaryStepChance bias along the floor's primary axis (so it reads as "head this way" but still snakes), then 1–2 dead-end branches are hung off it. Cells become Start / Normal / Exit.
  2. Boss floors — floors 5 and 10 skip layout entirely and build a single arena (RoomType.Boss / RoomType.FinalBoss).
  3. Theming — themes (Resources/SciFiShooter/Themes) are assigned deterministically in ThemeBlockSize (3×3) grid blocks so the floor reads as connected wings rather than a new look at every doorway.
  4. Room geometry — each room instantiates a RoomTemplate prefab (one of five RoomShapes: Square, Cross, PixelCircle, Octagon, Donut), optionally rotated 180°. Only true rotation is safe — mirroring breaks Synty's corner-pivot floor tiles and produces negative-scale collider warnings.
  5. Perimeter walls & doors — boundary walls with doorway gaps for connected neighbours, wired to directional Door triggers with sealed/open visuals.
  6. Runtime NavMesh bake — a NavMeshSurface per room with useGeometry = NavMeshCollectGeometry.PhysicsColliders.
  7. Content spawning — enemy roster, boss, loot crates and XP orbs placed onto the fresh NavMesh.
  8. Floor exit — a dormant FloorTeleporter is placed in the Exit room (and in floor 5's arena); it powers up when the room clears and calls GameManager.AdvanceFloor.

📈 Progression & persistence

In-run — kills drop XP orbs; each level presents three weighted cards from PlayerUpgradeType (LevelUpUI.BuildOfferPool → PlayerUpgradeInfo.OfferWeight, filtered by MetaProgressService.IsContentUnlocked). Reroll and banish charges come from meta perks. Repeat picks rank up. Loot crates and enemy drops give instant boons.

Between runs — MetaProgressService holds Echoes, banked once per run by AwardRun(gm): (score + survival + milestone + win) × GlobalRate × threatMult. The Hub medbay terminal (HubMetaTerminal) spends them on MetaUpgradeCatalog tracks — stat tiers, perks, and one-time content unlocks — which fold into MetaBonuses and are applied at run start by MetaProgressApplier.

Adding a meta upgrade: one MetaUpgradeDef in MetaUpgradeCatalog.All, plus (for content unlocks) one arm in MetaProgressService.IsContentUnlocked. Nothing else.

Threat Tiers — DifficultyService, five tiers per stage. Tier N+1 unlocks by clearing that stage at tier N; higher tiers scale enemy health, damage and count, and multiply the Echoes payout.

Layer Store Holds
Options PlayerPrefs, sfs. prefix (SettingsService) Audio, graphics, gameplay, accessibility
Meta progression PlayerPrefs (MetaProgressService) Echoes, lifetime Echoes, purchased tiers
Threat tiers PlayerPrefs, sfs.threat. (DifficultyService) Per-stage cleared + selected tier
Mid-run resume JU SaveLoad (GameSaveData) Redoubt runs only; discarded on a deliberate fresh launch
Cross-device Unity Cloud Save (CloudSaveSync / CloudProgress) Echoes, meta tiers, threat unlocks; last-write-wins by rev

⚙️ Critical technical rules & invariants

1. NavMesh baking must use PhysicsColliders

Synty and imported FBX assets have ModelImporter.isReadable = false. Always keep surface.useGeometry = NavMeshCollectGeometry.PhysicsColliders. Never RenderMeshes.

2. Player & enemy collider setup (anti-pushing)

  • Player: CharacterController for movement, plus a trigger CapsuleCollider for bullets/pickups.
  • Enemies: trigger CapsuleCollider + kinematic Rigidbody. Contact damage is dealt through proximity/trigger volumes (UpdateProximityContactDamage), never by solid bodies shoving the player.
  • Melee NavMeshAgents need stoppingDistance >= 1.1f so they don't walk into the player's centre.

3. SafeDestroy for procedural teardown

Destroy in Play Mode, DestroyImmediate in Edit Mode / tests.

4. All input goes through InputHelper

Never call Input.GetAxis or InputSystem directly from gameplay code — InputHelper is what keeps keyboard+mouse, gamepad and touch all working.

5. All runtime assets go through RuntimeAssetLibrary

It is the only Resources.Load site (root SciFiShooter/). Add a typed loader there rather than loading by path from gameplay code, and register anything a stage cannot run without in ValidateRequiredAssets.

6. All scene loads go through LoadingScreenController.LoadScene

It is the only place that may call SceneManager.LoadSceneAsync.

7. Shots spawn in LateUpdate, not Update

Aim is resolved in Update but the projectile and muzzle flash spawn in LateUpdate (ResolvePendingShot) — Unity applies animation and IK between the two, so spawning in Update reads last frame's muzzle transform and visibly throws bolts off the rifle tip at low frame rates.

8. UI is built in code

Title, settings, pause, HUD, level-up cards and the Hub panels are constructed at runtime and share one palette (faint frame, near-black fill, saturated accent, muted labels). Synty menu prefabs survive only as canvas hosts. Match the existing builders rather than authoring new hierarchies in a scene.

9. Serialized values beat code defaults

Where a knob exists both as a [SerializeField] default in .cs and as a serialized value on a scene object or a .asset ScriptableObject (audio mix levels, spawn counts, biome/run configs), the serialized value is what ships. Change both, or you will "fix" something that never moves.


🎮 Controls

Action Keyboard & Mouse Gamepad Mobile
Move W A S D / Arrows Left stick Left joystick
Aim Mouse (raycast plane) Right stick Right joystick
Fire Hold LMB Hold RT Drag right joystick
Dodge / dash Space / LShift A or LT Chevron button (press)
Active ability E X Ability button (above dash)
Grenade G RB Grenade button
Reload R Y Auto on empty
Weapon swap Q / Tab LB —
Cycle camera angle V R3 —
Pick level-up card 1–4 D-pad Tap
Pause / cancel Esc Start / Select, B Pause button

Keyboard actions can be rebound in Settings → Controls (desktop only); movement, fire and gamepad bindings are fixed in InputHelper.


🚀 Building & distribution

Assets/Editor/EchoBuild.cs provides a Build menu and matching -executeMethod entry points under SciFiShooter.EditorTools.EchoBuild:

Menu item Method Output
Windows (x64) .Windows build/windows/
macOS (Universal) .MacOS build/macos/
Linux (x64) .Linux build/linux/
Both (Win + Mac) .Both both of the above
Desktop (Win + Mac + Linux) .Desktop all three
WebGL (itch.io) .WebGL build/webgl/
Android (AAB) / (APK) .AndroidAppBundle / .AndroidApk build/android/

build/ is git-ignored. There is no iOS target yet.

Steam — app 3885300. com.rlabrecque.steamworks.net in the manifest, steam_appid.txt at the repo root, and a guarded self-booting SteamManager (SciFiShooter.Steam asmdef). Gate every Steamworks call on SteamManager.Initialized. SteamPipe VDFs live in steam/; macOS signing is scripts/sign_notarize_mac.sh (+ git-ignored scripts/mac_signing.env). Full runbook in STEAM_RELEASE.md.

Steamworks.NET only compiles on a Standalone/Android build target — switch the editor to macOS or Windows first, or the Steamworks namespace won't resolve.

itch.io / WebGL — publish_itch.sh (butler) plus WebGL hardening (Sidekick/SQLite kept editor-only, link.xml, audio import overrides). See ITCH_RELEASE.md.

Website + privacy policy (GitHub Pages)

pages/ is a small static site: index.html (landing page), privacy/index.html (the privacy policy), style.css and an empty .nojekyll (tells Pages to serve the files as-is). It is tracked in this repo and pushed to a separate public GitHub repo with git subtree, so the game repo itself stays on Forgejo and only pages/ ever reaches GitHub.

Live site (repo RobertAGoins/echo-protocol-site, remote github in this repo):

PRIVACY_POLICY.md is the source of truth for the policy. When it changes, mirror the change into pages/privacy/index.html and bump the effective date in both.

One-time setup (already done for this repo — kept for reference or a new machine)

  1. On GitHub, create an empty public repository, e.g. echo-protocol-site — no README, licence or .gitignore, so the first push isn't rejected.

  2. Add it as a second remote in this repo:

    git remote add github https://github.com/RobertAGoins/echo-protocol-site.git
    
  3. Commit any pending changes under pages/, then push just that folder to the GitHub repo's main:

    git subtree push --prefix pages github main
    
  4. In the GitHub repo open Settings → Pages. Under Build and deployment choose Deploy from a branch, branch main, folder / (root), and save.

  5. Wait a minute or two for the first deploy (the repo's Actions tab shows pages build and deployment). The site is then live at:

    • landing page: https://robertagoins.github.io/echo-protocol-site/
    • privacy policy: https://robertagoins.github.io/echo-protocol-site/privacy/

    Pages addresses use the account name in lowercase (RobertAGoins → robertagoins.github.io); a project repo is served under /<repo-name>/, while a repo named <account>.github.io is the root user site.

  6. Paste the privacy-policy URL into Steamworks → App 3885300 → Store Page → Basic Info → Privacy Policy (and App Store Connect / Google Play Console when those ship).

Publishing an update

  1. Edit pages/ (and PRIVACY_POLICY.md if the policy changed) and commit as usual.
  2. Push the folder again:
    git subtree push --prefix pages github main
    
  3. Pages redeploys automatically; hard-refresh the page to see the change.

Troubleshooting

  • git subtree push rejected (non-fast-forward) — the GitHub repo has commits that aren't in pages/ (e.g. a README created on GitHub). pages/ is the source of truth, so overwrite it:
    git push github "$(git subtree split --prefix pages HEAD)":main --force
    
  • 404 after enabling Pages — check Settings → Pages shows branch main, folder / (root), and that index.html sits at the top of the GitHub repo (it will if you pushed with --prefix pages).
  • Styles missing — pages link style.css with relative paths, so open the site through its github.io URL rather than a raw file view.
  • Authentication prompt fails — GitHub needs a personal access token (or the GitHub CLI / credential manager) for HTTPS pushes; your account password won't work.

📦 Key packages & assets

  • Engine: Unity 6000.5.7f1, URP 17.5.0, Linear color space
  • Unity packages: com.unity.ai.navigation 2.0.14, Input System 1.20.0, com.unity.services.authentication 3.7.4, com.unity.services.cloudsave 3.4.1, Visual Effect Graph, Shader Graph, Recorder 5.1.7, Test Framework 1.7.0
  • Third-party: Steamworks.NET 2025.164.0, Feel / Nice Vibrations (MoreMountains), Highlight Plus, INab Interactive Dissolve, Julhiecio TPS Controller (JUTPS), Joystick Pack
  • Synty: Polygon Sci-Fi Space / Sci-Fi Worlds / Sci-Fi City / Sci-Fi Horror / Cyber City / Mech / Generic / Nature Biomes / Maps Sci-Fi Outpost / Particle FX, Sidekick Characters, Interface Sci-Fi Soldier HUD + Sci-Fi Menus, Animation Idles
  • Audio: 30 Sci-fi Space Tracks, plus procedurally synthesised feedback cues (ProceduralSfx — drop a correctly-named .wav under Resources/SciFiShooter/Audio/Cues/ to upgrade a cue with no code change)

🕳️ Known gaps

Tracked honestly so nobody re-discovers them:

  • One held weapon model. PlayerController.EquipWeapon hard-forces WeaponType.Rifle; the Knife / Axe / TwinKnives paths are fully coded but their models are not built, so the weapon-swap key is a no-op. Remove the force line to re-enable. The starting-weapon loadouts (Player/WeaponLoadout.cs: Pulse Rifle, Scattergun, Rail Carbine - unlocked at the medbay, picked on the stage briefing) only change the firing profile; all three still show the rifle model.
  • RoomType.Treasure / Shop / Secret are declared but never generated. The Redoubt emits only Start / Normal / Exit / Boss / FinalBoss. There is no in-run economy.
  • No leaderboards or rich presence. Steam achievements + a kill stat are implemented (Core/Achievements.cs, see STEAM_ACHIEVEMENTS.md) but must be created and published in Steamworks; no icons are made yet, and other platforms (Game Center / Play Games achievements) have no backend.
  • No aim-sensitivity option in the settings screen, and gamepad buttons can't be rebound (keyboard rebinding, display mode, resolution and frame-rate cap exist).
  • No localization layer. Every string is an inline literal.
  • No automated tests. com.unity.test-framework is installed; no test assemblies exist.
  • Platform sign-in: Steam works; Game Center and Google Play Games need their plugins. At boot AuthService signs in with AuthProviderRegistry.PlatformIdentity (Steam on desktop, Game Center on iOS, Play Games on Android), links it to an existing guest, or switches to the player that identity already owns. Steam is live (see STEAM_RELEASE.md). Game Center and Play Games are implemented but compile out until their SDKs are installed - SciFiShooter.Auth.asmdef sets SCIFI_AUTH_GAMECENTER / SCIFI_AUTH_GPGS automatically when the packages are present, and references the assemblies Apple.GameKit / Google.Play.Games by name (check those names match the installed plugins):
    • Game Center: install Apple's com.apple.unityplugin.core + com.apple.unityplugin.gamekit (built from github.com/apple/unityplugins), enable the Game Center capability for the bundle id, and add the Apple Game Center provider in the Unity Cloud dashboard.
    • Google Play Games: install the Google Play Games plugin for Unity v2 (com.google.play.games), run its Android setup with the Play Console Games Services config, create a Web OAuth client, and add the Google Play Games provider (Web client id + secret) in the Unity Cloud dashboard.
    • Apple / Google / Facebook ID-token sign-in remain stubs. On iOS, App Review guideline 4.8 may require Sign in with Apple if the Unity Player Account link is offered there.
  • Music has no intensity ramp — one track per stage plus a per-boss theme (MusicLibrary); nothing reacts to horde size or player health.