Skip to content

Set up an initial framework to allow megastructures to spawn in the world #454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

patowen
Copy link
Collaborator

@patowen patowen commented Apr 14, 2025

This PR adds all the fundamental additions required to support the random spawning of large structures. To demonstrate this framework, a temporary option is available to spawn a random assortment of horospheres throughout the world.

While I tried to document the code well to explain the reasoning behind the algorithm, a summary would be worthwhile, so I'll repeat the summary I gave in Discord a while back (with a few changes):

One nice thing about the dodecahedral tiling, which isn't true of every tiling but works in our case, is that every time you get to a neighbor of a node, you are crossing a plane that divides the space into two halves. You can never cross that plane again without crossing a descender. Here is a 2D representation:
image
A horosphere in any given position can only be generated by one unique node. The criteria for a node to be able to generate a horosphere are as follows:

  • The horosphere must be entirely contained within all of the half-spaces bound by the descenders of the node.
  • The horosphere must not be able to be generated by any child node following the rule above.

Following these two rules and carefully choosing a probability distribution within a given node allows you to generate a random distribution of horospheres in a completely isotropic way.

Implementation-wise, each node containing part of a horosphere needs to have a reference to said horosphere. To avoid unbounded memory usage, we can "forget" about a horosphere the moment we cross a plane that leaves that horosphere behind.

The problem is that some of these horospheres may intersect with each other. To prevent this, one naive approach would be to just skip generating a horosphere if it would overlap with one of the horospheres it (or a parent) already generated. The issue is that it doesn't capture all possible overlaps because two nodes that aren't ancestors of each other can generate intersecting horospheres. The picture below shows a 2D example, where dots with arrows show which node owns which horocycle.
image

The problem arises because although these two nodes initially appear unrelated, they are related after all because it is possible to reach the same node from both of them. Nodes of the same depth in the graph with this property need to be somewhat aware of each other to avoid causing conflicts when they generate any kind of megastructure. In this PR, I call them "peer nodes", and a fair amount of logic is needed to determine which nodes are peer nodes. See the PR's diff for details.

To get the full list of potential horospheres a node needs to know about, there are three steps:

  • Step 1: The node needs to inherit all horospheres from its parents. This requires the parents to be done generating horospheres (as in, done with step 3).
  • Step 2: The node needs to generate candidate horospheres it owns. This should be done after step 1 to avoid generating candidate horospheres that obviously won't work.
  • Step 3: The node needs to determine which candidate horospheres should actually become horospheres. This has to be done after step 1 and step 2, and it also requires all peer nodes to be done with step 2 so that it knows what other candidate horospheres might interfere.

For more detail on step 3, a candidate can only become a horosphere if it doesn't intersect with any other inherited horospheres or higher-priority candidate horospheres known by the node we're focused on or any of its peers. Candidate horosphere priority can be chosen pretty much arbitrarily. This PR just uses their node-local w-coordinate.

The interaction of steps 1, 2, and 3 means that the order in which various properties are initialized in various nodes interact in intricate ways, so it is no longer feasible to populate NodeState for each node in the order it's added to the graph. Instead, we split it into PartialNodeState (for nodes that completed step 2), and NodeState (for nodes that completed step 3).

One consequence of the importance of peer nodes is that it effectively widens node paths in the graph. In practice, this should be fine, as we already tend to want to form a large sphere of nodes around each player, but it may increase the cost of some things. I don't expect it to be an important bottleneck, though.

For a visual of this, see the below image. For the circled node to have a fully set up NodeState, all red nodes need a NodeState, and all blue nodes need a PartialNodeState.
image

Fortunately, this approach should be reusable for any kind of megastructure, as the only property this relies on is the fact that it can be bounded by a plane.

One concern I have had in the past is that this intersection test is somewhat asymmetric, so isotropy may no longer be preserved. I don't think this is a major concern, as things look correct. Perhaps there is a way that someone could use this to find the origin if they're lost using some tricky analysis on the distribution of megastructures, but I don't think that's possible, and if it is, it probably isn't something we need to address.

For simplicity, this PR only allows a node to reference at most one horosphere, which effectively means that all horospheres are separated so that the convex hulls of the nodes containing them will never intersect.

@patowen patowen requested a review from Ralith April 14, 2025 02:50
@patowen patowen force-pushed the initial-megastructure-framework branch from feb3991 to 3624275 Compare April 17, 2025 00:57
@Ralith
Copy link
Owner

Ralith commented Apr 17, 2025

Please rebase for clarity.

@patowen patowen force-pushed the initial-megastructure-framework branch from 3624275 to 345639b Compare April 17, 2025 04:00
@patowen
Copy link
Collaborator Author

patowen commented Apr 17, 2025

Please rebase for clarity.

Done! Thanks for reviewing the other PRs so quickly!

@patowen patowen force-pushed the initial-megastructure-framework branch from 345639b to 1a7c2bc Compare April 29, 2025 02:21
@patowen patowen force-pushed the initial-megastructure-framework branch 2 times, most recently from e59b65d to b9a4c40 Compare May 24, 2025 20:47
@patowen patowen requested a review from Ralith May 24, 2025 20:49
@patowen
Copy link
Collaborator Author

patowen commented May 24, 2025

I think this is ready for another review, as I've fixed the worst readability issues. The lint build step will fail until #456 is merged.

EDIT: I just realized that some of my comments on the implementation of the new Horosphere struct make no sense because they were copied from the old pos field in HorosphereNode. I'll update this PR with a small change to fix that soon.

EDIT2: Done.

@patowen patowen force-pushed the initial-megastructure-framework branch 3 times, most recently from 80cbf9c to 26ed3de Compare May 25, 2025 21:49
@patowen patowen force-pushed the initial-megastructure-framework branch from 26ed3de to 99dc590 Compare June 1, 2025 23:40
@patowen patowen force-pushed the initial-megastructure-framework branch 2 times, most recently from 17e70ab to 4d39487 Compare June 30, 2025 03:43
@patowen patowen force-pushed the initial-megastructure-framework branch from 4d39487 to a056b96 Compare July 21, 2025 15:55
@patowen
Copy link
Collaborator Author

patowen commented Jul 21, 2025

@Ralith: I finally got around to addressing all of the PR comments. For re-reviewing this, I believe the main thing to focus on is NodeBoundedRegion and its tests. Most new logic in this PR is based on this struct, and I don't have a good understanding of how understandable it is, code-readability wise. I think it's a good abstraction, but there might be tweaks I can do to make it easier to understand.

One notable logic change I made is in should_generate. To hopefully make it easier to reason about, now that the concept of NodeBoundedRegion exists, I now check whether these regions in the two peer nodes intersect instead of computing horosphere propagation on the fly. This may increase false positives slightly, but not enough to be noticeable, and I think it's worth the added simplicity.

Minor: Also, I would like to double-check that hash(spice, HOROSPHERE_SEED) makes sense in terms of how the random u64 associated with horospheres in general is applied, as well as whether HOROSPHERE_SEED is a good name for this constant.

@patowen patowen requested a review from Ralith July 21, 2025 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants