Kepler. Documentation · Scripts 325 functions available right now Français

Celestials and resources

Your planets and your moons, what they produce, what has been built on them. This is the most used family: almost every script starts by knowing where it is and what it has.

What version 13 changed, and what you do not have to do

In version 13 of the game, several pages changed nature. The readings that used them fail for everyone, and their message does not name the real cause:

  • page=fetchResources and page=fetchTechs now return the whole game page instead of the expected JSON. The reading then blames your celestial id ("invalid planet id") when that id is perfectly good.
  • the auctioneer page no longer carries the variable the grouped reading looked for there, hence "failed to get resources json", which suggests a missing JSON.
  • the address of the production sliders changed case, and the server answers "An error has occured!".

Five functions on this page have been repaired. They first try the normal route, and take a fallback route when it does not answer. This is transparent: you call them as before, they return the same number of values as before. The day the normal route answers again, the fallback will go unused, without a single line for you to change.

FunctionWhat the fallback doesWhat it costs on top
GetResourcesre-reads the resources at the new address1 request
GetResourcesDetailsthe same1 request
GetAllResourcesgoes through the empire page, planets then moons2 requests
GetTechsrebuilds the reading from five readings that do answer5 requests, about 1.5 s against 0.3 s
GetResourceSettingsre-reads the sliders on the lowercase page1 request

These costs come on top of the attempt lost on the normal route, since it is that failure that triggers the fallback.

On a celestial, the same repair

Ninja offers a second form everywhere, on the celestial: resources, err = celestial.GetResources(). It goes through the same repair as the function: same fallback, same cost, same result, and like the function it waits for manual mode to end. Both forms are equivalent.

c = GetCachedCelestial("1:2:3")

res, err = c.GetResources()          // ✓ goes through the fallback
res, err = GetResources(c.GetID())   // ✓ the same reading

This holds for GetResources, GetResourcesDetails and GetTechs on a planet as on a moon, for GetResourceSettings and GetResourcesProductions on a planet, and for Phalanx on a moon. Up to and including version 1.82.322, those methods failed on version 13: if one of your scripts replaced them with the functions, it has nothing to change.

Three details:

  • GetTechs() returns a single structure. On a planet, it is the whole reading of GetTechs2, lifeforms included; on a moon, that of GetTechs.
  • planet.GetResourceSettings() used to fire ten attempts with a growing wait before giving up. Measured on 2 September 2026 on a real account: 4 min 14 s during which the account was held, no worker, no screen, no built-in browser. Like the function, it now gives up retrying: its first failure lands in three hundred milliseconds, and it then reads the right page.
  • planet.GetResourcesProductions() read the sliders through that same route and then computed on sliders at zero. It now reads the right ones, and a reading that fails makes it return an error rather than a wrong figure.

Knowing where you are

FunctionReturns
GetUniverseName()1: a stringThe universe name.
GetServerNumber()1: an integerThe server number: s152-en gives 152.
GetLang()1: a stringThe server language code, fr, en…
IsLoggedIn()1: a booleanSays whether the session is open.
Printf("%s s%d-%s", GetUniverseName(), GetServerNumber(), GetLang())

With no session, those four return the zero value: empty string, zero, false. They are the only ones on the page that write nothing to the log in that case, because that zero value already says all there is to say.

GetLang and GetServerNumber are specific to Kepler. Ninja knows them, but as methods on its internal IVMBot object, not as functions offered to your scripts.

Knowing who you are

FunctionReturns
CharacterClass()1: an integerThe class: NO_CLASS, COLLECTOR, GENERAL or DISCOVERER.
IsCollector()1: a booleanShortcut on the class.
IsGeneral()1: a booleanThe same.
IsDiscoverer()1: a booleanThe same.
HasCommander()1: a booleanIs the officer hired.
HasAdmiral()1: a booleanThe same.
HasEngineer()1: a booleanThe same.
HasGeologist()1: a booleanThe same.
HasTechnocrat()1: a booleanThe same.
IsVacationModeEnabled()1: a booleanIs the account in vacation mode.
IsPioneers()1: a booleanDoes the account use the "pioneers" lobby.

