The resolved star population has no bound once the halo drain is fixed, and dim stars are most of the cost for almost none of the light #72
Labels
No labels
burndown-2026-06
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-gaming/galaxy-gen#72
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Raised by Kai while reviewing #70: should the sim limit total star count, or do the gas mechanics already do it?
Measured answer: the gas mechanics do not, and the thing that currently does is the bug #70 fixes.
Nothing bounds the count
BIRTH_MAX_STARS = 24is per birth event.SN_GAS_RETURN = 0.8of progenitor mass and the fountain cycles the rest. Count is a birth-rate against retirement-rate balance, not a mass budget.900 * (30/m)^2and the IMF is Salpeter-flavored between 3 and 120, so within a 2500-tick run only about 4% of stars born reach end of life. Half of all births are under 5 solar masses, which live 64800 ticks.That leaves the spatial phase-mixing drain in
process_stellar_haloas the only meaningful sink for the population that dominates the count. #70 cuts that drain by 30-80x, on purpose, because it was #65's halo bump.Measured, size 500, irregular=>spiral, seed 12345
Baseline turns over and starts falling, with 8993 stars retired. Neither fixed configuration turns over: the fix is still adding 4340 per thousand ticks at t=5000, and with the radiation gate open it is adding 18870 and accelerating. Gas does not stop them - 612690 remains at t=5000 against 1932968 at t=0.
So the baseline's plateau is the drain, and #70 removes the drain.
Why this matters beyond tidiness
The e2e runtime spec already reports
stars=32081 fps=16.8 jank=99/101at size 500. The render is marginal at 32k. 131k is not a slower version of that, it is a different product.Proposal: retire on luminosity, not a count cap
Luminosity is
m^2, so the number-heavy end of the IMF is the light-poor end:Half the population produces 4% of the light. Retiring dim main-sequence stars into the existing diffuse stellar-halo reservoir removes most of the point count while losing almost none of the light - and what it loses is smooth, spread-out glow that a gradient renders better than tens of thousands of individual dots. The standing population skews further this way than the birth numbers do, since the massive stars are the ones that die.
This is a generalization of a pattern already in the sim, not a new concept:
REMNANT_RESOLVED_AGE,WHITE_DWARF_RESOLVED_AGE, andNEUTRON_STAR_RESOLVED_AGEalready retire dim remnants intostellar_halo_mass. Today the criterion is "dim because dead". This extends it to "dim because small", which is the criterion that actually decides whether a star reads as a point on screen.It is asymptotic by construction: the resolved count settles at birth rate times the time a star spends above the threshold. No arbitrary ceiling, no policy about which star to evict, and the mass ledger already has somewhere to put them.
Why not a hard cap
A count cap needs an eviction policy, and every policy is arbitrary in a way the luminosity threshold is not. It also fails the honesty test: a galaxy that stops forming stars because it hit a render budget is a rendering decision wearing physics clothing. The luminosity floor is a statement about what is resolvable, which is true independent of the budget.
Work
stellar_halo_mass, preserving the baryonic ledger the way the remnant paths already do.runtime-perfspec as the target - it already measures fps against star count.Sequencing
Blocks #70's item 6. The #70 fix removes the drain, so landing it without a replacement sink ships an unbounded population. Worth doing first, since it also helps the site today at 32k stars and 16.8 fps.
The floor works. Three seeds, and it lands the population where the site already runs.
Implemented as
GALAXY_ABL_RESOLVED_LUMINOSITY_FLOOR, default off, onmain. Rule: an unbound main-sequence star below the floor retires into the diffuse reservoir through the same path aged remnants already use. Association members are exempt whatever their brightness - a cluster should read as a cluster while it is one.It produces a genuine asymptote
Resolved stars at size 500, t=5000, seeds 12345 / 12346 / 12347:
A floor of 100 - stars under ten solar masses - settles the population at 23-30k across every seed, against 123-131k unbounded. That is where the site already runs (
stars=32081in the perf spec), so #70 lands with no perf regression instead of a 4x one.There is a neat symmetry: #70 removes a bad sink and #72 adds a good one, and they very nearly cancel. The halo drain was ejecting stars that should never have been ejected; the floor retires stars that were never resolvable. Same population size, honest mechanism.
Floor 25 is not enough - it bounds the growth but the count still drifts up to 77k.
It does not disturb the galaxy
Gas rotation
vtsits at 1.0-1.56 across every configuration and seed with no systematic shift, and rotational support 0.17-0.32. So dropping roughly 47% of stellar mass out of the gravitating set does not perturb the gas, and the coarse diffuse-mass grid I expected to need is not needed. That is the measurement paying for itself: I would have built it on spec.Gas spiral coherence at t=5000, baseline against floor: 0.31/0.34 against 0.26/0.30, and with the #70 fix 0.11/0.27 against 0.21/0.27. Comparable. The first seed showed 0.53 dropping to 0.09 and I nearly reported that as a real effect - the other two seeds show it was noise, which is what spiral coherence does when read off one run.
One result I am deliberately not banking
v/sigmaimproves with the floor: 1.48-1.69 to 2.24-2.84 at t=5000 under the #70 fix, and 0.20-0.26 to 0.79-1.03 under baseline. Some of that is real and some is compositional - the floor removes the old dim field stars, and those stars were the mush. That is not cheating, since #66's complaint was literally a uniform grey mess of dim points over good structure, and removing them from the point layer addresses it directly. But it is partly a change in what is being measured rather than in how the disk moves, and it should not be counted twice alongside #70's dynamical improvement.The remaining piece is the renderer, and it is a fork
The retired light has to go somewhere visible. Today
stellar_halo_massrenders as a halo ring from 0.62 to 1.32disk_rwith alpha capped at 0.085. Retired disk stars would add light to a component drawn outside the disk, and capped so low it barely brightens. Landing the sink as-is would remove about 12% of the light from the disk and put it nowhere - the galaxy would get dimmer and sparser. That is a visual regression, so the switch stays off until this is decided.Two credible designs:
A. Retire to a diffuse disk glow. Track retired luminosity on a coarse grid, expose it, render it as a smooth additive layer. Physically cleanest and matches what the light actually is. Costs new Rust state, a serialization version bump, a wasm getter, and a JS render pass. Makes the point layer sparser and adds smooth light underneath.
B. Super-particle subsampling. Instead of retiring dim stars, keep one in every N and multiply its luminosity and mass by N. Total light and total mass are preserved exactly by construction, the spatial distribution statistically, and the count drops by N. No renderer change, no new state, no serialization change. Changes the texture though - fewer, brighter specks rather than many faint ones.
A is the honest physics. B is far cheaper and preserves the light exactly. They look different on screen and that is the deciding factor, not the code.
Status
Landed. The floor ships, and neither renderer design was needed.
The visual comparison answered a different question than it was asked
Captured the same galaxy at the same tick - seed 424242, size 500, t=1500, 39880 stars - under four treatments, and measured the disk region rather than eyeballing it:
glowwas both the most expensive and the furthest from the original: the haze read as grey lumps competing with the gas rather than as starlight. Tunable, but it needed real work to become good.superbeatdropby 0.0015 RMSE, which is not worth super-particle bookkeeping.So the premise both designs shared was false. They each existed to give 12% of the light somewhere to go, and deleting that light outright costs 1.5% of frame brightness. It is spread too thinly to matter. The Rust floor alone is the whole fix - no renderer change, no new state, no serialization bump.
One process note: the first capture run photographed a fresh galaxy at tick 0 for all three modes. Ticking the frontend on the main thread does not repaint what the worker publishes. Three internally consistent, plausible, useless frames - the same shape of error as the retracted
scircresult earlier in this family of issues. The tick counter in the corner is what caught it. The harness now paints explicitly and says so in a comment.What shipped
RESOLVED_LUMINOSITY_FLOOR = 100.0, ten solar masses, as a per-scenario parameter next tostar_drag. An unbound main-sequence star below it retires into the diffuse reservoir through the path aged remnants already use. Association members are exempt whatever their brightness.Verified active at size 500: 22380 stars at t=2500 with 25309 retired, against 43656 and 3200 before.
The elliptical opts out, and that is work item 4 answered
Applying the disk floor to the elliptical drops
spheroid_concentrationfrom the required 0.45-0.85 to 0.28-0.32, failing its scenario test. The reason is structural: the elliptical's defining feature is a concentrated stellar spheroid, and that spheroid is made of the accumulated faint old population. Retiring it is not shedding invisible light, it is deleting the object.It also exposes a limit on the visual evidence above - those captures were taken on a spiral. For a scenario where the faint population is the image, "drop is fine" does not transfer, and I should not have assumed it would. The elliptical needs its retired light rendered before it can have a floor, so design A is not dead, it is scoped to the one scenario that actually needs it. Left open here.
Status
?dim=scaffolding is removed. The capture harness stays ase2e/visual-capture.spec.ts, gated behindGALAXY_CAPTURE, because #70 will change the look considerably more than this did.91 Rust tests, clippy, rustfmt, and
check-jsgreen; e2e running. This unblocks #70 item 6.