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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ the richer, screenshot-laden versions live there. `(#123)` references the pull r

## [Unreleased]

### ✨ Real asteroid belts (#683)

- **Asteroids now orbit in belts** (new worlds): all of a system's landable asteroids share 1–2
orbit annuli — the outer belt just beyond the outermost planet, big systems sometimes a second
inner belt — instead of scattering randomly across planet orbits. Existing worlds keep their
layout untouched.
- **Belts are worth flying to**: every asteroid body carries a cluster of ship-laser-mineable rocks
at its position, and launching *from* an asteroid surrounds you with a dense 9-rock field instead
of the usual three.
- **The flight chart shows the belt as a belt**: one translucent band with its own label
("Asteroid belt" / "Asteroidengürtel") instead of a smear of stacked orbit rings.

### ✨ Planetary rings in more colours (#684)

- Ring systems now roll a seeded **material family** — icy white stays the norm, but dusty tan,
rocky grey and a rare pale violet appear too — so two ringed planets under the same star no longer
wear the same colour. Applies everywhere a ring is drawn (orbit view, surface sky, horizon band),
including in existing worlds (same seed → same tilt and band pattern, just richer tints).

### ✨ Star systems & planets got real names (#678)

- **Several system-name registries** instead of one pattern: coined proper names ("Tharion"),
Expand Down
26 changes: 26 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,32 @@ With #685's hardness parity the feel scales by material for free: ice pops in on
titanium still wants the tier-2 drill. Hull tracking, laser carving and the client structure
renderer were already size-agnostic — no client change.

### ★ Asteroid belts: shared orbit annuli + mineable rock density; seeded ring-colour families (#683, #684, 2026-08-03, branch feat/asteroid-belts)
Asteroids used to scatter uniformly over the whole system disc — regularly inside a planet's orbit
lane — and the ship-laser rocks were the same trio next to EVERY launch body, so a "belt" had no
mining pull. New worlds (flag `WorldDescription.AsteroidBelts`, default true on creation like
`SystemVariance`; `--belts off` escape hatch) now put all landable asteroids of a system on 1–2
shared orbit annuli: the outer belt one orbit step beyond the outermost planet, big systems (5+
planets, 4+ asteroids) sometimes a second inner belt in a ≥620-unit orbit gap. Members sit on evenly
spaced angular slots (Hash01 salt series 8xx, never the body rng — old saves regenerate
byte-identically with the flag off) so the flight-view clear-gap guarantee holds by construction.
Mining follows the geometry: every other asteroid body in the system carries a 4-rock mineable
cluster at its flight-view position (server + client agree via the shared
`SystemBodyLayout.FlightViewScale`; capped at 24), and launching FROM an asteroid spawns a dense
9-rock local field (per-instance respawn target; only rocks within 60 units of the launch point
count, or belt clusters would starve the replenish loop). The flight chart groups belt members into
ONE translucent band with a localized label (`ui.map.belt`, DE "Asteroidengürtel") instead of N
stacked orbit rings; legacy scattered worlds keep per-body rings. Star-centring zoom budget in
`SystemChartLayoutTests` raised 2.0→2.1 (measured ~2.01× with the outer belt). Also #684: planetary
ring COLOUR was near-constant ice-grey (±0.06 drift; the hue lerp target is the star, same for the
whole system) — `PlanetRings.TintFor` now rolls a seeded material family per ring (ice white 55 %,
dusty tan 20 %, rocky grey 15 %, pale violet 10 %), consistent across flight view, surface sky and
horizon band, retroactive-safe (same RingSeed → same geometry). Gotcha found on the way: a
never-launched ship's `CurrentLocationId` is the default planet TYPE ("varied"), not a body id — the
space-instance anchor now falls back to `_meta.ActiveLocationId` for the asteroid logic, while the
hostile-shading lookup deliberately keeps the legacy raw-id behaviour (fixing it would silently
raise fresh-start difficulty in pirate-space starts).

### ★ EVA asteroid mining plays by the rules: drill tiers, hardness hits, no lost ore (#685, 2026-08-03, branch fix/eva-asteroid-mining-hardness)
During a spacewalk any asteroid block popped with a SINGLE bare-hand click — no drill, no hardness,
titanium included — while the same block on a planet takes a tier-2 drill and several timed hits. And
Expand Down
15 changes: 12 additions & 3 deletions client/Assets/BlocksBeyondTheStars/Scripts/PlanetRings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,21 @@ public static float AzimuthDegrees(int ringSeed)
return (float)rng.NextDouble() * 360f;
}

