CONFIGURATION · FLAG AND TARGET BINDING

Fail Closed When a Flag and Its Saved Target Disagree

Verification statusChinese source article: Existing bilingual mapping retained; this English-only pass did not use the Chinese page as live mission evidence · Official documentation: Checked August 18, 2026 — Game.flags, Flag.name, Flag.pos, Flag.room visibility, Flag.memory, Flag.remove(), and Game.getObjectById() · Static code review: Passed — unresolved IDs are classified from visibility of flag.pos.roomName, object type and room identity are checked separately, configured IDs fail closed, and Flag names are not described as mutable

VERIFICATION

Evidence and test status

Chinese source article
Existing bilingual mapping retained; this English-only pass did not use the Chinese page as live mission evidence
Official documentation
Checked August 18, 2026 — Game.flags, Flag.name, Flag.pos, Flag.room visibility, Flag.memory, Flag.remove(), and Game.getObjectById()
Static code review
Passed — unresolved IDs are classified from visibility of flag.pos.roomName, object type and room identity are checked separately, configured IDs fail closed, and Flag names are not described as mutable
Identity boundary
A globally valid object ID is rejected when its type or room disagrees with the named Flag contract
Fallback boundary
Fallback remains explicit and cannot hide an unresolved, wrong-type, or wrong-room configured ID
Mutation boundary
The reader does not move, recolor, remove, recreate, or rewrite the Flag target; the API has no Flag rename operation
Evidence level
Official-documentation review and static code review only; no live Flag migration or remote-vision trace is claimed
Console test pending
Not run in this editorial pass
Screeps Console test
Pending — no real-shard Flag configuration transcript was collected for this revision
Live multi-tick verification pending
No live Flag removal/recreation, room mismatch, unresolved target ID, visibility change, or explicit-fallback sequence was collected for this revision
Last editorial review
August 18, 2026

Use this guide when

Use this workflow when a named Flag defines a mission location and its Memory may also name a game object. The reader must prove that the current object has the expected type and belongs to the Flag room. It must not silently accept a valid ID from another room.

Make the Flag contract explicit

const FLAG_CONTRACTS = {
  remoteMine: {
    flagName: 'RemoteMine_W2N2',
    schemaVersion: 1,
    allowedModes: new Set([
      'observe',
      'harvest',
      'pause'
    ])
  }
};

The Flag name, schema version, mode set, and fallback policy are project configuration. Screeps does not infer those meanings from color, position, or name.

Validate Flag Memory before resolving objects

function readRemoteMineMemory(flag, contract) {
  if (!flag) {
    return {
      valid: false,
      reason: 'flag-missing'
    };
  }

  const value = flag.memory;
  if (!value || typeof value !== 'object') {
    return {
      valid: false,
      reason: 'memory-not-object'
    };
  }

  if (value.version !== contract.schemaVersion) {
    return {
      valid: false,
      reason: 'schema-version-mismatch'
    };
  }

  if (!contract.allowedModes.has(value.mode)) {
    return {
      valid: false,
      reason: 'invalid-mode'
    };
  }

  const sourceId = value.sourceId;
  if (
    sourceId !== undefined
    && (
      typeof sourceId !== 'string'
      || sourceId.length === 0
    )
  ) {
    return {
      valid: false,
      reason: 'invalid-source-id'
    };
  }

  const fallbackPolicy =
    value.fallbackPolicy ?? 'forbid';

  if (
    fallbackPolicy !== 'forbid'
    && fallbackPolicy !==
      'nearest-visible-source'
  ) {
    return {
      valid: false,
      reason: 'invalid-fallback-policy'
    };
  }

  return {
    valid: true,
    reason: 'valid',
    mode: value.mode,
    sourceId: sourceId ?? null,
    fallbackPolicy
  };
}

Missing fallback configuration becomes forbid. That is a deliberate fail-closed default.

Validate type and room binding

function isSourceObject(object) {
  return Boolean(
    object
    && object.pos
    && typeof object.energy === 'number'
    && typeof object.energyCapacity === 'number'
  );
}

function resolveBoundSource(flag, sourceId) {
  const expectedRoomName = flag.pos.roomName;
  const expectedRoomVisible = Boolean(
    Game.rooms[expectedRoomName]
  );
  const object = Game.getObjectById(sourceId);

  if (!object) {
    return {
      status: expectedRoomVisible
        ? 'target-missing-visible-room'
        : 'target-unresolved-no-vision',
      source: null,
      expectedRoomName
    };
  }

  if (!isSourceObject(object)) {
    return {
      status: 'target-wrong-type',
      source: null
    };
  }

  if (object.pos.roomName !== flag.pos.roomName) {
    return {
      status: 'target-room-mismatch',
      source: null,
      actualRoomName: object.pos.roomName
    };
  }

  return {
    status: 'configured-target-ready',
    source: object
  };
}

