Quick answer
Define required Flag names in code, resolve each one directly from Game.flags[name], validate every custom flag.memory field, recover an optional target ID with Game.getObjectById(), and use only a narrow room-local fallback when the ID is missing or stale. Record configuration status separately. Do not choose the first Flag or mutate Flags as a side effect of reading configuration.
Use exact Flag names
const FLAG_CONFIG = {
remoteMine: 'RemoteMine_W2N2',
rally: 'Rally_W1N1'
};
function getNamedFlag(name) {
return typeof name === 'string'
? Game.flags[name] || null
: null;
}
Names are a project contract. Document them like role names and Memory schema fields.
Treat Flag memory as untrusted input
const ALLOWED_REMOTE_MODES = new Set([
'observe',
'harvest',
'pause'
]);
function readRemoteFlagMemory(flag) {
if (!flag) {
return { valid: false, reason: 'flag-missing' };
}
const mode = flag.memory?.mode;
const sourceId = flag.memory?.sourceId;
if (!ALLOWED_REMOTE_MODES.has(mode)) {
return { valid: false, reason: 'invalid-mode' };
}
if (
sourceId !== undefined
&& typeof sourceId !== 'string'
) {
return { valid: false, reason: 'invalid-source-id' };
}
return {
valid: true,
reason: 'valid',
mode,
sourceId: sourceId || null
};
}
A misspelled value should fail closed rather than silently selecting another mission.
Prefer a configured object ID
function recoverConfiguredSource(sourceId) {
if (typeof sourceId !== 'string') {
return null;
}
const object = Game.getObjectById(sourceId);
return object
&& object.energy !== undefined
&& object.energyCapacity !== undefined
? object
: null;
}
Recover the current object each tick. An ID can become stale after a typo, object replacement or changed mission.
Use a narrow local fallback
function selectSourceNearFlag(flag) {
if (!flag?.room) {
return null;
}
return flag.room.find(FIND_SOURCES)
.map(source => ({
source,
range: flag.pos.getRangeTo(source)
}))
.sort((left, right) =>
left.range - right.range
|| left.source.id.localeCompare(right.source.id)
)[0]?.source || null;
}
The fallback is limited to the visible Flag room and uses stable range and ID ordering. It does not rewrite the configured ID automatically.
Complete Flag configuration reader
function readRemoteMiningConfiguration() {
const flagName = FLAG_CONFIG.remoteMine;
const flag = getNamedFlag(flagName);
const memory = readRemoteFlagMemory(flag);
if (!flag || !memory.valid) {
return {
ready: false,
reason: memory.reason,
flagName
};
}
if (memory.mode === 'pause') {
return {
ready: false,
reason: 'mission-paused',
flagName,
roomName: flag.pos.roomName
};
}
const configuredSource = recoverConfiguredSource(
memory.sourceId
);
const fallbackSource = configuredSource
? null
: selectSourceNearFlag(flag);
const source = configuredSource || fallbackSource;
if (!source) {
return {
ready: false,
reason: flag.room
? 'source-not-found'
: 'flag-room-not-visible',
flagName,
roomName: flag.pos.roomName,
configuredSourceId: memory.sourceId
};
}
return {
ready: true,
reason: 'ready',
flagName,
mode: memory.mode,
roomName: flag.pos.roomName,
x: flag.pos.x,
y: flag.pos.y,
sourceId: source.id,
sourceSelection: configuredSource
? 'configured-id'
: 'nearest-visible-fallback'
};
}
function recordFlagConfigurationStatus(key, status) {
Memory.configurationStatus ??= {};
Memory.configurationStatus.flags ??= {};
Memory.configurationStatus.flags[key] = {
checkedAt: Game.time,
...status
};
}
module.exports.loop = function () {
const configuration = readRemoteMiningConfiguration();
recordFlagConfigurationStatus('remoteMine', configuration);
if (!configuration.ready || Game.time % 100 === 0) {
console.log(JSON.stringify({
type: 'flag-configuration-status',
...configuration
}));
}
};
Do not use the first Flag or Source
const unsafeFlag = Object.values(Game.flags)[0];
const unsafeSource = unsafeFlag?.room?.find(FIND_SOURCES)[0];
Object order is not a mission contract. Exact names and stable target selection prevent unrelated additions from changing behavior.
Report missing and stale configuration
function summarizeRequiredFlags(names) {
return names.map(name => {
const flag = Game.flags[name];
return flag
? {
name,
present: true,
roomName: flag.pos.roomName,
x: flag.pos.x,
y: flag.pos.y
}
: { name, present: false };
});
}
Expose the exact missing name and stale ID instead of silently switching missions.
Debugging checklist
- Document every required Flag name.
- Resolve Flags by exact name.
- Validate custom Flag memory.
- Store object IDs, not live objects.
- Recover IDs each tick.
- Use a narrow deterministic fallback.
- Do not select the first Flag or Source.
- Do not mutate Flags while reading configuration.
- Record missing and stale state.
Scope and next steps
This guide does not create, move, recolor or remove Flags, implement remote harvesting, coordinate invisible rooms or define diplomacy. Continue with module organization.
Frequently asked questions
Why allow a fallback?
It keeps visible-room configuration diagnosable when an ID is missing, while clearly marking that the configured identity was not used.
Should the fallback save the new Source ID?
Not automatically. Review the reason for the missing ID before changing persistent configuration.
Can Flag memory be shared across modules?
Yes, but one documented reader should validate it so different modules do not interpret the same fields differently.