These eleven change the numbers of everything else. A Collector carries more, a General flies faster, a Geologist produces a tenth more, an Admiral adds fleet slots and expedition slots, a Technocrat cuts a quarter off every research. A script computing a load, a flight time or a build order without reading them is wrong by exactly that much, and nothing in its output will say so.

if IsCollector() {
	Print("higher cargo capacity, faster transporters")
}
if !HasAdmiral() {
	Print("fewer fleet slots than you think")
}
if IsVacationModeEnabled() {
	LogWarn("vacation mode: nothing will leave, this is not a script failure")
	return
}

All of it reads from cache, without a request to the game. These values arrive with every full page the bot loads anyway. Calling them in a loop costs nothing and never waits its turn in the queue.

IsVacationModeEnabled deserves a word. Nothing flies and nothing gets built while vacation mode is on. A script that ignores it reads every refusal from the game as a failure of its own code, and can keep trying for hours.

IsPioneers is not a class, despite the company it keeps: it is another Gameforge lobby, the one some servers go through. The account plays the same way there.

Putting the account into vacation mode

FunctionReturns
SetVacationMode()1: an errorFreezes the account. Forty-eight hours minimum.

Nothing cancels it for two days - not you, not support, not us. The game imposes it. It is also the only function in this whole API where one call too many cannot be undone: a fleet can be recalled, a worker switched back on, a campaign restarted; vacation mode cannot.

It refuses by default. The box "allow a script to put the account in vacation mode" must be ticked in the bot settings, "General" tab. Unticked, the function does not touch the game and returns an error that names the box: a script copied from a forum meets the refusal, not the freeze.

err = SetVacationMode()
if err != nil {
	LogWarn("vacation refused: " + err.Error())
}

The box is read again on every call. Unticking it takes effect at once, with nothing to restart.

Planets and moons

FunctionReturns
GetPlanets()1: a listAll your planets.
GetMoons()1: a listAll your moons.
GetPlanet(what)2: the planet and an errorOne given planet.
GetMoon(what)2: the moon and an errorOne given moon.
GetCelestial(what)2: the celestial and an errorOne given planet or moon, either one.
GetCelestials()2: the list and an errorAll your planets and moons together.
GetCachedCelestial(what)1: a celestialThe celestial as the bot already knows it.
GetCachedCelestials()1: a listAll your planets and moons, as the bot already knows them.
GetCachedPlanets()1: a listYour planets, taken from the same cache.
GetCachedMoons()1: a listYour moons, taken from the same cache.
planets = GetPlanets()                // ONE target: the list
Print(len(planets), "planets")

p, err = GetPlanet("1:2:3")           // TWO targets, the doc announces the error
if err != nil { LogError(err) }

GetPlanets and GetMoons return a single value. Written planets, err = GetPlanets(), they would leave err undefined, with the consequences described in the second pitfall.

What costs a request and what does not. GetPlanets, GetMoons, GetPlanet, GetMoon, GetCelestial and GetCelestials all re-read the same game overview page, on every call: GetCelestial and GetCelestials are no exception, despite a name that looks like GetCachedCelestial. GetCachedCelestial, GetCachedCelestials, GetCachedPlanets and GetCachedMoons ask for nothing: they answer from what the bot already holds in memory. Inside a loop, always prefer a Cached form.

How to name a celestial. A celestial is named by an id (a number) or by a coordinate, "1:2:3" or "M:1:2:3". The P, M or D prefix is optional and defaults to planet. Every function on this page that takes a celestial accepts these forms. coord, err = ParseCoord("M:1:2:3") does that reading for you, with no request: it returns an error if the prefix is anything other than P, M or D.

A number is not checked. The readings (resources, buildings, construction, sliders) send it to the game as is, without looking it up in the cache. That is deliberate: it lets you store an id with Put and read it back on the next start, before the celestial list is even loaded. In exchange, a wrong number is refused only by the game, not by the bot. GetCachedCelestial, GetFields and GetDiameter, on the other hand, do go through the cache.

