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=fetchResourcesandpage=fetchTechsnow 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.
| Function | What the fallback does | What it costs on top |
|---|---|---|
GetResources | re-reads the resources at the new address | 1 request |
GetResourcesDetails | the same | 1 request |
GetAllResources | goes through the empire page, planets then moons | 2 requests |
GetTechs | rebuilds the reading from five readings that do answer | 5 requests, about 1.5 s against 0.3 s |
GetResourceSettings | re-reads the sliders on the lowercase page | 1 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 ofGetTechs2, lifeforms included; on a moon, that ofGetTechs.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
| Function | Returns | |
|---|---|---|
GetUniverseName() | 1: a string | The universe name. |
GetServerNumber() | 1: an integer | The server number: s152-en gives 152. |
GetLang() | 1: a string | The server language code, fr, en… |
IsLoggedIn() | 1: a boolean | Says 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
| Function | Returns | |
|---|---|---|
CharacterClass() | 1: an integer | The class: NO_CLASS, COLLECTOR, GENERAL or DISCOVERER. |
IsCollector() | 1: a boolean | Shortcut on the class. |
IsGeneral() | 1: a boolean | The same. |
IsDiscoverer() | 1: a boolean | The same. |
HasCommander() | 1: a boolean | Is the officer hired. |
HasAdmiral() | 1: a boolean | The same. |
HasEngineer() | 1: a boolean | The same. |
HasGeologist() | 1: a boolean | The same. |
HasTechnocrat() | 1: a boolean | The same. |
IsVacationModeEnabled() | 1: a boolean | Is the account in vacation mode. |
IsPioneers() | 1: a boolean | Does 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
| Function | Returns | |
|---|---|---|
SetVacationMode() | 1: an error | Freezes 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
| Function | Returns | |
|---|---|---|
GetPlanets() | 1: a list | All your planets. |
GetMoons() | 1: a list | All your moons. |
GetPlanet(what) | 2: the planet and an error | One given planet. |
GetMoon(what) | 2: the moon and an error | One given moon. |
GetCelestial(what) | 2: the celestial and an error | One given planet or moon, either one. |
GetCelestials() | 2: the list and an error | All your planets and moons together. |
GetCachedCelestial(what) | 1: a celestial | The celestial as the bot already knows it. |
GetCachedCelestials() | 1: a list | All your planets and moons, as the bot already knows them. |
GetCachedPlanets() | 1: a list | Your planets, taken from the same cache. |
GetCachedMoons() | 1: a list | Your 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
| Function | Returns | |
|---|---|---|
GetSortedCelestials(destination) | 1: a list | Your celestials, from nearest to farthest. |
GetSortedPlanets(destination) | 1: a list | Your planets alone. |
GetSortedMoons(destination) | 1: a list | Your 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
| Function | Returns | |
|---|---|---|
GetResources(celestial) | 2: the resources and an error | Metal, crystal, deuterium, energy, dark matter, population, food. |
GetResourcesDetails(celestial) | 2: the detail and an error | The same, with storage capacity and production. |
GetAllResources() | 2: a table and an error | The 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.CurrentProductionandEnergy.Consumption, whileEnergy.Availableis right,Food.StorageCapacity,Food.Overproductionand the rest of the food block,- all of
PopulationexceptAvailable, Darkmatter.PurchasedandDarkmatter.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
| Function | Returns | |
|---|---|---|
GetEmpire(type) | 2: a list and an error | Everything 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
| Function | Returns | |
|---|---|---|
GetResourcesBuildings(celestial) | 2: the levels and an error | The nine: mines, solar plant, fusion reactor, satellites, storages. |
GetFacilities(celestial) | 2: the levels and an error | The eleven: factories, shipyard, lab, depot, silo, terraformer, dock, lunar base, phalanx, jump gate. |
GetShips(celestial) | 2: the ships and an error | What is parked there, not what is flying. |
GetDefense(celestial) | 2: the defences and an error | Missiles included. |
GetTechs(celestial) | 6: buildings, facilities, ships, defences, researches, an error | The whole reading at once. |
GetTechs2(celestial) | 8: the six above, plus the lifeform buildings and researches | The entire reading of a celestial. |
GetResearch() | 1: your researches | They 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
| Function | Returns | |
|---|---|---|
GetProduction(celestial) | 3: the queue, the seconds left, an error | Ships and defences at the shipyard. |
ConstructionsBeingBuilt(celestial) | 4: building, seconds, research, seconds | The 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
| Function | Returns | |
|---|---|---|
GetResourceSettings(planet) | 2: the settings and an error | The 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
| Function | Returns | |
|---|---|---|
GetFields(celestial) | 1: the fields, Built and Total | |
GetDiameter(celestial) | 1: an integer | The 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
| Function | Returns | |
|---|---|---|
Build(celestial, what, count) | 1: an error | Building, technology, ship or defence. |
BuildBuilding(celestial, building) | 1: an error | Refuses anything that is not a building. |
BuildTechnology(celestial, technology) | 1: an error | Refuses anything that is not a technology. |
TearDown(celestial, building) | 1: an error | Tears one level down. |
CancelBuilding(celestial) | 1: an error | Cancels the construction under way. |
CancelResearch(celestial) | 1: an error | Cancels 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
| Function | Returns | |
|---|---|---|
GetLfBuildings(celestial) | 2: the buildings and an error | One request per call. |
GetLfResearch(celestial) | 2: the researches and an error | One request per call. |
GetLfBonuses() | 2: the bonuses and an error | Reads the bonuses from the game. |
GetCachedLfBonuses() | 2: the bonuses and an error | What the bot already knows. No request. |
GetPlanetLifeformType(planet) | 1: the lifeform | One 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
| Function | Returns | |
|---|---|---|
CancelLfBuilding(celestial) | 1: an error | Cancels the lifeform building under way. |
GetLfResearchDetails(celestial) | 2: the details and an error | Costs, 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
| Function | Returns | |
|---|---|---|
GetChapter(chapter) | 2: the chapter and an error | Its tasks, and the state of each. |
ChapterCollectReward(task) | 1: an error | Collects the reward of one task. |
ChapterClaimAll(chapter) | 1: an error | Collects everything collectable. |
SelectLfResearchSelect(planet, slot) | 1: an error | Accepts the research on offer. |
SelectLfResearchRandom(planet, slot) | 1: an error | Draws another one at random. |
SelectLfResearchArtifacts(planet, slot, tech) | 1: an error | Trades artifacts for it. |
FreeResetTree(planet, tier) | 1: an error | Wipes a whole tier. Free, but limited in number. |
BuyResetTree(planet, tier) | 1: an error | The 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
| Function | Returns | |
|---|---|---|
NewResourceSettings(metal, crystal, deuterium, solar, fusion, satellite, crawler) | 1: settings | Composes the seven sliders. |
SetResourceSettings(planet, settings) | 1: an error | Applies them. |
TechnologyDetails(celestial, what) | 2: the details and an error | Price, 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.