/// <summary>The ring's base colour: mostly pale ice-grey (real rings are water ice) pulled a
/// little toward the given body/star hue, with a small seeded warm/cool drift per planet.</summary>
/// <summary>The ring's base colour (#684): a seeded MATERIAL FAMILY per ring system — mostly
/// pale water-ice white (real rings are water ice, so it stays the norm), sometimes dusty tan
/// or bare rocky grey, rarely a pale exotic violet — pulled a little toward the given body/star
/// hue, with a small seeded warm/cool drift on top. Before the families every ring in a system
/// wore virtually the same ice-grey (the drift alone is ±0.06); now two ringed planets under
/// the same star read as different ring systems. Same RingSeed → same family in every view
/// (flight orbit, surface sky, horizon band).</summary>
public static Color TintFor(int ringSeed, Color bodyHue)
{
var rng = new System.Random(ringSeed * 17 + 3);
var pale = new Color(0.93f, 0.90f, 0.85f);
double family = rng.NextDouble();
var pale = family < 0.55 ? new Color(0.93f, 0.90f, 0.85f) // water ice — the common look
: family < 0.75 ? new Color(0.86f, 0.73f, 0.55f) // dusty tan
: family < 0.90 ? new Color(0.70f, 0.71f, 0.74f) // bare rocky grey
: new Color(0.82f, 0.73f, 0.93f); // rare pale violet
var c = Color.Lerp(pale, bodyHue, 0.28f);
float drift = ((float)rng.NextDouble() - 0.5f) * 0.12f;
return new Color(Mathf.Clamp01(c.r + drift), Mathf.Clamp01(c.g), Mathf.Clamp01(c.b - drift));
Expand Down
50 changes: 49 additions & 1 deletion client/Assets/BlocksBeyondTheStars/Scripts/SpaceMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public sealed class SpaceMap : MonoBehaviour
private const float OrbitAlpha = 0.34f; // faint enough to stay behind the markers it connects
private const float MaxBodyDisc = 46f; // body discs are clamped to this diameter, however big the body
private const float MarkerPad = MaxBodyDisc * 0.5f + 8f; // chart room a rim body's disc + backing needs
private const float BeltGroupGap = 26f; // flight units: asteroid orbit radii this close = one belt (#683)
private const float BeltBandPad = 8f; // chart units the belt band extends past its outermost member

private static readonly Color WaypointCol = new Color(1f, 0.85f, 0.3f);
private static readonly Color DiscCol = new Color(0.01f, 0.03f, 0.07f, 0.78f); // WorldMap's backing disc
Expand Down Expand Up @@ -217,16 +219,62 @@ private void Build()
Centered(_chart, Vector2.zero, new Vector2(24f, 24f), UiKit.DiscSprite, new Color(1f, 0.94f, 0.74f));
}

// Asteroid belts (#683): belt members share (nearly) one orbit radius, so per-member rings
// would stack into a smeared blob. Group the star-orbiting asteroid bodies by projected
// orbit radius; three or more within a belt-tight spread read as ONE belt — drawn as a
// single translucent band with one localized label — and their own rings are suppressed.
// Legacy scattered systems rarely group and simply keep their per-body rings.
var beltMembers = new HashSet<string>();
if (starCentred && landables != null)
{
var fields = new List<(string Id, float R)>();
foreach (var b in landables)
{
var fnb = BodyFor(b.Id);
if (fnb != null && fnb.Kind == "AsteroidField" && string.IsNullOrEmpty(fnb.ParentId))
{
fields.Add((b.Id, new Vector2(b.Pos.x - _centre.x, b.Pos.z - _centre.z).magnitude));
}
}

fields.Sort((a, c) => a.R.CompareTo(c.R));
int start = 0;
for (int k = 1; k <= fields.Count; k++)
{
if (k < fields.Count && fields[k].R - fields[k - 1].R <= BeltGroupGap)
{
continue; // same annulus — keep extending the group
}

if (k - start >= 3)
{
float rMin = fields[start].R * _scale, rMax = fields[k - 1].R * _scale;
float outer = rMax + BeltBandPad;
var bandCol = new Color(0.78f, 0.72f, 0.6f, 0.12f);
UiOrbitRing.Create(_chart, Vector2.zero, new Vector2(outer * 2f, outer * 2f),
bandCol, rMax - rMin + BeltBandPad * 2f);
Label(_chart, new Vector2(0f, outer + 12f), L("ui.map.belt"));
for (int m = start; m < k; m++)
{
beltMembers.Add(fields[m].Id);
}
}

start = k;
}
}