Celestial not found: GetCachedCelestial returns nil. The method you chain onto it will then fail outright, and the script will stop. That is deliberate: better that than a made-up id going out in a fleet order.

Picking the nearest base

FunctionReturns
GetSortedCelestials(destination)1: a listYour celestials, from nearest to farthest.
GetSortedPlanets(destination)1: a listYour planets alone.
GetSortedMoons(destination)1: a listYour moons alone.
base = GetSortedPlanets("4:212:9")[0]     // the nearest to the target
Print("leaving from", base.GetName())

At equal distance, moons come before planets. A moon carries its planet's coordinate, so its distance: that is what produces both the M1, P1, M2, P2 order when the distances differ, and "all the moons then all the planets" when they are equal.

The destination must be a coordinate, written "4:212:9", or an object carrying one, a celestial for instance. A plain celestial id is not accepted: the function then returns an empty list and notes the failure in the log.

These three readings work on the cache. They cost no request, and return an empty list with no session.

Resources

FunctionReturns
GetResources(celestial)2: the resources and an errorMetal, crystal, deuterium, energy, dark matter, population, food.
GetResourcesDetails(celestial)2: the detail and an errorThe same, with storage capacity and production.
GetAllResources()2: a table and an errorThe resources of every celestial at once.
r, err = GetResources("1:2:3")
if err != nil { LogError(err); return }
Print(r.Metal, r.Crystal, r.Deuterium)

d, err = GetResourcesDetails("1:2:3")
Print("metal", d.Metal.Available, "out of", d.Metal.StorageCapacity)
Print("production", d.Metal.CurrentProduction, "per hour")

Production is hourly. The version 13 page serves it per second; it is converted before it reaches you, to match what the game displays. This is the real production, officers, items and lifeforms included.

On a version 13 server, the detail is partial. The fallback route fills in what the new page gives, and nothing more. So these stay at zero, and that zero is not a fact of the game:

  • Energy.CurrentProduction and Energy.Consumption, while Energy.Available is right,
  • Food.StorageCapacity, Food.Overproduction and the rest of the food block,
  • all of Population except Available,
  • Darkmatter.Purchased and Darkmatter.Found.

GetAllResources returns a table keyed by celestial id.

all, err = GetAllResources()
if err != nil {
	LogWarn("grouped reading unavailable, falling back celestial by celestial")
} else {
	for p in GetPlanets() {
		Print(p.GetName(), all[p.GetID()].Metal)
	}
}

Plan for its failure. It deliberately refuses to return an incomplete reading: a silent half, your planets without your moons, would have you conclude you own no moon. It returns an error instead, and it is up to your script to fall back on GetResources, celestial by celestial.

That check compares the reading against the celestial list the session knows. As long as that list is empty, just after startup, it cannot play its part.

The empire in one request

FunctionReturns
GetEmpire(type)2: a list and an errorEverything about one kind of celestial: resources, mines, facilities, defences, ships.
// TWO targets. Written "for c in GetEmpire(PLANET_TYPE)", the loop would run
// twice: once on the list, once on the error. That is the first pitfall.
empire, err = GetEmpire(PLANET_TYPE)
if err != nil { LogError(err); return }
for c in empire {
	Print(c.Name, c.Coordinate, c.Resources.Metal, c.Supplies.MetalMine)
}

The type is given with PLANET_TYPE or MOON_TYPE. DEBRIS_TYPE makes no sense here and comes back as an error.

The fields you read off an entry: Name, ID, Type, Coordinate, Diameter, Fields, Temperature, Resources, Supplies (the mines and storages), Facilities, Defenses, Ships and Researches.

It is the cheapest reading of the lot: one request per kind of celestial, where a per-celestial reading would take four per planet. It also costs you less activity: reading celestial by celestial writes activity on each of them, which your neighbours read in the galaxy.

