STORAGE · RAWMEMORY SEGMENT LIFECYCLE

How to Use RawMemory Segments Safely in Screeps

Validate segment IDs, activate one consolidated set for the next tick, distinguish unavailable from empty data, parse versioned string payloads safely, merge updates, and avoid multiple setActiveSegments calls overwriting each other.

Verification statusChinese source article: Reviewed in full · Official docs: Checked — segment IDs 0–99, 100 KB per segment, up to 10 active, next-tick activation and last-call override · Timing boundary: setActiveSegments() schedules availability for the next tick; same-tick reads are not promised

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official docs
Checked — segment IDs 0–99, 100 KB per segment, up to 10 active, next-tick activation and last-call override
Timing boundary
setActiveSegments() schedules availability for the next tick; same-tick reads are not promised
Coordination boundary
One manager makes the final activation call so later modules cannot silently replace the requested set
JavaScript syntax
Passed
Offline lifecycle review
Passed — invalid IDs, deduplication, 10-segment cap, waiting, unavailable, empty, corrupt, versioned and merged payload states
Screeps Console test
Pending
Live segment activation, persistence and multi-module test
Pending
Last verified
July 25, 2026

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

RuleOfficial boundaryApplication consequence
Segment IDs0 through 99Reject negative, fractional, and greater-than-99 IDs
Active segmentsUp to 10Prioritize and deduplicate requests
Segment size100 KB eachMeasure encoded string bytes before writing
Activation timingAvailable on the next tickUse an explicit request/read lifecycle
Repeated activation callsThe last call defines the next setCall 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 undefined distinct 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.

Official documentation

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide is rewritten for a focused search intent while preserving the technical scope and verification boundaries of its Chinese source. Live-room evidence is claimed only when the verification record says it exists.