From 07f5d8e56dfd2cec4be51795ef1c0233d7da8a9c Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Thu, 28 May 2026 18:15:19 -0400 Subject: [PATCH 01/15] does the thing --- .../DMProject/Tests/change_area_appearance.dm | 2 +- .../DMProject/Tests/range.dm | 162 ++++++++++++++---- Content.IntegrationTests/DMProject/code.dm | 8 + .../Objects/Types/DreamObjectClient.cs | 4 +- .../Objects/Types/DreamObjectWorld.cs | 4 +- .../Procs/Native/DreamProcNativeHelpers.cs | 84 ++++++++- .../Procs/Native/DreamProcNativeRoot.cs | 56 +----- OpenDreamShared/Dream/ViewRange.cs | 14 +- 8 files changed, 231 insertions(+), 103 deletions(-) diff --git a/Content.IntegrationTests/DMProject/Tests/change_area_appearance.dm b/Content.IntegrationTests/DMProject/Tests/change_area_appearance.dm index f1e805efb0..2940477c6f 100644 --- a/Content.IntegrationTests/DMProject/Tests/change_area_appearance.dm +++ b/Content.IntegrationTests/DMProject/Tests/change_area_appearance.dm @@ -2,7 +2,7 @@ color = rgb(255,0,0) /datum/unit_test/test_change_area_appearance/RunTest() - var/area/subtype/S = new() + var/area/subtype/S = areas_by_type[/area/subtype] var/list/block_turfs = block(locate(1,1,1), locate(2,2,2)) for(var/turf/T in block_turfs) S.contents += T diff --git a/Content.IntegrationTests/DMProject/Tests/range.dm b/Content.IntegrationTests/DMProject/Tests/range.dm index b4e92d4086..7ea0f5dd20 100644 --- a/Content.IntegrationTests/DMProject/Tests/range.dm +++ b/Content.IntegrationTests/DMProject/Tests/range.dm @@ -1,40 +1,128 @@ -//Tests that /proc/range() is iterating along the correct, wonky path +// tests all of range's possible cases + +#define LOC(x, y) locate(x, y, 3) + +/obj/contained/one +/obj/contained/two + +/datum/unit_test/range/proc/run_case(atom/center, list/expected, identifier, isorange) + var/error_index = 0 + var/list/result = isorange ? orange(center, 1) : range(center, 1) + try + if(result.len != expected.len) + error_index = expected.len + CRASH("result is [result.len > expected.len ? "longer" : "shorter"] than expected") + for(var/index in 1 to result.len) + if(result[index] != expected[index]) + error_index = index + CRASH("result does not match expected") + catch(var/exception/exc) + var/list/error_output = list() + error_output += "[identifier]: [exc]" + error_output += "expected:" + for(var/i in 1 to expected.len) + var/atom/A = expected[i] + error_output += ("\t([A.x], [A.y]) [A.type] [i == error_index ? "<-- here" : null]") + error_output += "got:" + for(var/i in 1 to result.len) + var/atom/A = result[i] + error_output += ("\t([A.x], [A.y]) [A.type] [i == error_index ? "<-- here" : null]") + + + CRASH(error_output.Join("\n")) + +// Tests the implementation of range() and orange() /datum/unit_test/range/RunTest() world.maxx = world.maxy = 5 - //Test that it goes in the right order - var/list/correctCoordinates = list( - list(3,3), - list(2,2), - list(2,3), - list(2,4), - list(3,2), - list(3,4), - list(4,2), - list(4,3), - list(4,4) + + var/turf/center = LOC(3, 3) + var/area/outer_area = areas_by_type[/area] + var/obj/container = new /obj(center) + var/obj/contained = new /obj/contained/one(container) + var/obj/contained_trash_1 = new /obj/contained/two(contained) + var/obj/contained_trash_2 = new /obj/contained/two(contained) + var/obj/contained_trash_3 = new /obj/contained/two(contained) + + var/list/turf_range_case = list( + LOC(3, 3), + outer_area, + container, + LOC(2, 2), + LOC(2, 3), + LOC(2, 4), + LOC(3, 2), + LOC(3, 4), + LOC(4, 2), + LOC(4, 3), + LOC(4, 4), ) - var/i = 1 - var/turf/centre = locate(3,3,1) - for(var/x in range(1,centre)) - var/turf/T = x - ASSERT(!isnull(T)) - var/list/coords = correctCoordinates[i] - ASSERT(coords[1] == T.x) - ASSERT(coords[2] == T.y) - i += 1 - if(i != 10) - CRASH("range(1,centre) iterated over [i - 1] tiles, expected 9") - //Test that arguments are parsed correctly - var/std = range(1,centre) - if(std ~! range(centre,1)) - CRASH("range(1,centre) and range(centre,1) do not return the same result.") - if(std ~! range("3x3",centre)) - CRASH("ViewRange argument parsing for range() isn't working correctly.") - //Test that getting the range from a mob includes the mob's loc. - var/list/mob_seen_turfs = list() - var/mob/test/timmy = new(centre) - for(var/turf/x in range(1,timmy)) - mob_seen_turfs += list(x) - if(std ~! mob_seen_turfs) - CRASH("Using a non-/turf Center for range() did not work correctly.") - del(timmy) \ No newline at end of file + + var/list/turf_orange_case = list( + LOC(2, 2), + outer_area, + LOC(2, 3), + LOC(2, 4), + LOC(3, 2), + LOC(3, 4), + LOC(4, 2), + LOC(4, 3), + LOC(4, 4), + ) + + var/list/container_range_case = list( + contained, + LOC(3, 3), + outer_area, + container, + LOC(2, 2), + LOC(2, 3), + LOC(2, 4), + LOC(3, 2), + LOC(3, 4), + LOC(4, 2), + LOC(4, 3), + LOC(4, 4), + ) + + var/list/container_orange_case = list( + LOC(3, 3), + outer_area, + LOC(2, 2), + LOC(2, 3), + LOC(2, 4), + LOC(3, 2), + LOC(3, 4), + LOC(4, 2), + LOC(4, 3), + LOC(4, 4), + ) + + var/list/contained_range_case = list( + contained_trash_1, + contained_trash_2, + contained_trash_3, + container, + contained, + ) + + var/list/contained_orange_case = list( + container, + ) + + + run_case(center, turf_range_case, nameof(turf_range_case), FALSE) + run_case(center, turf_orange_case, nameof(turf_orange_case), TRUE) + run_case(container, container_range_case, nameof(container_range_case), FALSE) + run_case(container, container_orange_case, nameof(container_orange_case), TRUE) + run_case(contained, contained_range_case, nameof(contained_range_case), FALSE) + run_case(contained, contained_orange_case, nameof(contained_orange_case), TRUE) + + // FIXME: these pass in BYOND, but the way we iterate over area turfs diverges + // var/list/area_range_case = list(outer_area) + outer_area.contents + // var/list/area_orange_case = area_range_case.Copy() + // run_case(outer_area, area_range_case, nameof(area_range_case), FALSE) + // run_case(outer_area, area_orange_case, nameof(area_orange_case), TRUE) + + del(container) + +#undef LOC diff --git a/Content.IntegrationTests/DMProject/code.dm b/Content.IntegrationTests/DMProject/code.dm index fdf82c2fb0..52cf462802 100644 --- a/Content.IntegrationTests/DMProject/code.dm +++ b/Content.IntegrationTests/DMProject/code.dm @@ -3,6 +3,11 @@ /turf/border /mob/test +var/global/list/areas_by_type = list() +/area/New() + areas_by_type[type] = src + + //The actual tests //NOTE: Tests placed in the IntegrationTests suite // should actually require a normal server in order to work. @@ -26,6 +31,9 @@ throw EXCEPTION("You must override RunTest()") /world/New() + // prepare areas + for(var/area_subtype in typesof(/area) - /area) + new area_subtype() for(var/subtype in typesof(/datum/unit_test)) if(subtype == /datum/unit_test) //skip the base class continue diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs index 209d6c870b..683c4a1417 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs @@ -60,8 +60,8 @@ protected override bool TryGetVar(string varName, out DreamValue value) { return true; case "view": // Number if square & centerable, string representation otherwise - if (View is { IsSquare: true, IsCenterable: true }) { - value = new DreamValue(View.Range); + if (View.CanSquareRange) { + value = new DreamValue(View.SquareRange.Value); } else { value = new DreamValue(View.ToString()); } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs b/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs index 434781f3a7..dba2a799ce 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs @@ -232,8 +232,8 @@ protected override bool TryGetVar(string varName, out DreamValue value) { case "view": // Number if square & centerable, string representation otherwise - if (DefaultView.IsSquare && DefaultView.IsCenterable) { - value = new DreamValue(DefaultView.Range); + if (DefaultView.CanSquareRange) { + value = new DreamValue(DefaultView.SquareRange.Value); } else { value = new DreamValue(DefaultView.ToString()); } diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index f599f2b3e4..321832d092 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -199,6 +199,86 @@ public static (DreamObjectAtom?, ViewRange) ResolveViewArguments(DreamManager dr return tiles; } + public static DreamValue HandleRange(NativeProc.Bundle bundle, DreamObject? usr, bool includeCenter) { + (DreamObjectAtom? center, ViewRange range) = DreamProcNativeHelpers.ResolveViewArguments(bundle.DreamManager, usr as DreamObjectAtom, bundle.Arguments); + if (center is null) + return new DreamValue(bundle.ObjectTree.CreateList()); + + HashSet seenAreas = []; + DreamList rangeList = bundle.ObjectTree.CreateList(range.Height * range.Width); + + void addToList(DreamValue value) { + rangeList.AddValue(value); + if(value.TryGetValueAsDreamObject(out var turfValue) && !seenAreas.Contains(turfValue.Cell.Area)) { + var area = turfValue.Cell.Area; + rangeList.AddValue(new(area)); + seenAreas.Add(area); + } + } + + if(center is DreamObjectArea areaCenter) { // yeah you can do this + // setting rangeList directly cause we'll never hit the area case + rangeList.AddValue(new(center)); + foreach(var turf in areaCenter.Turfs) { + rangeList.AddValue(new(turf)); + foreach(var content in turf.Contents.EnumerateValues()) { + rangeList.AddValue(content); + } + } + return new(rangeList); + } + else if(center is DreamObjectTurf turfCenter) { + if(includeCenter) { // if we're orange, we want to skip the else block too + addToList(new(center)); + foreach(DreamValue content in turfCenter.Contents.EnumerateValues()) { + addToList(content); + } + } + } + else { // we're getting the range of a container + // add our contents first + if(includeCenter) { + if(center.TryGetVariable("contents", out var centerContents) && centerContents.TryGetValueAsDreamList(out var centerContentsList)) { + foreach(DreamValue content in centerContentsList.EnumerateValues()) { + addToList(content); + } + } + centerContents.Dispose(); + } + + // then we include our loc and the loc's contents (which includes us) + if (center.TryGetVariable("loc", out DreamValue centerLoc)) { + if (centerLoc.TryGetValueAsDreamObject(out var centerLocObject)) { + addToList(centerLoc); + + using var contents = centerLocObject.GetVariable("contents"); + if (contents.TryGetValueAsDreamList(out var locContentsList)) { + foreach (DreamValue content in locContentsList.EnumerateValues()) { + if(!includeCenter && content.TryGetValueAsDreamObject(out var dreamObject) && dreamObject == center) + continue; + addToList(content); + } + } + } + centerLoc.Dispose(); + // if center isn't a turf either, abort here + if(centerLocObject is not DreamObjectTurf) { + return new(rangeList); + } + } + } + + // finally, add the surrounding turfs + foreach (var turf in DreamProcNativeHelpers.MakeViewSpiral(center, range)) { + addToList(new DreamValue(turf)); + foreach (DreamValue content in turf.Contents.EnumerateValues()) { + addToList(content); + } + } + + return new(rangeList); + } + public static DreamValue HandleViewersHearers(NativeProc.Bundle bundle, DreamObject? usr, bool ignoreLight) { DreamValue? depthValue = null; DreamObjectAtom? center = null; @@ -228,7 +308,7 @@ public static DreamValue HandleViewersHearers(NativeProc.Bundle bundle, DreamObj var centerPos = bundle.AtomManager.GetAtomPosition(center); if (depthValue is null || !depthValue.Value.TryGetValueAsInteger(out var depth)) - depth = bundle.DreamManager.WorldInstance.DefaultView.Range; + depth = bundle.DreamManager.WorldInstance.DefaultView.BiggestAxis; foreach (var mob in bundle.MapManager.GetMobsInRange(centerPos, depth)) { var (_, range) = ResolveViewArguments(bundle.DreamManager, mob, bundle.Arguments); @@ -283,7 +363,7 @@ public static DreamValue HandleOviewersOhearers(NativeProc.Bundle bundle, DreamO var centerPos = bundle.AtomManager.GetAtomPosition(center); if (depthValue is null || !depthValue.Value.TryGetValueAsInteger(out var depth)) - depth = bundle.DreamManager.WorldInstance.DefaultView.Range; + depth = bundle.DreamManager.WorldInstance.DefaultView.BiggestAxis; foreach (var atom in bundle.AtomManager.EnumerateAtoms(bundle.ObjectTree.Mob)) { var mob = (DreamObjectMob)atom; diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 93eeea6915..cc88ef74dc 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -1815,18 +1815,7 @@ public static DreamValue NativeProc_ohearers(NativeProc.Bundle bundle, DreamObje [DreamProcParameter("Dist", Type = DreamValueTypeFlag.Float, DefaultValue = 5)] [DreamProcParameter("Center", Type = DreamValueTypeFlag.DreamObject)] public static DreamValue NativeProc_orange(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - (DreamObjectAtom? center, ViewRange range) = DreamProcNativeHelpers.ResolveViewArguments(bundle.DreamManager, usr as DreamObjectAtom, bundle.Arguments); - if (center is null) - return new DreamValue(bundle.ObjectTree.CreateList()); - DreamList rangeList = bundle.ObjectTree.CreateList(range.Height * range.Width); - foreach (var turf in DreamProcNativeHelpers.MakeViewSpiral(center, range)) { - rangeList.AddValue(new DreamValue(turf)); - foreach (DreamValue content in turf.Contents.EnumerateValues()) { - rangeList.AddValue(content); - } - } - - return new DreamValue(rangeList); + return DreamProcNativeHelpers.HandleRange(bundle, usr, false); } [DreamProc("oview")] @@ -1972,48 +1961,7 @@ public static DreamValue NativeProc_rand_seed(NativeProc.Bundle bundle, DreamObj [DreamProcParameter("Dist", Type = DreamValueTypeFlag.Float, DefaultValue = 5)] [DreamProcParameter("Center", Type = DreamValueTypeFlag.DreamObject)] public static DreamValue NativeProc_range(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - (DreamObjectAtom? center, ViewRange range) = DreamProcNativeHelpers.ResolveViewArguments(bundle.DreamManager, usr as DreamObjectAtom, bundle.Arguments); - if (center is null) - return new DreamValue(bundle.ObjectTree.CreateList()); - - DreamList rangeList = bundle.ObjectTree.CreateList(range.Height * range.Width); - - //Have to include centre - rangeList.AddValue(new DreamValue(center)); - - if(center.TryGetVariable("contents", out var centerContents) && centerContents.TryGetValueAsDreamList(out var centerContentsList)) { - foreach(DreamValue content in centerContentsList.EnumerateValues()) { - rangeList.AddValue(content); - } - } - - centerContents.Dispose(); - - // If it's not a /turf, we have to include its loc and the loc's contents - if (center is not DreamObjectTurf && center.TryGetVariable("loc",out DreamValue centerLoc)) { - if (centerLoc.TryGetValueAsDreamObject(out var centerLocObject)) { - rangeList.AddValue(centerLoc); - - using var contents = centerLocObject.GetVariable("contents"); - if (contents.TryGetValueAsDreamList(out var locContentsList)) { - foreach (DreamValue content in locContentsList.EnumerateValues()) { - rangeList.AddValue(content); - } - } - } - - centerLoc.Dispose(); - } - - //And then everything else - foreach (var turf in DreamProcNativeHelpers.MakeViewSpiral(center, range)) { - rangeList.AddValue(new DreamValue(turf)); - foreach (DreamValue content in turf.Contents.EnumerateValues()) { - rangeList.AddValue(content); - } - } - - return new DreamValue(rangeList); + return DreamProcNativeHelpers.HandleRange(bundle, usr, true); } [DreamProc("ref")] diff --git a/OpenDreamShared/Dream/ViewRange.cs b/OpenDreamShared/Dream/ViewRange.cs index cc216c210e..5bfe7ad040 100644 --- a/OpenDreamShared/Dream/ViewRange.cs +++ b/OpenDreamShared/Dream/ViewRange.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Robust.Shared.Maths; namespace OpenDreamShared.Dream; @@ -9,20 +10,23 @@ namespace OpenDreamShared.Dream; /// public readonly struct ViewRange { public readonly int Width, Height; - public bool IsSquare => (Width == Height); + public int BiggestAxis => Math.Max(Width, Height); public int CenterX => Width / 2; public int CenterY => Height / 2; public Vector2i Center => (CenterX, CenterY); - //View can be centered in both directions? + public bool IsSquare => Width == Height; public bool IsCenterable => (Width % 2 == 1) && (Height % 2 == 1); + [MemberNotNullWhen(true, nameof(SquareRange))] + public bool CanSquareRange => IsSquare && IsCenterable; + /// - /// The distance this ViewRange covers in every direction if and - /// are true + /// The distance this ViewRange covers in every direction + /// if is true /// - public int Range => (IsSquare && IsCenterable) ? (Width - 1) / 2 : 0; + public int? SquareRange => CanSquareRange ? (Width - 1) / 2 : null; public ViewRange(int range) { // A square covering "range" cells in each direction From 7293f08e83844d9cdaed0e949b971c831bc065b1 Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Thu, 28 May 2026 18:36:59 -0400 Subject: [PATCH 02/15] tools --- .../Procs/Native/DreamProcNativeHelpers.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index 321832d092..05084fcffb 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -207,7 +207,7 @@ public static DreamValue HandleRange(NativeProc.Bundle bundle, DreamObject? usr, HashSet seenAreas = []; DreamList rangeList = bundle.ObjectTree.CreateList(range.Height * range.Width); - void addToList(DreamValue value) { + void AddToList(DreamValue value) { rangeList.AddValue(value); if(value.TryGetValueAsDreamObject(out var turfValue) && !seenAreas.Contains(turfValue.Cell.Area)) { var area = turfValue.Cell.Area; @@ -225,13 +225,14 @@ void addToList(DreamValue value) { rangeList.AddValue(content); } } + return new(rangeList); } else if(center is DreamObjectTurf turfCenter) { if(includeCenter) { // if we're orange, we want to skip the else block too - addToList(new(center)); + AddToList(new(center)); foreach(DreamValue content in turfCenter.Contents.EnumerateValues()) { - addToList(content); + AddToList(content); } } } @@ -240,28 +241,29 @@ void addToList(DreamValue value) { if(includeCenter) { if(center.TryGetVariable("contents", out var centerContents) && centerContents.TryGetValueAsDreamList(out var centerContentsList)) { foreach(DreamValue content in centerContentsList.EnumerateValues()) { - addToList(content); + AddToList(content); } } + centerContents.Dispose(); } - // then we include our loc and the loc's contents (which includes us) + // the loc's contents will include us if (center.TryGetVariable("loc", out DreamValue centerLoc)) { if (centerLoc.TryGetValueAsDreamObject(out var centerLocObject)) { - addToList(centerLoc); + AddToList(centerLoc); using var contents = centerLocObject.GetVariable("contents"); if (contents.TryGetValueAsDreamList(out var locContentsList)) { foreach (DreamValue content in locContentsList.EnumerateValues()) { if(!includeCenter && content.TryGetValueAsDreamObject(out var dreamObject) && dreamObject == center) continue; - addToList(content); + AddToList(content); } } } + centerLoc.Dispose(); - // if center isn't a turf either, abort here if(centerLocObject is not DreamObjectTurf) { return new(rangeList); } @@ -270,9 +272,9 @@ void addToList(DreamValue value) { // finally, add the surrounding turfs foreach (var turf in DreamProcNativeHelpers.MakeViewSpiral(center, range)) { - addToList(new DreamValue(turf)); + AddToList(new DreamValue(turf)); foreach (DreamValue content in turf.Contents.EnumerateValues()) { - addToList(content); + AddToList(content); } } From e1beeaba67468cbffe3cf19ce9b496c4e276a50f Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Thu, 4 Jun 2026 01:01:41 -0400 Subject: [PATCH 03/15] re-implement MakeViewSpiral I honestly just rewrote the entire thing in JS first --- .../DMProject/Tests/range.dm | 57 ++++++++- .../Procs/Native/DreamProcNativeHelpers.cs | 119 ++++++++++-------- 2 files changed, 121 insertions(+), 55 deletions(-) diff --git a/Content.IntegrationTests/DMProject/Tests/range.dm b/Content.IntegrationTests/DMProject/Tests/range.dm index 7ea0f5dd20..6ed1d6a076 100644 --- a/Content.IntegrationTests/DMProject/Tests/range.dm +++ b/Content.IntegrationTests/DMProject/Tests/range.dm @@ -5,9 +5,9 @@ /obj/contained/one /obj/contained/two -/datum/unit_test/range/proc/run_case(atom/center, list/expected, identifier, isorange) +/datum/unit_test/range/proc/run_case(atom/center, list/expected, identifier, isorange, view_size = 1) var/error_index = 0 - var/list/result = isorange ? orange(center, 1) : range(center, 1) + var/list/result = isorange ? orange(center, 1) : range(center, view_size) try if(result.len != expected.len) error_index = expected.len @@ -117,6 +117,59 @@ run_case(contained, contained_range_case, nameof(contained_range_case), FALSE) run_case(contained, contained_orange_case, nameof(contained_orange_case), TRUE) + // unusual cases + + var/list/turf_range_2x2 = list( + center, + outer_area, + container, + LOC(2, 2), + LOC(2, 3), + LOC(3, 2), + ) + + var/list/turf_orange_2x2 = list( + LOC(2, 2), + outer_area, + LOC(2, 3), + LOC(3, 2), + ) + + var/list/turf_range_1x3 = list( + center, + outer_area, + container, + LOC(3, 2), + LOC(3, 4), + ) + + var/list/turf_orange_1x3 = list( + LOC(3, 2), + outer_area, + LOC(3, 4) + ) + + var/list/turf_range_3x1 = list( + center, + outer_area, + container, + LOC(2, 3), + LOC(4, 3), + ) + + var/list/turf_orange_3x1 = list( + LOC(2, 3), + outer_area, + LOC(4, 3), + ) + + run_case(center, turf_range_2x2, nameof(turf_range_2x2), FALSE, "2x2") + run_case(center, turf_orange_2x2, nameof(turf_orange_2x2), TRUE, "2x2") + run_case(center, turf_range_1x3, nameof(turf_range_1x3), FALSE, "1x3") + run_case(center, turf_orange_1x3, nameof(turf_orange_1x3), TRUE, "1x3") + run_case(center, turf_range_3x1, nameof(turf_range_3x1), FALSE, "3x1") + run_case(center, turf_orange_3x1, nameof(turf_orange_3x1), TRUE, "3x1") + // FIXME: these pass in BYOND, but the way we iterate over area turfs diverges // var/list/area_range_case = list(outer_area) + outer_area.contents // var/list/area_orange_case = area_range_case.Copy() diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index 05084fcffb..a1f5e6e760 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -37,51 +37,55 @@ internal static partial class DreamProcNativeHelpers { /// This helper attempts to mimic this iteration pattern. /// NOTE: THIS DOES NOT ITERATE OVER THE CENTRE. THAT'S THE PROC'S JOB. /// - /// - /// This proc tries to handle the rectangular case, but I am not totally confident that it's up to parity. - /// - /// Turfs, in the correct, parity order for the above procs. + /// Turfs, in the above order for the above procs. public static IEnumerable MakeViewSpiral(DreamObjectAtom center, ViewRange distance) { var mapMgr = IoCManager.Resolve(); var atomMgr = IoCManager.Resolve(); var centerPos = atomMgr.GetAtomPosition(center); - int widthRange = (distance.Width - 1) >> 1; // TODO: Make rectangles work. - int heightRange = (distance.Height - 1) >> 1; - int donutCount = Math.Max(widthRange, heightRange); - for(int d = 1; d <= donutCount; d++) { // for each donut - int sideLength = d + d + 1; - //The left column - { - int leftColumnX = centerPos.X - d; - int startingLeftColumnY = centerPos.Y - d; - for (int i = 0; i < sideLength; ++i) { - if (mapMgr.TryGetTurfAt((leftColumnX, startingLeftColumnY + i), centerPos.Z, out var turf)) { + int bottomRange = distance.Height / 2; + int topRange = (distance.Height - 1) / 2; + int leftRange = distance.Width / 2; + int rightRange = (distance.Width - 1) / 2; + + int donutCount = Math.Max(bottomRange, leftRange); + for(int donut = 1; donut <= donutCount; donut++) { + // left column + if(donut <= leftRange) { + int posX = centerPos.X - donut; + int startingPosY = centerPos.Y - Math.Min(donut, bottomRange); + int endingPosY = centerPos.Y + Math.Min(donut, topRange); + for(int posY = startingPosY; posY <= endingPosY; posY++) { + if(mapMgr.TryGetTurfAt((posX, posY), centerPos.Z, out var turf)) { yield return turf; } } } - //The criss-cross-apple-sauce - { - int crissCrossLength = sideLength - 2; - int startingCrossX = centerPos.X - d + 1; - for(int i = 0; i < crissCrossLength; ++i) { - //the criss - if (mapMgr.TryGetTurfAt((startingCrossX+i, centerPos.Y - d), centerPos.Z, out var crissTurf)) { + + // the criss-cross-apple-sauce + if(donut <= bottomRange) { + int startingPosX = centerPos.X - donut + 1; + int endingPosX = centerPos.X + donut - 1; + for(int posX = startingPosX; posX <= endingPosX; posX++) { + // the criss + if(mapMgr.TryGetTurfAt((posX, centerPos.Y - donut), centerPos.Z, out var crissTurf)) { yield return crissTurf; } - //the cross - if (mapMgr.TryGetTurfAt((startingCrossX + i, centerPos.Y + d), centerPos.Z, out var crossTurf)) { + if(donut > topRange) continue; + // the cross + if(mapMgr.TryGetTurfAt((posX, centerPos.Y + donut), centerPos.Z, out var crossTurf)) { yield return crossTurf; } } } - //The right column - { - int rightColumnX = centerPos.X + d; - int startingRightColumnY = centerPos.Y - d; - for (int i = 0; i < sideLength; ++i) { - if (mapMgr.TryGetTurfAt((rightColumnX, startingRightColumnY + i), centerPos.Z, out var turf)) { + + // right column + if(donut <= rightRange) { + int posX = centerPos.X + donut; // this is the only difference + int startingPosY = centerPos.Y - Math.Min(donut, bottomRange); + int endingPosY = centerPos.Y + Math.Min(donut, topRange); + for(int posY = startingPosY; posY <= endingPosY; posY++) { + if(mapMgr.TryGetTurfAt((posX, posY), centerPos.Z, out var turf)) { yield return turf; } } @@ -90,7 +94,7 @@ public static IEnumerable MakeViewSpiral(DreamObjectAtom center } /// - /// A variation of + /// A variation of /// that works on the view algorithm's collection of tiles /// public static IEnumerable MakeViewSpiral(ViewAlgorithm.Tile?[,] tiles, bool includeCenter) { @@ -101,35 +105,44 @@ public static IEnumerable MakeViewSpiral(DreamObjectAtom center if (includeCenter) yield return tiles[centerPos.X, centerPos.Y]; - int widthRange = (width - 1) >> 1; // TODO: Make rectangles work. - int heightRange = (height - 1) >> 1; - int donutCount = Math.Max(widthRange, heightRange); - for(int d = 1; d <= donutCount; d++) { // for each donut - int sideLength = d + d + 1; + int bottomRange = height / 2; + int topRange = (height - 1) / 2; + int leftRange = width / 2; + int rightRange = (width - 1) / 2; + int donutCount = Math.Max(bottomRange, leftRange); + for(int donut = 1; donut <= donutCount; donut++) { // for each donut //The left column - int leftColumnX = centerPos.X - d; - int startingLeftColumnY = centerPos.Y - d; - for (int i = 0; i < sideLength; ++i) { - yield return tiles[leftColumnX, startingLeftColumnY + i]; + if(donut <= leftRange) { + int posX = centerPos.X - donut; + int startingPosY = centerPos.Y - Math.Min(donut, bottomRange); + int endingPosY = centerPos.Y + Math.Min(donut, topRange); + for(int posY = startingPosY; posY <= endingPosY; posY++) { + yield return tiles[posX, posY]; + } } //The criss-cross-apple-sauce - int crissCrossLength = sideLength - 2; - int startingCrossX = centerPos.X - d + 1; - for(int i = 0; i < crissCrossLength; ++i) { - //the criss - yield return tiles[startingCrossX + i, centerPos.Y - d]; - - //the cross - yield return tiles[startingCrossX + i, centerPos.Y + d]; + if(donut <= bottomRange) { + int startingPosX = centerPos.X - donut + 1; + int endingPosX = centerPos.X + donut - 1; + for(int posX = startingPosX; posX <= endingPosX; posX++) { + //the criss + yield return tiles[posX, centerPos.Y - donut]; + if(donut > topRange) continue; + //the cross + yield return tiles[posX, centerPos.Y + donut]; + } } - //The right column - int rightColumnX = centerPos.X + d; - int startingRightColumnY = centerPos.Y - d; - for (int i = 0; i < sideLength; ++i) { - yield return tiles[rightColumnX, startingRightColumnY + i]; + //The left column + if(donut <= rightRange) { + int posX = centerPos.X + donut; // this is the only difference + int startingPosY = centerPos.Y - Math.Min(donut, bottomRange); + int endingPosY = centerPos.Y + Math.Min(donut, topRange); + for(int posY = startingPosY; posY <= endingPosY; posY++) { + yield return tiles[posX, posY]; + } } } } From 4b43a621f266f84e36c725fdffdf603459c5b57d Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Thu, 4 Jun 2026 01:13:56 -0400 Subject: [PATCH 04/15] oops --- Content.IntegrationTests/DMProject/Tests/range.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.IntegrationTests/DMProject/Tests/range.dm b/Content.IntegrationTests/DMProject/Tests/range.dm index 6ed1d6a076..8817d2500b 100644 --- a/Content.IntegrationTests/DMProject/Tests/range.dm +++ b/Content.IntegrationTests/DMProject/Tests/range.dm @@ -7,7 +7,7 @@ /datum/unit_test/range/proc/run_case(atom/center, list/expected, identifier, isorange, view_size = 1) var/error_index = 0 - var/list/result = isorange ? orange(center, 1) : range(center, view_size) + var/list/result = isorange ? orange(center, view_size) : range(center, view_size) try if(result.len != expected.len) error_index = expected.len From a0fd25f25a55954d0fcf1556ff9c67f9a02fec9a Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Thu, 4 Jun 2026 01:14:43 -0400 Subject: [PATCH 05/15] code style --- OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index a1f5e6e760..4c45bcea6e 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -129,6 +129,7 @@ public static IEnumerable MakeViewSpiral(DreamObjectAtom center for(int posX = startingPosX; posX <= endingPosX; posX++) { //the criss yield return tiles[posX, centerPos.Y - donut]; + if(donut > topRange) continue; //the cross yield return tiles[posX, centerPos.Y + donut]; From afd6a0a7805f91ce5e057f72fbf096b54f8f03e5 Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Thu, 4 Jun 2026 01:22:03 -0400 Subject: [PATCH 06/15] wrong one --- OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index 4c45bcea6e..afac68a7cd 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -71,6 +71,7 @@ public static IEnumerable MakeViewSpiral(DreamObjectAtom center if(mapMgr.TryGetTurfAt((posX, centerPos.Y - donut), centerPos.Z, out var crissTurf)) { yield return crissTurf; } + if(donut > topRange) continue; // the cross if(mapMgr.TryGetTurfAt((posX, centerPos.Y + donut), centerPos.Z, out var crossTurf)) { @@ -129,7 +130,6 @@ public static IEnumerable MakeViewSpiral(DreamObjectAtom center for(int posX = startingPosX; posX <= endingPosX; posX++) { //the criss yield return tiles[posX, centerPos.Y - donut]; - if(donut > topRange) continue; //the cross yield return tiles[posX, centerPos.Y + donut]; From 88298cf3cd1a7320bc9060cc2b8fdb58455d558f Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:21:58 -0400 Subject: [PATCH 07/15] Separates spiral algorithm into its own method It'll be easier to maintain this way --- .../Procs/Native/DreamProcNativeHelpers.cs | 134 +++++++----------- 1 file changed, 48 insertions(+), 86 deletions(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index afac68a7cd..26e9256a1e 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -25,79 +25,78 @@ internal static partial class DreamProcNativeHelpers { ]; /// - /// This is a helper proc for oview, view, orange, and range to do their strange iteration with.
- /// BYOND has a very strange, kinda-spiralling iteration pattern for the above procs,
- /// pretty much like this:
- /// 13 15 17 19 24
- /// 12 03 05 08 23
- /// 11 02 00 07 22
- /// 10 01 04 06 21
- /// 09 14 16 18 20
- ///
- /// This helper attempts to mimic this iteration pattern. - /// NOTE: THIS DOES NOT ITERATE OVER THE CENTRE. THAT'S THE PROC'S JOB. + /// This is a helper method for oview, view, orange, and range to do their strange iteration with.
+ /// BYOND has a very strange, kinda-spiralling iteration pattern for these procs, + /// which looks like this in a 3x3 case: + /// + /// 13 15 17 19 24 + /// 12 03 05 08 23 + /// 11 02 00 07 22 + /// 10 01 04 06 21 + /// 09 14 16 18 20 + /// ///
- /// Turfs, in the above order for the above procs. - public static IEnumerable MakeViewSpiral(DreamObjectAtom center, ViewRange distance) { - var mapMgr = IoCManager.Resolve(); - var atomMgr = IoCManager.Resolve(); - var centerPos = atomMgr.GetAtomPosition(center); - - int bottomRange = distance.Height / 2; - int topRange = (distance.Height - 1) / 2; - int leftRange = distance.Width / 2; - int rightRange = (distance.Width - 1) / 2; + /// + /// Does not iterate over the center. + /// + /// Tuple representing X/Y coordinates. + private static IEnumerable<(int X, int Y)> DantomSpiral((int X, int Y) center, (int Width, int Height) range) { + int bottomRange = range.Height / 2; + int topRange = (range.Height - 1) / 2; + int leftRange = range.Width / 2; + int rightRange = (range.Width - 1) / 2; int donutCount = Math.Max(bottomRange, leftRange); for(int donut = 1; donut <= donutCount; donut++) { // left column if(donut <= leftRange) { - int posX = centerPos.X - donut; - int startingPosY = centerPos.Y - Math.Min(donut, bottomRange); - int endingPosY = centerPos.Y + Math.Min(donut, topRange); + int posX = center.X - donut; + int startingPosY = center.Y - Math.Min(donut, bottomRange); + int endingPosY = center.Y + Math.Min(donut, topRange); for(int posY = startingPosY; posY <= endingPosY; posY++) { - if(mapMgr.TryGetTurfAt((posX, posY), centerPos.Z, out var turf)) { - yield return turf; - } + yield return (posX, posY); } } // the criss-cross-apple-sauce if(donut <= bottomRange) { - int startingPosX = centerPos.X - donut + 1; - int endingPosX = centerPos.X + donut - 1; + int startingPosX = center.X - donut + 1; + int endingPosX = center.X + donut - 1; for(int posX = startingPosX; posX <= endingPosX; posX++) { - // the criss - if(mapMgr.TryGetTurfAt((posX, centerPos.Y - donut), centerPos.Z, out var crissTurf)) { - yield return crissTurf; - } + yield return (posX, center.Y - donut); // the criss if(donut > topRange) continue; - // the cross - if(mapMgr.TryGetTurfAt((posX, centerPos.Y + donut), centerPos.Z, out var crossTurf)) { - yield return crossTurf; - } + + yield return (posX, center.Y + donut); // the cross } } // right column if(donut <= rightRange) { - int posX = centerPos.X + donut; // this is the only difference - int startingPosY = centerPos.Y - Math.Min(donut, bottomRange); - int endingPosY = centerPos.Y + Math.Min(donut, topRange); + int posX = center.X + donut; // this is the only difference + int startingPosY = center.Y - Math.Min(donut, bottomRange); + int endingPosY = center.Y + Math.Min(donut, topRange); for(int posY = startingPosY; posY <= endingPosY; posY++) { - if(mapMgr.TryGetTurfAt((posX, posY), centerPos.Z, out var turf)) { - yield return turf; - } + yield return (posX, posY); } } } } - /// - /// A variation of - /// that works on the view algorithm's collection of tiles - /// + /// + public static IEnumerable MakeViewSpiral(DreamObjectAtom center, ViewRange distance) { + var mapMgr = IoCManager.Resolve(); + var atomMgr = IoCManager.Resolve(); + var centerPos = atomMgr.GetAtomPosition(center); + + foreach((int posX, int posY) in DantomSpiral((centerPos.X, centerPos.Y), (distance.Width, distance.Height))) { + if(mapMgr.TryGetTurfAt((posX, posY), centerPos.Z, out var turf)) { + yield return turf; + } + } + } + + /// public static IEnumerable MakeViewSpiral(ViewAlgorithm.Tile?[,] tiles, bool includeCenter) { var width = tiles.GetLength(0); var height = tiles.GetLength(1); @@ -106,45 +105,8 @@ public static IEnumerable MakeViewSpiral(DreamObjectAtom center if (includeCenter) yield return tiles[centerPos.X, centerPos.Y]; - int bottomRange = height / 2; - int topRange = (height - 1) / 2; - int leftRange = width / 2; - int rightRange = (width - 1) / 2; - int donutCount = Math.Max(bottomRange, leftRange); - for(int donut = 1; donut <= donutCount; donut++) { // for each donut - - //The left column - if(donut <= leftRange) { - int posX = centerPos.X - donut; - int startingPosY = centerPos.Y - Math.Min(donut, bottomRange); - int endingPosY = centerPos.Y + Math.Min(donut, topRange); - for(int posY = startingPosY; posY <= endingPosY; posY++) { - yield return tiles[posX, posY]; - } - } - - //The criss-cross-apple-sauce - if(donut <= bottomRange) { - int startingPosX = centerPos.X - donut + 1; - int endingPosX = centerPos.X + donut - 1; - for(int posX = startingPosX; posX <= endingPosX; posX++) { - //the criss - yield return tiles[posX, centerPos.Y - donut]; - if(donut > topRange) continue; - //the cross - yield return tiles[posX, centerPos.Y + donut]; - } - } - - //The left column - if(donut <= rightRange) { - int posX = centerPos.X + donut; // this is the only difference - int startingPosY = centerPos.Y - Math.Min(donut, bottomRange); - int endingPosY = centerPos.Y + Math.Min(donut, topRange); - for(int posY = startingPosY; posY <= endingPosY; posY++) { - yield return tiles[posX, posY]; - } - } + foreach((int posX, int posY) in DantomSpiral((centerPos.X, centerPos.Y), (width, height))) { + yield return tiles[posX, posY]; } } From 1748b67ed0a9e77749eca6b0d2006a4e8cfeb803 Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:27:44 -0400 Subject: [PATCH 08/15] Remove some redundant math --- .../Procs/Native/DreamProcNativeHelpers.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index 26e9256a1e..16f9b3a7d2 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -48,12 +48,13 @@ internal static partial class DreamProcNativeHelpers { int donutCount = Math.Max(bottomRange, leftRange); for(int donut = 1; donut <= donutCount; donut++) { + int columnBottom = center.Y - Math.Min(donut, bottomRange); + int columnTop = center.Y + Math.Min(donut, topRange); + // left column if(donut <= leftRange) { int posX = center.X - donut; - int startingPosY = center.Y - Math.Min(donut, bottomRange); - int endingPosY = center.Y + Math.Min(donut, topRange); - for(int posY = startingPosY; posY <= endingPosY; posY++) { + for(int posY = columnBottom; posY <= columnTop; posY++) { yield return (posX, posY); } } @@ -74,9 +75,7 @@ internal static partial class DreamProcNativeHelpers { // right column if(donut <= rightRange) { int posX = center.X + donut; // this is the only difference - int startingPosY = center.Y - Math.Min(donut, bottomRange); - int endingPosY = center.Y + Math.Min(donut, topRange); - for(int posY = startingPosY; posY <= endingPosY; posY++) { + for(int posY = columnBottom; posY <= columnTop; posY++) { yield return (posX, posY); } } From 27818a4507fed53446b8059710fbb7ee2e4bbe80 Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:42:26 -0400 Subject: [PATCH 09/15] This was annoying me --- Content.IntegrationTests/DMProject/Tests/range.dm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Content.IntegrationTests/DMProject/Tests/range.dm b/Content.IntegrationTests/DMProject/Tests/range.dm index 8817d2500b..fd65f436a1 100644 --- a/Content.IntegrationTests/DMProject/Tests/range.dm +++ b/Content.IntegrationTests/DMProject/Tests/range.dm @@ -1,4 +1,4 @@ -// tests all of range's possible cases +// Tests the implementation of range() and orange() #define LOC(x, y) locate(x, y, 3) @@ -31,7 +31,6 @@ CRASH(error_output.Join("\n")) -// Tests the implementation of range() and orange() /datum/unit_test/range/RunTest() world.maxx = world.maxy = 5 From 9a1a783ab3a5263e9c8079f254df465f2017c08e Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Sun, 7 Jun 2026 14:42:57 -0400 Subject: [PATCH 10/15] This too --- Content.IntegrationTests/DMProject/Tests/range.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/Content.IntegrationTests/DMProject/Tests/range.dm b/Content.IntegrationTests/DMProject/Tests/range.dm index fd65f436a1..7718f5b9e9 100644 --- a/Content.IntegrationTests/DMProject/Tests/range.dm +++ b/Content.IntegrationTests/DMProject/Tests/range.dm @@ -28,7 +28,6 @@ var/atom/A = result[i] error_output += ("\t([A.x], [A.y]) [A.type] [i == error_index ? "<-- here" : null]") - CRASH(error_output.Join("\n")) /datum/unit_test/range/RunTest() From b6d67eafd1518c9a9ab1024aafba3e3727b91907 Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:03:25 -0400 Subject: [PATCH 11/15] fix incorrect criss cross iteration --- .../DMProject/Tests/range.dm | 26 ++++++++++++------- .../Procs/Native/DreamProcNativeHelpers.cs | 4 +-- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Content.IntegrationTests/DMProject/Tests/range.dm b/Content.IntegrationTests/DMProject/Tests/range.dm index 7718f5b9e9..9b8c35918a 100644 --- a/Content.IntegrationTests/DMProject/Tests/range.dm +++ b/Content.IntegrationTests/DMProject/Tests/range.dm @@ -133,40 +133,48 @@ LOC(3, 2), ) - var/list/turf_range_1x3 = list( + var/list/turf_range_1x5 = list( center, outer_area, container, LOC(3, 2), LOC(3, 4), + LOC(3, 1), + LOC(3, 5), ) - var/list/turf_orange_1x3 = list( + var/list/turf_orange_1x5 = list( LOC(3, 2), outer_area, - LOC(3, 4) + LOC(3, 4), + LOC(3, 1), + LOC(3, 5), ) - var/list/turf_range_3x1 = list( + var/list/turf_range_5x1 = list( center, outer_area, container, LOC(2, 3), LOC(4, 3), + LOC(1, 3), + LOC(5, 3), ) - var/list/turf_orange_3x1 = list( + var/list/turf_orange_5x1 = list( LOC(2, 3), outer_area, LOC(4, 3), + LOC(1, 3), + LOC(5, 3), ) run_case(center, turf_range_2x2, nameof(turf_range_2x2), FALSE, "2x2") run_case(center, turf_orange_2x2, nameof(turf_orange_2x2), TRUE, "2x2") - run_case(center, turf_range_1x3, nameof(turf_range_1x3), FALSE, "1x3") - run_case(center, turf_orange_1x3, nameof(turf_orange_1x3), TRUE, "1x3") - run_case(center, turf_range_3x1, nameof(turf_range_3x1), FALSE, "3x1") - run_case(center, turf_orange_3x1, nameof(turf_orange_3x1), TRUE, "3x1") + run_case(center, turf_range_1x5, nameof(turf_range_1x5), FALSE, "1x5") + run_case(center, turf_orange_1x5, nameof(turf_orange_1x5), TRUE, "1x5") + run_case(center, turf_range_5x1, nameof(turf_range_5x1), FALSE, "5x1") + run_case(center, turf_orange_5x1, nameof(turf_orange_5x1), TRUE, "5x1") // FIXME: these pass in BYOND, but the way we iterate over area turfs diverges // var/list/area_range_case = list(outer_area) + outer_area.contents diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index 16f9b3a7d2..be1dc661d1 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -61,8 +61,8 @@ internal static partial class DreamProcNativeHelpers { // the criss-cross-apple-sauce if(donut <= bottomRange) { - int startingPosX = center.X - donut + 1; - int endingPosX = center.X + donut - 1; + int startingPosX = center.X - Math.Min(donut, leftRange + 1) + 1; + int endingPosX = center.X + Math.Min(donut, rightRange + 1) - 1; for(int posX = startingPosX; posX <= endingPosX; posX++) { yield return (posX, center.Y - donut); // the criss From 4086931f3a30335ee8f84597ab2116b8c143d20c Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Wed, 17 Jun 2026 02:16:26 -0400 Subject: [PATCH 12/15] Range does not include 101 invisibility atoms --- .../Procs/Native/DreamProcNativeHelpers.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index be1dc661d1..0ace7cd170 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -183,6 +183,11 @@ public static DreamValue HandleRange(NativeProc.Bundle bundle, DreamObject? usr, DreamList rangeList = bundle.ObjectTree.CreateList(range.Height * range.Width); void AddToList(DreamValue value) { + if(value.TryGetValueAsDreamObject(out var atomValue)) { + MutableAppearance appearance = atomValue.GetVariable("appearance").MustGetValueAsAppearance(); + if(appearance.Invisibility >= 101) + return; + } rangeList.AddValue(value); if(value.TryGetValueAsDreamObject(out var turfValue) && !seenAreas.Contains(turfValue.Cell.Area)) { var area = turfValue.Cell.Area; @@ -192,12 +197,12 @@ void AddToList(DreamValue value) { } if(center is DreamObjectArea areaCenter) { // yeah you can do this - // setting rangeList directly cause we'll never hit the area case - rangeList.AddValue(new(center)); + rangeList.AddValue(new(areaCenter)); // dodges the invisibility check + seenAreas.Add(areaCenter); foreach(var turf in areaCenter.Turfs) { - rangeList.AddValue(new(turf)); + AddToList(new(turf)); foreach(var content in turf.Contents.EnumerateValues()) { - rangeList.AddValue(content); + AddToList(content); } } From 3feb2b3d72f40b296db6ee0b3084fe6d77a38612 Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Wed, 17 Jun 2026 02:25:16 -0400 Subject: [PATCH 13/15] dwqaiophju --- OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index 0ace7cd170..770a744194 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -184,10 +184,11 @@ public static DreamValue HandleRange(NativeProc.Bundle bundle, DreamObject? usr, void AddToList(DreamValue value) { if(value.TryGetValueAsDreamObject(out var atomValue)) { - MutableAppearance appearance = atomValue.GetVariable("appearance").MustGetValueAsAppearance(); + using MutableAppearance appearance = atomValue.GetVariable("appearance").MustGetValueAsAppearance(); if(appearance.Invisibility >= 101) return; } + rangeList.AddValue(value); if(value.TryGetValueAsDreamObject(out var turfValue) && !seenAreas.Contains(turfValue.Cell.Area)) { var area = turfValue.Cell.Area; From 4c60642de013af9e19e17608ce229c5d2a301da2 Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:17:31 -0400 Subject: [PATCH 14/15] Speed, please, I need this! --- OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs index 770a744194..e57879f54f 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs @@ -184,7 +184,8 @@ public static DreamValue HandleRange(NativeProc.Bundle bundle, DreamObject? usr, void AddToList(DreamValue value) { if(value.TryGetValueAsDreamObject(out var atomValue)) { - using MutableAppearance appearance = atomValue.GetVariable("appearance").MustGetValueAsAppearance(); + using var appearanceValue = atomValue.GetVariable("appearance"); + using var appearance = appearanceValue.MustGetValueAsAppearance(); if(appearance.Invisibility >= 101) return; } From 4e2864673218a18150b0b20be8f575e91227cb7d Mon Sep 17 00:00:00 2001 From: tontyGH <39193182+tontyGH@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:38:25 -0400 Subject: [PATCH 15/15] empty commit