Keep a fallback. This page is optional in the game, and its shape has already changed from one version to the next.

What sits on a celestial

FunctionReturns
GetResourcesBuildings(celestial)2: the levels and an errorThe nine: mines, solar plant, fusion reactor, satellites, storages.
GetFacilities(celestial)2: the levels and an errorThe eleven: factories, shipyard, lab, depot, silo, terraformer, dock, lunar base, phalanx, jump gate.
GetShips(celestial)2: the ships and an errorWhat is parked there, not what is flying.
GetDefense(celestial)2: the defences and an errorMissiles included.
GetTechs(celestial)6: buildings, facilities, ships, defences, researches, an errorThe whole reading at once.
GetTechs2(celestial)8: the six above, plus the lifeform buildings and researchesThe entire reading of a celestial.
GetResearch()1: your researchesThey belong to the account, not to the celestial: no argument.
// SIX targets. It is the function on this page that asks for the most.
bld, fac, shp, def, rch, err = GetTechs("1:2:3")
if err != nil { LogError(err); return }
Print(bld.MetalMine, fac.RoboticsFactory, shp.LargeCargo, rch.Astrophysics)

Those four readings do answer on version 13, and so does a fifth, GetResearch, which belongs to the account and not to the celestial. Those five are what the GetTechs fallback leans on. GetResearch returns one value only, with no error: a reading that fails returns empty researches and says so in the log.

GetTechs is no longer the cheapest reading. As long as the fallback is in use, it costs six requests, the lost attempt included, where the four separate readings cost four. It does return the researches on top, which those four do not give. If all you need is the mines, call GetResourcesBuildings.

The four readings can answer by id, which lets you loop over the constant arrays.

bld, err = GetResourcesBuildings("1:2:3")
Print("metal mine", bld.ByID(METALMINE))

Careful when looping over BuildingsArr, which has twenty-three entries: ByID on a buildings reading knows only the nine resource buildings, and the facilities one only the eleven facilities. The three protected dens, SHIELDEDMETALDEN, UNDERGROUNDCRYSTALDEN and SEABEDDEUTERIUMDEN, therefore always come back at zero by that route.

GetTechs2 returns the WHOLE reading, lifeforms included, in a single call:

// EIGHT targets, and the error last, as in Ninja.
bld, fac, shp, def, rch, lfBld, lfRch, err = GetTechs2("1:2:3")
if err != nil { LogError(err); return }
Print(bld.MetalMine, fac.RoboticsFactory, rch.Astrophysics)

It costs seven requests while the fallback is in use, against five for GetTechs: the two lifeform readings are added. One call instead of five on the script side, and a single transaction, so the celestial does not change state in the middle of the reading, which a hand-written join cannot guarantee.

Ninja's reference example is wrong on this function. It assigns eight targets with no error, where its own declaration puts the error in eighth position. The declaration is what we follow here.

Construction under way

FunctionReturns
GetProduction(celestial)3: the queue, the seconds left, an errorShips and defences at the shipyard.
ConstructionsBeingBuilt(celestial)4: building, seconds, research, secondsThe building and the research under way.
queue, left, err = GetProduction("1:2:3")
for q in queue {
	Print(q.ID, "x", q.Nbr)
}
Print("queue empty in", left, "seconds")
// FOUR values, and NO error. A fifth target would stay undefined.
bID, bSec, rID, rSec = ConstructionsBeingBuilt("1:2:3")
if bID != 0 {
	Print("under construction:", bID, "another", bSec, "s")
}

The three countdowns are in seconds, but not for the same reason. GetProduction returns the one the game writes itself, in seconds. ConstructionsBeingBuilt converts: the game library carries its two countdowns in nanoseconds, and without that conversion ten minutes of construction would read 600,000,000,000.

Zero reads as "nothing under construction", and that is also what a failure returns. ConstructionsBeingBuilt has no return value to lodge an error in: unreadable page or closed session, it returns four zeros, like an idle celestial. The only way to tell them apart is the log, where the failure is noted. A script that starts another build without checking can therefore start one too many.

