STORAGE · SEGMENT ACTIVATION LIFECYCLE

Use RawMemory Segments Without Same-Tick Assumptions

CURATED PATH

These links are maintained for a concrete prerequisite, failure mode, debugging step, or next lesson instead of generic topic similarity.

Memory / Cross-boundary

Recommended next steps

Use InterShardMemory when your own state must cross shardsCross-shard branch
Verification statusChinese source article: Reviewed in full · Official documentation: Checked August 14, 2026 — RawMemory.segments, setActiveSegments(), Segment IDs 0–99, up to 10 active Segments, and the documented 100 KB boundary · Official implementation source: Checked current screeps/driver runtime — saved Segment values must be strings, no more than 10 Segment keys may be saved in one tick, and the current size guard compares JavaScript string .length with 100 * 1024

VERIFICATION

Evidence and test status

Chinese source article
Reviewed in full
Official documentation
Checked August 14, 2026 — RawMemory.segments, setActiveSegments(), Segment IDs 0–99, up to 10 active Segments, and the documented 100 KB boundary
Official implementation source
Checked current screeps/driver runtime — saved Segment values must be strings, no more than 10 Segment keys may be saved in one tick, and the current size guard compares JavaScript string .length with 100 * 1024
Static code review
Passed — next-tick activation, last-call-wins coordination, late-request rejection, unavailable/empty separation, schema validation, and current-driver length preflight reviewed
Screeps Console test
Pending
Live multi-tick verification
Pending
Live boundary test
Pending — no live Segment activation, non-ASCII payload, persistence, or oversize write trace was collected
Evidence level
Official documentation, official implementation source, repository review, JavaScript review and static lifecycle analysis only
Last editorial review
August 14, 2026

Use this guide when

Use this page when data must persist outside ordinary Memory and your code can follow a request-now, read-later lifecycle. The important boundary is not serialization syntax; it is activation timing. Calling RawMemory.setActiveSegments() does not make those Segments readable in the same tick.

A safe manager therefore separates three concerns: which IDs should be active next tick, what data is actually available this tick, and whether a replacement string is safe to write.

The boundaries that shape the design

BoundaryEngineering consequence
Segment IDs are 0–99Reject negative, fractional, and greater-than-99 IDs before scheduling.
Up to 10 Segments may be activeRank requests and report deferred IDs instead of silently dropping them.
Activation applies on the next tickDo not treat a same-tick request as proof that RawMemory.segments[id] is available.
Later setActiveSegments() calls replace earlier plansUse one coordinator and one final activation call.
Docs describe a 100 KB Segment limitThe current official driver enforces string.length <= 100 * 1024; do not substitute a UTF-8 byte counter and call it the server rule.

The last row is deliberately precise. The API documentation uses “100 KB,” while the current official screeps/driver implementation checks JavaScript string .length. A custom or future driver can differ, so code that targets a private server should verify that server's implementation instead of assuming this implementation detail is universal.

Request on one tick, read on a later tick

tick N
feature modules request Segment 7
one manager calls RawMemory.setActiveSegments([7])

tick N + 1
RawMemory.segments[7] may be available
read and validate the current string
write a replacement only from a state you understand
schedule the next active set

undefined and '' are not equivalent. undefined means the Segment is not available to the current script. An empty string means the active Segment currently contains no data. Collapsing both into “empty” can turn an activation bug into destructive overwrite.

Collect requests and finalize activation exactly once

function getSegmentCoordinator() {
  global.segmentCoordinator ??= {
    requested: new Map(),
    finalizedAt: null,
    plan: null
  };

  return global.segmentCoordinator;
}

function requestSegment(id, priority = 0) {
  const coordinator = getSegmentCoordinator();

  if (coordinator.finalizedAt === Game.time) {
    return {
      accepted: false,
      status: 'activation-already-finalized'
    };
  }

  if (
    !Number.isInteger(id)
    || id < 0
    || id > 99
    || !Number.isFinite(priority)
  ) {
    return {
      accepted: false,
      status: 'segment-request-invalid'
    };
  }

  const previous = coordinator.requested.get(id);
  coordinator.requested.set(
    id,
    previous === undefined
      ? priority
      : Math.max(previous, priority)
  );

  return { accepted: true, status: 'segment-requested' };
}

