Fix storyloc structure spawning - #305
Conversation
|
The only location that didn't generate is the "university" from the Better Ruins mod. It might be taller than the world height limit of 128. I need to check. |
Zaldaryon
left a comment
There was a problem hiding this comment.
Changes requested\n\nThe current head still moves a structure whose lower edge is exactly .\n\nIn , sets and clamps when . That still shifts to , contrary to the boundary rule described in the patch. Please clamp only values below zero, or document and test why block is invalid.\n\nThe patch also spells the closing marker as . Please correct it to so the marker remains recognizable.\n\n## Verification\n\n- Reviewed the complete current head .\n- Confirmed the PR targets .\n- The PR has no reported CI checks.\n- The existing bootstrap, build, and smoke evidence does not cover the valid boundary case.
Zaldaryon
left a comment
There was a problem hiding this comment.
Changes requested
The current head still moves a structure whose lower edge is exactly Y=0.
In patches/VSSurvivalMod/Systems/WorldGen/Standard/ChunkGen/6.GenStructures/Story/GenStoryStructures.cs.patch, ClampStructureY sets minValidY = 1 and clamps when blockMinY < minValidY. That still shifts Y=0 to Y=1, contrary to the boundary rule described in the patch. Please clamp only values below zero, or document and test why block Y=0 is invalid.
The patch also spells the closing marker as // Startum end. Please correct it to // Stratum end so the marker remains recognizable.
Verification
- Reviewed the complete current head
087e1fc3. - Confirmed the PR targets
indev. - The PR has no reported CI checks.
- The existing bootstrap, build, and smoke evidence does not cover the valid
Y=0boundary case.
Removed duplicate review after correcting its formatting in the follow-up review.
|
Yes. I’ve added a clarification to the description explaining exactly why Y must be 1 rather than 0. This is because structures can replace any block; the mantle/bedrock layer is at coordinate 0, and during testing, a structure could easily overwrite it, which poses a potential risk to players. I updated the comments and ran the build and smoke test; both passed successfully. |
Zaldaryon
left a comment
There was a problem hiding this comment.
Changes requested
The latest head fixes the two earlier points: it changes the closing marker to // Stratum end and documents why the patch reserves Y=0.
One boundary case remains in patches/VSSurvivalMod/Systems/WorldGen/Standard/ChunkGen/6.GenStructures/Story/GenStoryStructures.cs.patch. ClampStructureY reserves minValidY = 1, but the guard rejects only sizeY > maxY. With maxY = 128, sizeY = 128, and an initial lower edge of Y=0, the lower clamp moves blockMinY to 1. blockMaxY was calculated as 127 before that move, so the upper-bound branch does not run, and the final strucloc.Y2 becomes 129. The structure can still extend above the valid world range.
Please reject structures larger than the available range [minValidY, maxY), or recompute and enforce the upper bound after the lower clamp. Add a test for a structure exactly as tall as the world with the reserved lower boundary.
Verification
- Reviewed the complete current head
a34c059d. - Confirmed the PR targets
indev. - Confirmed the corrected marker and the new
Y=0explanation. - The PR reports no CI checks.
- Existing build and smoke evidence does not cover the exact-height boundary case.
Pixnop
left a comment
There was a problem hiding this comment.
I went through the current head and I confirm Zaldaryon's open point: with a world height of 128 and a structure 128 blocks tall whose lower edge starts at 0, the lower clamp moves blockMinY to 1 while blockMaxY was already computed as 127, so the upper branch never runs and strucloc.Y2 ends at 129.
There is a second way into the same hole, and it matters more, because one line closes both.
Underground placement never reaches ClampStructureY. The three branches test UseWorldgenHeight, then SurfaceRuin, then Surface. Anything else falls through with isDirty false, so the only thing that happens is startPos.Y = strucloc.Y1, exactly as before the PR. That is not a hypothetical branch: in the game's own storystructures.json, resonancearchive is declared placement: "underground", and no story structure at all sets useWorldgenHeight, so that first branch is dead for vanilla content. The Y of those structures comes from DetermineStoryStructures, which rebuilds the cuboid from CenterPos.Y, and CenterPos.Y is pinned to 1 for every placement that is not Surface or SurfaceRuin. A structure sitting at Y=1 writes its last block at sizeY, so it lands outside the world exactly when sizeY equals the world height. The guard misses that by one, since 128 > 128 is false, and the clamp that would have caught it is never called on this path.
To be fair about the size of it: the shipped underground structure is 101 blocks tall, so nothing in vanilla overflows today. This bites a mod-added story structure as tall as the world, which is the same family of content that motivated the PR in the first place.
Changing the guard to reject anything that cannot fit in the usable range, sizeY > maxY - minValidY, closes the underground path and Zaldaryon's arithmetic case in one test, without touching the branches. With minValidY reserving the mantle, the usable range really is 127 blocks in a 128 world, so a structure of exactly 128 has nowhere to go and rejecting it with the existing log line is the honest outcome. Worth noting while you are in there: blockMaxY = maxValidY in the upper branch is a dead store, nothing reads it afterwards, and that is precisely why the lower clamp can push the top back out.
Smaller things:
- The third hunk carries no
// Stratummarker and no fix. Its only two edits areif(structure.GenerateGrass)becomingif (structure.GenerateGrass)and an opening brace moved onto its own line. That is 22 of the 205 lines in the patch, and it will come back as conflict noise on the next vanilla bump. Three more cosmetic edits sit inside the marked region: theIntersectsguard split across two lines, and the twoelse if(spacing fixes. CONTRIBUTING asks to match the surrounding style rather than reformat, so reverting those five lines in the working tree and re-runningextract-patchesshrinks the patch for free. - The comment above
stratumReportedTooTalluses an em dash, which the style section rules out. A colon does the job. - The branch is 50 commits behind
indevand carries a commit named "comment edits". None of those 50 commits touch this patch or its worldgen path, so the rebase should be uneventful, but 17 of them build thetests/StratumScenariossuite, which is where a boundary test would live. A rebase plus a squash into one commit with a descriptive subject would leave a clean history.
Questions rather than requests:
- The clamp translates a structure instead of refusing it, silently. A SurfaceRuin 60 blocks tall on terrain at 50 ends up floating, and a Surface building 100 blocks tall in a 128 world ends up buried below sea level, with no log line either way. Is a shifted structure preferable to a skipped one, and should it say so in the log?
- The config lookup and the height guard moved ahead of the
Intersectstest, so every generated column now pays a linear scan per story structure instead of only the columns the structure actually touches. Six structures make that cheap, but the section says "No changes" for performance. Computing the too-tall set once inInitWorldGenwould remove both the per-column cost and the need for the session HashSet. ClampStructureYtakesref Cuboidiandref BlockPos, both reference types, and does not use instance state. Dropping therefkeywords and making it static would say what it does more plainly.
On the test Zaldaryon asked for: a full Atlas scenario boots a server per test class, which is heavy for a world height of 128. If you would rather not go that far, making the clamp static and testable on its own would still pin the boundary case, and that is the part that keeps regressing.
Reply to @Zaldaryon
Fixed on both fronts:
Reply to @Pixnop
Done exactly like this. The same guard now rejects oversized structures on the underground path too: a structure sitting at
Removed — replaced with the
Reverted the formatting-only changes so the patch stays minimal.
Rephrased with a colon; the field is now named
Planned for the git step (not done yet). The branch rebase + single squashed commit is part of the pending work. On your questions rather than requests:
The build completes successfully, and the smoke test proceeds to the GameReady stage without errors. |
There was a problem hiding this comment.
Changes requested
1. Ignore my whitespaces messages.
Sorry for them.
2. Commit the exact-height boundary test
The current arithmetic covers the requested case, but the PR diff contains only the patch file. The latest author evidence describes a local check for sizeY = 128 and sizeY = 127 with the reserved lower boundary, but no test is committed that would fail if the guard or ClampStructureY regresses. Add the focused test requested in the prior review, or add it to the existing scenario suite if that is the supported test location.
3. Mark the new vanilla fields
The additions at the top of the patch introduce stratumTooTallStructures and stratumReportedShifted, but their comments begin with // key structure code and do not carry a // Stratum marker. Please mark this vanilla-only block as required by CONTRIBUTING.md.
Verification
- Reviewed the complete current head
4f39a0c3df0b9ae37e4aadc5e6471c52fc499f08. - Confirmed the PR targets
indev. - Confirmed the underground path now reaches
ClampStructureYand the guard uses the usable range. git diff --check origin/indev...HEADreports the added patch whitespace, including theY2line above.- The current PR diff contains no test file and GitHub reports no CI checks.
|
Changes made:
The build completes successfully, and the smoke test proceeds to the GameReady stage without errors. Don't scold me for the whitespaces; there's nothing I can do about it.😥 |
There was a problem hiding this comment.
The boundary arithmetic and the committed regression scenario are now present
git diff --check origin/indev...HEAD reports 12 trailing-whitespace errors in
patches/VSSurvivalMod/Systems/WorldGen/Standard/ChunkGen/6.GenStructures/Story/GenStoryStructures.cs.patch.
Each reported line is an added blank line containing a space. Remove those
spaces from the generated patch and rerun git diff --check before requesting
the final review.
Verification
- Reviewed the complete current head
22afd1. - Confirmed the PR targets
indev. - Confirmed the exact-height boundary test is committed in
tests/StratumScenarios/StoryStructureBoundaryScenarios.cs. - The PR reports no repository CI checks.
|
tehtelev, I want to apologize for the whitespace requests in my reviews on this PR today, especially reviews 5190483161 and 5191378847. I treated formatting in the generated patch as a blocking issue, and that was not appropriate. Please disregard those requests. I am sorry for the unnecessary churn. |
Zaldaryon
left a comment
There was a problem hiding this comment.
Approving the current head. The boundary guard now rejects structures that cannot fit in the usable world range, and the committed regression scenario covers the exact-height, lower-boundary, upper-boundary, and underground cases. The earlier whitespace finding is withdrawn and is not a blocker.
Superseded by the current head and the new approval. The earlier review is no longer an active blocker.
|
Pixnop, the current head |
Pixnop
left a comment
There was a problem hiding this comment.
The fix itself is right, and I checked it rather than reading the description. From a clean vanilla tree the patch applies, the post-image hash matches the header, every hunk count is consistent, and the added lines carry no tabs. The guard is the one we asked for, sizeY > maxY - stratumMinValidY, computed once in InitWorldGen behind the same genStoryStructures early return that protects the column pass, so the underground path is covered and the cache can never be read before it exists. The clamp recomputes the ceiling after lifting the floor, the dead store is gone, the config lookup sits after Intersects again, and the three formatting edits from the first round were reverted. I recalculated all eighteen rows of the new test by hand and they are correct, and the reflection fails loudly when a method is missing instead of going green.
One thing blocks the merge, and it is not about this fix.
The branch carries all of #313. Commit 89220a1, "Fix vanished player privacy leaks", brings in 10 files and 953 lines of the vanish work: both server patches, the staff command state, the privacy scenarios and their docs. That is about three quarters of what this PR now diffs against indev. The content is byte identical to the head of #313 I approved earlier, so nothing wrong would land, but two open PRs would be shipping the same commit under different names, and the second one to merge would either conflict or turn out empty. My guess is a merge from a local branch that already had #313 checked out. Once #313 lands, a rebase onto indev drops that commit on its own; if you would rather not wait, an interactive rebase that removes it does the same today.
Two things to do at the same time as that rebase:
- Run the suite on the head you actually push. The last comment reports 18 scenarios passing, but the branch holds 20, the 17 from the suite plus the two vanish ones plus yours, so that run predates the merge that pulled #313 in.
- Bring the suite README along. It says nineteen scenarios and nine server boots, which is #313's count; with your class it is twenty and ten.
Smaller, none of it blocking. Three vanilla blank lines are dropped outside any marked block, one before blockLayerConfig and two around ExecuteOrder; I would not have raised whitespace again after today's thread, except that these are the only edits in the patch that a future vanilla bump has to carry for no reason, so reverting them in the working tree and re-running extract-patches keeps the diff honest. stratumMinValidY never changes and could be a const. The performance section says "No changes", but the change is real and in the good direction: the per-column path went from a linear scan plus a size check on every generated column to one HashSet.Contains, and that is worth stating rather than hiding. And the five commits, "Fix unfixable" included, would read better squashed into one with a subject that says what the fix does.
Once the branch contains only its own work and the suite has run on that head, this has my approval.
…hether the structure size fits within the world.
Pixnop
left a comment
There was a problem hiding this comment.
Approved.
The fix was already verified in the last round: the patch applies to a clean vanilla tree, the post-image hash matches, the guard and the clamp do what the issue asked, and the eighteen rows of the boundary test check out by hand. What blocked was the branch, not the code, and that is settled: it now carries only its own four commits on top of indev, with the same tree as before, and the merge commit is gone.
The suite README count moves out of this pull request: #333 drops the number altogether, as you suggested, since the run prints it. I am running the scenario suite on this exact head on my side and will post the numbers here when it finishes.
|
Suite run on this head (a5b74f2, same tree as the branch after the rebase), through |