A globally valid object ID is not automatically valid configuration for this Flag. The room check prevents a stale copy-and-paste from binding the mission to another visible room.

Make fallback a reviewed mode, not an accident

function selectExplicitFallback(flag, config) {
  if (config.sourceId !== null) {
    return {
      status: 'configured-target-required',
      source: null
    };
  }

  if (
    config.fallbackPolicy !==
      'nearest-visible-source'
  ) {
    return {
      status: 'fallback-forbidden',
      source: null
    };
  }

  if (!flag.room) {
    return {
      status: 'fallback-room-not-visible',
      source: null
    };
  }

  const source = flag.room
    .find(FIND_SOURCES)
    .map(candidate => ({
      candidate,
      range: flag.pos.getRangeTo(candidate)
    }))
    .sort((left, right) =>
      left.range - right.range
      || left.candidate.id.localeCompare(
        right.candidate.id
      )
    )[0]?.candidate ?? null;

  return {
    status: source
      ? 'explicit-fallback-ready'
      : 'fallback-source-not-found',
    source
  };
}

Once a sourceId is configured, a missing, wrong-type, or wrong-room target blocks the mission. The fallback does not hide drift in reviewed identity.

Return one explainable configuration state

function readRemoteMineConfiguration() {
  const contract = FLAG_CONTRACTS.remoteMine;
  const flag = Game.flags[contract.flagName] ?? null;
  const config = readRemoteMineMemory(
    flag,
    contract
  );

  if (!config.valid) {
    return {
      ready: false,
      reason: config.reason,
      flagName: contract.flagName
    };
  }

  if (config.mode === 'pause') {
    return {
      ready: false,
      reason: 'mission-paused',
      flagName: flag.name,
      roomName: flag.pos.roomName
    };
  }

  const resolution = config.sourceId
    ? resolveBoundSource(flag, config.sourceId)
    : selectExplicitFallback(flag, config);

  if (!resolution.source) {
    return {
      ready: false,
      reason: resolution.status,
      flagName: flag.name,
      roomName: flag.pos.roomName,
      configuredSourceId: config.sourceId,
      actualRoomName:
        resolution.actualRoomName ?? null
    };
  }

  return {
    ready: true,
    reason: resolution.status,
    flagName: flag.name,
    roomName: flag.pos.roomName,
    mode: config.mode,
    sourceId: resolution.source.id,
    sourceSelection: config.sourceId
      ? 'configured-id'
      : 'explicit-fallback'
  };
}

The caller receives a reason it can log, visualize, or use to pause the mission. The reader does not create, move, recolor, remove, or recreate the Flag. The Flag API does not provide a rename operation.

Write diagnostics only when status changes

function recordConfigurationChange(
  key,
  status
) {
  Memory.configurationStatus ??= {};
  const previous =
    Memory.configurationStatus[key] ?? null;

  const comparable = {
    ready: status.ready,
    reason: status.reason,
    flagName: status.flagName,
    roomName: status.roomName ?? null,
    sourceId: status.sourceId ?? null,
    configuredSourceId:
      status.configuredSourceId ?? null,
    actualRoomName:
      status.actualRoomName ?? null
  };

  const signature = JSON.stringify(comparable);
  if (previous?.signature === signature) {
    return {
      changed: false,
      status: previous
    };
  }

  const next = {
    ...comparable,
    signature,
    changedAt: Game.time
  };
  Memory.configurationStatus[key] = next;

  return {
    changed: true,
    status: next
  };
}

This avoids rewriting the same persistent diagnostic every tick. It does not prove the configuration was tested by live mission behavior.

Distinguish missing vision from invalid identity

StatusMeaningAutomatic fallback?
target-unresolved-no-visionThe object cannot be resolved and the Flag room is not visible.No
target-missing-visible-roomThe expected room is visible but the ID does not resolve.No
target-wrong-typeThe ID resolves to another object kind.No
target-room-mismatchThe object exists in a different room.No
explicit-fallback-readyNo ID was configured and reviewed fallback is enabled.Already explicit

Treat the Flag name as immutable identity

A Flag's name is chosen when the Flag is created and cannot be changed later. If an operator needs a different configuration key, that is a remove-and-create operation, not a rename. Treat the replacement as a new identity boundary: re-check the new Flag's Memory, room, and configured target instead of assuming the old contract moved with the name.

This reader never removes and recreates a Flag to simulate renaming. A configuration migration should be explicit, observable, and separate from normal per-tick reads.

Keep the reader side-effect boundary narrow

The reader may write a separate diagnostic record. It does not alter flag.memory.sourceId after a fallback and does not move the Flag. Review the reason, then change configuration deliberately.

Keep target recovery and Flag policy separate

Use the saved-target guide for a general object-resolution state machine. Use the visibility guide when Game.rooms[roomName] is missing. This page owns the extra policy that a configured target must agree with the named Flag.

Official documentation

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.