Skip to content
This repository was archived by the owner on Aug 31, 2026. It is now read-only.

Commit 149a260

Browse files
committed
many bugfixes
- Space transit: a jump now cuts, parks and delivers the ship it NAMES on both legs, instead of whatever craft its anchor happened to reach, and a ship keeps that name when it crosses between worlds - Space transit: an arrival is not finished until its crew is aboard, and a jump's hyperspace lane belongs to the world, so two jumps cannot share one - Space subsystem: its services became one state object with a lifecycle, so a test stack no longer decapitates production for the rest of the session - Entry on-ramp: an entry that does not happen now reports which step declined it, and a refusal reports how many people aboard it reached - Flight control: a flight command names one ship and one computer instead of a global channel, and a ship keeps obeying its computer after a chunk cycle - Flight control: crossing an atmosphere no longer asks whether somebody is holding a key - an unpiloted craft crosses too - Descent: the boundary is drawn and the range to it counted, so a pilot can see where the cell ends - Client sky: the hyperspace corridor is gated on the world the frame is drawn in, so standing up mid-jump no longer empties the sky - Physics integration: a ship record carries the mod's durable name, and the crossing keeps a craft's physics identity - Test probes: the entry gate, the jump census and the transit reports can answer "never asked" instead of staying silent
1 parent 6b3f27f commit 149a260

59 files changed