Summary
Issue:
Y < 0) or poke above the max world height (Y2 > MapSizeY), which crashes the chunk generator; and when a previously generated structure was re-accessed (WorldgenHeight >= 0),startPos.Ywas recomputed from raw terrain height instead of the saved coordinates, pushing it out of bounds again.worldHeight = 128), story or mod-added structures taller than the usable vertical space could never be placed, leaving their locations impossible to complete.Changes:
ClampStructureY, which constrains a structure's Y range to the valid world interval[1, MapSizeY - 1]. Why1and not0: atY = 0lies the mantle/bedrock layer, so reserving it keeps structures from replacing bedrock (and the player can otherwise fall into that layer). The clamp runs for every placement type (UseWorldgenHeight,SurfaceRuin,Surfaceand underground), recomputing the lower edge first and then enforcing the upper bound by pulling the base down if needed, so the top can never exceed the world. It is now a plainstaticmethod (noref) that only transforms its inputs.InitWorldGen. The guard was changed fromsizeY > MapSizeYtosizeY > MapSizeY - 1(usable range[1, MapSizeY)), which rejects a structure exactly as tall as the world and closes the underground path that never reached the clamp before. Such structures are skipped once per session with an error log (stratumTooTallStructures).stratumReportedShifted) instead of logging silently.startPos.Yis always derived from the savedstrucloc.Y1, never recomputed from raw terrain whenWorldgenHeight >= 0..First(...)with.FirstOrDefault(...)plus a null check to avoid exceptions.IsStructureTooTallstatic method and the reserved lower boundary into astratumMinValidYfield, soInitWorldGenand the test suite share a single source of truth for the boundary formula.Tests:
Added
StoryStructureBoundaryScenariosto theStratumScenariossuite. The scenario boots a patched server through Atlas and exercises bothClampStructureYandIsStructureTooTallvia reflection on realistic world heights (128 from the bug report, 256 vanilla default, 448 tall-world option), covering:Y1to 1, top lands exactly onmaxY).CenterPos.Ypinned to 1, so a structure as tall as the world wrote its last block atY = sizeY).maxValidY.Result:
Structures of any height generate at any world height without crashes or top clipping. Structures taller than the usable vertical space are skipped (logged once) instead of overflowing the world. Both boundary methods are pinned by a regression test, so the formula cannot drift without breaking the suite.
Type
Checklist
.\scripts\extract-patches.ps1ran clean.dotnet build VintageStory.slnx -c Releaseis green.// Stratummarker.Performance numbers
No changes
Related issues
World setting - default, but world height - 128.
Seed - 1347010773
Used mods:
BetterRuinsv0.6.3
Before
Structures break or get cut off at the top (depending on luck).
/tpstoryloc devastationarea/tpstoryloc devastationarea/tpstoryloc sunriftAfter
All story and mod-added locations generated successfully, without crashes or distortions. The University structure failed to generate entirely, as it did not fit within the world's vertical height limits.
/tpstoryloc devastationarea/tpstoryloc devastationareaSunrift structure will generate but may protrude slightly from the ground.

/tpstoryloc sunriftThe same structure with a world height of 256 or greater.

If the lower threshold for sunrift structure spawning is set to

int minValidY = 0;, we see that the structure replaces the mantle.