ConstructionsBeingBuiltLf, for the lifeforms, does not exist yet.

The production sliders

FunctionReturns
GetResourceSettings(planet)2: the settings and an errorThe seven output sliders.
settings, err = GetResourceSettings("1:2:3")
if err != nil { LogError(err); return }
Print(settings.MetalMine, settings.CrystalMine, settings.DeuteriumSynthesizer)
Print(settings.SolarPlant, settings.FusionReactor, settings.SolarSatellite, settings.Crawler)

Planets only. A moon has no mines to set, and asking one for them will get you nothing but an error.

A missing slider comes back at zero. A planet with no satellite and no crawler has only five of the seven, and the returned structure has no room to say "missing". So do not conclude from a zero that the building is throttled.

A page where no slider at all is readable, on the other hand, is refused outright, with an error. You will never read "everything at 0%" on a planet set to a hundred.

There is no way to write these settings back: SetResourceSettings does not exist yet.

A celestial's record

FunctionReturns
GetFields(celestial)1: the fields, Built and Total
GetDiameter(celestial)1: an integerThe diameter, in kilometres.
f = GetFields("1:2:3")
Print("free:", f.Total - f.Built)
Print("radius:", GetDiameter("1:2:3") / 2)

Those two read the cache, with no request. They do not move from one session to the next, apart from fields gained from a terraformer.

One value each, and that is what lets you drop them into a calculation: GetDiameter(c) / 2 really does give half the diameter. A function returning two values would give zero there without the slightest error. That is the flaw these two had before repair, and this is where it did the most damage.

Those two global functions are specific to Kepler. Ninja knows only the celestial methods, c.GetFields() and c.GetDiameter(), which also work and return the same thing.

Starting and cancelling construction

FunctionReturns
Build(celestial, what, count)1: an errorBuilding, technology, ship or defence.
BuildBuilding(celestial, building)1: an errorRefuses anything that is not a building.
BuildTechnology(celestial, technology)1: an errorRefuses anything that is not a technology.
TearDown(celestial, building)1: an errorTears one level down.
CancelBuilding(celestial)1: an errorCancels the construction under way.
CancelResearch(celestial)1: an errorCancels the research under way.

The celestial is written as everywhere else: a "1:2:3" coordinate, a record returned by GetCachedCelestial, or a bare id.

The count only matters for what can be counted. Zero on a mine or a technology asks for the next level: that is the reference tool's convention, not a special case.

c = GetCachedCelestial("1:2:3")
err = Build(c.GetID(), LIGHTFIGHTER, 5)
if err != nil { LogError("fighters:", err) }
Build(c.GetID(), LIGHTLASER, 5)
BuildBuilding(c.GetID(), METALMINE)
BuildTechnology(c.GetID(), ENERGYTECHNOLOGY)
TearDown(c.GetID(), SOLARPLANT)
CancelBuilding(c.GetID())
CancelResearch(c.GetID())

Read this before using them: your script and Brain aim at the same building slot. The game accepts only one per celestial, so whichever arrives second is refused — and which one arrives second is not something you decide, only something you observe. That is not a bot defect, it is the game's rule.

So choose, celestial by celestial: your script or Brain, never both. StopBrain() switches Brain off everywhere; to take it off a single celestial, empty that celestial's queue from the panel.

Cancelling what does not exist returns NO error. Checked in game on 05/09/2026: CancelBuilding and CancelResearch on a celestial that is building nothing both return nil, as though the cancellation had happened. So do not rely on the return value to learn whether there was anything to cancel; read ConstructionsBeingBuilt first if the answer matters to you.

When there was a construction, the game gives it all back. Measured in game on 05/09/2026 on a real account: a metal mine started then cancelled within the minute left the stock identical to the unit. It is tearing down, TearDown, that returns only half.

And do not judge a ship order by the production queue. On a fast universe, three small cargos are finished before the next read comes in: the queue looks empty while the order went through. Count the ships, not the queue.