Lines changed: 4810 additions & 1193 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/java/zmaster587/advancedRocketry/AdvancedRocketry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,7 @@ public void postInit(FMLPostInitializationEvent event) {
12141214
// aside the moment no loaded world is locked, so the default-everything case pays nothing.
12151215
MinecraftForge.EVENT_BUS.register(new zmaster587.advancedRocketry.world.TimeCommandGuard());
12161216
// Movable-ship space subsystem GC ticker (idle unless a server-start builds the controller).
1217-
MinecraftForge.EVENT_BUS.register(new zmaster587.advancedRocketry.space.SpaceSubsystem.Ticker());
1217+
MinecraftForge.EVENT_BUS.register(new zmaster587.advancedRocketry.space.SpaceSubsystemEvents());
12181218
// Login restore (a returning player goes back to his ship, not to a stale pool slot) and the
12191219
// cell-divergence hook. Independent of the controller so it stays quiet while it is down.
12201220
MinecraftForge.EVENT_BUS.register(new zmaster587.advancedRocketry.space.SpaceEventHandler());

src/main/java/zmaster587/advancedRocketry/client/render/planet/BoundarySky.java

Lines changed: 133 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,29 @@
1717
import zmaster587.advancedRocketry.dimension.DimensionManager;
1818
import zmaster587.advancedRocketry.dimension.DimensionProperties;
1919
import zmaster587.advancedRocketry.network.PacketSystemBodiesSync;
20+
import zmaster587.advancedRocketry.space.HyperspaceWorld;
2021
import zmaster587.advancedRocketry.universe.SystemBodyKind;
2122

2223
import java.util.List;
2324

2425
/**
25-
* Slot-world sky renderer for a settled tier-2 ship: draws the descent boundary ring plus a
26-
* billboard for every nearby system body the server has synced for THIS slot dimension.
26+
* Slot-world sky renderer for a settled tier-2 ship: draws a billboard for every nearby system body
27+
* the server has synced for THIS slot dimension.
28+
*
29+
* <p><b>There is no fixed horizon band here, and there must not be one.</b> This renderer used to
30+
* draw a "descent boundary ring" at a constant radius in the CAMERA's frame — a band the viewpoint
31+
* could never leave, identical at ten blocks from a planet and at ten million, with no coupling to
32+
* the descent radius it was named after. It asserted "a descent boundary is here" in every cell,
33+
* including cells with nothing to descend to. A boundary belongs to a BODY: it is drawn around the
34+
* body's own bearing, at the angle that body's shell actually subtends.</p>
2735
*
2836
* <p>The body data comes from {@link PacketSystemBodiesSync#bodiesForDim(int)} (the shared
2937
* server-&gt;client render channel), keyed on {@code world.provider.getDimension()}. Bodies flagged
3038
* {@link PacketSystemBodiesSync.RenderBody#descendTarget} are highlighted so the pilot can see which
3139
* body the ship will descend into once inside its proximity radius.</p>
3240
*
3341
* <p>This provider replaces the ENTIRE sky rather than adding to one, so whatever is not drawn here is
34-
* not drawn at all -- hence the starfield alongside the ring and the billboards.</p>
42+
* not drawn at all -- hence the starfield alongside the billboards.</p>
3543
*
3644
* <p>Everything emitted here is wound to face the camera and drawn with vanilla's back-face culling
3745
* left on, matching {@link RenderPlanetarySky}. That is a hard requirement, not a style choice: the sky
@@ -46,10 +54,11 @@
4654
public class BoundarySky extends IRenderHandler {
4755

4856
// Render tunables (appearance-only; never pinned by a test).
49-
private static final float BOUNDARY_RADIUS = 100.0F;
50-
private static final float BOUNDARY_HEIGHT = 6.0F;
51-
private static final int BOUNDARY_SEGMENTS = 48;
5257
private static final float BODY_DISTANCE = 90.0F;
58+
/** Sky-frame radius the boundary circle is emitted on. Inside the starfield, around the body. */
59+
private static final float BOUNDARY_SKY_RADIUS = 95.0F;
60+
/** Samples around one boundary circle. Enough that a great circle does not read as a polygon. */
61+
private static final int BOUNDARY_SEGMENTS = 64;
5362
/** Sky-frame scale the label text is drawn at, so it reads at the billboard's distance. */
5463
private static final float LABEL_SCALE = 0.28F;
5564

@@ -64,52 +73,32 @@ public class BoundarySky extends IRenderHandler {
6473
public static volatile int labelsDrawnLastFrame;
6574

6675
/**
67-
* Frames on which the descent-boundary ring has been drawn. It exists so "the ring is suppressed
68-
* in hyperspace" is a statement a test can falsify: a counter that only ever goes up cannot tell
69-
* a suppressed ring from a sky renderer that stopped running altogether, so the corridor's own
70-
* counter is read in the same breath and the pair has to move in opposite directions.
76+
* How many atmosphere boundaries the client's LAST FRAME drew. Same shape as the label counter
77+
* and for the same reason: "a boundary is drawn for a descend target and for nothing else" is
78+
* a claim a test can only take without looking at pixels if the renderer counts what it drew.
79+
*
80+
* <p>Read it beside {@link #skyFramesDrawn}, never alone — a zero here means "no boundary was
81+
* drawn" only if the renderer ran at all, and the two are separate questions.</p>
7182
*/
72-
public static volatile long ringFramesDrawn = 0L;
83+
public static volatile int boundariesDrawnLastFrame;
7384

7485
/**
7586
* Frames on which this sky renderer ran AT ALL, counted before any branch inside it.
7687
*
77-
* <p>Without it the ring counter answers two different questions with the same zero: "the ring
78-
* was suppressed" and "nothing rendered here". The first control leg written against that pair
79-
* could not tell them apart, and said so by failing on its own arrangement.</p>
88+
* <p>It is what makes "X was not drawn" falsifiable: a per-feature counter that stays at zero
89+
* cannot tell "the feature was suppressed" from "nothing rendered here", and the first control
90+
* leg written without this pair could not tell them apart and said so by failing on its own
91+
* arrangement.</p>
8092
*/
8193
public static volatile long skyFramesDrawn = 0L;
8294

8395
private final Minecraft mc = Minecraft.getMinecraft();
8496

85-
// Cached static geometry: the descent boundary ring (position-only; colour set at call time).
86-
private final int glBoundaryList;
8797
// Cached static geometry: the shared starfield, so empty space is not a black void.
8898
private final int glStarList;
8999

90100
public BoundarySky() {
91-
int lists = GLAllocation.generateDisplayLists(2);
92-
this.glBoundaryList = lists;
93-
this.glStarList = lists + 1;
94-
95-
BufferBuilder buffer = Tessellator.getInstance().getBuffer();
96-
GL11.glNewList(this.glBoundaryList, GL11.GL_COMPILE);
97-
buffer.begin(GL11.GL_QUAD_STRIP, DefaultVertexFormats.POSITION);
98-
for (int i = 0; i <= BOUNDARY_SEGMENTS; i++) {
99-
double ang = (Math.PI * 2.0D * i) / BOUNDARY_SEGMENTS;
100-
float x = (float) (Math.cos(ang) * BOUNDARY_RADIUS);
101-
float z = (float) (Math.sin(ang) * BOUNDARY_RADIUS);
102-
// TOP vertex before BOTTOM. The strip advances anticlockwise around +Y, so this pairing is
103-
// what makes each quad wind anticlockwise -- i.e. front-facing -- as seen from INSIDE the
104-
// cylinder. The camera is always inside it: the ring is drawn in the camera-centred sky
105-
// frame, at a radius no viewpoint can leave. Emitting bottom-first faces the ring outwards
106-
// and vanilla's GL_CULL_FACE/GL_BACK (enabled in EntityRenderer.renderWorldPass just before
107-
// the sky pass, and never turned off here) discards every quad of it.
108-
buffer.pos(x, BOUNDARY_HEIGHT, z).endVertex();
109-
buffer.pos(x, -BOUNDARY_HEIGHT, z).endVertex();
110-
}
111-
Tessellator.getInstance().draw();
112-
GL11.glEndList();
101+
this.glStarList = GLAllocation.generateDisplayLists(1);
113102

114103
// The starfield is the mod's existing one, compiled into a list of our own rather than
115104
// duplicated: same seed, same 2000 quads, same radius as every other AR sky. Without it a slot
@@ -135,7 +124,7 @@ public void render(float partialTicks, WorldClient world, Minecraft mc) {
135124

136125
GlStateManager.disableTexture2D();
137126

138-
// Stars first: the ring and the billboards are meant to sit in front of them.
127+
// Stars first: the billboards are meant to sit in front of them.
139128
GlStateManager.color(1.0F, 1.0F, 1.0F, STAR_ALPHA);
140129
GL11.glCallList(this.glStarList);
141130

@@ -155,16 +144,24 @@ public void render(float partialTicks, WorldClient world, Minecraft mc) {
155144
return;
156145
}
157146

158-
// Descent boundary ring (untextured colour band).
159-
GlStateManager.color(0.35F, 0.65F, 1.0F, 0.35F);
160-
GL11.glCallList(this.glBoundaryList);
161-
ringFramesDrawn++;
147+
// The atmosphere boundaries FIRST, untextured, while the texture unit is still off: they
148+
// belong behind the bodies they surround, and drawing them here saves toggling the texture
149+
// unit twice per frame.
150+
int boundaries = 0;
151+
BufferBuilder buffer = Tessellator.getInstance().getBuffer();
152+
if (bodies != null) {
153+
GlStateManager.color(0.35F, 0.65F, 1.0F, 0.55F);
154+
for (PacketSystemBodiesSync.RenderBody body : bodies) {
155+
boundaries += drawBoundary(buffer, body) ? 1 : 0;
156+
}
157+
}
158+
boundariesDrawnLastFrame = boundaries;
159+
162160
GlStateManager.enableTexture2D();
163161

164162
// One billboard per synced body.
165163
int labelled = 0;
166164
if (bodies != null && !bodies.isEmpty()) {
167-
BufferBuilder buffer = Tessellator.getInstance().getBuffer();
168165
boolean labels = SkyLabels.enabled();
169166
for (PacketSystemBodiesSync.RenderBody body : bodies) {
170167
labelled += drawBody(buffer, body, labels) ? 1 : 0;
@@ -175,6 +172,78 @@ public void render(float partialTicks, WorldClient world, Minecraft mc) {
175172
restoreState();
176173
}
177174

175+
/**
176+
* Draw {@code body}'s atmosphere boundary: the circle on the sky where its shell meets the
177+
* viewer's line of sight. Returns whether anything was emitted.
178+
*
179+
* <p><b>Why a circle around the body and not a band around the camera.</b> The boundary is a
180+
* sphere of radius R about the body, so from a distance d it subtends a half-angle
181+
* {@code asin(R/d)} about the body's own bearing. That makes it a thing in the world: it OPENS
182+
* as the ship closes, and at the crossing it is a great circle — the boundary is all around
183+
* you, because you are on it. A fixed band at the camera's horizon can express none of that; it
184+
* is identical at every distance, which is why the one this replaced said nothing.</p>
185+
*
186+
* <p><b>No singularity, deliberately.</b> Points are sampled ON the sky sphere as
187+
* {@code cosθ·n + sinθ·(cosφ·u + sinφ·v)} rather than projected onto the billboard plane, where
188+
* the radius would go as {@code tan θ} and blow up exactly at the crossing. The ratio is
189+
* clamped at 1, so at or inside the shell θ is a right angle and the great circle is the honest
190+
* limit rather than a NaN.</p>
191+
*
192+
* <p>Emitted as a LINE LOOP: the sky pass runs with {@code GL_CULL_FACE}/{@code GL_BACK} on
193+
* (see the class note), and a filled band would have to be wound correctly for a viewpoint that
194+
* moves through it — the failure mode being silent invisibility. A line has no facing.</p>
195+
*/
196+
private boolean drawBoundary(BufferBuilder buffer, PacketSystemBodiesSync.RenderBody body) {
197+
// A body with no shell has no boundary to draw. Read off the number the SERVER sent rather
198+
// than off the kind, so the renderer never has to know which kinds have one.
199+
if (!body.descendTarget || body.boundaryRadius <= 0L)
200+
return false;
201+
202+
double dx = body.localX;
203+
double dy = body.localY;
204+
double dz = body.localZ;
205+
double len = Math.sqrt(dx * dx + dy * dy + dz * dz);
206+
if (len < 1.0E-6D)
207+
return false;
208+
209+
double nx = dx / len, ny = dy / len, nz = dz / len;
210+
// The angle comes from the same place the range beside the body does, so the circle and the
211+
// number can never describe two different surfaces.
212+
double theta = zmaster587.advancedRocketry.space.DescentShell
213+
.boundaryHalfAngle(len, body.boundaryRadius);
214+
double ct = Math.cos(theta), st = Math.sin(theta);
215+
216+
// Any axis not parallel to n spans the perpendicular plane with it. Take the world axis n is
217+
// LEAST aligned with: a fixed choice degenerates to a zero-length cross product for a body
218+
// that happens to lie along it, and that body is precisely the one dead ahead.
219+
double hx = 0.0D, hy = 0.0D, hz = 0.0D;
220+
double ax = Math.abs(nx), ay = Math.abs(ny), az = Math.abs(nz);
221+
if (ax <= ay && ax <= az) {
222+
hx = 1.0D;
223+
} else if (ay <= az) {
224+
hy = 1.0D;
225+
} else {
226+
hz = 1.0D;
227+
}
228+
double ux = ny * hz - nz * hy, uy = nz * hx - nx * hz, uz = nx * hy - ny * hx;
229+
double ul = Math.sqrt(ux * ux + uy * uy + uz * uz);
230+
if (ul < 1.0E-9D)
231+
return false;
232+
ux /= ul; uy /= ul; uz /= ul;
233+
double vx = ny * uz - nz * uy, vy = nz * ux - nx * uz, vz = nx * uy - ny * ux;
234+
235+
buffer.begin(GL11.GL_LINE_LOOP, DefaultVertexFormats.POSITION);
236+
for (int i = 0; i < BOUNDARY_SEGMENTS; i++) {
237+
double phi = (Math.PI * 2.0D * i) / BOUNDARY_SEGMENTS;
238+
double cp = Math.cos(phi), sp = Math.sin(phi);
239+
buffer.pos((ct * nx + st * (cp * ux + sp * vx)) * BOUNDARY_SKY_RADIUS,
240+
(ct * ny + st * (cp * uy + sp * vy)) * BOUNDARY_SKY_RADIUS,
241+
(ct * nz + st * (cp * uz + sp * vz)) * BOUNDARY_SKY_RADIUS).endVertex();
242+
}
243+
Tessellator.getInstance().draw();
244+
return true;
245+
}
246+
178247
/**
179248
* Restore a sane GL state for the rest of the world render.
180249
*
@@ -256,13 +325,30 @@ private boolean drawBody(BufferBuilder buffer, PacketSystemBodiesSync.RenderBody
256325
}
257326

258327
/**
259-
* Write the body's name and its current distance under the billboard, inside the already-rotated
260-
* body frame so the text faces the camera exactly as the quad does.
328+
* Write the body's name and the range still to fly under the billboard, inside the
329+
* already-rotated body frame so the text faces the camera exactly as the quad does.
330+
*
331+
* <p><b>The number is the distance to the body's ATMOSPHERE, not to the body.</b> Crossing that
332+
* surface is what puts the ship on the planet, so it is the only range a pilot on approach can
333+
* act on: a body-centre range tells him to cover a distance he does not have to cover, and
334+
* still reads a whole shell's worth of blocks at the very instant he crosses. A body with no
335+
* shell to cross — a star, anything not a descend target — carries a zero radius and is
336+
* therefore labelled with its plain distance by the same arithmetic, with no special case
337+
* here.</p>
338+
*
339+
* <p>The billboard's SIZE keeps using the distance to the body itself
340+
* ({@link ApparentSize#halfSizeFor}, at the call site): how big a thing looks is a property of
341+
* the thing, how far there is left to fly is a property of the approach. They are easy to
342+
* conflate because both are "distance to that body", and conflating them would shrink a planet
343+
* to nothing exactly as you arrive.</p>
261344
*/
262345
private boolean drawLabel(PacketSystemBodiesSync.RenderBody body, float half, double distance) {
263346
if (mc.fontRenderer == null)
264347
return false;
265-
String text = nameOf(body) + " " + ApparentSize.formatDistance(distance);
348+
String text = nameOf(body) + " "
349+
+ ApparentSize.formatDistance(
350+
zmaster587.advancedRocketry.space.DescentShell.distanceToShell(
351+
distance, body.boundaryRadius));
266352
GlStateManager.pushMatrix();
267353
// The sky frame's +Y is up while the font renders DOWN its own +Y, so both axes are negated
268354
// here; without it every label reads upside down and mirrored.

0 commit comments

Comments
 (0)