- C# 48.6%
- C 46.4%
- ShaderLab 2.4%
- GLSL 1.9%
- C++ 0.2%
- Other 0.1%
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> |
||
|---|---|---|
| Assets | ||
| docker/webgl | ||
| Packages | ||
| pages | ||
| ProjectSettings | ||
| scripts | ||
| steam | ||
| store_art | ||
| trailer | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| BUG_AUDIT.md | ||
| BUG_AUDIT_2026-09-27.md | ||
| compose.yaml | ||
| DEMO.md | ||
| GRAPHICS_AUDIT.md | ||
| HUB_NPC_ANIMATION_PLAN.md | ||
| ITCH_RELEASE.md | ||
| PERFORMANCE.md | ||
| PRIVACY_POLICY.md | ||
| README.md | ||
| STEAM_ACHIEVEMENTS.md | ||
| steam_appid.txt | ||
| STEAM_RELEASE.md | ||
| 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.
- Floor layout — a critical path is walked from the start cell with a
PrimaryStepChancebias 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 becomeStart/Normal/Exit. - Boss floors — floors 5 and 10 skip layout entirely and build a single arena
(
RoomType.Boss/RoomType.FinalBoss). - Theming — themes (
Resources/SciFiShooter/Themes) are assigned deterministically inThemeBlockSize(3×3) grid blocks so the floor reads as connected wings rather than a new look at every doorway. - Room geometry — each room instantiates a
RoomTemplateprefab (one of fiveRoomShapes: 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. - Perimeter walls & doors — boundary walls with doorway gaps for connected neighbours, wired to
directional
Doortriggers with sealed/open visuals. - Runtime NavMesh bake — a
NavMeshSurfaceper room withuseGeometry = NavMeshCollectGeometry.PhysicsColliders. - Content spawning — enemy roster, boss, loot crates and XP orbs placed onto the fresh NavMesh.
- Floor exit — a dormant
FloorTeleporteris placed in theExitroom (and in floor 5's arena); it powers up when the room clears and callsGameManager.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
MetaUpgradeDefinMetaUpgradeCatalog.All, plus (for content unlocks) one arm inMetaProgressService.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:
CharacterControllerfor movement, plus a triggerCapsuleColliderfor bullets/pickups. - Enemies: trigger
CapsuleCollider+ kinematicRigidbody. Contact damage is dealt through proximity/trigger volumes (UpdateProximityContactDamage), never by solid bodies shoving the player. - Melee
NavMeshAgents needstoppingDistance >= 1.1fso 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
Steamworksnamespace 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):
- landing page: https://robertagoins.github.io/echo-protocol-site/
- privacy policy: https://robertagoins.github.io/echo-protocol-site/privacy/ — the URL for Steamworks, App Store Connect and Play Console.
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)
-
On GitHub, create an empty public repository, e.g.
echo-protocol-site— no README, licence or.gitignore, so the first push isn't rejected. -
Add it as a second remote in this repo:
git remote add github https://github.com/RobertAGoins/echo-protocol-site.git -
Commit any pending changes under
pages/, then push just that folder to the GitHub repo'smain:git subtree push --prefix pages github main -
In the GitHub repo open Settings → Pages. Under Build and deployment choose Deploy from a branch, branch
main, folder/ (root), and save. -
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.iois the root user site. - landing page:
-
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
- Edit
pages/(andPRIVACY_POLICY.mdif the policy changed) and commit as usual. - Push the folder again:
git subtree push --prefix pages github main - Pages redeploys automatically; hard-refresh the page to see the change.
Troubleshooting
git subtree pushrejected (non-fast-forward) — the GitHub repo has commits that aren't inpages/(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 thatindex.htmlsits at the top of the GitHub repo (it will if you pushed with--prefix pages). - Styles missing — pages link
style.csswith relative paths, so open the site through itsgithub.ioURL 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.navigation2.0.14, Input System 1.20.0,com.unity.services.authentication3.7.4,com.unity.services.cloudsave3.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.wavunderResources/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.EquipWeaponhard-forcesWeaponType.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/Secretare 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, seeSTEAM_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-frameworkis installed; no test assemblies exist. - Platform sign-in: Steam works; Game Center and Google Play Games need their plugins. At boot
AuthServicesigns in withAuthProviderRegistry.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 (seeSTEAM_RELEASE.md). Game Center and Play Games are implemented but compile out until their SDKs are installed -SciFiShooter.Auth.asmdefsetsSCIFI_AUTH_GAMECENTER/SCIFI_AUTH_GPGSautomatically when the packages are present, and references the assembliesApple.GameKit/Google.Play.Gamesby 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.
- Game Center: install Apple's
- Music has no intensity ramp — one track per stage plus a per-boss theme (
MusicLibrary); nothing reacts to horde size or player health.