Lifeforms

FunctionReturns
GetLfBuildings(celestial)2: the buildings and an errorOne request per call.
GetLfResearch(celestial)2: the researches and an errorOne request per call.
GetLfBonuses()2: the bonuses and an errorReads the bonuses from the game.
GetCachedLfBonuses()2: the bonuses and an errorWhat the bot already knows. No request.
GetPlanetLifeformType(planet)1: the lifeformOne request. No error: see below.
c = GetCachedCelestial("1:2:3")
b, err = GetLfBuildings(c.GetID())
if err != nil { LogError("lifeforms:", err) }

res, err = GetLfResearch(c.GetID())
Print(res, err)

bonus, err = GetCachedLfBonuses()
Print(bonus, err)

fresh, err = GetLfBonuses()
Print(fresh, err)

The bonuses change your figures. They apply to production, speed and cargo: a script sizing a transport without them is reasoning about a fleet that no longer exists. CalcCargo and the flight family already account for them on your behalf; it is your own arithmetic that needs to read them.

Which of the two bonus functions to take. GetCachedLfBonuses costs nothing and is enough inside a loop: the bonuses only move when your researches do. GetLfBonuses reads the game again, which is only worth it after finishing one. The cache of an account that has just connected is empty and returns an error, rather than zeros that would read as a truth.

The researches apply to the whole account, but the game files them on the celestial you consult them from: hence the argument, which the reference tool asks for too.

When a reading fails

Two regimes live side by side on this page, and the "Returns" column tells you which one applies.

The functions that return an error hand it to you: test it.

The others have nowhere to lodge it. They return the zero value, empty list, zero, nil, or four zeros for ConstructionsBeingBuilt, and write a line to the bot's log, Logs page, at Warn level:

celestial api: read failed   script=my-pass   function=GetPlanets   err=account not connected

That line does not go to your script's console, only to the log. It is the first place to look when a script starts believing you have no planets left.

Only GetUniverseName, GetServerNumber, GetLang and IsLoggedIn stay silent: their zero value already says "no session".

Lifeforms, beyond the levels

FunctionReturns
CancelLfBuilding(celestial)1: an errorCancels the lifeform building under way.
GetLfResearchDetails(celestial)2: the details and an errorCosts, durations and what is unlocked.

The lifeform queue is SEPARATE from the ordinary building queue. Cancelling one does not touch the other, and a celestial can build in both at once: CancelBuilding and CancelLfBuilding do not replace each other.

GetLfResearchDetails reads a page of the game, where GetLfResearch makes do with the levels already known. Not something to put in a loop over twenty celestials.

Chapters, and choosing a lifeform research

FunctionReturns
GetChapter(chapter)2: the chapter and an errorIts tasks, and the state of each.
ChapterCollectReward(task)1: an errorCollects the reward of one task.
ChapterClaimAll(chapter)1: an errorCollects everything collectable.
SelectLfResearchSelect(planet, slot)1: an errorAccepts the research on offer.
SelectLfResearchRandom(planet, slot)1: an errorDraws another one at random.
SelectLfResearchArtifacts(planet, slot, tech)1: an errorTrades artifacts for it.
FreeResetTree(planet, tier)1: an errorWipes a whole tier. Free, but limited in number.
BuyResetTree(planet, tier)1: an errorThe same, paid in dark matter.

A chapter pays out with no risk: it hands over resources and items for tasks already done. There is no reason not to collect them, except that you have to remember every day — which a script does better than a player.

CronExec("@09h00", func() {
	err = ChapterClaimAll(4006)               // ← yours: the chapter id
	if err != nil { LogError("chapter:", err) }
})

ChapterClaimAll is specific to Kepler, a shortcut the reference tool does not have: it forces you to loop over the tasks and collect one at a time. The game can do it in a single call, and twenty tasks collected by hand are twenty requests for a gesture it counts as one.

