Repository navigation
Pier 26.40.1
Built for BDS 1.26.40 and LeviLamina 26.40.0. Two fixes, both of the same shape: a value
that was wrong rather than missing, and therefore acted on.
PIER_ABI_VERSION stays at 2. The two new server_info_str keys are additive — a mod
built against 26.40.0 keeps working without a rebuild, though see the note on
PIER_SRV_PROTOCOL_VERSION below before relying on the number it used to return.
Fixed
-
A per-player sidebar stopped updating after its first send.
PIER_PACT_SIDEBAR_SET
updates a sidebar of unchanged row count in place, sending only the rows whose text
differs. The client stores a row asscoreboard id -> (name, score)and reads a
ChangeFakePlayerScorefor an id it already knows as a score update, not a rename: the
number moved, the label did not. So the first send was correct and every later one
silently did nothing.A changed row now releases its id with a
RemoveScorebefore theChangeFakePlayerScore
re-adds it. The removes go out as their own packet ahead of the changes: nothing here can
verify that a client applies one packet's entries in order, and a remove applied after
the re-add blanks the row instead of updating it. If the second packet cannot be created
after the removes have gone out, the cached state is dropped so the next call rebuilds
from scratch — keeping it would leave those rows blank until the row count changed. -
PIER_SRV_PROTOCOL_VERSIONreported the save file's protocol, not the server's. It
readLevelData::mNetworkVersion, which is a storage tag sitting among
mStorageVersion,mMinCompatibleClientVersionandmLastOpenedWithVersion, and
records the version the level was last written by. A 1.21.93 world opened on a 1.26.40
server answered 819. This looked correct for as long as the test machine's world had
been created by the server running it.It is now derived from
SharedConstants::CurrentGameSemVersion, which unlike
NetworkProtocolVersionis not behind#ifdef LL_PLAT_Cand so is readable on a
dedicated server, mapped through a table of confirmed versions.On a version the table does not list, and on any pre-release build, the key now
fails. That is deliberate. The old behaviour was not a crash, it was a confident wrong
answer, and a caller handed a plausible number acts on it. A pre-release shares its
major.minor.patchwith the retail build but not its protocol — 1.26.40-beta.0 speaks
2168 where retail 1.26.40 speaks 2169 — so a build carrying amPreReleaseis refused
rather than answered from a retail row. The table matches the exact triple: Bedrock
bumps the protocol on patch releases, so matching onmajor.minorwould return a number
from the wrong end of a release line.
Added
-
PIER_SRV_LEVEL_PROTOCOL_VERSION, theLevelData::mNetworkVersionvalue under the
name it deserves. It answers how old the save is, which is a real question and exactly
what the old key was accidentally answering.Host::level_protocol_versionon the Rust
side. -
PIER_SRV_GAME_SEM_VERSION,"major.minor.patch"of the running build, so a mod can
carry its own version-to-protocol table. A Pier release should not be on the critical
path for a mod supporting a BDS that shipped yesterday.Host::game_sem_versionon the
Rust side. -
pier-probecoversserver_info_strkeys 0 through 3. ARefusedon key 1 is recorded
as the correct answer for an unlisted version rather than as a fault; comparing it
against key 2 shows directly when a save is older than the server.
Added
-
A mod can supply its own terrain, and this host stops trying to understand it.
md_add_dimension_generatedregisters a dimension whose spec carries seed, height and
sky and no terrain section at all; the mod hands over two palettes, resolved once here
against the block and biome registries, and a callback the chunk workers call with a
buffer of indices.md_set_dimension_cellsgives the confinement rules the grid the
mod generated, since the hooks are engine hooks but the geometry is not. What crosses
the boundary per chunk is indices and never names: a chunk is 98304 entries and looking
names up per chunk is the difference between a generator and a stall. The threading
contract onPierGenerateChunkFnis the opposite of the rest of this file and is
written out there rather than left to the default.A payload with no terrain section is what says the terrain is a mod's, so
md_add_dimension
refuses one -- taking it there would register a dimension nothing fills and report success --
and a supplied dimension whose mod is gone loads as a void with its chunks intact rather than
falling through to the pack branch, where reading the terrain as a pack would throw on a chunk
thread. -
terrain.kind: layersis served again, and needs no tool and no file. A layer stack,
optionally cut into a grid of cells and gaps, is what a plot world and a superflat world
are, and it had been withdrawn in favour of terrain packs built offline with
tools/pier-pack. A caller now sends the recipe inline, as before, and it is assembled
into a template pack in memory and handed to the pack path: the chunk fill, the layout
expansion and the confinement hook underneath are the ones the pack path uses and the
ones the tests cover, so this adds a front door rather than a second generator.
pierpack/from_layers.pyperforms the identical mapping offline and stays the reference.
Every expression the assembled pack carries is a constant, because the inline recipe
names concrete numbers, so nothing has to be bound and no parameter can fail to bind.
A recipe whose border does not fit inside its cell, or whose stack runs past the top of
the dimension, is refused with the numbers rather than quietly shrunk.terrain.kind: noiseis still not served inline: it is a volume pack and not a layer stack, and the
in-memory assembly does not reach it.
Fixed
- A setting with no implementation behind it is refused, not recorded.
aquifers_enabled
andore_veins_enabledeach had a bit in the volume pack format and nothing anywhere that
read it: the builder wrote the flag, the host loaded the pack, and the terrain came out
without the aquifers or the ore veins its author had asked for, with no line on either
side. The tool refuses the two keys now, the host refuses a pack whose settings carry
either bit, and a bit outside the known set is refused as well, so a pack from a newer
tool cannot be half-honoured. The bit values stay recorded so their meaning is fixed if
either is ever implemented. - A source key the builder does not read is refused. Neither pack builder validated its
top-level keys, so a misspelled name, or a field copied from a Java datapack that this
format has no equivalent for, was dropped without a word and the pack was built as though
the author had not written it. Both builders now carry the list of keys they act on and
refuse anything else. - The shipped terrain packs are built for the world bottom that exists.
fixtures/town.json,
fixtures/plot.json,pierpack/from_layers.pyand two of the tool's tests all declared
a bottom of -512, the valuekWorldMinYheld before it was lowered to the vanilla -64;
nothing updated them at the time. A pack made for -512..320 does not fit a dimension of
-64..320, so mounting one was refused withPIER_PACK_HEIGHTand no world could be built
from it. The declared range is -64..320 now, the empty bottom layer that ran from -512 to
the bedrock is gone, and the two equivalence tests mount at the range the packs actually
carry. - A binary the host cannot open names the path it tried.
binaryin a pack config is
joined onto the config's own directory, and a config written with a path relative to the
server root instead produces a doubled prefix; the refusal said only that a file could
not be opened, which sends the reader to check a path the host never tried. - A mistyped field in a spec is a refusal with a reason, not an exception. The
conversions onCompoundTagVariantare not uniform: a number reached through the wrong
type throwsstd::runtime_error, a string does it throughstd::getand throws
std::bad_variant_access, and.get<CompoundTag>()does the same for a section. Any of
them escaped the reader, was caught by the API guard several frames away, and came back
as a registration that failed naming neither the field, the dimension, nor the recipe.
Every field is now type-checked before it is read, a wrong one reads as absent and is
reported, andmd_add_dimensionnames the dimension and the spec text if anything still
throws. - The host's startup line carries a build stamp, so a report can be told apart from one
taken against the previous binary without asking. md_add_dimensionno longer throws on a layers terrain. The slot refused a pack
terrain and then read the spec as native withstd::get, which throws for the third
alternative rather than falling through. A layers recipe reached that line and the
registration came back as a refusal with no reason attached, since the throw was caught
by the API guard. Both served kinds are read withget_ifnow, and the biome a layers
recipe names is verified at registration for the same reason the pack path verifies its
own:TemplateGeneratorreports a biome it cannot find as the registry having changed
underneath it, which is the wrong thing to tell someone who mistyped a name.- This host owns the dimensions it registers. The instance ledger held a
WeakRef,
and the engine does not keep anOwnerPtrhanded toDimensionRegistry::registerDimension
alive: the reference the call returned locked, the level listed the dimension, and the
weak reference was expired again as soon as the frame that built it returned. Every path
that reads the ledger treats an empty answer as permission to build, so the same
dimension was built and registered twice within a millisecond, and the second
registration replaced an object the engine had already been told about during
initializeDimension. The next player to connect into it dereferenced a null and the
server died before they had spawned. The ledger now holds anOwnerPtr, it is asked
before the engine on every path that decides whether to build, and letting go is a
handover to the quarantine rather than a destruction, because a player may be standing
in the dimension at that moment.native::forgetAllInstancesreleases them while the
level is still standing, rather than among the static destructors at process exit. - The by-name ledger answers to the qualified name too. The engine calls by
native::engineNameOf, the ledger is keyed by the name the caller chose, and the detour
on the by-name overload therefore handed the engine's own call straight back to it. - A refused registration no longer takes the server down with it.
initializeDimension
is where the engine wires aDimensioninto the level, and the references it hands out
there it does not own.buildAndRegistermoved its onlyOwnerPtrinto
registerDimension, so a refused registration destroyed a dimension the engine had
already been told about, and the level tick reached one of those references about a
second later and aborted the process. The reference is now kept across the call, and a
dimension the registry declines is held rather than released. - An empty return from
registerDimensionis no longer read as a refusal on its own.
Whether the dimension was stored is asked of the level, throughILevel::forEachDimension,
and an empty reference beside a dimension the level is holding is reported as the return
value being unreliable rather than as a failed registration. - A dimension is in the host ledger before anything builds it. The detours on the
resolution calls decide whether an id belongs to this host by asking the ledger, and the
ledger was written only after registration returned. During its own registration a
dimension was therefore invisible to them, and the call went to the engine, which built
and registered it on a path this host does not see. - A player is no longer sent into a dimension their game has not been told about. The
set of dimensions reaches a client once, in the data sent while joining, and there is no
way to send it again mid-session, so a dimension registered while a player is online is
one that player cannot enter: the transfer runs on the server, the client has no
definition for the id, and the player is left standing where they were with nothing said.
Level::requestPlayerChangeDimensionnow refuses the transfer and tells the player to
rejoin. A player whose session start this host did not see is allowed through. - The startup check on the vanilla dimensions asked the wrong question. The nether and
the end are built on first use, so a boot with nobody in them holds the overworld alone,
and reporting that as two destroyed dimensions was wrong. Only a missing overworld is
reported now. - A custom dimension no longer takes the overworld's slot in
DimensionRegistry. A
Dimensioncarries two numbers:mId, aDimensionType, the signed int this host, the
commands and the save all speak, andmRegistryId, aDimensionIdType, the unsigned
short that keysDimensionRegistry::mDimensionsand thatregisterDimensiontakes. The
only thing mapping between them isDimensionManager::mDimensionNameIdStore, the table
serverRegisterCustomDimensionwrote until 26.20 and that nothing exported writes now,
so a dimension built for a name that table has never held is constructed with a registry
id that is not its id.registerDimensionassigns into the map, so such a dimension is
stored over the slot that number belongs to and theOwnerPtrthat was there is
dropped. The level goes on running against a destroyed dimension: the first custom
dimension of a boot reports success, a second is refused because the slot is taken, and
the server dies shortly after anyone joins, on a chunk worker or in the level tick,
inside engine code that names neither this host nor the dimension that was added. The
terrain the new dimension generates has no bearing on it.native::claimRegistryKey
now sets the registry id of any dimension at or abovefirstCustomDimensionIdto its
ownDimensionTypebefore it enters the registry, and a detour on
DimensionRegistry::registerDimensionapplies it to the engine's own creation paths as
well.buildAndRegisterreports the as-built registry id on the line it already writes,
and refuses to register when the two still disagree. Chunks a dimension wrote under a
registry id it no longer holds stay in the save and are not reachable from it. - Teleporting into a custom dimension lands in that dimension. Every engine path that
resolves a dimension goes throughgetOrCreateDimension, which maps an id to a name
throughDimensionManager::mDimensionNameIdStore. Nothing exported writes that table
sinceserverRegisterCustomDimensionwas inlined away, so it comes back empty for every
dimension this host registers.TeleportCommand::computeTarget, which is what
Actor::teleportand the teleport commands run on, only attaches a
ChangeDimensionRequestwhen it can resolve the destination, so without one the player
is moved to the coordinates inside the dimension they were already standing in. The host
had worked around the same gap for its own calls by building the instance into
DimensionRegistrydirectly, where the plaingetDimensionlookup does find it, which
is why block writes worked while teleports did not. Four detours now answer for this
host's dimensions out of that registry, on bothgetOrCreateDimensionoverloads, on
Level::getOrCreateDimensionand onLevel::isDimensionTypeActive; every other id goes
to the original. Nothing is invented: an id is answered only when the host ledger holds
its name and an instance is registered under it. - A custom dimension is no longer rebuilt over the one already running, which crashed
the server on the first teleport into it. On BDS 1.26.40 bothgetOrCreateDimension
overloads come back empty for a custom dimension, so everygetOrCreateByNamefell
through tobuildAndRegister, andDimensionRegistry::registerDimensionassigns into
its map: the second registration under the same id destroyed theDimensionthe level,
the players and the chunk threads were holding. The log showed it plainly, with
built through the factory and registered as id 3twice at startup and once more when
a player was sent in, followed by an access violation on a chunk worker thread with no
frame of the mod on its stack.getOrCreateByNamenow asks
DimensionManager::getDimensionfirst and returns the live instance,buildAndRegister
refuses an id that already carries one, and a registry that cannot be read refuses to
build rather than guessing. A correct startup now logs the build line once. - The registry table is read directly;
getDimensionis not the judge. The fix above
askedDimensionManager::getDimensionwhether an id carries an instance. On a live
server it answered empty for a dimension that was registered and running, so every
teleport still went throughbuildAndRegister, andregisterDimensionthen returned an
empty reference twice and a full one the third time for the same id and the same code.
The lookup resolves aDimensionTypethrough the engine name table, which has no row
for a host-registered dimension, so it answers empty for exactly these ids while the
table it writes into holds them.registeredInstancenow walks
DimensionRegistry::mDimensionswhen the lookup comes back empty, which needs only a
std::hashforDimensionIdTypethat the SDK does not supply for the derived type;
the table, not the returned reference, decides whether a registration took, the built
instance is held by a second owner until that is known so it is never destroyed under
the chunk workinitializeDimensionstarted, and a registration the engine stored
nowhere is placed in the table directly. The threegetOrCreateDimensiondetours hand
the enginenative::instanceRef, a reference out of the same table, instead of calling
getDimensionafter a build and returning the empty answer that told the teleport there
is no such dimension. - A custom dimension is allocated an id from 1000, not from 3. Everything above was
necessary and none of it was sufficient: with the instance built, placed in the registry
and handed back by the detours, a teleport still left the player standing where they
were. The number was the problem.VanillaDimensions::Undefined()sits just past the
vanilla three, and the engine compares against it to mean no dimension, so a dimension
holding that number is read as absent by every path that makes the comparison:
DimensionRegistry::registerDimensionstored nothing for it,getDimensioncame back
empty, andTeleportCommand::computeTargetdropped theChangeDimensionRequest. Writing
blocks through aBlockSourcetaken straight out of the registry never makes that
comparison, which is why the id looked usable for everything except entering it. Ids are
now allocated fromnative::firstCustomDimensionId, 1000, which is where the engine's own
script-api registration allocates from and where the ids in a save written by
MoreDimensions 0.14 come from. Adimension_config.jsonentry below that floor keeps its
name and payload and is reallocated on the next registration, with a line saying that
anything built under the old number stays in the save and will not appear in the new
dimension. - The engine registers its own dimensions again, and this host no longer puts one into
the registry by hand. Entering a dimension registered that way fastfailed the server on
a chunk worker thread withFAST_FAIL_GUARD_ICALL_CHECK_FAILURE, an indirect call
through a pointer that was never set:DimensionRegistry::registerDimensionis the
engine's own entry point and does more than store a pointer, so aDimensionplaced
beside it is one the engine never finished wiring. A registration the engine declines is
now reported as a failed registration and the dimension is unavailable, which is a
server that says so instead of one that crashes minutes later. For the same reason the
threegetOrCreateDimensiondetours calloriginfirst and build only when it comes
back with nothing: the evidence that the engine could not create these dimensions was
gathered while they were numbered 3, where every engine path refuses on principle. getDimensionanswers for a dimension this host registered. It is a plain lookup and
every part of the engine uses it, and it resolves through the engine name table, which has
no row for a dimension registered from outside: it answered empty for one that was
registered and running, so whatever asked walked into nothing. That is the shape of every
crash in this series, on the tick and on the chunk workers alike, and the thing that
proves it is a void dimension crashing exactly like one with terrain, at the same
instruction, with no generator of the engine's involved at all.DimensionManager:: getDimensionandLevel::getDimensionnow fall back to the host's ledger after the
engine's own answer, the same order thegetOrCreateDimensiondetours use, reading the
ledger directly so a hook cannot call back into itself.- The engine's own terrain generators are used again, which is what an overworld, nether
or end template asks for. They had been replaced with a void carrying the same sky,
because entering such a dimension took the server down every time it was tried. That
reason was wrong: the fault was this host destroying aDimensionthe engine was still
using, a void dimension died at the same instruction with no generator involved, and it
is fixed. What the fallback cost meanwhile was the whole point of those three templates,
which arrived as empty voids.engine_terrain: 0bin a dimension's terrain turns the
engine generator off again, for an operator who meets a fault in engine code this host
cannot repair and still needs the dimension to open. A dimension that generated void
chunks under the old default keeps them; only ground it has not reached yet comes out as
terrain, so a test world is worth recreating. - The end and nether generators are handed their own biome. Both were given the level's
biome override, which is what vanilla passes and which on a normal level is empty, so the
lookup answered with nothing and the generator kept it.OverworldGeneratorMultinoise
builds its own biome source and does not care; these two are each written for one biome.
A server generating end terrain read a pointer of all ones inside the column loop, at the
y the registers put at the top of the dimension, which is what a biome that was never
there looks like once something walks it. The vanilla name is used when the level names
nothing, and when neither is in the registry the dimension is generated as a void with a
line saying so, rather than handing the generator nothing. - A dimension keeps the id it already holds. The suggestion stepped over every id in
the caller's ledger, including the dimension's own: one recorded as 1000 was handed 1001,
the record was corrected to 1001, and the next boot handed it 1000 again. It changed id
on every start, and the chunks it had written stayed behind under the number it no longer
had. The id from the record is now preferred when nothing else holds it. - A dimension whose terrain is an engine generator gets that generator's own height.
These generators are not parameterised by the dimension they are handed:TheEndGenerator
carries a fixed 16x16x128 block buffer and each of them indexes columns from the bottom
of the dimension it was written for. A custom end dimension running from -64 instead of 0
had it write past what it allocated, and the server died on the tick after a player
entered, with the end sky already drawn and no blocks in it.spec::dimensionHeightOf
now answers with the generator's range for a native terrain, so theDimension, the
definition and the height check all get the same number, and says so in the log when it
differs from what the spec asked for. A spec's own height still governs terrain that
comes from this host. - A generator is told about structure sets only when its structure features went in.
ChunkGeneratorStructureState::createNormalfillsmPossibleStructuresfrom the level's
structure sets, and placing one means looking the matching feature up in the generator's
ownStructureFeatureRegistry. That registry is filled by three engine functions this
host reaches by symbol, and on 26.40 they are not there: the generator was told about
every structure set in the game while its feature registry stayed empty, and the first
tick after a player entered called through a pointer nothing had set. With no features
the state is nowcreateFlatwith an empty list, which says there are no structures to
place, and the log says the dimension generates without structures. - The factory closure builds the dimension with the id the engine's definition holds,
not the number indimension_config.json. With the closure finally being called, its
first build of a boot tookshared->idfrom the record, which is the previous boot's
answer; the engine had allocated a different number, so the object said 1001 while it
was registered under 1000, the read-back reported 1001, the record was "corrected" to
1001, and the first player to ask had a second instance built and registered under 1001.
The level tick died with two instances for one name. The closure now asks the definition
group first, which is what the registration is happening against, andbuildAndRegister
refuses to register an object under a key that is not its own id. - The host's factory closure is bound after the engine registers its own, so the
dimension the engine builds is the host's class. Every build so far has logged "built
through the factory" and never the line the closure itself prints when it runs, which
meansDimensionFactory::createwas not finding it:_registerCustomDimensionWithFactory,
called after the closure was put in the map, registers the engine's own factory for the
name over whatever is there, and what that built for the definition was a vanilla-class
dimension with the definition's generator, neverSpecDimension. That dimension was
registered under the host's id, handed to a player, and fastfailed a chunk worker a third
of a second later. The closure is now bound again right after that call, and the "is
building" line in the log is what proves the host's class is the one being built. On
26.20serverRegisterCustomDimensionused the closure already in the map, which is why
the previous host never had to do this. - The default bottom of a custom dimension is the vanilla bottom, -64, not -512. The
-512 was a workaround: a client with no definition of its own to go by fell back to the
largest possible world and requested subchunks -32..-24, which a server validating
against -64 refused. With the definition now carrying the height that fallback has
nothing left to do, and the workaround had a bill of its own: a dimension 52 subchunks
tall is outside anything the engine ships, and across every crash on entering one the
height was the single thing about the dimension that never varied. A spec may still ask
for more; nothing forbids it and nothing vouches for it. - The engine's dimension definition is given the height and generator the dimension is
actually built with._registerCustomDimensionWithDimensionDefinitionGrouptakes a name
and an id and nothing else, so the definition carried the engine's defaults while the
Dimensionwas constructed from the stored spec, and the two are read by different parts
of the engine. A server that entered such a dimension died on a chunk worker a third of a
second later, every time, on an indirect call through a value that is not a function,
which is what a shape written into an allocation sized for a different shape looks like
once something walks it. The definition entry is now reached through the engine's own
iterator, checked against the id just registered so the fields are known to be where the
header says, written, and read back; both the engine's original values and the spec's are
logged either way. - A custom dimension no longer pretends to be the overworld to upgrade a chunk.
SpecDimensionimplementedlevelChunkNeedsUpgrade,upgradeLevelChunk,fixWallChunk
and_upgradeOldLimboEntitybyreinterpret_cast-ing itself toOverworldDimensionand
calling that class's exported thunks, on the reasoning thatOverworldDimensiondeclares
no data member and the two layouts therefore coincide. With the registration finally
working end to end, a server fastfailed on a chunk worker inside those bodies a third of
a second after a player entered the dimension, onFAST_FAIL_GUARD_ICALL_CHECK_FAILURE:
an indirect call through a value that is not a function. Whatever those bodies read at
that offset, it is not what this object holds there. The four now do nothing, which is
also the right answer on its own terms: these upgrade a chunk written by an older BDS,
and a dimension of this kind is created by the version that is running, so every chunk in
it was written by that version. TheWorldGeneratorthat adds vanilla structure features
through resolved symbols is untouched, but each of its three paths now logs a line, so
the next report says whether it ran. - The dimension factory hands back the instance in use instead of building a second
one. The closure this host puts inDimensionFactory::mFactoryMapis called by the
engine too, on paths the host never sees and without a line in the log, and it built a
newDimensionevery time; whatever the engine then did with it ended in an assignment
over the object the level, the players and the chunk threads were holding. It now returns
the live instance when there is one, which makes that assignment a no-op, and it logs
every call, so the log shows who is asking for a dimension to be built. - A dimension this host registered is built once, because the host remembers it. This
was the fault behind every crash in this series.DimensionManager::getDimensionresolves
aDimensionTypethrough the engine name table, and nothing exported writes a row there
for a dimension registered from outside, so it answers empty for one that is registered
and running. With no memory of its own, the host read that as "not registered" on every
resolution and built another, andDimensionRegistry::registerDimensionassigns into its
map: each new instance replaced the one the level, the players and the chunk threads were
holding, and a chunk worker fastfailed on a call through a pointer inside the destroyed
object. A live server built one dimension six times in three seconds, then began logging a
refusal thirty times a second until it died. The instanceregisterDimensionreturns is
now kept in a host-side ledger ofWeakRef<Dimension>by id, consulted right after the
engine's own lookup and dropped when the dimension is retired or the engine lets it go. - The registry table is no longer read for an answer, only reported. It is reached
through a header-declared offset into a private field, and the first server to print it
read two entries with ids 59024 and 0, which the engine's dimension table cannot contain:
that offset does not describe the running binary, so aDimension*taken out of it is a
pointer into whatever the memory really is. Every decision is back on
DimensionManager::getDimension, on whatregisterDimensionhands back and on the host's
own ledger, and the field is not touched at all any more. - A dimension is registered with the engine under
namespace:name. The engine's own
registration path takes a qualified name, and MoreDimensions, the only implementation
known to work on this generation, rejects anything else before it reaches the engine. A
bare name is now qualified withpier:at the boundary that talks to the engine, which
is also the key the factory map uses becauseDimensionFactory::createis called with
the name the engine knows. The ledger,dimension_config.jsonand every mod-facing call
keep the name the caller chose. A dimension already registered under a bare name is a new
dimension under the qualified one and gets a new id. - An id already spoken for in
dimension_config.jsonis never suggested to the engine.
The suggestion walked the definition group, which holds only what the current boot has
registered so far, so a dimension named in the config but not yet registered was
invisible and its number could be handed to another dimension. A collision already in the
config now names both sides and says that the chunks of the loser are not reachable.
Added
service_caller: a provider can ask who is calling it. APierServiceCbreceives a
request and nothing about its sender, so a provider keying anything on a name inside
the request was trusting the request. The new slot, appended and struct_size-gated
(the ABI stays at v2), sinks the manifest name of the mod whoseservice_callis on
the stack; nested calls report the innermost one, and outside a callback or for a call
made without a handle the sink is not called.levilamina::service::caller()is the
Rust side. The first consumer is the RSW permission manager, which binds a wire owner
to the mod that first announced it and refuses the name from any other mod.