Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ backups/
plans/

# Generated Unity client content (reproduce via scripts/sync-client-libs.ps1).
# Ignore the whole synced folders incl. their folder .meta so no dangling metas are tracked.
client/Assets/Plugins/
# Ignore synced plugin payloads, but keep hand-written WebGL browser plugins versioned.
client/Assets/Plugins/*
!client/Assets/Plugins/BbsWebSocket.jslib
!client/Assets/Plugins/BbsWebSocket.jslib.meta
client/Assets/Plugins.meta
client/Assets/StreamingAssets/
client/Assets/StreamingAssets.meta
Expand Down Expand Up @@ -92,6 +94,14 @@ client/schtask-build.log
client/Assets/BlocksBeyondTheStars/Scripts/BugReportBuildSecrets.Generated.cs
client/Assets/BlocksBeyondTheStars/Scripts/BugReportBuildSecrets.Generated.cs.meta

# Local secrets and Glitch build credentials (never committed)
.env
.env.*
!.env.example
glitch.local.env
client/Assets/BlocksBeyondTheStars/Scripts/GlitchIntegrationSecrets.Generated.cs
client/Assets/BlocksBeyondTheStars/Scripts/GlitchIntegrationSecrets.Generated.cs.meta

# Build/capture run logs (client)
client/build-run.log
client/capture-run.log
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![GitHub stars](https://img.shields.io/github/stars/marceld23/BlocksBeyondTheStars?style=social)](https://github.com/marceld23/BlocksBeyondTheStars/stargazers)

⬇️ **[Download & Play](https://github.com/marceld23/BlocksBeyondTheStars/releases/latest)** (latest release — Windows · Linux · experimental macOS) · 🌐 [Website](https://www.blocksbeyondthestars.com/en) · ⭐ [Play on Itch.io](https://jumavegames.itch.io/blocks-beyond-the-stars) · 🎬 [Let's Play](https://youtu.be/43oAgdaT1OE) (German audio) · ⭐ [Star us on GitHub](https://github.com/marceld23/BlocksBeyondTheStars) · 🐛 [Report a Bug](CONTRIBUTING.md)
⬇️ **[Download & Play](https://github.com/marceld23/BlocksBeyondTheStars/releases/latest)** (latest release — Windows · Linux · experimental macOS) · 🌐 [Website](https://www.blocksbeyondthestars.com/en) · ⭐ **[Play on Itch.io](https://jumavegames.itch.io/blocks-beyond-the-stars)** · 🎬 [Let's Play](https://youtu.be/43oAgdaT1OE) (German audio) · ⭐ [Star us on GitHub](https://github.com/marceld23/BlocksBeyondTheStars) · 🐛 [Report a Bug](CONTRIBUTING.md)

> **An experimental 3D Voxel Space Game, 100% generated by AI, driven by the imagination of a 10-year-old.**

Expand Down Expand Up @@ -216,17 +216,17 @@ oxygen, damage, blueprints or travel.
| Client | Unity 6 LTS (6000.4.x), URP + C# (Windows, Linux, experimental macOS) — see [`client/`](client/) |
| Server | .NET 8, standalone console host (no Unity runtime) |
| Admin UI | ASP.NET Core 8 minimal API + HTML dashboard |
| Database | SQLite (default, portable); PostgreSQL later |
| Realtime net | LiteNetLib (UDP) + MessagePack |
| Database | SQLite (default, portable); optional PostgreSQL for hosted realms |
| Realtime net | LiteNetLib UDP + MessagePack for native clients; WebSocket + JSON envelope for WebGL |
| Shared logic | `netstandard2.1` so the same code runs in Unity *and* the server |

## Repository layout

```
src/BlocksBeyondTheStars.Shared/ data models, data-driven definitions, localization, protocol DTOs
src/BlocksBeyondTheStars.WorldGeneration/ seed-based deterministic chunk generation
src/BlocksBeyondTheStars.Persistence/ SQLite repository, savegame layout, autosave, backups
src/BlocksBeyondTheStars.Networking/ transport abstraction (LiteNetLib + loopback), messages, codec
src/BlocksBeyondTheStars.Persistence/ SQLite/PostgreSQL repositories, savegame layout, autosave, backups
src/BlocksBeyondTheStars.Networking/ transport abstraction (LiteNetLib + WebSocket + loopback), messages, codec
src/BlocksBeyondTheStars.GameServer/ authoritative tick loop + console host
src/BlocksBeyondTheStars.Api/ admin web UI + API
src/BlocksBeyondTheStars.Tools/ validate/info/backup CLI
Expand Down
49 changes: 45 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ plans live under [docs/](docs/) (committed); this file is the high-level status.
keep it current when controls/features change. Last consolidated 2026-06-04.

**Build:** `scripts/build-client.ps1` (Windows) or `scripts/build-client.sh` (Linux) — publishes shared libs + bundled server + Unity player.
**Test:** `dotnet test` — currently **779 server + 96 client passing** (2026-06-28). Locale parity (en/de) is enforced by a test.
**Test:** `./scripts/run-tests.sh` — currently **783 server + 96 client passing** (2026-06-28). Locale parity (en/de) is enforced by a test.
**Conventions:** English docs/comments; in-game text bilingual DE+EN; commit to `main` with the
Claude `Co-Authored-By` trailer; OpenAI texture + ElevenLabs sound generation is blanket-approved
(no per-batch gate).

Architecture: Unity 6 (URP since 2026-06-10) client + authoritative .NET 8 server, everything built in
code (no scene authoring). One shared world; contractless MessagePack networking; deterministic seed
world-gen; SQLite persistence.
code (no scene authoring). One shared world; MessagePack networking for native clients plus a WebGL JSON
envelope at the WebSocket edge; deterministic seed world-gen; SQLite default persistence with opt-in PostgreSQL.

---

Expand Down Expand Up @@ -81,6 +81,36 @@ Per-item detail lives in the dated work log below.

---

### ★ Hosted WebGL transport + optional browser platform hooks (2026-06-28) — ✅ DONE locally
Added the reusable hosted-browser path without committing provider credentials: the Unity client now has a
dormant-by-default `GlitchIntegration` (Aegis heartbeat/validation plus helper calls for scores, stats,
leaderboards, achievements and raw cloud saves), with credentials injectable only through a git-ignored generated
partial. The committed defaults are empty, so public builds never call a platform API unless a build explicitly
provides title id, token and API base URL. `BuildScript.BuildWebGL()` creates a Unity WebGL folder while
stripping stale native-server StreamingAssets from browser builds.
- **Browser play follow-up (2026-06-29):** WebGL now has a browser `IClientTransport` backed by WebSockets, a
JSON `NetCodec` envelope for IL2CPP/AOT, and a JavaScript bridge that handles `ArrayBuffer`, typed-array,
string and `Blob` frames. A local WebGL build joined a real .NET server, received authoritative chunks and
rendered a playable world.
- **Browser smoke follow-up (2026-06-29):** the HAR/log capture for the "empty world" report showed the Unity
payload and every `StreamingAssets/data` JSON loading successfully; the failure was gameplay entering the native
local-server/UDP path in a browser. Singleplayer/Host remain intentionally unavailable in WebGL, but Join now
uses the hosted WebSocket path.
- **Hosted realm prep (2026-06-29):** server persistence now keeps SQLite as the default but adds opt-in
PostgreSQL (`databaseProvider=postgresql`, `BBS_POSTGRES_CONNECTION_STRING`) through `WorldRepositoryFactory`.
The admin API/tools report/use the selected backend, and WebGL builds can embed a generated-only default
hosted server host/port without changing ordinary desktop/native builds.
Verified against Docker `postgres:16-alpine` reporting PostgreSQL 16.14 via `BBS_POSTGRES_TEST_CONNECTION_STRING`.
- **Hosted usability follow-up (2026-06-29):** the full-screen Codex/DataQubes menu screens now close reliably
with **Esc** or **Tab** before the app shell can open the leave-game prompt. Free space flight is enabled by
default and can be forced for hosted/container worlds with `BBS_FREE_FLIGHT=true`; existing worlds that saved
the old disabled value are upgraded only for that rule.
- **PR review follow-up (2026-06-29):** committed the WebGL `.jslib` bridge, fixed the `link.xml` client
assembly preserve entry, added JSON/frame-size guards plus browser-payload drop logging, and moved
provider-specific deployment scripts/README links out of this merge PR for a later platform-owned deploy PR.

---

### ★ Server portal serves the native Linux + macOS client downloads (2026-06-28) — ✅ MERGED to main (#83, #107)
The dedicated server's `/portal` + `/download` pages now offer the **native Linux client (AppImage)** (#83) and the
**macOS client (.app zip)** (#107) alongside the Windows installer, picking the right asset per the visitor's platform.
Expand All @@ -97,7 +127,7 @@ Opt-in automatic exception reporting to the website (reuses the existing Wix `po
**Server:** the Tick loop is hardened (per-system `Guard` + a top-level backstop so one uncaught exception no longer takes the
whole server down) + `CrashReportWriter`/`CrashReportUploader` + AppDomain/unobserved-Task handlers + startup/periodic flush.
**Client:** a `CrashReporter` hooks `Application.logMessageReceivedThreaded` → dedup/spool → `FeedbackUploader`, silent, with a
startup retry. Payloads run through a shared `CrashPiiScrubber`. 779 server + 96 client tests. (Gave the user updated
startup retry. Payloads run through a shared `CrashPiiScrubber`. 783 server + 96 client tests. (Gave the user updated
`http-functions.js` with triage/size-guard/rate-limit; OPTIONAL Wix CMS fields category/source/kind remain.)

### ★ Playtest report issue template (2026-06-28) — ✅ MERGED to main (#85)
Expand Down Expand Up @@ -1527,6 +1557,17 @@ not user-moddable. Full design: [docs/developer/MINIGAMES_AND_WIKI.md](docs/deve
synced + bundled into the server build). Side fix: the server now resolves `<DataDir>/minigames/catalog.json`
(was `<DataDir>/../minigames/...`, which was missing on a dedicated server). Dropped the now-empty `web JS`
lint job + the `javascript-typescript` CodeQL language (no JS left in the repo).
- **✅ WebGL content startup fixed locally (2026-06-29):** the first hosted WebGL smoke booted Unity/Aegis but crashed
because WebGL exposes `Application.streamingAssetsPath` as an HTTPS URL, while the shared `ContentLoader`
expects a local directory. `BuildScript.BuildWebGL()` now writes a `StreamingAssets/data/manifest.json`, the
Unity shell downloads/caches that data through `UnityWebRequest` before loading content, Wiki/Arcade read the
corrected `data/...` paths, shared content reflection metadata is preserved for IL2CPP, and the generated
WebGL `index.html` no longer auto-requests fullscreen on load. Follow-up browser smoke testing also preserved
`SphereCollider` so startup components requested by name are not stripped from WebGL.
- **✅ WebGL hosted-world rendering fixed locally (2026-06-29):** production HAR/log review confirmed content
assets loaded; the screenshot came from attempting native singleplayer/UDP gameplay in WebGL. The shell now
logs remote content/cache counts, blocks only browser-incompatible Singleplayer/Host, and routes Join through
the browser WebSocket transport with the WebGL JSON envelope.

## ▶ Open backlog — priority order (updated 2026-06-07)
At-a-glance order of everything still open (new items added 2026-06-07 interleaved with the remaining
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"rootNamespace": "BlocksBeyondTheStars.Client",
"references": [
"Unity.RenderPipelines.Universal.Runtime",
"Unity.RenderPipelines.Core.Runtime"
"Unity.RenderPipelines.Core.Runtime",
"UnityEngine.UnityWebRequestModule"
],
"includePlatforms": [],
"excludePlatforms": [],
Expand Down
156 changes: 154 additions & 2 deletions client/Assets/BlocksBeyondTheStars/Editor/BuildScript.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// This file is part of Blocks Beyond the Stars. See LICENSE for the full AGPL-3.0 text.
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using BlocksBeyondTheStars.Client;
using UnityEditor;
using UnityEditor.Build;
Expand Down Expand Up @@ -69,12 +73,23 @@ public static void BuildLinux()
public static void BuildMacOS()
=> BuildPlayer(BuildTarget.StandaloneOSX, "BlocksBeyondTheStars.app", "Build/macOS");

/// <summary>Builds the WebGL player folder for browser deployment. The browser path uses WebSockets
/// against a hosted authoritative server, so this target keeps browser-client packaging repeatable.</summary>
[MenuItem("BlocksBeyondTheStars/Build WebGL Player")]
public static void BuildWebGL()
=> BuildPlayer(BuildTarget.WebGL, string.Empty, "Build/WebGL");

private static void BuildPlayer(BuildTarget target, string exeName, string defaultOutDir)
{
EnsureLauncherScene();
EnsureShadersIncluded();
EnsureRendererFeatures();
EnsureAppIcon();
if (target == BuildTarget.WebGL)
{
ConfigureWebGLPlayer();
EnsureStreamingAssetsManifest();
}

string version = GetArg("-buildVersion");
if (!string.IsNullOrEmpty(version))
Expand All @@ -96,11 +111,12 @@ private static void BuildPlayer(BuildTarget target, string exeName, string defau

string outDir = GetArg("-buildOut") ?? defaultOutDir;
Directory.CreateDirectory(outDir);
string locationPathName = target == BuildTarget.WebGL ? outDir : Path.Combine(outDir, exeName);

var options = new BuildPlayerOptions
{
scenes = new[] { ScenePath },
locationPathName = Path.Combine(outDir, exeName),
locationPathName = locationPathName,
target = target,
options = BuildOptions.None,
};
Expand All @@ -116,9 +132,145 @@ private static void BuildPlayer(BuildTarget target, string exeName, string defau
EditorApplication.Exit(1);
}

if (target == BuildTarget.WebGL)
{
RemoveAutoFullscreen(outDir);
}

File.WriteAllText(Path.Combine(outDir, "version.txt"), PlayerSettings.bundleVersion);
}

/// <summary>Best-effort WebGL production defaults. Reflection keeps this resilient across Unity 6 patch
/// API shuffles while still applying Brotli + 512 MB heap when those properties are available.</summary>
public static void ConfigureWebGLPlayer()
{
bool fastLocal = string.Equals(Environment.GetEnvironmentVariable("BBS_WEBGL_FAST_LOCAL"), "1", StringComparison.OrdinalIgnoreCase)
|| string.Equals(Environment.GetEnvironmentVariable("BBS_WEBGL_FAST_LOCAL"), "true", StringComparison.OrdinalIgnoreCase);

EditorUserBuildSettings.development = false;
PlayerSettings.SetManagedStrippingLevel(NamedBuildTarget.WebGL, ManagedStrippingLevel.Low);
SetWebGLProperty("memorySize", 512);
SetWebGLProperty("compressionFormat", fastLocal ? "Disabled" : "Brotli");
SetWebGLProperty("decompressionFallback", !fastLocal);
SetWebGLProperty("dataCaching", true);
if (fastLocal)
{
Debug.Log("BBS_WEBGL_FAST_LOCAL enabled: WebGL build compression disabled for local browser verification.");
}
}

private static void EnsureStreamingAssetsManifest()
{
string dataRoot = Path.Combine(Application.dataPath, "StreamingAssets", "data");
if (!Directory.Exists(dataRoot))
{
Debug.LogWarning($"StreamingAssets data folder not found at {dataRoot}; WebGL content manifest skipped.");
return;
}

var files = new List<string>();
foreach (string file in Directory.GetFiles(dataRoot, "*", SearchOption.AllDirectories))
{
if (file.EndsWith(".meta", StringComparison.OrdinalIgnoreCase)
|| string.Equals(Path.GetFileName(file), "manifest.json", StringComparison.OrdinalIgnoreCase))
{
continue;
}

string relative = file.Substring(dataRoot.Length + 1)
.Replace(Path.DirectorySeparatorChar, '/')
.Replace(Path.AltDirectorySeparatorChar, '/');
files.Add(relative);
}

files.Sort(StringComparer.Ordinal);

var json = new StringBuilder();
json.AppendLine("{");
json.AppendLine(" \"files\": [");
for (int i = 0; i < files.Count; i++)
{
json.Append(" \"").Append(EscapeJson(files[i])).Append('"');
if (i < files.Count - 1)
{
json.Append(',');
}

json.AppendLine();
}

json.AppendLine(" ]");
json.AppendLine("}");

string manifestPath = Path.Combine(dataRoot, "manifest.json");
File.WriteAllText(manifestPath, json.ToString());
AssetDatabase.ImportAsset("Assets/StreamingAssets/data/manifest.json", ImportAssetOptions.ForceUpdate);
Debug.Log($"WebGL StreamingAssets manifest written with {files.Count} files.");
}

private static void RemoveAutoFullscreen(string outDir)
{
string indexPath = Path.Combine(outDir, "index.html");
if (!File.Exists(indexPath))
{
Debug.LogWarning($"WebGL index.html not found at {indexPath}; fullscreen patch skipped.");
return;
}

string[] lines = File.ReadAllLines(indexPath);
var kept = new List<string>(lines.Length);
bool changed = false;
foreach (string line in lines)
{
if (line.Trim() == "unityInstance.SetFullscreen(1);")
{
changed = true;
continue;
}

kept.Add(line);
}

if (changed)
{
File.WriteAllLines(indexPath, kept);
Debug.Log("Removed generated WebGL auto-fullscreen call; fullscreen remains available from the button.");
}
}

private static string EscapeJson(string value)
=> value.Replace("\\", "\\\\").Replace("\"", "\\\"");

private static void SetWebGLProperty(string propertyName, object value)
{
Type webGlType = typeof(PlayerSettings).GetNestedType("WebGL", BindingFlags.Public | BindingFlags.NonPublic);
var property = webGlType?.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (property == null || !property.CanWrite)
{
Debug.Log($"WebGL PlayerSettings.{propertyName} is not available in this Unity version; leaving default.");
return;
}

try
{
object converted = value;
if (property.PropertyType.IsEnum)
{
converted = Enum.Parse(property.PropertyType, value.ToString());
}
else if (property.PropertyType != value.GetType())
{
converted = Convert.ChangeType(value, property.PropertyType);
}

property.SetValue(null, converted, null);
}
catch (Exception ex)
{
Debug.LogWarning($"Could not set WebGL PlayerSettings.{propertyName}: {ex.Message}");
}
}

/// <summary>
/// Adds the runtime <see cref="Shader.Find"/> shaders to GraphicsSettings' "Always Included
/// Shaders" so the player build keeps them (otherwise Shader.Find returns null at runtime and
Expand Down Expand Up @@ -196,7 +348,7 @@ public static void EnsureRendererFeatures()
if (f != null)
{
AssetDatabase.RemoveObjectFromAsset(f);
Object.DestroyImmediate(f, true);
UnityEngine.Object.DestroyImmediate(f, true);
}

changed = true;
Expand Down
Loading