A task id is not its chapter's id. The two look alike and are not interchangeable: ChapterCollectReward wants the task's, which GetChapter gives you.

SelectLfResearchRandom costs, and cannot be undone: whatever was on offer in that slot is gone. A script drawing in a loop until it gets what it wants pays every round.

All three SelectLfResearch* want a planet: lifeform researches do not exist on a moon, and a moon passed in their place returns an error that names it.

The two ResetTree wipe a tier, which is the half the loop was missing: read what is on offer, take it if it fits, wipe the tier if it does not, try again. SelectLfResearchRandom comes close, but it redraws one slot - it does not clear a tier.

p = GetCachedCelestial("1:2:3").GetID()

if GetPlanetLifeformType(p) != KAELESH {
	return                                    // this script only handles Kaelesh
}

err = FreeResetTree(p, 1)                     // tier 1, blank again
if err != nil {
	LogError("no free reset left:", err)
	// err = BuyResetTree(p, 1)               // this one costs dark matter
}

The tier goes from 1 to 3, nothing else is worth trying: any other number is refused before a single request goes out.

Wiping cannot be undone. Everything picked in that tier is gone, and the points spent on it with it. That is what you are asking for, but a script that wipes the wrong tier has nothing to fall back on.

The game only grants a limited number of free resets, and nobody says how many - not the library, not the reference tool, not Kepler. There is no way to know in advance: you try, and you read the error. That is why the example above falls through to the paid version instead of counting.

BuyResetTree spends dark matter, which is bought with real money. The bot itself never spends any on its own initiative: this function exists only because a script is the player's own move, writing out the name of what it spends.

The price cannot be read before you pay, not here and not in the game: you learn it by watching your reserve drop. Measured once, on 2026-09-22, on a tier 2 in a v13 universe: 8,000. Nothing says it is the same everywhere, or that it does not climb with the number of resets already done. It is one measurement, not a price list. A script calling this in a loop empties a reserve that is bought with real money.

GetPlanetLifeformType returns a number, not a name: 0 none, 1 HUMANS, 2 ROCKTAL, 3 MECHAS, 4 KAELESH. Constants of the same names exist, and == KAELESH compares correctly. Print will show the digit.

It returns no error, the way the reference tool declares it. It does read a game page, which can fail: it then returns 0, and the failure goes to the script console and to the log - it is not silent, it is simply somewhere other than the return value. A moon returns 0 too, and there it is the right answer.

It costs a full page on every call. Twelve planets read on every round of a loop is twelve pages per round, for something that hardly ever changes: read it once, keep it.

Production sliders, and the price on the spot

FunctionReturns
NewResourceSettings(metal, crystal, deuterium, solar, fusion, satellite, crawler)1: settingsComposes the seven sliders.
SetResourceSettings(planet, settings)1: an errorApplies them.
TechnologyDetails(celestial, what)2: the details and an errorPrice, duration and level, on that celestial.

All seven count, and that is the trap. The game applies the WHOLE setting, not the difference. A script wanting to change only the metal mine must read back the other six and set them again as they were — otherwise it puts them all to zero, and the planet stops producing without a word.

c = GetCachedPlanets()[0]
before, err = GetResourceSettings(c.GetID())
if err != nil { LogError(err); return }

// Changing ONLY deuterium: the other six are copied over.
after = NewResourceSettings(before.MetalMine, before.CrystalMine, 100,
	before.SolarPlant, before.FusionReactor, before.SolarSatellite, before.Crawler)
err = SetResourceSettings(c.GetID(), after)

Sliders run from zero to a hundred, in steps of ten: the game refuses anything else. A slider at zero stops the mine, it does not set it to a minimum.

SetResourceSettings wants a planet: a moon produces nothing, and a moon passed in its place returns an error that names it.

TechnologyDetails asks the game, where GetPrice computes. The difference is one word: GetPrice prices a level you give it, this one asks what it costs here, with this celestial's facilities, lifeform bonuses and reductions. It is a page of the game — one request per call — and it also says whether tearing down is available, which no computation knows.