Quick answer
RawMemory.setActiveSegments(ids) schedules up to 10 valid segment IDs for the next tick. On the next tick, read active strings from RawMemory.segments[id]. An undefined value means unavailable, not empty. Store strings only, validate JSON and schema versions, and let one central manager make the final activation call because a later call in the same tick replaces the earlier request.
Official segment limits
| Rule | Official boundary | Application consequence |
|---|---|---|
| Segment IDs | 0 through 99 | Reject negative, fractional, and greater-than-99 IDs |
| Active segments | Up to 10 | Prioritize and deduplicate requests |
| Segment size | 100 KB each | Measure encoded string bytes before writing |
| Activation timing | Available on the next tick | Use an explicit request/read lifecycle |
| Repeated activation calls | The last call defines the next set | Call once from a central manager |
Segments are persistent string storage. They do not behave like ordinary synchronous object properties that can be activated and read immediately in one function call.
Activation and read timeline
tick 500
modules request segments 2, 4, and 7
manager consolidates [2, 4, 7]
manager calls RawMemory.setActiveSegments([2, 4, 7])
tick 501
RawMemory.segments[2], [4], and [7] may be available
read and validate payloads
write updated strings if needed
collect the next activation request
Writing RawMemory.segments[id] modifies the segment string. Activation controls which segments are available to read and write in the current tick.
Unavailable is not empty
function describeSegmentAvailability(id) {
const raw = RawMemory.segments[id];
if (raw === undefined) {
return {
status: 'segment-unavailable',
id,
raw: null
};
}
if (raw === '') {
return {
status: 'segment-empty',
id,
raw
};
}
return {
status: 'segment-readable',
id,
raw
};
}
Do not initialize an unavailable segment to an empty object. That can overwrite data after a scheduling mistake or when another module replaced the active set.
Use one activation manager
Modules should request IDs from your manager rather than call setActiveSegments() directly.
function getSegmentRequestSet() {
global.segmentRequests ??= new Set();
return global.segmentRequests;
}
function requestSegment(id) {
if (!Number.isInteger(id) || id < 0 || id > 99) {
return false;
}
getSegmentRequestSet().add(id);
return true;
}
The request set is disposable global coordination for the current tick. Persistent job state belongs in Memory or a segment payload.
Validate and deduplicate IDs
function normalizeSegmentIds(ids, limit = 10) {
if (!Array.isArray(ids)) {
return [];
}
const normalized = [...new Set(
ids.filter(
id => Number.isInteger(id)
&& id >= 0
&& id <= 99
)
)].sort((a, b) => a - b);
return normalized.slice(0, Math.min(10, limit));
}
Silently slicing is acceptable only after your scheduler has assigned priorities. A production manager should report which requests were deferred rather than pretending every module received its segment.
Parse a versioned payload safely
function parseSegmentJson(raw, expectedVersion) {
if (raw === undefined) {
return {
status: 'unavailable',
value: null
};
}
if (raw === '') {
return {
status: 'empty',
value: null
};
}
let parsed;
try {
parsed = JSON.parse(raw);
} catch (error) {
return {
status: 'invalid-json',
value: null,
error: error instanceof Error
? error.message
: String(error)
};
}
if (
!parsed
|| parsed.version !== expectedVersion
|| typeof parsed.data !== 'object'
) {
return {
status: 'schema-mismatch',
value: null
};
}
return {
status: 'ready',
value: parsed.data
};
}
A schema mismatch is not automatically corrupt data. It may require a migration. Preserve the old raw string until the migration or recovery policy succeeds.
Serialize and enforce the byte limit
function encodeSegmentPayload(version, data) {
const raw = JSON.stringify({
version,
writtenAt: Game.time,
data
});
const bytes = new TextEncoder().encode(raw).length;
if (bytes > 100 * 1024) {
return {
ok: false,
status: 'segment-too-large',
raw: null,
bytes
};
}
return {
ok: true,
status: 'encoded',
raw,
bytes
};
}
The official boundary is 100 KB. Byte length is safer than JavaScript character count because non-ASCII characters can use multiple encoded bytes.
Complete segment request manager
State impact: the manager writes one activation request for the next tick and records a compact plan in Memory. It does not modify segment payloads.
function getSegmentManager() {
global.segmentManager ??= {
requested: new Map()
};
return global.segmentManager;
}
function requestActiveSegment(id, priority = 0) {
if (
!Number.isInteger(id)
|| id < 0
|| id > 99
|| !Number.isFinite(priority)
) {
return false;
}
const manager = getSegmentManager();
const previous = manager.requested.get(id);
manager.requested.set(
id,
previous === undefined
? priority
: Math.max(previous, priority)
);
return true;
}
function finalizeSegmentActivation() {
const manager = getSegmentManager();
const ranked = [...manager.requested.entries()]
.sort(
(a, b) => b[1] - a[1] || a[0] - b[0]
);
const active = ranked
.slice(0, 10)
.map(([id]) => id);
const deferred = ranked
.slice(10)
.map(([id]) => id);
RawMemory.setActiveSegments(active);
Memory.segmentPlan = {
requestedAt: Game.time,
activeNextTick: active,
deferred
};
manager.requested.clear();
return Memory.segmentPlan;
}
Call finalizeSegmentActivation() exactly once after every module has submitted requests. A priority policy prevents incidental module order from deciding which data disappears.
Complete read, merge, and write workflow
State impact: this workflow reads one active segment and may replace its string with a merged versioned payload.
function readVersionedSegment(id, version) {
const raw = RawMemory.segments[id];
if (raw === undefined) {
return {
status: 'unavailable',
data: null
};
}
if (raw === '') {
return {
status: 'empty',
data: {}
};
}
try {
const parsed = JSON.parse(raw);
if (
parsed?.version !== version
|| !parsed.data
|| typeof parsed.data !== 'object'
|| Array.isArray(parsed.data)
) {
return {
status: 'schema-mismatch',
data: null
};
}
return {
status: 'ready',
data: parsed.data
};
} catch (error) {
return {
status: 'invalid-json',
data: null,
error: error instanceof Error
? error.message
: String(error)
};
}
}
function mergeAndWriteSegment(
id,
version,
patch
) {
const current = readVersionedSegment(id, version);
if (
current.status !== 'ready'
&& current.status !== 'empty'
) {
return {
ok: false,
status: current.status
};
}
const data = {
...current.data,
...patch
};
const raw = JSON.stringify({
version,
writtenAt: Game.time,
data
});
const bytes = new TextEncoder().encode(raw).length;
if (bytes > 100 * 1024) {
return {
ok: false,
status: 'segment-too-large',
bytes
};
}
RawMemory.segments[id] = raw;
return {
ok: true,
status: 'segment-written',
bytes,
keys: Object.keys(data).length
};
}
The shallow merge suits flat namespaces. Nested records need an explicit merge contract to avoid replacing unrelated fields.
Why multiple activation calls are dangerous
RawMemory.setActiveSegments([1, 2]);
RawMemory.setActiveSegments([7]);
// The second call defines the next active set.
// Segments 1 and 2 are not preserved automatically.
This is why libraries should not call the API independently. Request IDs centrally, inspect deferred work, and perform one final call.
Use double buffering for critical updates
function chooseBuffer(activeIndex) {
return activeIndex === 0
? { readId: 20, writeId: 21, nextIndex: 1 }
: { readId: 21, writeId: 20, nextIndex: 0 };
}
A double-buffer design writes a complete new payload to the inactive segment, validates it, then flips a small persistent pointer. It can reduce damage from partial application logic, but it doubles storage and activation demand. The actual atomicity and recovery protocol still need live verification.
Keep public and foreign segments separate
setPublicSegments(), setDefaultPublicSegment(), setActiveForeignSegment(), and RawMemory.foreignSegment support sharing. They add identity, trust, schema, availability, and size concerns. Do not mix untrusted foreign JSON directly into your private write path.
function parseForeignSegment(foreign) {
if (
!foreign
|| typeof foreign.username !== 'string'
|| !Number.isInteger(foreign.id)
|| typeof foreign.data !== 'string'
) {
return null;
}
return {
username: foreign.username,
id: foreign.id,
data: foreign.data
};
}
Debugging checklist
- Validate IDs 0–99.
- Deduplicate requests and cap the active set at 10.
- Make one final
setActiveSegments()call. - Record which IDs are expected next tick.
- Keep
undefineddistinct from an empty string. - Parse JSON inside
try/catch. - Validate schema version before using data.
- Measure encoded bytes before writing.
- Do not overwrite unavailable or corrupt data automatically.
- Define merge and migration contracts.
- Keep public or foreign data outside trusted private state.
Scope and next steps
This guide does not implement compression, checksums, full migrations, a multi-shard storage service, public-segment discovery, foreign-segment retries, or a proven atomic commit protocol. Console and live multi-tick segment verification remain Pending.
Frequently asked questions
Are Segments normal JavaScript objects?
No. They are persistent strings exposed through an activation lifecycle.
Can I activate all 100 segments?
No. Up to 10 can be active at once.
Should undefined create a new empty payload?
No. First distinguish unavailable from an active empty string.
Why record activeNextTick in Memory?
It provides a durable expectation that survives a global reset and helps diagnose scheduling conflicts.