function finalizeSegmentRequests() {
  const coordinator = getSegmentCoordinator();

  if (coordinator.finalizedAt === Game.time) {
    return {
      status: 'activation-already-finalized',
      ...coordinator.plan
    };
  }

  const ranked = [...coordinator.requested.entries()]
    .sort((a, b) => b[1] - a[1] || a[0] - b[0]);
  const activeNextTick = ranked
    .slice(0, 10)
    .map(([id]) => id);
  const deferred = ranked
    .slice(10)
    .map(([id]) => id);

  RawMemory.setActiveSegments(activeNextTick);

  coordinator.finalizedAt = Game.time;
  coordinator.plan = { activeNextTick, deferred };
  coordinator.requested.clear();

  return {
    status: 'activation-scheduled',
    activeNextTick,
    deferred
  };
}

This guard makes repeated same-tick finalization idempotent and exposes requests that arrive too late. It cannot protect code that bypasses the coordinator and calls RawMemory.setActiveSegments() directly, so keep the raw API behind one module boundary.

Distinguish unavailable, empty, corrupt, and ready

function readSegment(id, expectedVersion) {
  const raw = RawMemory.segments[id];

  if (raw === undefined) {
    return { status: 'segment-unavailable', data: null };
  }

  if (raw === '') {
    return { status: 'empty', data: {} };
  }

  try {
    const parsed = JSON.parse(raw);

    if (
      parsed?.version !== expectedVersion
      || !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)
    };
  }
}

A schema mismatch is not automatically corrupt data. Preserve the original string until a migration, rollback, or recovery policy succeeds.

Preflight writes against the current official driver

function writeSegment(id, version, data) {
  if (RawMemory.segments[id] === undefined) {
    return {
      ok: false,
      status: 'segment-unavailable'
    };
  }

  const raw = JSON.stringify({
    version,
    writtenAt: Game.time,
    data
  });

  // Current official screeps/driver boundary.
  // This is JavaScript string length, not UTF-8 encoded bytes.
  const length = raw.length;

  if (length > 100 * 1024) {
    return {
      ok: false,
      status: 'segment-too-large-for-current-driver',
      length
    };
  }

  RawMemory.segments[id] = raw;

  return {
    ok: true,
    status: 'segment-written',
    length
  };
}

Do not use new TextEncoder().encode(raw).length and describe that result as the current Screeps server's enforcement rule. UTF-8 byte length and JavaScript string length diverge for some non-ASCII text. A byte-based application budget can still be useful as your own portability policy, but label it as project policy rather than official driver behavior.

The current driver also validates saved Segment values as strings and rejects saving more than 10 Segment keys in one tick. The safest public-API workflow remains simple: write only Segments that are actually available to this tick and keep the active set coordinated.

A minimal read, merge, write workflow

function updateIntelSegment() {
  const segmentId = 7;
  const version = 1;
  const request = requestSegment(segmentId, 100);

  if (!request.accepted) {
    return { status: request.status };
  }

  const current = readSegment(segmentId, version);
  if (
    current.status !== 'ready'
    && current.status !== 'empty'
  ) {
    return { status: current.status };
  }

  return writeSegment(segmentId, version, {
    ...current.data,
    lastSeenTick: Game.time
  });
}

function runSegmentTick() {
  const update = updateIntelSegment();
  const activation = finalizeSegmentRequests();
  return { update, activation };
}

On a first request, updateIntelSegment() commonly reports segment-unavailable while the finalizer schedules the ID for the next tick. That is an expected lifecycle state, not a reason to initialize or overwrite the Segment.

Failure modes worth logging separately

StatusWhat it actually tells you
segment-request-invalidThe ID or priority failed local validation.
activation-already-finalizedA module requested data after the one activation plan for this tick was already committed.
segment-unavailableThe Segment is not exposed to this tick; do not infer that its persistent contents are empty.
schema-mismatchThe payload is readable JSON but not the version your consumer understands.
segment-too-large-for-current-driverThe serialized JavaScript string exceeds the current official driver's length guard.

How to verify across ticks

Record the requested IDs and Game.time, then inspect availability on the next tick. Test unavailable, empty, valid, invalid-JSON, schema-mismatch, deferred, repeated-finalize, ASCII boundary, and non-ASCII boundary cases separately. This revision contains no fabricated Segment contents, Console transcript, or live persistence trace.

Choose another guide when

Use the Memory guide for small durable application state. Use the global-cache guide when data is disposable and rebuildable after a global reset.

Official documentation and source

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide keeps the technical scope and verification boundaries of its Chinese source. The verification record shows which checks were completed and which runtime observations are still pending.