// Orbit paths: one ring per body that circles the star — planets and the landable asteroid
// bodies. Moons are deliberately left out: they are re-laddered onto clearance slots just
// outside their parent's drawn radius, so their rings would collapse into the planet's disc
// and turn the chart into noise. Each radius comes from the body's own projected position, so
// the ring is guaranteed to pass through its marker whatever the layout passes did to it.
// Belt members are covered by their belt's band above instead of a ring each.
if (starCentred && landables != null)
{
foreach (var b in landables)
{
if (!OrbitsStar(BodyFor(b.Id)))
if (beltMembers.Contains(b.Id) || !OrbitsStar(BodyFor(b.Id)))
{
continue;
}
Expand Down
6 changes: 4 additions & 2 deletions client/Assets/BlocksBeyondTheStars/Scripts/SpaceView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3785,8 +3785,10 @@ public bool TryResolveSpaceWaypoint(out Vector3 target, out float arriveSq)
private float _landTargetSq; // squared distance to the land target (for station-vs-body priority)
private float _nearStationSq; // squared distance to the near station
private float _bounds = Bounds; // flight clamp, enlarged to span the resident system
private const float SystemViewScale = 0.16f; // system units → flight-view units (kept compact so
// neighbouring planets are a short cruise apart, not minutes)
private const float SystemViewScale = SystemBodyLayout.FlightViewScale; // system units → flight-view units
// (kept compact so neighbouring planets are a short cruise
// apart, not minutes; shared — the server parks belt rock
// clusters at the same transform, #683)
private const float KeepOutMargin = 10f; // how far outside a body's surface the ship is held
private const float LandBand = 40f; // land prompt shows within (body radius + margin + band)

Expand Down
1 change: 1 addition & 0 deletions data/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@
"ui.map.kind_station": "Raumstation",
"ui.map.kind_planet": "Planet",
"ui.map.kind_moon": "Mond",
"ui.map.belt": "Asteroidengürtel",
"ui.map.kind_asteroidfield": "Asteroidenfeld",
"ui.map.kind_wreck": "Wrack",
"ui.map.status_notgenerated": "Unerforscht",
Expand Down
1 change: 1 addition & 0 deletions data/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@
"ui.map.kind_station": "Space Station",
"ui.map.kind_planet": "Planet",
"ui.map.kind_moon": "Moon",
"ui.map.belt": "Asteroid belt",
"ui.map.kind_asteroidfield": "Asteroid field",
"ui.map.kind_wreck": "Wreck",
"ui.map.status_notgenerated": "Uncharted",
Expand Down
14 changes: 14 additions & 0 deletions docs/developer/WORLD_GENERATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ The data shapes are in [`Galaxy.cs`](../../src/BlocksBeyondTheStars.Shared/World
4000–16000 instead of 5000–12000). Bias 0 is bit-identical to the classic hash — that invariant
protects every existing save's terrain. The client receives the bias via `NetBody.SizeBias` and
must pass it to every `CircumferenceFor` call (orbit spheres, sky bodies, pad-map bakes).
- **Asteroid belts (#683, worlds created with `WorldDescription.AsteroidBelts`):** the system's
landable asteroids share 1–2 **orbit annuli** instead of scattering across the whole disc (which
regularly parked them inside a planet's orbit lane). The outer belt always sits one orbit step
beyond the outermost planet; big systems (5+ planets, 4+ asteroids) may roll a second, inner belt
into a ≥620-unit gap between two planet orbits. Members occupy evenly spaced angular slots with a
small wobble (radial jitter ±60), so the flight view's clear-gap guarantee holds by construction.
Geometry uses the `Hash01` **8xx salt series** and never the body rng. Like `SystemVariance`, the
flag **defaults to false** (old saves keep the legacy `DiscPoint` scatter byte-identically) and to
**true** on `ServerConfig`'s creation-time description; `--belts off` is the escape hatch
(mirrors `--variance`). At runtime every asteroid body also carries a **mineable rock cluster** at its flight-view
position (server: `AddBeltRockClusters`, shared transform `SystemBodyLayout.FlightViewScale`),
and launching *from* an asteroid spawns a dense 9-rock local field instead of the classic trio.
The flight chart draws grouped members as one translucent belt band (`ui.map.belt`) instead of
stacked per-body orbit rings.

A `CelestialBody` stores only Id, Name, `Kind` (Planet/Moon/AsteroidField/SpaceStation/Wreck), a
**`PlanetType` key**, and orbit data. The body's *content* is generated only when a player enters it.
Expand Down
8 changes: 8 additions & 0 deletions docs/user/USER_MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,14 @@ separate unlock; admins can still disable it through server world rules.
- Fly within local space instances; asteroids + NPC drones can damage hull/shield. Whether you can lose your
ship is set by the world's **"Keep ship on death"** rule (see *Repairing your own ship* above).

### Asteroid belts
- In worlds created with belts (the default for new worlds), a system's landable asteroids orbit
together in **1–2 shared belts** — the flight chart (M) draws them as one translucent band with an
"Asteroid belt" label instead of separate orbit rings.
- Belts are the place to mine with the ship laser: **every asteroid body has a cluster of mineable
rocks floating around it**, and launching from an asteroid you landed on puts you inside a dense
local rock field (nine rocks instead of the usual three near planets).

### Peaceful NPC trader ships
- Space and busy systems feel alive with **civilian trader traffic**: merchant ships **warp in** at the system
edge, **cruise** to a station to **dock**, or head into the inner system and **land on a planet/moon** if a
Expand Down
Loading