Skip to content

Latitude And Longitude

Where AstraTerra's sky gets its observer position, and how much of that Vintage Story actually provides. The short version: latitude is real and comes from the game; longitude does not exist in Vintage Story and AstraTerra invents it.

All findings below are from the shipped assemblies (VintagestoryLib, VSEssentials, VSSurvivalMod) for the version this mod targets.

Latitude, as Vintage Story defines it

IGameCalendar.OnGetLatitude is a settable delegate taking a world Z and returning -1 to +1 (south pole to north pole). Two things assign it:

Assigned by Value
GameCalendar constructor (posZ) => 0.5 — a constant
VSEssentials, at world load The real mapping below

The real mapping

Driven by two world-configuration values:

Config key Default Meaning
worldClimate realistic Whether latitude varies at all
polarEquatorDistance 50000 Blocks from the equator to a pole

On a realistic world, latitude is a triangle wave in Z:

// VSEssentials, decompiled
double h = polarEquatorDistance / climateMapScale / climateMapSubScale;   // 32 and 16
double z = posZ / climateMapScale / climateMapSubScale + ZOffset;
return 2.0 / h * (h - Math.Abs(Math.Abs(z / 2.0 - h) % (2.0 * h) - h)) - 1.0;

Which works out to:

Quantity Default value
Equator to pole 50,000 blocks (polarEquatorDistance)
Full cycle, equator → N pole → equator → S pole → equator 200,000 blocks (4 x polarEquatorDistance)
Degrees of latitude per block ~0.0018

Latitude repeats. Travel far enough north and you arrive back at the equator, then at a south pole. The world is a set of stacked climate bands, not a globe.

ZOffset means the equator is not at z = 0

The mapping includes a world-specific ZOffset taken from the climate noise generator, so the equator sits wherever that seed put it. Never assume z = 0 is the equator — always ask OnGetLatitude.

A patchy-climate world has no latitude at all

When worldClimate is anything other than realistic, getLatitude short-circuits:

if (!latdata.isRealisticClimate) return 0.5;

It returns a constant 0.5 — which AstraTerra maps to 45°N — for every position in the world. The sky is then identical everywhere, and the mod's central promise that the stars change as you travel north or south silently does not happen. realistic is the default, so most worlds are fine, but this is worth knowing before debugging a "latitude is stuck" report.

Hemisphere

Separately, OnGetHemisphere / GetHemisphere(BlockPos) returns an EnumHemisphere, assigned by VSSurvivalMod. AstraTerra does not currently use it; latitude sign already carries the same information.

Longitude, which Vintage Story does not have

There is no longitude concept anywhere in the Vintage Story API. No delegate, no calendar property, nothing derived from world X.

The sun looks like it might depend on X — the delegate signature accepts one:

SolarSphericalCoordsDelegate(double posX, double posZ, float yearRel, float dayRel)

It does not. The survival mod's implementation takes posX and never references it:

// VSSurvivalMod, decompiled
public SolarSphericalCoords GetSolarSphericalCoords(double posX, double posZ, float yearRel, float dayRel)
{
    double lat = api.World.Calendar.OnGetLatitude.Invoke(posZ) * Math.PI / 2.0;   // posZ
    float hourAngle = (float)Math.PI * 2f * (dayRel - 0.5f);                      // dayRel
    double dec = -EarthAxialTilt * Math.Cos(2f * Math.PI * (yearRel + 0.0274f));  // yearRel
    ...
}

Latitude from Z, hour angle from the world clock, declination from the year. X is ignored.

The consequence: sunrise happens at the same instant for every player on a server, wherever they stand. The entire world is one time zone. Daylight strength, which is derived from the sun vector, inherits this.

What AstraTerra does with both

AstraTerra Source Agrees with Vintage Story?
MapGameLatitude OnGetLatitude, scaled to degrees Yes — delegates to the game
MapWorldZ / MapRepeatingLatitude Own triangle wave, WorldLatitudeBandSize = 100000 Fallback only, and see below
MapWorldLongitude polarEquatorDistance, centred on mapSizeX * 0.5 Scale only — Vintage Story has no longitude origin

Latitude is handled correctly. MapGameLatitude asks the calendar and multiplies by 90, so it inherits the real mapping, the ZOffset, and the repeating bands for free.

The MapWorldZ fallback only runs when OnGetLatitude is null, which never happens in practice — GameCalendar's constructor always assigns one. Note its WorldLatitudeBandSize of 100,000 is half Vintage Story's actual 200,000-block cycle, so if it ever did run it would disagree.

Longitude is invented, because there is nothing to inherit — but it now uses the same polarEquatorDistance scale as latitude:

// LatitudeMapper.MapWorldLongitude — polarEquatorDistance from world.Config, default 50_000
var polarEquatorDistance = WorldClimateScale.GetPolarEquatorDistance(world);

Longitude now matches Vintage Story's latitude scale

MapWorldLongitude reads polarEquatorDistance from the world's synced configuration (world.Config), falling back to 50,000 blocks when absent. With that fallback value, 90° of longitude and 90° of latitude both cover 50,000 blocks, so eastward travel shifts the sky at roughly 8,300 blocks per hour of rotation instead of the old map-size-derived scale. World presets may configure a different distance, and both axes inherit it together.

The prime meridian remains at mapSizeX * 0.5; only the degrees-per-block scale changed.

The divergence, and how it was resolved

Vintage Story's own sun ignores longitude, so while AstraTerra's star field shifted with X and the sun did not, travelling east or west drifted the stars out of step with the sun that is supposed to anchor them. Three clocks were in play and only two agreed:

Clock Longitude applied before Longitude applied now
Vintage Story's sun and daylight No Yes, while longitudeAwareSun is on
AstraTerra's star field Yes, always Yes, but only when the sun does
AstraTerra's astrolabe clock No Yes, as displayed local time
The moon No Yes where AstraTerra draws it; vanilla's own disc cannot be moved

This was settled on issue #43 in favour of making longitude real: AstraTerra installs its own OnGetSolarSphericalCoords that uses posX, so the sun, daylight and the stars all shift together. The delegate accepts an X coordinate because longitude was meant to matter in the original design of this system and the idea was shelved, not rejected.

One amendment came with the decision: the world's internal clock stays a single universal time, and longitude is applied only to the time a player is shown. Nothing about scheduling, save data or multiplayer sync changes.

This is a large behavioural change, and deliberately so

Day/night timing becomes a function of X for everything downstream of the sun vector — crops, temperature, mob spawning, other mods, and other players on the same server. That cost was weighed and accepted rather than overlooked; it ships behind a config flag so a server owner can keep vanilla's single time zone.

The sky may only use a longitude the sun has

Turning the flag off, or another mod taking the solar delegate back, would otherwise recreate the very divergence this resolved — a star field shifted east of a sun that never moved. Nothing outside LongitudeAwareSunInstaller maps observer longitude for itself: the star field, the instruments, the recorded sightings and the displayed clock all go through ObserverLongitude.ForObserver, which answers zero unless AstraTerra's wrapper is the delegate the calendar is holding right then.

Shipped: #44 (the scale above, a prerequisite — at the old scale longitude would have been too coarse to notice), #122 (the longitude-aware sun) and #123 (displayed local time, and what a recorded sighting's hour means once the hour depends on where you stand). Still open: #124 (longitude by chronometer).

  • Celestial Model — how the sidereal angle these feed into becomes a position in the sky