Большие координаты: точность и перенос начала мира
Что ограничивает float, как проверить установленный SDK и сохранить логическую позицию при переносе.
Сначала проверьте масштаб
Речь о величине координат и требуемой точности, а не только о площади карты или числе объектов. При больших значениях соседние float отстоят дальше друг от друга; маленькое перемещение может исчезнуть при округлении. В проверенном SDK Vector3.X имеет тип Single, размер Vector3 — 12 байт. Тип можно вывести PhysicsBackendProbe.
| Порядок координаты | Шаг между соседними float около этой величины |
|---|---|
| 10 000 | 0.0009765625 |
| 100 000 | 0.0078125 |
| 1 000 000 | 0.0625 |
| 10 000 000 | 1 |
| 100 000 000 | 8 |
При обычных метровых единицах шаг 8 — восемь метров. Это оценка представления числа, не готовый предел размера игры. В тесте Vector3 с X = 100 000 000 теряет прибавку 0.1. Увеличение масштаба модели или дальности камеры не возвращает потерянные разряды. Сначала держите разумные размеры объектов и проверяйте минимально нужную точность контактов/рисунка.
Разделить логический мир и локальную симуляцию
Храните глобальную позицию как логические координаты с нужной точностью — например double или целые индексы участков плюс локальное смещение. Физику/рендер активной области держите около начала координат. При переносе начала сохраните логический offset и согласованно сдвиньте активные сущности. Сетевые сообщения, сохранения, навигация и фоновые задания должны знать, в какой системе находится каждая точка.
Перенос не загружает участки мира и не удаляет далёкие. Потоковая загрузка, владение участками и политика активации — отдельные системы проекта. Очень большая плоскость сама по себе не даёт точную бесконечную симуляцию.
Рабочий пример переноса
Прикрепите OriginShiftLab3D.cs к корню Entity3D отдельного C#-уровня, соберите и запустите F6. Физика 60 шагов/с, гравитация 9.8 вниз; корень статичный с единичным масштабом. Персонаж автоматически идёт по X. При расстоянии 32 от нуля код сдвигает прямых пространственных детей WorldRoot, сохраняет offset в double и пишет OriginShift в Output. Локальная позиция возвращается близко к нулю, логическая продолжает расти.
OriginShiftLab3D.cs
using Reckfell;
public partial class OriginShiftLab3D : Entity3D
{
public Entity3D WorldRoot { get; private set; } = null!;
public CharacterBody3D Player { get; private set; } = null!;
public Camera3D Camera { get; private set; } = null!;
public Vector2 MoveInput { get; set; } = Vector2.Right;
public float Speed { get; set; } = 12;
public float Threshold { get; set; } = 32;
public double OriginX { get; private set; }
public double OriginY { get; private set; }
public double OriginZ { get; private set; }
public int Shifts { get; private set; }
public (double X,double Y,double Z) LogicalPosition => (OriginX + Player.GlobalPosition.X,OriginY + Player.GlobalPosition.Y,OriginZ + Player.GlobalPosition.Z);
public override void _Ready()
{
WorldRoot = new Entity3D { Name = "World" }; AddChild(WorldRoot);
var floor = new StaticBody3D { Position = new Vector3(0,-.25f,0), CollisionLayer = 1, CollisionMask = 0 };
floor.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = new Vector3(512,.5f,32) } });
floor.AddChild(new MeshInstance3D { Mesh = new BoxMesh { Size = new Vector3(512,.5f,32) }, MaterialOverride = Material(new Color(.55f,.55f,.6f)) }); WorldRoot.AddChild(floor);
Player = new CharacterBody3D { Position = new Vector3(0,.9f,0), CollisionLayer = 2, CollisionMask = 1, FloorSnapLength = .2f };
Player.AddChild(new CollisionShape3D { Shape = new CapsuleShape3D { Radius = .35f, Height = 1.8f } });
Player.AddChild(new MeshInstance3D { Mesh = new CapsuleMesh { Radius = .35f, Height = 1.8f }, MaterialOverride = Material(new Color(.25f,.45f,1)) }); WorldRoot.AddChild(Player);
Camera = new Camera3D { Current = true }; WorldRoot.AddChild(Camera); FollowCamera(); ResetPhysicsInterpolation();
}
private static StandardMaterial3D Material(Color color) => new() { AlbedoColor = color, ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded };
private void FollowCamera() { Camera.GlobalPosition = Player.GlobalPosition + new Vector3(7,5,8); Camera.LookAt(Player.GlobalPosition); }
public override void _PhysicsProcess(double delta)
{
Vector2 input = MoveInput.IsFinite() ? MoveInput.LimitLength() : Vector2.Zero;
Vector3 velocity = Player.Velocity;
velocity.X = input.X * Speed; velocity.Z = input.Y * Speed;
if (!Player.IsOnFloor()) velocity += Player.GetGravity() * (float)delta;
Player.Velocity = velocity; Player.MoveAndSlide(); FollowCamera(); RebaseIfNeeded();
}
public bool RebaseIfNeeded()
{
if (!float.IsFinite(Threshold) || Threshold <= 0) return false;
Vector3 shift = Player.GlobalPosition;
if (!shift.IsFinite() || shift.Length() < Threshold) return false;
foreach (Entity child in WorldRoot.GetChildren())
if (child is Entity3D spatial) { spatial.GlobalPosition -= shift; spatial.ResetPhysicsInterpolation(); }
OriginX += shift.X; OriginY += shift.Y; OriginZ += shift.Z; Shifts++;
ReckfellApi.Print($"OriginShift {Shifts}: ({OriginX:F2}, {OriginY:F2}, {OriginZ:F2})"); return true;
}
}Это ограниченный уровень с полом длиной 512, без загрузки новых участков. Камера, StaticBody и CharacterBody — прямые дети статичного WorldRoot. Сам WorldRoot не накапливает огромный transform. Вложенные обычные сущности следуют за своим пространственным родителем; отдельные TopLevel-ветви, другие viewport/world, сохранённые мировые точки и внешние системы этим циклом автоматически не исправляются.
Для RigidBody, физических костей, SoftBody, частиц, мировых шейдерных координат, trail и сетевых буферов нужны собственные согласованные действия. Не переносите весь произвольный проект таким foreach без аудита систем. Физические перемещения здесь выполняются после MoveAndSlide в том же _PhysicsProcess; после скачка сбрасывается визуальная интерполяция. Она не заменяет изменение логической системы координат.
Двойная точность сборки
В исходной системе сборки есть precision=double; управляемые математические типы также зависят от ReckfellFloat64. Нельзя переключить одну C#-константу или взять SDK другой точности к имеющемуся native runtime. Согласованно пересобираются runtime, управляемые bindings/SDK, расширения и шаблоны экспорта; такой набор нужно отдельно проверить и установить. Этот материал проверен на текущей single-сборке, double-сборка здесь не выполнялась.
Double не делает все GPU-вычисления, материалы, частицы или внешние библиотеки автоматически 64-битными. Сохраняйте понятные единицы и проверяйте свой рендер/экспорт. Не меняйте физическую частоту или интерполяцию ради исправления арифметической потери точности: они решают другие задачи.
Проверить свой перенос
Сравнивайте логическую позицию до/после сдвига, относительные расстояния, контакт с полом, камеры и сохранённые пути/цели. Тест выполняет шесть переносов и проверяет сохранение позиции и контакта на обоих backend. Он не подтверждает произвольный стриминг, все системы шейдеров/анимации или сетевую согласованность. Начните с минимального уровня и добавляйте системы по одной.