Skip to content

Debugging

This is the canonical reference for debugging tools, console helpers, inspect commands, status values, and planned diagnostics in this Screeps bot.

See ../ROADMAP.md for milestone-level debugging and observability plans. See LIVE_API.md for live Screeps API research, console/WebSocket behavior, and the recommended Codex integration architecture.

When adding a new console helper, global command, inspect function, profiler, visualization helper, or debugging utility, update this file in the same pull request as the implementation. Operator-facing debug surfaces drift quickly; keep statuses, intent meanings, console examples, and inspection workflows updated whenever the code changes.

Available Debug Helpers

Debug globals are registered by registerDebugGlobals() in src/debug/index.ts when src/main.ts loads.

Available globals:

  • inspect(name)
  • debug.help(topic?)
  • debug.enable()
  • debug.disable()
  • debug.status()
  • debug.history(limit?)
  • debug.colonies()
  • debug.colony(roomName)
  • debug.creep(name)
  • debug.room(roomName?)
  • debug.cia(roomName?)
  • debug.dossier(roomName)
  • debug.assessment(roomName)
  • debug.ciaEvents(roomName?)
  • debug.spawn(roomName?)
  • debug.spawnPlan(roomName?, role?)
  • debug.spawnOverrides(roomName?)
  • debug.boostedWorkforce(roomName?)
  • debug.setSpawnCount(role, count, duration?, roomName?)
  • debug.setBoostedCount(role, count, duration?, roomName?)
  • debug.clearSpawnCount(role, roomName?)
  • debug.clearSpawnCounts(roomName?)
  • debug.clearBoostedCount(role, roomName?)
  • debug.body(roomName?)
  • debug.gov()
  • debug.approvedLeases()
  • debug.approveLease(workClass, duration?)
  • debug.clearApprovedLease(workClass)
  • debug.economy(roomName?)
  • debug.mineral(roomName?)
  • debug.factory(roomName?)
  • debug.factoryStart(product, amount, roomName?)
  • debug.factoryCancel(roomName?)
  • debug.lab(roomName?)
  • debug.labStart(product, amount, roomName?)
  • debug.labCancel(roomName?)
  • debug.marketQuotes(resource, direction, roomName?)
  • debug.marketBuy(resource, amount, maximumUnitPrice?, roomName?)
  • debug.marketSell(resource, amount, minimumUnitPrice, roomName?)
  • debug.marketDeal(roomName?)
  • debug.marketCancel(roomName?)
  • debug.marketBuyLimit(resource, maximumUnitPrice, minimumCreditReserve?)
  • debug.marketBuyLimits()
  • debug.marketAutonomy(mode?)
  • debug.clearMarketBuyLimit(resource)
  • debug.links(roomName?)
  • debug.logistics(roomName?)
  • debug.logisticsNetwork(roomName?)
  • debug.logisticsNodes(roomName?)
  • debug.logisticsRoutes(roomName?)
  • debug.logisticsRoute(routeId, roomName?)
  • debug.remote(homeRoomName?)
  • debug.remoteOperations(homeRoomName?)
  • debug.remoteOperation(operationIdOrTargetRoomName, homeRoomName?)
  • debug.externalOperations(homeRoomName?)
  • debug.externalOperation(operationIdOrTargetRoomName, homeRoomName?)
  • debug.externalAllocation(homeRoomName?)
  • debug.externalPortfolio(homeRoomName?)
  • debug.externalAuthority(kind?, mode?)
  • debug.externalControl(action, objectiveId, homeRoomName?)
  • debug.deposits(homeRoomName?)
  • debug.depositOperation(homeRoomName?)
  • debug.depositAuthority(mode?)
  • debug.approveDepositOperation(opportunityId)
  • debug.remoteRoads(homeRoomName, targetRoomName?)
  • debug.colonize(targetRoomName, bootstrapperRoomName)
  • debug.colonization(targetRoomName)
  • debug.cancelColonization(targetRoomName)
  • debug.buildOrders()
  • debug.buildOrder(id)
  • debug.pending(roomName?)
  • debug.infrastructureAuthority(mode?)
  • debug.show(id)
  • debug.approve(id)
  • debug.reject(id, reason?)
  • debug.cancel(id, reason?)
  • debug.approveRemoteRoad(id)
  • debug.labCluster(roomName?)
  • debug.reserveLabCluster(roomName, candidateId)
  • debug.releaseLabCluster(roomName)
  • debug.cancelBuildOrder(id, reason?)
  • debug.remoteConstruction(roomName?)
  • debug.clearAllFlags()
  • debug.infrastructurePlan(roomName?, mode?)
  • debug.colonyLayout(roomName?, visualize?)
  • debug.reconsiderPlan(id, reason?)
  • debug.explainPlan(id)
  • debug.terminal(roomName?)
  • debug.maintenance(roomName?)
  • debug.intents()
  • debug.towers()
  • debug.beginLabSnapshot(roomName)
  • debug.labSnapshotStatus()
  • debug.cancelLabSnapshot()
  • debug.clearLabSnapshot()

All helpers return strings so they can be called directly from the Screeps console.

Infrastructure Authority

Normal infrastructure construction authority is a persistent operator policy:

debug.infrastructureAuthority()
debug.infrastructureAuthority("autonomous")
debug.infrastructureAuthority("manual")

autonomous is the default. It authorizes authoritative desired local construction directly and releases retained remote/defense build orders without creating a local operator ticket. It does not bypass placement: the ordinary execution pass still revalidates ownership, RCL and structure limits, construction-site limits, exact coordinates, tile legality, anchors, topology, existing structures/sites, and remote-operation authority.

Changing manual to autonomous releases existing pending normal work in place; it does not create replacement plans, orders, or packages. Stale work is therefore still rejected by execution revalidation. Changing back to manual affects future proposals; work already authorized continues normally.

Remote-road routes remain explicitly manual in both modes. Use debug.pending(), debug.show(id), and debug.approve(id) for those routes and for normal infrastructure while manual authority is active. debug.pending() reports the current authority mode so an empty normal-infrastructure queue is not mistaken for missing planning.

Migrated fixed local structures and local Roads no longer execute through build orders or packages. In manual mode, inspect and approve one exact desired item:

debug.localConstruction("E48N13")
debug.localConstruction("E48N13", "local-road-intent:E48N13:20:21", "approve")
debug.localConstruction("E48N13", "local-road-intent:E48N13:20:21", "cancel")

The approval stores an exact fingerprint only. It does not copy geometry or track construction lifecycle. Fixed-local orders and packages are not consulted.

debug.mineral(roomName?)

Displays the persisted local mineral-operation state plus direct lookups of its assigned mineral, extractor, container, and miner. Output includes mineral type and amount, regeneration ticks, extractor cooldown, mineral/container capacity, desired miner count, CPU population mode, economy eligibility, and the concise policy reason. When native Storage allocation is active, it also reports density, target/stored/remaining inventory, protected native capacity, actual Storage energy and free capacity, effective energy capacity/fill, regeneration refresh tick, route state, transferable amount, assigned hauler, and the current capacity or policy reason. A separate assignment line explains whether the route is inactive, physically blocked, waiting for inventory or a protected-service hauler, eligible for bounded assignment, or already assigned. It does not rerun population or mineral policy.

debug.mineral()
debug.mineral("E48N13")

The implemented route ends at primary Storage and authorizes only the exact adjacent operation container and native resource. The command does not imply Terminal staging, terminal sends, market behavior, industry, remote minerals, or arbitrary-resource hauling. Spawn-count overrides may pause the miner with 0, but cannot exceed one or bypass infrastructure, economy, workforce-recovery, or CPU safety gates.

Operator-Directed Factory Production

Factory production begins only through an explicit operator command:

debug.factory()
debug.factoryStart("utrium_bar", 1000)
debug.factoryStart("utrium_bar", 1000, "E48N13")
debug.factoryCancel()

debug.factoryStart(product, amount, roomName?) requires a visible owned room, an owned completed Factory, a positive finite amount, and a recipe compatible with the Factory's current level. Recipe output is cycle-sized: a request that is not an exact multiple of the recipe output is normalized upward to the smallest whole number of cycles, and the returned message states both the requested and normalized target. A second order is rejected while production or cancellation cleanup is active.

The room-scoped Factory workflow stages at most the deficits for one recipe cycle from primary Storage. It supports every resource type accepted by the recipe, including energy. One ordinary local hauler at most receives semantic factory-supply or factory-output work; urgent spawn, extension, and tower service remains higher priority, and the final local-service hauler is never assigned optional Factory work. Terminal sourcing, inter-room imports, prerequisite production, market activity, recipe recommendations, and autonomous product selection are not part of this workflow.

debug.factory(roomName?) reports Factory id, level, cooldown, relevant store contents, product, requested and normalized targets, successfully produced and remaining amounts, output per cycle, components, staged and missing next-cycle inputs, lifecycle state, blocker, assigned hauler, last produce() result, and CPU/economy eligibility. Common states are:

  • staging: one cycle's available input deficits are moving from Storage.
  • waiting-materials: one or more required resources are absent from Storage.
  • waiting-cooldown: the full next cycle is staged but the Factory is cooling.
  • paused-cpu: recovery or emergency bucket governance has paused new staging and production without deleting the order.
  • paused-economy: the existing optional-spending policy is closed.
  • paused-factory: a levelled recipe needs a matching active PWR_OPERATE_FACTORY effect.
  • evacuating: finished product is moving to primary Storage.
  • complete: the normalized target was produced through successful OK results and its remaining Factory output was evacuated.

debug.factoryCancel(roomName?) immediately prevents further produce() calls. Existing Factory assignments finish safely; staged recipe inputs, finished product, and tagged carried cargo are returned to primary Storage. Starting a replacement order waits until this bounded cleanup is finished. Cancellation does not delete the compact final operation summary, so debug.factory() can explain what happened.

Operator-Directed UO/LH Chemistry and Market Deals

The bounded reaction pipeline accepts UO and LH orders:

debug.labStart("UO", 1000, "E48N13")
debug.labStart("LH", 360, "E48N13")
debug.lab("E48N13")
debug.labCancel("E48N13")

The start command requires owned Storage and at least three mutually compatible Labs. It assigns two stable reagent Labs and every compatible output Lab, normalizes output to five-unit reaction increments, and counts only successful reaction intents toward the requested batch. Ordinary haulers stage exact remaining reagent deficits and bank product when an output reaches 500 units, fills, or the order finishes. UO consumes U/O; LH consumes L/H. One industry transfer job may be active per hauler, urgent energy work preempts new collection, and the final local-service hauler remains protected. While authorized work is active, the population plan temporarily maintains a second local hauler for bounded industry transfers; CPU or economy emergency and active defense suppress that additional demand. Cancellation stops reactions and evacuates assigned minerals.

For the configured first hub, an active E48N13 order derives its missing O from live Storage, Terminal, and reagent-Lab inventory. E48N14 stages at most that shortfall and sends bounded 1,000-unit Terminal transfers while preserving its Terminal energy target. Authorized chemistry and Market work stages energy from Storage up to the target plus the currently calculable transaction cost while preserving the room's minimum operating reserve. This is not general room balancing or arbitrary resource distribution. Dynamic Lab optimization is deferred; assignment IDs remain stable for the order and cannot change until cleanup empties them.

Immediate Market deals use existing counterparties rather than persistent exchange orders:

debug.marketQuotes("utrium_bar", "sell", "E48N13")
debug.marketSell("utrium_bar", 1000, 0.8, "E48N13")
debug.marketBuyLimit("LH", 1.25, 10000)
debug.marketBuy("LH", 1000, undefined, "E48N13")
debug.marketDeal("E48N13")
debug.marketCancel("E48N13")

Sales stage only the unsold amount. Purchases require either an explicit maximum price or a stored buy limit; stored limits also preserve the configured credit reserve. Deals are capped at 1,000 units per execution, preserve Terminal target energy, and refresh a missing/invalid counterparty no more often than every 25 ticks. Purchased resources return to Storage. Explicit operator directives may run while optional-work recovery or reopening admission is closed. CPU governor emergency and economy emergency pause reactions, material staging, mineral authorization, Terminal sends, and Market execution without deleting directives. No persistent Market orders, repricing, or speculative purchasing are included. Narrow autonomous selling is documented in docs/MARKET_AUTONOMY.md; it applies only to regenerated native mineral surplus and reuses these immediate-deal controls.

Inspect the current surplus evaluation or switch persistent authority:

debug.marketAutonomy()
debug.marketAutonomy("manual")
debug.marketAutonomy("autonomous")

The inspection explains native-target eligibility, protected and committed inventory, executable buy-order prices, normalized path values, the selected path and gross advantage, processing-premium acceptance, safety gates, and the active Factory/Lab/Market action. It also reports the plan's last material progress tick/age and distinguishes preempted boost handoff from stale no-progress cancellation. Both terminal states wait for normal current-state reevaluation; neither resumes the old price or resource assumption. Manual mode retains recommendations without starting new autonomous work.

Operator-Directed Boosted Workforce

Patch 2 adds sibling workforce intent without changing final-count override semantics:

debug.setBoostedCount("builder", 2, 5000, "E48N14")
debug.setBoostedCount("harvester", 1, "forever", "E48N13")
debug.boostedWorkforce()
debug.boostedWorkforce("E48N14")
debug.clearBoostedCount("builder", "E48N14")

Builders are additive: two requested boosted builders are maintained in addition to automatic builder demand. Harvester counts select source-service slots instead; a count cannot exceed the target's visible sources and does not add permanent harvesters. A boosted harvester is committed only when its ordinary source worker enters a conservative replacement window. Uncommitted preparation reserves capacity only while the source remains covered. Once the boosted creep starts spawning, its reservation survives incumbent loss for a bounded spawn, boost, and travel window so an ordinary replacement cannot shoot the handoff gap. A missing or overdue committed creep restores ordinary local replacement authority.

The operator names only role, count, duration, and target. The configured capability list currently selects E48N13, maps builders to LH and harvesters to UO. Builders use the ordinary selected body; UO harvesters use three boosted WORK parts, which harvest 12 energy per tick and saturate a normal source without the two excess WORK parts in the ordinary five-WORK body. Missing UO reuses the bounded Patch 1 reaction order when U and O are ready; otherwise the corresponding stored Market buy limit is required. Missing LH similarly starts a bounded local reaction when enough L and H are already available, falling back to its stored direct-LH buy limit otherwise. A committed boost-preparation slot preempts an active surplus-liquidation plan, whose owner cancels only subordinate liquidation-authored Market, Lab, or Factory work. Explicit operator directives remain authoritative, so provisioning waits with an explainable blocker rather than replacing operator work. Preempted liquidation waits for the ordinary market-autonomy cadence to reevaluate current state and does not resume its old sale automatically.

One compact slot at a time is prepared for spawning/boosting. Ordinary protected haulers clear and fill its assigned output/boost Lab with the exact compound and energy required by the selected body's boostable WORK parts. Compound staging prefers Storage and falls back directly to Terminal inventory, so replacing a completed Market directive cannot strand purchased boost material. A fresh creep keeps its ordinary builder or harvester role while temporary lifecycle memory moves it through spawn, boost, deployment, and target ownership handoff. That temporary memory and its provider slot are removed after handoff.

If delayed provisioning lets an ordinary safety replacement take the originally reserved source, the boosted harvester rechecks the visible target at handoff. It prefers an uncovered source, then the source with the oldest ordinary incumbent, while excluding sources already boosted or reserved by another slot.

Final spawn-count overrides and boosted intent for the same room/role are mutually exclusive and rejected in either order. Expiration or clearing removes only uncommitted preparation; already spawning, boosting, or deploying creeps finish and surviving boosted workers are never killed or recalled. CPU governor emergency, emergency CPU austerity, economy emergency, missing essential provider workforce, and unmet defense demand pause new provisioning. Recovery or reopening closure alone does not. cpu.profile.sections.boostedWorkforce isolates the global lifecycle cost; broad creep discovery is skipped when no provider slots exist, and new preparation discovery is cadence-bounded to five ticks.

The infrastructure planner remains authoritative for the existing factory-core need. Factory commands never place construction sites. If no Factory exists, the start/status command explains that condition.

debug.help() is the discoverability source for this surface. It groups commands by approvals, spawning, infrastructure, remoteOps, logistics, cpu, rooms, intelligence, colonization, snapshots, and general, and marks every command [read] or [write]. Use a category or command name for more detail:

debug.help()
debug.help("remoteOps")
debug.help("approve")
debug.help("setSpawnCount")

The help registry is static metadata. Calling it does not execute commands, scan rooms, recalculate plans, or perform fuzzy business-data searches.

Operator Console

The preferred approval workflow is:

debug.pending()
debug.show(17)
debug.approve(17)

Numeric approval tickets

Every new approval-requiring retained build-order or remote-road revision receives a process-wide monotonically increasing numeric ticket. It is displayed as eight zero-padded decimal digits. Padding is display-only, and exact canonical string IDs remain valid.

A numeric ticket stores only lifecycle kind, room, canonical ID, exact revision, and a fixed-width fingerprint hash. Before dispatch, the operator layer re-reads authoritative Memory and rejects stale revisions or changed topology. References are bounded, closed tickets never reopen, and pending build orders still expire through normal reconciliation.

Migrated local construction does not create a generic ticket. In manual mode it uses the exact fingerprint surface documented under debug.localConstruction(roomName, itemId, action).

Generic approval commands

Use:

debug.pending()
debug.pending("E48N13")
debug.show(17)
debug.show("remoteRoad:E48N13:E48N14:<sourceId>")

debug.approve(17)
debug.reject(17)
debug.reject(17, "poor extension position")
debug.cancel(17)
debug.cancel(17, "operation retired")

debug.pending(roomName?) scans only compact visible or remembered owned-room infrastructure Memory when invoked, sorts current tickets numerically, and returns a bounded readable list. A visibly lost room is excluded and its retained pending references are closed on inspection; explicit remembered non-ownership continues to win after vision disappears instead of reviving a stale colony marker. Each entry shows the numeric and canonical IDs, kind, room, revision, purpose or target, coordinate or road counts, concise state, and a copyable action. An ordinary actionable proposal shows debug.approve(number); expired, blocked, or stale entries show only valid close actions or explain why no numeric action is safe. If the 100-reference store is full, a legacy pending target remains visible as #unassigned with an allocation warning and exact canonical recovery actions instead of disappearing from the list.

debug.show(id) accepts a numeric or exact canonical ID and prints the ticket and current revision/fingerprint validity, lifecycle state, target or bounded coordinates, proposal reason, blockers, approval state, and only the actions that are valid for that lifecycle. It does not dump unrelated room Memory.

Generic commands dispatch to the retained authoritative build-order or remote-road approval operation. Build orders support rejection and cancellation according to their lifecycle rules. Remote roads currently support approval only; generic reject or cancel reports that the operation is unsupported and does not invent a new state. Remote-road approval also revalidates that the stable backing operation still exists and is active or suspended, including when routine road planning was skipped; a retired operation cannot be approved through either a numeric or canonical command. Empty-path routes and routes carrying the planner's blocked fingerprint also fail closed in both the generic resolver and the authoritative manager.

The retained specialized aliases are:

debug.approveRemoteRoad(id)
debug.cancelBuildOrder(id, reason?)

Package-specific approval commands were removed with the package lifecycle.

All generic and specialized mutating approval entry points use the same exact resolver and record one operator-history entry.

Final spawn-count overrides

Spawn-count overrides are deliberately final operator authority. The live decision order is:

automatic economy/logistics demand
RCL and mature-population caps
CPU population caps
hard colony safety floors
operator override
effective desired count

No later code restores a safety floor after the override. Setting harvesters or haulers to zero is permitted and can strand a colony:

debug.spawnPlan()
debug.spawnPlan("E48N13")
debug.spawnPlan("E48N13", "builder")

debug.setSpawnCount("builder", 2)
debug.setSpawnCount("builder", 2, 5000)
debug.setSpawnCount("upgrader", 0, "forever")
debug.setSpawnCount("hauler", 4, 1000, "E48N13")

debug.spawnOverrides()
debug.spawnOverrides("E48N13")
debug.clearSpawnCount("builder")
debug.clearSpawnCount("builder", "E48N13")
debug.clearSpawnCounts()
debug.clearSpawnCounts("E48N13")

builder and harvester overrides collide with sibling boosted-workforce intent because one is a final count and the other needs additive/source-slot semantics. Clear one before setting the other.

Bucket CPU governor

Use debug.gov() for a compact view of the persisted CPU and Memory resource signals plus Launch Control policy:

debug.gov()
debug.help("gov")
debug.help("cpu")

The CPU output includes bucket posture, live bucket, completed-tick utilization EWMA and source, advisory CPU grade, commanded tier, state age, next transition threshold, numeric reopening capacity, capacity bucket trend, autonomous and effective targets, operator override, active lease count, admission cap, and peak-relative bucket loss, plus the persisted reason. It reads the existing snapshot and does not recalculate or mutate the governor.

The appended Memory Governor block reports the last measured incoming serialized RawMemory length, normalized sample growth, growth EWMA, pressure state, evaluation age, last reclamation tick, last reclaimed-item count, and cumulative run/item counters. It reads Memory.memoryGovernor without calling RawMemory.get(), reevaluating pressure, or invoking cleanup. healthy, elevated, and critical are independent of CPU posture and Launch Control leases.

The bucket governor is the sole producer of the tier consumed by managers. Its config-driven bands are:

  • open: remain above 5,000; normal autonomous reopening completion commands the operating tier
  • recovery: command at least constrained; remain until 9,000
  • emergency: enter at 1,500; command critical until 3,000, then recover

Within open posture, capacity zero commands constrained, capacity one commands healthy, and reaching the normal autonomous proven ceiling (currently four) commands abundant. Capacity above four during an operator experiment does not change the governor tier or the cadence of unrelated CPU-throttled work. Launch Control combines proven numeric capacity with the current target instead of relying on a separate managed-descent posture.

Returning to open from recovery does not release all optional work at once. Reopening capacity remains zero for 100 ticks, permits one optional-work lease for a 150-tick observation window, and then exposes each subsequent capacity level only after another 150-tick observation when the charged-CPU EWMA is no higher than 20 CPU per tick. Recovery-cadence periodic work remains deterministically staggered until normal autonomous reopening reaches capacity four. Losing more than 250 bucket points from the latest peak reached at a capacity level backs the ramp down exactly one level. This gives a single 100-tick Launch Control lease enough room to complete inside the 150-tick observation window while retaining a bounded response to sustained descent. The peak-relative reference detects renewed descent even when the capacity level began at a much lower bucket value. Harvesters, haulers, defenders, spawning, towers, and other colony-safety work remain continuous.

debug.gov() reports both the normalized governor EWMA and the comparable estimated engine charge. Calculated observations display the four-CPU observability offset explicitly, avoiding the appearance that a draining bucket is below the 20-CPU limit. The reopening lines report capacity age, change from capacity entry, loss from the capacity peak, normal/effective/operator targets, active leases, and the current admission cap.

The same policy owns a small optional-work admission state shown by debug.gov(). Every 50 ticks it classifies the useful runway as brief, moderate, or extended from the current bucket, distance to the 5,000 recovery boundary, and the existing smoothed charged-CPU trend. The class is deliberately coarse and remains unchanged between reviews.

Launch Control grants at most one lease for a moderate horizon and six for an extended horizon. Extended admission uses one urgency-driven priority lane and rotation lanes favoring never-admitted and longest-waiting eligible work. Subsystems submit a compact request with urgency and reason; classes without current work do not consume a lease. Mineral development uses a cheap, read-only current-state eligibility probe so a stale zero-demand operation can request reevaluation; the admitted mineral policy remains the only owner of operation state and population demand. Local logistics and infrastructure share the highest optional baseline, while scouting, intelligence, and mineralDevelopment sit above low-priority remoteDevelopment. In a one-slot moderate horizon, an applicant waiting 100 eligible ticks outranks a recurring same-priority winner. Eligible wait accrues only while the class is requested, affordable, unleased, and the governor is open.

The operator may prioritize a requesting class for one of those existing slots:

debug.approveLease("remoteDevelopment", 1000)
debug.approveLease("remoteDevelopment", "forever")
debug.approvedLeases()
debug.clearApprovedLease("remoteDevelopment")

This approval changes lease selection only. It does not create a work request, increase maximumActiveLeases, change the governor or commanded tier, reopen Launch Control, relax the work horizon, or bypass emergency safety. An approved class therefore remains pending without current demand and remains waiting when normal lease capacity is zero. When selected, it receives its configured normal lease duration, consumes one ordinary slot, and is shown with the operator lease lane. Remaining slots use the normal priority/rotation selection.

Existing active leases are preserved before new selection. If too many approved requesting classes compete for the remaining slots, Launch Control chooses the least recently admitted approved class; a tie uses the fixed CpuWorkClass order shown by cpuWorkClasses. This makes repeated approvals fair and fully deterministic without adding operator-configurable priorities.

The operator may separately replace the global lease-count target without certifying higher capacity immediately:

debug.setLeaseCap(5, 1000)
debug.setLeaseCap(5, "forever")
debug.setLeaseCap(1, 500)
debug.clearLeaseCap()
debug.gov()

Targets are bounded from zero through cpuWorkClasses.length (currently six). An upward override advances one capacity level per normal observation window; it does not jump directly to the target. A lower override clamps new admissions but preserves already proven normal capacity and lets excess active leases expire naturally. Clearing or expiration restores the horizon-derived autonomous target without revoking leases or discarding proven capacity within the six-class autonomous ceiling. The override changes no work-class minimum horizon, request freshness, lease duration, selection priority, fairness, governor state, emergency/recovery behavior, or operator-approved work-class preference.

scouting, intelligence, logistics, and infrastructure require a moderate horizon. mineralDevelopment and remoteDevelopment require an extended horizon. Leases last 100, 50, 100, 100, 250, and 500 ticks respectively. Requests expire after 100 ticks unless the owning subsystem renews them. An applicant below its minimum horizon is deferred to the next 50-tick review. Full lease capacity leaves waiting applicants ready for the next vacancy instead of phase-delaying them. Recovery clears deferrals because its zero-capacity horizon cannot grant work. A lease continues through ordinary bucket movement, but emergency posture overrides it.

Remote development no longer implies scouting. Stale or missing adjacent-room evidence requests scouting; a newly observed dossier requests matching CIA intelligence; only a current observation/assessment pair permits remoteDevelopment to apply. This preserves the dependency order without letting a remote lease manufacture its own prerequisite capability.

The bounded state is Memory.cpuPopulationPolicy.workAdmission plus the adjacent reopening and optional operatorApprovedLeases records:

{
  workAdmission: {
    horizon,
    horizonReviewedAt,
    classes: {
      remoteDevelopment: { admittedUntil?, deferredUntil?, lastAdmittedAt? },
      scouting: { admittedUntil?, deferredUntil?, lastAdmittedAt? },
      intelligence: { admittedUntil?, deferredUntil?, lastAdmittedAt? },
      logistics: { admittedUntil?, deferredUntil?, lastAdmittedAt? },
      infrastructure: { admittedUntil?, deferredUntil?, lastAdmittedAt? },
      mineralDevelopment: { admittedUntil?, deferredUntil?, lastAdmittedAt? }
    }
  },
  reopening: {
    capacity,           // bounded 0..cpuWorkClasses.length
    capacityEnteredAt,
    capacityStartedBucket,
    capacityPeakBucket
  },
  operatorLeaseCap: {
    target,
    createdAt,
    expiresAt?,         // omitted only when indefinite=true
    indefinite
  },
  operatorApprovedLeases: {
    remoteDevelopment: {
      createdAt,
      expiresAt?,       // omitted only when indefinite=true
      indefinite
    }
  }
}

Each class may additionally contain bounded requestedAt, urgency, requestReason, leaseLane, and eligibleWaitTicks fields. The six class keys are fixed; there is no queue or history. Inspect them directly when validating a deployment:

debug.gov()
JSON.stringify(Memory.cpuPopulationPolicy.workAdmission)
Object.keys(Memory.cpuPopulationPolicy.workAdmission.classes)

debug.gov() prints work admission as one indented line per class so request reasons, operator approval, effective lease lane, and lease timing remain readable in the Screeps console. debug.approvedLeases() gives the compact management view with current request and lease state plus the clear command. Expired approvals and deferral markers are removed during the normal admission update instead of appearing as deferred(0 ticks) after their request has ended.

Launch Control enhancement leases

debug.gov() appends the separate six-owner enhancement pool. It shows capacity, active count, each owner's baseline/enhanced state, base-lease validity, current cheap request with urgency and reason, remaining lease time, exact baseline-to-enhanced budgets, the last ending reason, and cumulative lifecycle counters. This is a read-only projection of the current Resource Governor and enhancement records; it does not run admission, planning, pathfinding, logistics evaluation, or resource measurement.

Base leases grant optional-work permission. A binary enhancement lease only raises bounded work quality for that already-admitted owner. It never grants independent domain authority. Capacity is zero under CPU recovery/emergency, a brief or constrained CPU state, or critical Memory; moderate abundance provides one slot and abundant extended-horizon conditions provide two. Elevated Memory still permits bounded compute and fixed-size conclusions; critical Memory prevents enhancement.

Enhancement leases last 25 ticks and require owner demand no more than one tick old. Base-lease loss, stale demand, or zero capacity ends a lease promptly. Ranking reuses base class priority, urgency, bounded eligible wait, least-recent grant, and fixed class order. Useful demand is not manufactured to fill the pool, so capacity=2 with active=1 is healthy behavior. The retained Memory is six fixed current-state records, one optional fixed-size Mineral planning conclusion, and compact grant/revocation counters—not a history.

Infrastructure requests enhancement only when dirty domains or pending local route paths exceed its baseline evaluation bounds. Its dirty-domain budget is 2 -> 4 and route-path-calculation budget is 1 -> 2; approval, revision, legality, site placement, economy, and safety gates do not change. Logistics requests only from persisted material route evidence after the network is at least 10 ticks old but before its normal 25-tick evaluation is due. An enhanced early evaluation remains on the existing five-tick room stagger and never turns the network into every-tick discovery.

Intelligence requests enhancement when at least two early periodic reassessments, or one strategically relevant reassessment, wait between the 25-tick enhanced threshold and ordinary 50-tick refresh. The extra pass inspects at most 64 dossiers and updates at most four; no richer persistent dossier resolution is added. Scouting requests only when at least two legal colony-frontier targets wait. Enhanced selection compares at most four of those targets by current-room distance; route distance, candidate count, traversal, waypoint, avoidance, and population bounds are unchanged.

Remote Development requests only when at least two existing non-inactive candidates can be reconsidered between 25 and the ordinary 50 ticks. Enhanced mode changes only that evaluation interval; the active-room and workforce caps and every CIA, Economy, Defense, Logistics, Infrastructure, Spawn, and lifecycle gate remain unchanged. Mineral Development requests rarely: an RCL6+ room with missing completed mineral prerequisites must already have changed existing Extractor/Container planning evidence. It inspects at most 32 entries, summarizes at most four, and cannot propose, approve, place, spawn, or change inventory authority.

Admission gates optional discovery, planning, and population demand rather than colony safety. The first owned-room observation and live hostile ingestion can bypass the CIA lease. Local harvesters, one required local-service hauler, the minimum controller upgrader, defense, spawning, towers, and safe reconciliation remain protected. Ordinary builders require infrastructure; one active critical-defense repair builder and colony-bootstrap pioneers are explicit exceptions. Mineral policy and miner demand require mineralDevelopment; its read-only wake-up probe can only submit the request for that admission.

Additional local haulers require logistics plus a persistent route window showing deficit, low idle capacity, a usable destination, sufficient confidence, and ongoing production or consumption. Two new network evaluations at least 25 ticks apart must preserve that evidence before one additional count is committed. The colony grants at most one optional commitment every 150 ticks, choosing the highest-urgency mature candidate after a one-tick arbitration delay. debug.logisticsNetwork(roomName) exposes candidate evaluations, readiness, committed count, and the last authorization. Existing optional haulers may finish their lives after the logistics lease expires, but the spawn target returns to the protected floor until a logistics lease is active again. A fresh evaluation that withdraws sustained need removes the stored commitment above the protected floor. Workforce recovery compares against the same one-hauler floor so denied optional capacity cannot keep the room in recovery.

Downward restrictions are immediate. Reaching 9,000 from recovery enters open posture but retains the recovery tier, then relaxes one adjacent tier every configured 100-tick reopeningStepDelayTicks.

Recovery and emergency posture also constrain already-alive creep execution, not only future population. Optional strategic managers, scouts, minerals, bootstrap planning, snapshot publication, dashboards, and logistics evaluation pause. Remote and bootstrap creeps run at reduced staggered cadences; local builders and upgraders are staggered; and full stats collection runs every two ticks in recovery and every five in emergency. Local harvesters, haulers, spawning, towers, links, economy assessment, and controller work stay eligible.

The CPU spike flight recorder keeps exactly the latest tick whose final Game.cpu.getUsed() sample reached the configured 45 CPU threshold. It is independent of the normal stats cadence and stores the already-collected global, per-room, role, and hauler-phase CPU profile under Memory.cpuSpike, together with the bucket, governor/tier context, and incoming serialized RawMemory string length. The threshold is configured by cpuSpikeFlightRecorderThreshold in src/config/cpu.ts. RawMemory is read only after a spike qualifies, so this extra context adds no work to ordinary ticks. Inspect the latest snapshot with:

JSON.stringify(Memory.cpuSpike, null, 2)

The snapshot is replaced only by a later qualifying spike; it is not a history and is intentionally outside Memory.stats so normal telemetry does not ingest its forensic dimensions.

The CPU observer still uses the prior completed tick's utilization EWMA. Its newest input is engine-charged CPU derived from bucket movement, including post-loop costs such as memory serialization. When either sample is empty or full, it falls back to the late prior-tick Game.cpu.getUsed() sample. The shared 4 CPU cpuObservabilityOffset applies only to movement-derived samples. The observed grade can tighten recovery from constrained to critical, but cannot command managers independently. Effective observed thresholds are 18, 19.5, and 21 CPU for abundant, healthy, and constrained; higher is critical.

cpuPopulationUtilizationSmoothingFactor controls EWMA response; the default 0.05 is approximately a 39-tick effective window. The tier caps retain their existing behavior: constrained and critical cap optional population, while critical also suppresses remote-harvester replacement. Essential floors and final operator spawn-count overrides remain authoritative. Memory keeps only compact current state and observation values, not a transition history.

The default duration is 1,000 ticks. A numeric duration must be a positive finite integer. Zero never means indefinite; an indefinite override requires the explicit string "forever". Counts must be non-negative integers, roles must be valid CreepRole values, and rooms must be visibly owned or carry remembered owned-colony evidence. If a visible room is no longer owned, visible state wins and the command is rejected.

Temporary entries store the room, role, requested count, creation tick, and expiration tick. Indefinite entries carry an explicit marker. Expiration is effective immediately when Game.time >= expiresAt; the role returns to its automated count and the small expired entry is removed during that room's next population-plan or override inspection.

debug.clearSpawnCounts() clears every stored override map under Memory.rooms, including a stale override for a room whose ownership was later lost. Passing a room name limits the clear to that room. Explicit clear commands may remove an existing stored override after ownership loss, but setters remain restricted to visible or remembered owned rooms.

debug.spawnPlan() reuses the live population and body-selection logic on invocation. For every requested role it shows current effective workforce, automatic demand, RCL result, CPU result, safety-floor result, active operator override and expiration, final effective count and reason, plus selected body name, cost, size, WORK parts, and CARRY parts. Diagnostic body-scaling Memory is cloned so inspection does not update ordinary body-policy state. The diagnostic calculation uses the same workforce-recovery suppression flag as the live room pipeline, so recovery-mode counts and body choices do not diverge from spawning.

Logistics-driven hauler counts deliberately leave fractional ordinary carry demand unfilled. Routes with no movable energy and no current destination demand do not contribute to spawning, and recent logistics idle-capacity evidence can hold the current count. Full estimated coverage is reserved for actionable urgent or critical routes with at least 75 ticks of performance evidence, low destination blockage, and no more than 10% network idle capacity. These inputs refresh with the existing cadence-bounded logistics evaluation; debug.logisticsNetwork(roomName) exposes the idle ratio and route evidence used by the policy. Shared-source and shared-destination alternatives contribute only the limiting endpoint workload, rather than multiplying one source's production or one destination's demand. If economy recovery must replace the protected final hauler, body policy approves the stable tier when affordable; the minimal body remains the immediate low-energy fallback.

A successful setter response warns when the request is below a recorded safety floor or above an active RCL/CPU cap. The warning is informational; the request is stored and obeyed. Colony autonomy can still report a zero-essential-worker room as blocked or unrecoverable, but that status does not silently change the operator count. For a remembered room that is not visible, or one without a current economy assessment, the setter instead warns that a live floor/cap comparison is unavailable.

Bounded command history

Use:

debug.history()
debug.history(10)

History is newest-first and strictly limited to 8 compact entries. It records successful and failed generic/specialized approval mutations, spawn override set/clear actions, the room and ticket or role/count context, and a concise outcome. It never stores full command output or Memory objects, and its free-text context fields are length-bounded before persistence. A requested display limit is clamped to the configured bound.

All ticket resolution, help formatting, pending/show formatting, warning plan calculation, and history formatting occurs only when an operator invokes a command. Routine tick cost is limited to approval allocation when a genuinely new revision is created and up to eight direct room-map lookups while finalizing a population plan. This feature adds no pathfinding, telemetry, broad routine Game scans, unbounded history, or duplicate normal population-plan execution. If the bounded ticket store cannot accept another live proposal, that room's new approval planning is deferred on the existing infrastructure blocked-retry cooldown without replacing the prior authoritative plan or route and without aborting room execution. A successful approval, rejection, cancellation, or automatic terminal lifecycle transition clears the relevant deferred gate because it may have released closed-reference capacity; ordinary observation and dirty-domain changes remain the other invalidation sources. Allocation and deferred-proposal cost stays in the room CPU profile's infrastructure section, while the eight role lookups remain inside the existing spawnPlanning section.

Colony Autonomy

debug.colonies() prints one compact line per visible owned room with its autonomy state, local workforce, effective harvester and hauler counts, idle spawn availability, local recovery capability, and inter-colony assistance state.

debug.colony(roomName) expands one room with current energy, the 200-energy recovery boundary, economy status and mode, harvester demand, active home-owned remote operations, the latest bounded room-management failure, and the room's inter-colony assistance state. Assistance output includes the recipient deficit and recovery target, selected donor and current safe budget, authorized/transferred energy, assigned creep, and the active denial, suspension, completion, or recovery reason. See INTER_COLONY_ENERGY_ASSISTANCE.md for policy and live checks. The autonomy states are:

  • operational: essential local workforce and economy are healthy;
  • recovering-workforce: essential workforce is incomplete but locally recoverable;
  • recovering-economy: core workforce exists while the energy foundation rebuilds;
  • blocked: the room cannot currently restore a harvester with its own spawn and available energy.

These commands format their output only when invoked. They do not create cross-colony support requests, override autonomous assistance, or transfer ownership. debug.creep(name) also includes the explicit assistance assignment and phase when present.

debug.body(roomName?) reports current using the effective workforce used by spawn planning: creeps already spawning count, while creeps inside their replacement lead window do not. This can differ briefly from the number of live creeps shown by other room and stats views during a handoff.

Colony Bootstrap

Colonization begins only with an explicit operator command:

debug.colonize("E48N14", "E48N13")
debug.colonization("E48N14")
debug.cancelColonization("E48N14")

debug.colonize() validates room names, visible bootstrapper ownership, target non-ownership, duplicate/conflicting operations, and current GCL capacity. The command itself is authorization and creates durable intent; it does not issue a creep action or require debug.approve(). A capacity shortage is retained as an auto-retried blocker.

debug.colonization() works without target visibility and reports operation identity, authorization, phase and previous phase, blocker, remembered controller ownership/reservation/RCL/downgrade/safe-mode state, acquisition and pioneer assignments and demand, spawn plan and site progress, clearance, milestones, terminal state, and the ten most recent bounded events. Clearance may name either a selected-tile obstruction or a foreign spawn consuming the claimed room's RCL spawn allowance.

debug.cancelColonization() is terminal and idempotent. It stops new bootstrap demand, safely releases assignments, retains history, and never kills creeps or destroys room state. Starting again requires a new command and operation ID. There is intentionally no debug.resumeColonization(); temporary visibility, energy, spawn, GCL, safe-mode, cooldown, replacement, and construction blockers are reevaluated by the tick manager. Pioneer rows in debug.intents() use the colonyBootstrap workflow and report current travel, withdraw, pickup, harvest, dismantle, build, upgrade, or idle behavior. See COLONY_BOOTSTRAP.md.

Screeps Lab Snapshot Export

The Screeps Lab exporter is operator-triggered and captures one visible owned room as a versioned portable artifact. It uses RawMemory segments 70-79 and stores only small status/control data in Memory.screepsLabSnapshot.

Use:

debug.beginLabSnapshot("E48N13")
debug.labSnapshotStatus()
debug.cancelLabSnapshot()
debug.clearLabSnapshot()

debug.beginLabSnapshot(roomName) validates the room, captures state during the current tick, and starts publishing chunks over later ticks. It does not print the full snapshot. debug.labSnapshotStatus() shows chunk progress, segment IDs, byte count, checksum, and errors. Download completed artifacts locally with npm run lab:snapshot:download -- --room E48N13.

See SCREEPS_LAB_SNAPSHOT.md for the schema, segment reservation, lifecycle, downloader, limitations, and compatibility policy.

Stats Export

Memory.stats is the repository-owned telemetry contract. The canonical metric catalog, numeric mappings, counter/gauge guidance, and ownership boundary live in STATS.md. Keep this section focused on debugging context and console inspection.

collectStats() from src/stats/collect.ts runs once near the end of each tick and writes the exported telemetry object to Memory.stats. The Screeps bot owns only the projection from game state and internal Memory into Memory.stats; any StatsD exporter, Graphite storage, or Grafana dashboard is downstream. The stats collector receives the RoomContext snapshots produced by RoomManager, so room telemetry reuses the same per-room scans where practical instead of rediscovering common room state.

Memory.stats.cpu.profile records low-cardinality per-tick CPU measurements. Memory.stats.cpu.governor exports bucket posture as a numeric enum: 0=open, 2=recovery, and 3=emergency; value 1 is retired. Use cpu.profile.sections.* to compare main-loop phases and cpu.profile.rooms[roomName].* to compare room-manager phases such as remoteOperations, infrastructure, spawnPlanning, and creeps. cpu.profile.sections.creepIndex isolates the single whole-creep indexing pass that supplies all owned-room contexts and global role dispatchers. The owned-room creep dispatch profile also emits stable role child series under cpu.profile.rooms.<roomName>.creeps.<role> for supported creep roles. The parent creeps value remains the full dispatch-section wall-clock cost, while role children sum CPU by the creep's current room. This includes global scouts, remote harvesters, remote reservers, remote construction builders, remote haulers, colonizers, and colonization pioneers. Consequently, a bootstrap room can have nonzero role children even when its ordinary RoomManager is deferred, and role children are not expected to reconcile with the parent dispatcher value. Global section totals and current-room role children are alternate views of the same execution and must not be added together.

The loop deliberately reads Memory.creeps under cpu.profile.sections.memoryParse before cleanup. This isolates Screeps memory deserialization from cleanDeadCreepMemory, which now measures only the actual stale-entry pass. cpu.profile.sections.memoryCompaction measures the versioned persistent Memory migration. It handles at most ten sorted room records per tick, records its cursor under Memory.persistentMemoryCompaction, then runs bounded global intelligence-retention maintenance every 500 ticks after migration. Live observation refreshes continue enforcing per-dossier retention rules between maintenance passes. The CIA portion drops per-object hostile detail after 2,500 ticks, removes unused names/owners/types from sighting summaries, and converts at most six 10,000-tick CIA events per room to compact tuples. debug.ciaEvents() hydrates those tuples back into readable event objects. The staggered migration cost is included in cpu.profile.sections.memoryCompaction. The same bounded hook also hydrates the active logistics, planned-structure, and infrastructure-position storage codecs early in the tick. Outside CPU recovery it prepares newly replaced records again near the end of the loop; recovery suppresses the managers that can replace those records, so the redundant late pass is skipped. Debug helpers therefore continue seeing the documented object and array contracts rather than storage tuples or packed coordinate strings. Across consecutive ticks in one global, a tick-local runtime cache reuses the already hydrated network, planned-structure map, and route position arrays. The compact serialized form remains the reset-safe source of truth; a global reset simply pays one normal decode before repopulating the cache.

The fixed cpu.profile.rooms.<roomName>.hauler.* gauges split local hauler CPU between task validation, urgent/link/route/fallback selection, and task execution. Assigned remote haulers are covered by the fixed remoteExecution gauge because they bypass the local role pipeline. These metrics intentionally contain no creep names or target identifiers. Maintenance pressure uses a bounded classification/count pass and reuses its route-road value index for the full maintenance plan later in the same tick. Empty haulers use room-context gauges to test for urgent delivery demand before optional link gathering; they do not pathfind merely to answer that boolean question.

updateColonyStats() from src/stats/colonyStats.ts runs once per tick for each owned room managed by RoomManager and writes population planning telemetry to Memory.rooms[roomName].stats.creeps. That room-memory cache is an input to the stats collector and debug views, not itself the Grafana contract.

Scout execution is global because scouts can leave owned rooms. RoomManager executes only room-bound economic roles: harvester, hauler, builder, upgrader, and mineral miner. Scouts still appear in room snapshots, room debug output, population stats, and spawn planning, but src/main.ts is the intended behavior dispatcher for roleScout.run(). debug.intents() continues to report every living scout, including scouts in adjacent unowned rooms.

The global loop disables Screeps' built-in attack notifications for scouts. Attack notifications for every other creep role retain the engine default.

Routine scout targets are the deduplicated union of rooms one exit from every currently owned room; owned rooms are excluded. memory.homeRoom remains the scout's lifecycle owner and does not limit this geographic frontier. Per-room scouting demand and Launch Control leases remain independent, but the final spawn admission gate permits at most one active or spawning scout colony-wide.

Memory.stats.rooms contains detailed operational telemetry only for visible owned rooms. Observed-room telemetry is exported separately from persistent room intelligence, so previously scouted rooms can remain visible in Memory.stats after the scout leaves. Internal Memory may contain usernames, ids, room positions, arrays, reasons, and manager state that are intentionally not exported; inspect those through console helpers such as debug.room(), debug.economy(), debug.terminal(), debug.maintenance(), and debug.intents().

For quick debugging, remember these high-level encodings from STATS.md:

  • booleans use 0=false and 1=true
  • economy status uses 0=starved, 1=strained, 2=stable, 3=surplus
  • economy mode uses 0=normal, 1=emergency
  • strategic surplus uses 0=inactive, 1=building, 2=sustained, 3=overflow
  • link route mode uses 0=none, 1=source-to-storage, 2=storage-to-controller, 3=source-to-controller
  • terminal energy state uses 0=none, 1=empty, 2=low, 3=charging, 4=balanced, and 5=surplus
  • room intelligence energy state uses 0=normal and 1=starved in debug output

debug.economy(roomName?) prints the latest persisted economy assessment for the requested owned room, or the first visible owned room when no room is provided. It includes the current status, smoothed net energy trend, stored energy and ratio, primary storage reserve thresholds, reserve deficits, spending permissions, economy mode, emergency entry/recovery state, source/controller container energy, strategic surplus state/reason/storage/source pressure, construction remaining, important repair pressure, builder/hauler/upgrader desired counts and reasons, last evaluation tick, and active population cooldowns.

debug.body(roomName?) prints the body-scaling policy for the requested owned room, or the first visible owned room when no room is provided. It uses the latest persisted economy assessment and the same body policy as spawning. For each role it shows current and desired population, selected option, selected tech tier, cost, fallback status, highest technically eligible tier, highest economically approved tier, selection reason, and every configured option with its eligible/approved/affordable state and rejection reason.

debug.links(roomName?) prints current link classification and recent transfer decisions for the requested owned room, or the first visible owned room when no room is provided. It includes source/controller/storage/unclassified counts, each link id, position, energy, free capacity, cooldown, classification reason, whether controller feeding is currently allowed by economy policy, per-tick transfer counts, sent/received/lost energy, usable and blocked route counts, cooldown and receiver blockage counts, cumulative transferred energy, last route, and recent sender to receiver decisions.

debug.logistics(roomName?) prints the hauler-facing logistics view for a visible owned room. It includes completed source-to-storage route readiness, available load-source-link and unload-receiver-link jobs with priorities and reasons, per-tick hauler link-service counters, and haulers with link jobs or persistent local route assignments. Local assignment lines include the source node, destination node, stable route ID, and assignment tick. Use this alongside debug.links() to distinguish structure transfer pressure from creep service pressure.

debug.logisticsNetwork(roomName?) prints the shared room-owned logistics network summary: known, operational, degraded, and blocked route counts, aggregate route demand, source backlog, required carry capacity, assigned carry capacity, capacity deficit, active route-backed jobs, last evaluation tick, and bounded recent route events. It adds an infrastructure line with fully, partially, degraded, and blocked support counts, completed/required components, missing and damaged components, completed/required route roads, and aggregate road completion ratio. Full logistics-network derivation is cadence-bounded; the room summary also shows actual throughput, efficiency, average cycle/job duration, source/destination/unavailable ticks, congestion, fatigue, fallback and idle-capacity ratios, backlog delta, and healthy/watch/degraded/critical counts. These values are read from persisted summaries and do not rerun route policy. route and infrastructure state shown here can lag live room state by up to the configured logistics-network evaluation interval.

debug.logisticsNodes(roomName?) lists node identifiers referenced by the persisted route summaries. Nodes are derived from adapter facts and are not stored as large objects in Memory.

debug.logisticsRoutes(roomName?) prints deterministic route summaries sorted by route id, including lifecycle state, preferred and active transport method, source node, destination node, service class, source backlog, destination demand, movable amount, estimated throughput, estimated cycle ticks, required carry capacity, assigned carry capacity, capacity deficit, idle carry capacity, plain-language capacity status, policy permission, current blockers, and fallback route. Each line also shows infrastructure support state, worst critical endpoint state, completed/required components, planned/approved and site counts, missing and damaged components, road completion/site/missing/ damaged counts and ratio, infrastructure blockers, fallback usability, and the last infrastructure validation tick.

Each route line additionally shows current service obligation, expected and actual throughput, EWMA efficiency, estimated and actual cycle time, average job duration, observable collection- and delivery-leg duration, blockage ratios, fallback rate, idle capacity/ratio, backlog delta, congestion and fatigue ratios, path recalculation count, retained traffic/chokepoint counts, health and primary reason (including EWMA throughput only when service is obligated), window/confidence age, and path-cache status. Cache status eligible-telemetry-only means evidence crossed the configured threshold; custom serialized path following remains disabled. invalidated means the stable route/endpoint, room, lifecycle, infrastructure-component, or road coverage fingerprint changed.

debug.logisticsRoute(routeId, roomName?) prints one persisted route summary. All logistics-network debug helpers are read-only; they inspect the representation layer and do not assign haulers, transfer links, approve infrastructure, change remote operations, or send terminal resources. Route-backed jobs are stored under Memory.rooms[roomName].logisticsNetwork.jobs; use debug.logisticsRoute() to find the route id and inspect the bounded job record directly when needed. Existing execution systems remain authoritative while the shared network progressively becomes the route-demand and route-job source of truth.

Infrastructure support and logistics route lifecycle are separate. For example, an operational link-assisted route can show infra=degraded and fallbackUsable=true when its preferred link is missing but the conventional creep route still works. critical=unverified means current visibility did not prove the endpoint; it is not equivalent to completed. Planned, approved, and construction-site component counts describe intent and are not runtime support.

debug.remote(homeRoomName?) is a shorthand for debug.remoteOperations(homeRoomName?).

debug.externalOperations(homeRoomName?) lists the read-only normalized external-operation view for one home room or all remembered home rooms. Each line includes the stable external ID, kind, persistence, generalized lifecycle, home and target rooms, resource, objective count, existing logistics route IDs, and the current runtime authority.

debug.externalOperation(operationIdOrTargetRoomName, homeRoomName?) shows one normalized operation in detail. It lists each selected source objective and its stable identity, resource, and associated existing route IDs. Candidates with no source selected by RemoteOperationsManager show zero objectives. Both commands derive their output on demand and do not write remote-operation or logistics Memory; debug.remoteOperations() remains the view of authoritative pilot state.

debug.deposits(homeRoomName?) lists every currently known advisory highway deposit opportunity, optionally filtered to one owned home. Each entry explains deposit type, observation age and freshness, confidence, remaining lifetime, cooldown, estimated yield, round-trip travel, spawn/workforce burden, required carry capacity and current logistics deficit, protected-economy affordability, route risk, Industry and inventory demand, cached executable Market value, gross and burden-adjusted value, recommendation, and explicit blockers.

debug.deposits()
debug.deposits("E48N13")
debug.depositAuthority()
debug.depositAuthority("operator-approved")
debug.depositAuthority("autonomous")
debug.approveDepositOperation("highway-deposit:E48N13:E50N13:deposit-id")
debug.depositOperation()
debug.depositOperation("E48N13")
debug.externalOperation("E50N13", "E48N13")
debug.externalAllocation("E48N13")
debug.externalAuthority()
debug.externalAuthority("remote-energy", "operator-approved")
debug.externalControl("suspend", "E48N13-E48N14-source-id", "E48N13")

Unapproved deposit entries appear in the normalized external-operation helpers as kind=highway-deposit, persistence=transient, and authority=advisory-only. Deposit admission authority defaults to autonomous. On a fresh 25-tick deposit assessment, autonomous policy may select the highest-value eligible opportunity only while remoteDevelopment holds a Launch Control lease. Lease expiry does not revoke committed work; the ordinary operation lifecycle remains responsible for safety, economics, winding down, and cargo evacuation. The most recently retired exact opportunity is excluded from immediate readmission.

debug.depositAuthority(mode?) inspects or switches the persistent global mode between autonomous and operator-approved. Manual debug.approveDepositOperation(opportunityId) remains available in either mode. Both paths accept only one exact currently eligible and allocated ID per home room and create preparing state. Neither bypasses live identity, CIA freshness, route, risk, economy, essential-workforce, CPU, lifetime, cooldown, yield, actual-body repayment, or Spawn Manager checks. The allocation layer rejects a second transient operation for the same home room while allowing independent home rooms to proceed.

debug.externalAllocation(homeRoomName?) shows budget used versus available for spawn energy, workforce, hauling capacity, CPU, infrastructure, and reservation rooms. It lists deterministically ranked objectives, minimum requests, admitted capacity, shared-overhead charges, incumbent state, and exact defer/preemption reasons. This is the authoritative inspection surface for why an otherwise eligible external objective did or did not receive optional investment. Operation-owned hard admission blockers appear as deferred decisions with zero allocated capacity; an incumbent marker never overrides those blockers.

debug.externalAuthority(kind?, mode?) inspects or changes the independent policy for remote-energy and highway-deposit. Each kind supports manual, recommendation-only, operator-approved, and autonomous. Manual and operator-approved modes require exact approval for new work; recommendation-only retains healthy incumbents but does not admit a new objective; autonomous mode may admit only within the same allocator and native safety gates. Existing deposit authority configured through debug.depositAuthority() remains compatible with the deposit half of this policy.

debug.externalControl(action, objectiveId, homeRoomName?) applies an exact approve, suspend, resume, or retire override to a live remote-source or deposit objective. Resume and approval return through full intelligence, economy, risk, and allocation revalidation. Suspension preserves Memory and causes assigned workers to evacuate carried resources. Retirement removes future workforce and infrastructure priority; deposit retirement winds down through cargo evacuation, while stale persistent records are pruned only after the bounded retention interval and participant checks.

debug.externalPortfolio(homeRoomName?) explains the comparable ranking input used by that allocator. For each candidate it shows the preserved native score/value, final portfolio and effective allocator ranks, every signed dimension, strategic-demand level, cached executable Market contribution, performance adjustment and evidence, expected/measured return, exact minimum capacity request, admission result, and compact evidence reasons. The view is read-only and does not refresh discovery, Market orders, paths, or logistics.

debug.depositOperation(homeRoomName?) shows the runtime ID, lifecycle, age, exact deposit/type, home/target, worker, selected body/cost/spawn ticks, route and carry estimate, movement/delivery evidence, cooldown samples, lifetime, expected versus harvested yield, in-transit and returned cargo, economics, suspension or winding-down reason, and the eight bounded transitions. After retirement it shows the one retained compact outcome. Runtime entries normalize with authority=DepositOperationsManager and their existing resource-typed route ID. No deposit command approves infrastructure or executes a Market action.

debug.remoteOperations(homeRoomName?) prints the remote-operation summary for one owned home room, or all home rooms with remote-operation memory when no room is provided. It includes candidate count, eligible/blocked/evaluating totals, best score, compact room-pilot state, and one line per source objective with state, marginal economics, exact workforce, production, throughput, carry, backlog, infrastructure, and reason, followed by candidate-room summaries.

Owned rooms are excluded from adjacent remote candidates and removed from retained candidate records. Candidate reassessment is normally bounded by the configured 50-tick cadence. It can run earlier for adjacency changes or decision-relevant intelligence changes, but a dossier observation timestamp changing without a material ownership, source, infrastructure, threat, freshness, confidence, or score-input change does not trigger reassessment. Home operating-reserve fluctuations are sampled on the normal cadence. Active pilot operations retain an every-tick visible-risk gate; foreign ownership or creeps immediately force the full ownership and safety refresh instead of waiting for the normal five-tick lifecycle phase.

debug.remoteOperation(operationIdOrTargetRoomName, homeRoomName?) prints the detailed read-only assessment for one target room or one source-objective id. A target room prints every sibling source objective. Objective details include home room, state, risk, selected source, assigned creep, intelligence age/confidence, ownership/reservation, separate foreign-creep and actionable-threat counts, tower readiness, tower energy/capacity, reason, last safe/unsafe observations, reactivation cooldown, remote source-container infrastructure state, build order id, selected position, reason, hauling authorization, assigned hauler, active/spawning hauler counts, hauling phase, exact container id and position, visible container energy, carried remote energy, workload route/cycle/production and required capacity, desired hauler count, assigned carry capacity, capacity deficit, recommended remote logistics body cost/carry/move/capacity, throughput capacity, replacement lead, route confidence, fallback reason, last workload evaluation tick, hauling suspension reason, participant counts, container visibility/fill, reservation remaining ticks, target ticks, renewal state, active reserver count, harvested/hauled/delivered-home counters, gross delivery ratio, in-transit energy, haul and reservation activity ticks, last activity/delivery ticks, remote CPU, and telemetry. It does not approve construction, attack, or mutate operator-controlled infrastructure.

debug.creep(name) and inspect(name) include remoteHauling memory for a hauler assigned to the pilot. Use this to confirm the home room, target room, operation id, source id, exact container id, phase, suspension reason, and trip count.

A visible foreign creep with no active ATTACK, RANGED_ATTACK, HEAL, WORK, or CLAIM parts should appear in the foreign-creep count while the actionable-threat count remains zero. The operation may show conditional risk, but it remains active and does not block the shared remote-road or construction gate.

For live remote hauling verification:

  1. Run debug.remoteOperation("<targetRoom>", "<homeRoom>") and confirm hauling is authorized, the operation is active, and there are no suspension blockers.
  2. Run debug.creep("<haulerName>") while the hauler moves through travel-to-remote, withdraw-remote, return-home, and deliver-home.
  3. Check Memory.stats.rooms.<homeRoom>.remoteOperations for lifecycle, workforce, harvested/dropped/hauled/delivered-home energy, separated lifetime counters, container visibility, delivery ratio, in-transit energy, route distance, cycle ticks, required carry capacity, desired hauler count, assigned carry capacity, selected body metrics, route confidence/fallback flags, reservation state, haul and reservation activity, and CPU.
  4. Suspend or invalidate the operation and confirm the hauler changes to suspended-return-home, returns home with carried energy, delivers normally, and clears the assignment when empty.

Infrastructure planning helpers inspect and operate the configured authority boundary for planned construction. debug.buildOrders() lists pending and recently relevant build orders with copyable approval and rejection commands. debug.buildOrder(id) returns complete JSON details for one order, including plan revision and validation state. debug.pending() and debug.show(id) provide the preferred cross-lifecycle ticket view. For a build-order ticket, debug.approve(id) records manual approval only; policy authorization and manual approval both place sites later during normal room execution after revalidation. debug.reject(id) rejects the order and starts a reconsideration cooldown. debug.cancel(id) is the generic cancellation command, while debug.cancelBuildOrder(id) remains an alias; both cancel without removing any site or structure. debug.infrastructurePlan(roomName?) now inspects the retained generic remote/defense lifecycle: cadence and dirty-domain state, remote/defense plans and orders, remote-road state, link-network summaries, and recent events. Use debug.infrastructurePlan(roomName, "detailed") for its persisted retained state. Local authority is inspected through debug.colonyLayout(), debug.localConstruction(), and debug.logisticsRoutes().

debug.colonyLayout(roomName?, visualize?) reports the persisted authoritative fixed-local layout: planner version, stable anchor and transform, 50/30/20 weighted score, core/Lab/operational/pod/lane counts, terrain distortion, and bounded exact blockers. Pass true as the second argument to draw the geometry for the current tick. Gold letters are core structures, purple letters are Labs, cyan letters are local operational endpoints, green circles are Extension reservations, gray dots are shared lanes, the anchor is marked, and red circles identify blockers. The command and visualization are read-only; the focused local construction executor consumes these fixed slots and unified Road intent without a retained local plan, order, or package copy. Visuals are rendered directly from the persisted plan for one tick and do not create a second visual cache or retain historical frames in Memory. The report also shows complete blocker totals for retiringRoad, migrationRequired, protectedConflict, and terrainImpossible. Each retained blocker sample includes its classification; totals cover blockers omitted from the bounded sample. The report also shows the fresh bounded Lab search range, generated and zero-relocation candidate counts, relocation and authoritative rejection totals, the dominant hard generation rejection, and an exact failure reason when no candidate survives. Its bounded authoritative-layout-conflict sample identifies Lab or service positions that overlap named non-Lab fixed geometry; derived local lanes reroute around an accepted Lab footprint. Live Extension obstructions remain no-relocation blockers. Persisted candidates and legacy Lab plans belong to the separate operational Lab planner and do not control authoritative colony-layout geometry.

debug.labCluster(roomName?) shows at most three generated ten-Lab footprints, their stable reagent inputs, and exact Lab or Extension relocation blockers. debug.reserveLabCluster() reserves one exact candidate and pauses builder work on conflicting non-Lab sites without removing anything. debug.releaseLabCluster() releases an unfulfilled operational reservation.

debug.reconsiderPlan(id, reason?) records a missing current coordinate as operator-rejected and queues immediate reconsideration. The veto is shared by sibling plans with the same room, target, structure type, purpose, and anchor. Coordinated Labs use the dedicated footprint commands. The room must be visible and the structure or site must already be absent. Ordinary destruction recovery retains the old coordinate; this explicit command communicates that the placement itself was unwanted. Attempts and successful coordinate vetoes are retained in the bounded debug.history() operator log. debug.explainPlan(id) shows the selected candidate, alternatives, score components, invalid-candidate summary, home room, target room, and remote operation/source metadata when present. The room plan summary now includes every authorized buildable structure type with completed, site, active plan, pending, approved, blocked, allowed, remaining, and doctrine state fields. Link-network details remain available where relevant.

debug.terminal(roomName?) prints current terminal readiness for the requested owned room, or the first visible owned room when no room is provided. It includes terminal energy state against the configured 250,000-energy standing target, target delta, total store usage, send/receive readiness, visible owned-room transaction-cost samples for a 1,000-resource transfer, the current recommendation, and the largest stored resource stacks.

debug.maintenance(roomName?) prints the latest persisted maintenance summary for the requested owned room, or the first visible owned room when no room is provided. It includes budget mode, backlog counts by class, backlog score, container and road health, known route-backed road count, current route-backed maintenance candidates and repair backlog, maintenance energy spent this tick and in total, and the current top-ranked job reason. It also derives the live mixed-emergency recovery state: critical Container and catastrophic Road job counts, outstanding Road sites, the deterministic Road-recovery builder, and each local builder's current lane and task. Route-backed roads remain important rather than critical and still obey the displayed budget mode.

When at least two unassigned local builders are available and critical Containers coexist with catastrophic Roads, one stable builder-name lane is reserved for Road recovery. The other builders distribute across unreserved emergency targets. The Road lane repairs catastrophic Roads first and then completes existing Road construction sites before ordinary construction. Remote-construction builders and active-defense Rampart emergencies are not reassigned into this lane.

Memory.stats.server.tick is derived from wall-clock time between consecutive executions of the main loop, so it measures elapsed real time rather than Game.time tick deltas. The first tick after deployment or a Memory reset records only the current wall-clock timestamp and may not emit tick-speed gauges yet.

hostileStructures[] stores strategically recent detailed hostile structure intelligence from scout observations in internal room memory. Towers and spawns are sorted first, structures with an energy store include current and maximum energy, and per-object records expire after 2,500 ticks without confirmation. debug.room(roomName) includes a compact hostileStructuresReport for quick console review.

Screeps Profiler

The optional screeps-profiler integration is controlled by enableScreepsProfiler in src/config/cpu.ts. When it is false, role dispatchers retain their original function references. When it is true, the following module-initialized function names are available for filtered profiler runs:

  • RoomManager.assessEconomy
  • RoomManager.runLinks
  • RoomManager.runTerminalLogistics
  • RoomManager.createMaintenanceManager
  • MaintenanceManager.createRouteRoadValueIndex
  • MaintenanceManager.buildRoadClassificationIndex
  • MaintenanceManager.createPlan
  • RoomManager.updateRoomIntelligence
  • RoomManager.runRemoteOperations
  • RoomManager.runRemoteReservation
  • RoomManager.runInfrastructure
  • RoomManager.runLogisticsNetwork
  • RoomManager.updateColonyStats
  • Role.builder.run
  • Role.harvester.run
  • Role.hauler.run
  • Role.hauler.task
  • Role.upgrader.run
  • Role.scout.run
  • Role.remoteHarvester.run
  • Role.remoteReserver.run
  • Role.remoteConstructionBuilder.run
  • Role.remoteHaulingHauler.run
  • Role.colonizer.run
  • Role.colonizationPioneer.run

RoomManager.updateRoomIntelligence, RoomManager.runRemoteOperations, and RoomManager.runInfrastructure now execute on separate stable five-tick room phases. Their manual room CPU gauges are near zero on skipped ticks and show the full pass on scheduled ticks; use a moving average of at least five ticks when comparing their cost. Visible remote ownership or foreign creeps bypass the remote-operation phase so safety refreshes run immediately. Colony bootstrap remains inside the manual remoteOperations CPU section every tick but is no longer inside the optional RoomManager.runRemoteOperations profiler wrapper.

For example:

Game.profiler.profile(50, "Role.hauler.run");
Game.profiler.profile(50, "Role.hauler.task");

Role.hauler.task is intentionally nested inside Role.hauler.run; the existing manual hauler CPU telemetry remains active alongside these optional profiler boundaries. Optional function wrappers register only when the Screeps runtime is present, so importing an individual manager in an offline verifier does not require Screeps runtime constructors or add a live per-call shim.

Maintenance route values and road classifications are cached for five ticks. The cache is rebuilt when its bounded lifetime expires; road classification is also rebuilt immediately when the owned-road or important-infrastructure topology changes. Current structure health, repair-job eligibility, scoring, and assignment selection are still evaluated every tick under MaintenanceManager.createPlan.

Infrastructure diagnostic summaries are rebuilt whenever the staggered infrastructure manager runs, normally every five ticks with a stable per-room offset. Local-construction and logistics-road state use their owner-specific bounded summaries; the retained generic summary covers remote and defense work.

Routine economic and remote movement uses bounded built-in moveTo path reuse: 50 ticks for unchanged local logistics targets, room-transition approaches, stable work targets, and remote legs. Movement is evaluated only when an action is out of range; fatigue and in-range hauler calls return before moveTo. Screeps replaces the cached path when the destination or requested range changes, while the reuse timeout bounds stale geometry. Inspect the cost under native Creep.moveTo, Room.findPath, and RoomPosition.findPathTo profiler rows, plus the fixed per-room hauler movementExecution gauge. When an unfatigued hauler repeatedly requests movement without leaving its tile, the movement helper clears only its cached _move path and requests one fresh path for that stationary episode. inspect(name) and debug.creep(name) expose haulerMovementTarget and haulerMovementProgress; the latter shows stationaryTicks, recoveryAttempted, and lastResult without emitting logs.

Inspecting Creeps

inspect(name)

Shortcut for inspectCreep(name) from src/debug/inspect.ts.

Parameters:

  • name: string - creep name from Game.creeps.

Returns:

  • "Not found" if Game.creeps[name] does not exist.
  • A formatted JSON string with:
  • role - creep.memory.role
  • status - status display object, for example { "moving": "🏃" }
  • pos - current RoomPosition
  • store - carried resources
  • fatigue - current fatigue
  • ttl - ticksToLive
  • task - creep.memory.task
  • logisticsJob - assigned hauler link-service job, when present
  • sourceId - assigned harvester source id, when present
  • localHaulingRoute - persistent ordinary-hauler route assignment, including its source container or storage identity, when present
  • haulerMovementTarget - current movement target and requested range key
  • haulerMovementProgress - compact stationary-path recovery state, including the last observed tile and tick, stationary count, recovery latch, and latest moveTo result
  • intent - raw creep.memory.intent
  • intentDetails - formatted intent summary

task is a persistent per-creep assignment in the format action:id, such as withdraw:abc123, build:abc123, repair:abc123, pickup:abc123, or deliver:abc123. Builders, upgraders, and haulers prefer continuing these assignments while the target remains valid, then clear them when the creep changes work phase or the target is no longer usable.

New-colony recovery appears as workflow colonyRecovery. A withdraw action means the builder is acquiring local energy for the stranded spawn; deliver means it is filling an owned spawn or extension until the first local harvester or hauler is alive.

Local haulers also keep compact haulerLoad provenance while carrying energy. It identifies the acquisition source, acquisition kind, and tick; it is cleared when the load is exhausted. Ordinary delivery selection rejects a destination whose ID matches that source because returning the same load produces no useful logistics state change. When this happens, the current intent reason names the source/destination match, and Memory.stats.rooms.<room>.haulers.coherence.preventedEnergyEchoes increments. Controller containers remain valid withdrawal fallbacks and delivery buffers when the source and destination differ and normal reserve policy permits them.

Hauler gathering may now show either withdraw:<id> or pickup:<id>. A pickup task means the hauler is collecting dropped energy selected by the logistics acquisition workflow. intentDetails shows the workflow/action, dropped-energy target, selection reason such as nearby, significant, or decay-urgent loose energy, and the latest pickup result. Haulers in delivery mode keep deliver tasks and do not interrupt delivery to collect drops.

Hauler link-service assignments are stored separately in creep.memory.logisticsJob. This keeps higher-level route/job identity visible without replacing the existing action-level task assignment.

Ordinary local route assignments are stored in creep.memory.localHaulingRoute. They preserve the route, source node, physical source (container or storage), destination node, and assignment tick across repeated trips; the action-level task still shows the current withdrawal or delivery endpoint.

Example:

inspect("Harvester123")

Sample output:

{
  "role": "harvester",
  "status": {
    "moving": "🏃"
  },
  "pos": {
    "x": 22,
    "y": 18,
    "roomName": "E48N13"
  },
  "store": {
    "energy": 25
  },
  "fatigue": 0,
  "ttl": 1372,
  "sourceId": "5bbcabcd9099fc012e635123",
  "intent": {
    "role": "harvester",
    "workflow": "harvester",
    "action": "move",
    "targetId": "5bbcabcd9099fc012e635456",
    "targetType": "container",
    "reason": "moving onto assigned source container",
    "tick": 12345678
  },
  "intentDetails": "workflow: harvester\naction: move\ntarget: container 5bbcabcd9099fc012e635456\nreason: moving onto assigned source container\nresult: unknown\ntick: 12345678"
}

debug.creep(name)

Detailed creep inspection from src/debug/debug.ts.

Parameters:

  • name: string - creep name from Game.creeps.

Returns:

  • "Creep not found" if Game.creeps[name] does not exist.
  • A formatted JSON string with everything from inspect(name), plus:
  • name
  • hits as "current/max"

Example:

debug.creep("Builder123")

Use this when health matters or when comparing several named creeps.

Inspecting Room State

debug.room(roomName?)

Returns a RoomManager-style room summary for a visible owned room. Pass a room name to inspect a specific visible room.

Parameters:

  • roomName?: string - optional visible room name. Defaults to the first owned visible room.

Current limitation:

  • Requires the selected room to be visible in Game.rooms.

Returns a formatted JSON string with:

  • name
  • energyAvailable
  • energyCapacity
  • controller - level, progress, progress total, and ticks to downgrade
  • sources
  • spawns
  • creeps - counts grouped by role
  • constructionSites
  • structures
  • containers
  • sourceContainers
  • controllerContainers
  • primaryEnergyReserve - storage id, energy, free capacity, and total capacity when completed owned storage exists
  • roads
  • hostiles
  • towers
  • intelligence - the persistent room intelligence stored in Memory.rooms[roomName].intelligence

CIA Debugging

The CIA never makes decisions. It provides intelligence.

Use debug.cia() for a compact index of known room dossiers:

debug.cia()

Use either form for a room-specific dossier readout:

debug.cia("E48N14")
debug.dossier("E48N14")
debug.assessment("E48N14")
debug.ciaEvents()
debug.ciaEvents("E48N14")

The room readout includes last observation ticks, confidence, ownership, ownership transitions, resource survey data, current or stale hostile observations, advisory strategic assessment, and the latest status summary. debug.assessment(roomName) refreshes and returns that room's advisory assessment, including category scores, category confidence, overall score, recommendation, recommendation reason, freshness, and recent CIA events.

Example:

debug.room()
debug.room("E48N13")

Sample output:

{
  "name": "E48N13",
  "energyAvailable": 300,
  "energyCapacity": 550,
  "controller": {
    "level": 3,
    "progress": 4520,
    "progressTotal": 135000,
    "ticksToDowngrade": 19876
  },
  "sources": 2,
  "spawns": ["Spawn1"],
  "creeps": {
    "builder": 1,
    "harvester": 2,
    "hauler": 1,
    "upgrader": 1
  },
  "constructionSites": 2,
  "structures": 18,
  "containers": 3,
  "sourceContainers": 2,
  "controllerContainers": 1,
  "towers": 1
}

debug.spawn(roomName?)

Returns a spawn summary for the first spawn in deterministic name order.

Parameters:

  • roomName?: string - optional owned room filter. When omitted, the helper uses the first owned spawn in deterministic name order.

If no matching spawn exists, the helper returns a descriptive message.

Returns a formatted JSON string with:

  • spawning - name of the creep currently being spawned, or null
  • energy - energy currently stored in the spawn

Example:

debug.spawn()

Sample output:

{
  "spawning": "Hauler12345678",
  "energy": 300
}

debug.body(roomName?)

Returns the current body-tier policy decision for a visible owned room.

Parameters:

  • roomName?: string - visible owned room name. When omitted, the helper uses the first visible owned room.

Returns a newline-delimited summary with:

  • CPU population mode, bucket, prior-completed-tick utilization EWMA, and last mode transition
  • economy status, mode, smoothed trend, spawn energy, reserve thresholds, and reserve spending permissions
  • local harvester full-tier lock state, primary Storage reserve ratio and threshold, approved-body wait state, and recovery fallback state
  • for each role, uncapped, RCL-capped, CPU-capped, safety-floor-adjusted, operator-override, and final effective desired count; selected option, tier, cost, size, WORK/CARRY capacity, fallback state, highest technically eligible tier, and highest economically approved tier
  • per-option eligibility, economic approval, current affordability, and reason

Example:

debug.body()
debug.body("E48N13")

Use this when a room reaches RCL 5 but does not choose a larger body, or when a selected body falls back because current spawn/extension energy is temporarily low. It is also the primary explanation for population throttling: inspect the uncapped/RCL/CPU/safety-floor/operator/effective sequence and the appended cap, safety-floor, or operator-authority reason. The current bucket bands and observed-utilization thresholds are reported by debug.gov() and Memory.stats.cpu.bucketTargets / .targets. For healthy mature rooms, confirm lines such as harvester full-tier lock: yes and storage reserve: 61234/1000000 (6.1%, threshold 5.0%) before diagnosing a local harvester down-tier.

debug.economy(roomName?)

Returns the latest persisted economy assessment for an owned room.

Parameters:

  • roomName?: string - visible owned room name. When omitted, the helper uses the first visible owned room.

Returns a newline-delimited summary with:

  • economy status and operating mode
  • smoothed net energy trend
  • stored energy, primary storage energy, reserve thresholds, deficits, and spending permissions
  • emergency state and recovery threshold
  • strategic surplus state, reason, storage pressure, source overflow pressure, and spending level
  • source and controller container energy totals
  • construction remaining and important repair count
  • CPU population mode, bucket, utilization EWMA, and last transition
  • effective builder, upgrader, and hauler counts with economy baselines and population-plan reasons
  • last full evaluation tick and active population cooldowns

The assessment is refreshed on the economy manager cadence. Fast-changing fields such as stored energy and construction remaining may update between full role-count evaluations, while desired-count reasons can reflect the last full evaluation tick.

Example:

debug.economy()
debug.economy("E48N13")

Sample output:

E48N13 economy: strained (normal)
trend: -0.2 energy/tick
stored: 95727/1010000 (9%)
primary storage: 89479/1000000
reserve: 89479/10000 target, min 3000, surplus at 15000
reserve deficits: operating=0, target=0, surplus=74479
reserve permissions: withdraw=yes, optional=yes, surplus=yes, store=no, controller=yes
emergency: no, entered=n/a, exit reserve=5000, reason=storage reserve is above emergency exit threshold
strategic surplus: inactive, active=no, entered=n/a, duration=0, spending=0
strategic reason: surplus signals are below entry thresholds
storage pressure: fill=9%, free=910521
source overflow: backlog=26%, count=0, risk=no
source containers: 648
controller containers: 5600
construction remaining: 1258
important repairs: 39
builder desired=2: 1258 construction remaining, 39 important repairs
upgrader desired=1: strained economy protects reserve and keeps controller work at baseline
last evaluation: 81458087
cooldowns: {"builder":{"increaseUntil":81453069,"decreaseUntil":81452434},"upgrader":{"increaseUntil":81458087,"decreaseUntil":81458062}}

Infrastructure and local construction

Colony layout is the authoritative fixed-local geometry source. It is generated inside the bounded infrastructure cadence and revalidated at most once per 100 ticks unless its version, anchor, or Lab recommendation changes. Local Roads come from the derived merge of layout lanes and logistics-owned arterial intent.

debug.localConstruction(roomName, itemId?, action?) is the local execution and manual-approval surface. Autonomous authority admits exact desired items directly. Manual authority requires an exact current fingerprint under localConstructionApprovals; old build orders and packages are never consulted. The list includes the last execution item/outcome and reports current-RCL or structure-capacity deferrals per item. Authoritative Lab items use their colony layout coordinates directly, have unique coordinate IDs, and do not require a separate operational Lab-cluster reservation.

The generic plan/order commands remain for remote source Containers and defensive Walls/Ramparts:

debug.buildOrders()
debug.buildOrder("E48N13-buildOrder0001")
debug.pending("E48N13")
debug.show(17)
debug.approve(17)
debug.reject(17, "unsafe remote placement")
debug.cancel(17, "no longer needed")
debug.infrastructurePlan("E48N13")
debug.infrastructurePlan("E48N13", "detailed")
debug.remoteRoads("E48N13", "E48N14")
debug.remoteConstruction("E48N13")

Remote Roads retain their dedicated exact approval lifecycle. Package commands, colony-layout rollout controls, and assisted Extension migration commands were removed by the completed infrastructure cutover.

infrastructureCutoverCleanupVersion = 1 filters existing mixed Memory once. It preserves colony layout, focused local-construction state, logistics road planning, remote source Containers/Roads, and defense records. It removes fixed-local plan/order mirrors, all work packages, and transitional rollout and migration state. The cleanup is bounded and idempotent.

debug.towers()

Returns a concise summary for every visible owned tower.

Parameters:

  • None.

Each tower includes:

  • energy as "current/max"
  • mode: Attack, Heal, Repair, or Idle
  • target when the tower would act this tick

Tower repair summaries follow src/config/towers.ts and only show roads or containers when the tower has enough energy to repair.

Example:

debug.towers()

Sample output:

E48N13 Tower1
Energy: 940/1000
Mode: Repair
Target: road (52%)

Inspecting Intent

Each action or workflow can write creep.memory.intent. Intent explains what the creep most recently tried to do and why.

Intent fields:

  • role - creep role at the time the intent was written.
  • workflow - workflow or role path, such as builder, builderWork, haulerDelivery, or acquireEnergy.
  • action - intended action, such as harvest, withdraw, pickup, deliver, build, repair, upgrade, move, work, or idle.
  • targetId - target object id when the target has one.
  • targetType - readable target type, such as source, container, spawn, extension, controller, road, energy drop, <structure> site, or <role> creep.
  • reason - human-readable selection reason.
  • result - Screeps result name, such as OK, ERR_NOT_FOUND, or ERR_NOT_IN_RANGE.
  • tick - Game.time when the intent was written.

Scout cross-room movement uses a fixed-size creep.memory.roomTransition record with approaching, crossing, and settling phases. A successful movement return code records only an accepted intent; the following tick's observed room and position establish actual progress. After entry, the scout records arrival and clears its mission target before settlement takes movement ownership. Settlement prefers the straight inward tile, then either inward diagonal, and uses bounded retries and a limited in-room fallback. The temporary transition record is deleted after settlement, target replacement, hostile retreat, or exhausted recovery.

When the requested destination is observed on an edge, its live room and position replace stale source-side crossing and recovery state before movement. Settlement remains the only movement owner for that tick; repeated transition calls in the same tick reuse the first result without issuing another move. Skipped CPU ticks do not alter the record, and normal role behavior resumes only after the creep is observed on a destination interior tile.

Scouts also prefer exits whose destination side has at least one terrain-walkable inward candidate before committing to a target room. Blocked routes and hostile scout rooms are tracked temporarily in Memory.scoutAvoidRooms with a reason and expiration tick. Approach paths to a selected exit are restricted to the current room with a bounded operation budget. This prevents Screeps pathfinding from treating an adjacent room as a shortcut to another edge of the same room; genuinely unreachable exits continue through the bounded alternate-exit recovery. Expired entries are pruned automatically so avoided rooms are retried later instead of being banned permanently. Hostile rooms clear the scout's active target when retreat begins, so they are revisited by the normal room intelligence refresh cycle rather than by an immediate enter/exit loop. Scout retreat intents include the hostile room, the current room, and the room or exit the scout is retreating toward; the movement return code remains in result. Hostile avoid entries expire after 100 ticks; blocked exits are retried after 500 ticks. When every current scout target is temporarily avoided, the scout caches the earliest avoidance expiration (or earlier intelligence-refresh deadline) and remains cheaply idle until that tick. The backoff is discarded if its room or home-room context changes, its avoidance snapshot changes, the scout is moved onto a room edge, or the cached tick is invalid or expires.

debug.intents()

Returns one line per creep, sorted by creep name.

Parameters:

  • None.

Each line contains:

  • creep name padded to 16 characters
  • action padded to 10 characters
  • target type padded to 12 characters
  • reason

Creeps that are still being spawned report spawning as their action until their role code runs and records an intent.

Example:

debug.intents()

Sample output:

Builder12345678 build      container site selected construction site: container
Harvester123456 harvest    source       continuously harvesting from assigned source container
Hauler12345678  deliver    spawn        spawn needs energy
Upgrader123456  withdraw   container    selected preferred container: 742 energy, 74% full

Use debug.intents() for a quick room-wide scan, then use debug.creep(name) or inspect(name) for the exact target id, result, store, TTL, and status.

Console Logs And Visuals

The main loop keeps routine console output intentionally low-noise. Most colony state is summarized by the room dashboard in src/debug/dashboard.ts instead of printing raw creep counts every tick.

Debug logging mode

Debug logging mode is stored in Memory.debug.enabled.

  • debug.enable() turns on per-tick dashboard logging.
  • debug.disable() returns dashboard logging to the normal interval.
  • debug.status() shows whether debug logging is enabled.

Example:

debug.enable()
debug.status()
debug.disable()

Room dashboard

logRoomDashboard(context, economyAssessment, maintenanceSummary) emits one multi-line room summary every 25 ticks by default. When Memory.debug.enabled is true, it emits every tick. The first line is Dashboard <roomName> so concurrent multi-room blocks remain attributable to their room.

Dashboard fields:

  • Dashboard <roomName> - owned room represented by this block.
  • [T<tick>] - current Game.time.
  • RCL<n> - room controller level, or RCL0 if no controller is visible.
  • ⚡<available>/<capacity> - room energy available and capacity.
  • 👥 H<n> Ha<n> U<n> B<n> - creep counts by role:
  • H - harvesters
  • Ha - haulers
  • U - upgraders
  • B - builders
  • 🏗<n> - construction site count.
  • src:<energy/capacity,...> - containers within range 1 of room sources, or src:none.
  • ctrl:<energy/capacity,...> - controller-area containers, or ctrl:none.
  • 🛡 tower:<energy/capacity,...> - owned tower energy, shown only when towers exist.
  • spawn:<status> - idle, current spawning creep role, or unknown.

Example:

Dashboard E48N14
[T123456] | RCL3 | ⚡450/800 | 👥 H2 Ha1 U2 B2 | 🏗6 | src:1850/2000,1980/2000 | ctrl:340/2000 | 🛡 tower:720/1000 | spawn:hauler

Event logs

The main loop also emits these event-oriented console messages:

  • Clearing non-existing creep memory: <name> when stale creep memory is deleted.
  • Started spawning <name> when spawn.spawnCreep returns OK.
  • Failed to spawn <name>: <result> when spawning fails for a non-busy result.
  • New creeps use preferredSpawnDirections from src/config/spawning.ts so the spawn can bias creeps away from local pinch points.

Spawn failure result names currently include:

  • OK
  • ERR_BUSY
  • ERR_NOT_ENOUGH_ENERGY
  • ERR_INVALID_ARGS
  • ERR_RCL_NOT_ENOUGH
  • Unknown codes are printed as UNKNOWN_RESULT_<code>.

Room visuals:

  • While Spawn1 is spawning, the room displays 🛠️<role> next to the spawn.
  • Spawn activity visuals are refreshed every tick while spawning, but no per-tick spawn status console message is emitted.
  • Movement calls use path visuals:
  • harvest and withdraw paths: #ffaa00
  • deliver, build, repair, and upgrade paths: #ffffff

Status Meanings

Statuses are stored in creep.memory.status and shown with creep.say(). inspect(name) and debug.creep(name) display them as an object like { "harvesting": "⛏️" }.

Status Emoji Meaning
building 🚧 The creep successfully built a construction site.
dropping 💧 The creep is dropping energy.
harvesting ⛏️ The creep successfully harvested a source.
hauling 🚚 The creep successfully transferred energy.
idle 😴 The creep had no usable target or no work to do.
moving 🏃 The creep is moving toward its current target.
pickingUp 💰 Reserved status for pickup behavior. No current action sets it.
repairing 🔧 The creep successfully repaired a structure.
scouting 👁️ The scout is observing or moving through scout workflow.
upgrading The creep successfully upgraded the controller.
withdrawing 📦 The creep successfully withdrew energy.

Movement may append a second intent emoji to 🏃:

Movement Say Meaning
🏃⛏️ Moving to harvest or to an assigned source container.
🏃📦 Moving to withdraw from a container or storage.
🏃🔋 Moving to an extension.
🏃🏠 Moving to a spawn.
🏃⚡ Moving to a controller.
🏃🚧 Moving to build a construction site.
🏃🔧 Moving to repair.
🏃🚚 Moving to deliver.

Common Debugging Workflow

Start with the room-wide view:

debug.intents()
debug.room()
debug.spawn()
debug.economy()

Then inspect the specific creep:

debug.creep("CreepName")

Check these fields first:

  • role
  • status
  • store.energy
  • task
  • sourceId
  • intentDetails
  • fatigue
  • ttl

Why is this creep idle?

  1. Run debug.creep("<name>").
  2. Read intentDetails.reason and intentDetails.result.
  3. If result is ERR_NOT_FOUND, the role could not find a valid target.
  4. Use role-specific checks:
  5. Harvesters: verify a source exists and sourceId resolves.
  6. Builders: verify energy, construction sites, damaged roads or containers, or controller access.
  7. Haulers: verify containers have energy or delivery targets need energy.
  8. Upgraders: verify energy and controller access.

Common idle reasons:

  • no active source found
  • no energy target found
  • no delivery target found
  • no usable container target
  • full hauler has no delivery target
  • no spawn, extension, builder, or upgrader needs energy
  • no construction site found
  • no repair target found
  • no controller found

Why isn't a hauler delivering?

Use:

debug.creep("Hauler123")
debug.economy()
debug.spawn()
debug.intents()

Check:

  • store.energy: if 0, the hauler should be withdrawing, not delivering.
  • memory.hauling: haulers switch to delivery after carrying any energy and switch back when empty.
  • task: active delivery tasks are stored as deliver:<targetId>.
  • intentDetails.reason:
  • gathering energy before delivery means the hauler is still filling.
  • selected preferred container: ... means it found a strong withdrawal target.
  • selected fallback container: ... means it found a weaker container.
  • no usable container target means no container has energy.
  • spawn needs energy or extension needs energy means spawn logistics are active.
  • <role> needs <n> energy means the hauler is refilling a working builder or upgrader.
  • no spawn, extension, builder, or upgrader needs energy means there is no eligible delivery target.

Haulers deliver in this priority order:

  1. Spawn or extension with free capacity.
  2. Working builders with at least 50 free energy capacity.
  3. Working upgraders with at least 50 free energy capacity.

Builders and upgraders are not delivery targets while their status is harvesting, pickingUp, or withdrawing.

Why won't a builder build?

Use:

debug.creep("Builder123")
debug.room()
debug.economy()
debug.remoteConstruction()

Check:

  • store.energy: empty builders run acquireEnergy.
  • memory.building: builders enter work mode when full and leave it when empty.
  • constructionSites in debug.room() output: 0 means there is nothing to build.
  • intentDetails.reason:
  • builder needs energy means the builder is refilling.
  • builder has energy means it is in the work workflow.
  • assigned approved remote construction <id> means the home room gave one ordinary builder an approved remote source-container or remote-road job.
  • building approved remote source container means the builder is executing that exact remote assignment.
  • building approved remote road means the builder is executing an approved remote-road construction site in the active remote target room.
  • withdrawing from approved remote source container means the empty assigned builder is refilling from the exact approved completed remote source container. For road jobs this is the route's remote endpoint container, not the current road-site tile.
  • picking up energy dropped at approved remote construction source means the empty assigned builder is using dropped energy adjacent to the authorized remote source, usually remote harvester overflow.
  • harvesting from approved remote construction source means the empty assigned builder is harvesting only the source tied to that approved remote container job.
  • returning to <room> to refill for remote construction means the builder is out of local usable energy or its remote assignment is suspended and it is walking home.
  • continuing from completed remote container repair to approved remote road means container repair reached the configured continuation target and the same builder was reassigned to a valid same-operation road site in the remote room.
  • repairing approved remote source container means the assigned builder is repairing only the exact completed container tied to its approved remote construction assignment after construction work is gone. Completed remote source-container repair starts below 60% hits and continues until 80% for the already-assigned builder.
  • selected construction site: <type> means it found build work.
  • <structure> hits <n>%, below <threshold>% means repair was chosen.
  • no build or repair targets means it is falling back to controller upgrading.

Builder work order:

  1. Continue a valid approved remote source-container assignment, if present. This includes exact-container repair only for that assignment when no remote construction site remains.
  2. Build construction sites, preferring container sites.
  3. Repair containers below 50%.
  4. Repair roads below 70%.
  5. Upgrade the controller.

If approved remote road sites are not being built, use:

debug.intents()
debug.remoteConstruction("E48N13")
debug.remoteRoads("E48N13", "E48N14")
debug.creep("Builder123")
debug.buildOrders()
debug.infrastructurePlan("E48N13")
debug.infrastructurePlan("E48N13", "detailed")

Check:

  • debug.intents() shows whether any builder is already assigned to remote construction. A line like repair container repairing approved remote source container means the current remote builder slot is occupied by the approved remote source-container assignment.
  • debug.remoteConstruction(homeRoomName) identifies the dispatch state: assigned builder, waiting for approval, waiting for site placement, waiting for an eligible builder, or the first validation/policy blocker.
  • debug.remoteRoads(homeRoomName, targetRoomName) should show the route as approved while active road construction sites remain. A route is complete only when completed equals required, sites=0, and missing=0; active sites are work for the remote builder, not completed road tiles.
  • debug.creep(builderName) shows memory.remoteConstruction.constructionKind, plannedStructureId, structureType, and intentDetails.reason for the assigned builder.
  • debug.buildOrders() and debug.infrastructurePlan(homeRoomName) are useful when dispatch says the route is waiting for approval, waiting for site placement, or invalid.

Remote construction dispatch currently assigns at most one ordinary builder per home room. If that builder is repairing the approved remote source container, approved remote-road work waits until the container repair assignment clears. Healthy remote containers should not hold that slot; check the container hits if the assigned builder keeps repairing one.

Why isn't a creep harvesting?

Use:

debug.creep("CreepName")
debug.intents()

Check:

  • role: only harvesters always prefer harvesting. Builders and upgraders first try stored energy before falling back to harvesting.
  • sourceId: harvesters should have an assigned source id.
  • intentDetails.result:
  • ERR_NOT_IN_RANGE means it is moving to the source.
  • OK with status harvesting means harvesting is working.
  • ERR_NOT_FOUND with no manager-assigned source means SpawnManager has not assigned the harvester yet.
  • ERR_NOT_FOUND with manager-assigned source is not visible or no longer exists means the saved sourceId did not resolve this tick.
  • ERR_NOT_FOUND with no active source found means no active source was reachable.
  • fatigue: high fatigue can make movement look stalled.

Harvester behavior:

  • SpawnManager owns harvester source allocation and rebalancing. Harvesters execute their assigned sourceId and idle for the current tick if that assignment is missing or unresolved.
  • If a container exists within range 1 of the assigned source, the harvester moves onto the container and continuously attempts to harvest there. It does not spend normal source-container ticks transferring carried energy; harvested overflow is allowed to fall onto the tile and enter the container underneath.
  • If no source container exists and the harvester has free capacity, it harvests the assigned source.
  • If no source container exists and the harvester is full, it delivers to spawn or extensions as a bootstrap fallback.

Harvest telemetry:

  • Memory.stats.rooms[roomName].energy.harvest.capacity.tick shows theoretical assigned harvester capacity from active WORK parts, capped at 10 energy/tick per source.
  • Memory.stats.rooms[roomName].harvesters.active counts living, non-spawning harvesters; successfulHarvests counts successful harvest actions; harvestAttempts counts all harvester harvest calls; and failedHarvests counts harvest calls returning anything other than OK.
  • Memory.stats.rooms[roomName].harvesters.moving counts harvesters moving to their assigned source or source container. idle means the harvester did not move, harvest, repair, or transfer this tick. repairing is currently expected to stay 0 because harvesters do not perform repair work.
  • Memory.stats.rooms[roomName].harvesters.results breaks harvest attempts into numeric result buckets such as ok, notInRange, and notEnoughResources.
  • Memory.stats.rooms[roomName].harvesters.expectedHarvestEnergy, energyHarvested, and missedHarvestEnergy distinguish expected harvest from actual successful harvest.
  • Memory.stats.rooms[roomName].harvesters.unresolvedSource, notOnContainer, noSourceContainer, sourceEmpty, and capacityBlocked separate missing assignments, walking to the source container, bootstrap mining without a completed source container, empty sources, and full creep/container capacity.
  • Memory.stats.rooms[roomName].sources[sourceId].harvest.tick shows estimated actual harvested energy per visible source for the current tick. Neighboring fields under the same path expose attempts, failures, expected/missed energy, assigned harvester count, active WORK parts, source energy, regeneration, and source-container capacity.
  • Memory.rooms[roomName].harvest.byName[creepName] contains transient, current-tick diagnostics for each living harvester. state uses 0=idle, 1=harvest, 2=move, 3=repair, 4=transfer, and 5=unresolved source. This operational map is deliberately excluded from Memory.stats to avoid creating permanent telemetry series for unique replacement-creep names.

For a 16-to-8 drop, check these first:

  • capacity=16, energyHarvested=8, harvestAttempts=1, moving=1: one harvester moved instead of attempting to harvest.
  • harvestAttempts=2, successfulHarvests=1, failedHarvests=1: one harvester attempted and failed; inspect results.* and per-source failed.
  • results.full>0 plus capacityBlocked>0: the creep and container under it had no harvest capacity.
  • sourceEmpty>0 or per-source energy=0: the source was empty or regenerating.
  • capacityBlocked>0 with expected energy 0: the creep and container under it had no free energy capacity.
  • In transient room harvest memory, normal source-container harvesters should remain in state=1 with repeated harvest attempts rather than alternating with state=4; state=4 should only appear for the no-container bootstrap delivery fallback.

Why isn't the spawn producing creeps?

Use:

debug.spawn()
debug.spawnPlan()
debug.spawnOverrides()
debug.body()
debug.room()
debug.intents()

Check:

  • spawning in debug.spawn() output: if set, the spawn is already busy.
  • energy in debug.spawn() output and energyAvailable in debug.room() output: current available energy must afford the selected body.
  • debug.body() output: confirm the role has an economically approved body, whether current energy can afford it, and whether the policy selected a recovery fallback or is briefly waiting for an optional larger body.
  • debug.spawnPlan() output: compare automatic, RCL-capped, CPU-capped, safety-floor-adjusted, operator-override, and final effective counts.
  • debug.spawnOverrides() output: confirm that a deliberate final count has not temporarily or indefinitely replaced automated demand.
  • Console logs:
  • Started spawning <name> means spawning succeeded. The log includes role, selected body option, tier, and whether a fallback body was used.
  • Failed to spawn <name>: ERR_NOT_ENOUGH_ENERGY means the selected body was not affordable at the time of spawning.
  • No failure log can mean no role is below its desired count, or no body could be chosen.

Spawn priority and desired counts:

  1. harvester: exactly one per source, recovery-safe bodies first; the five-WORK source-saturating body is the high tier and remains gated by source throughput and economy health.
  2. defender: zero normally; one survival-priority melee defender when the room defense assessment says towers and active defenders are inadequate.
  3. mineralMiner: one mature miner when the RCL 6 mineral operation is eligible; this optional role is considered before logistics and remote-operation demand.
  4. hauler: zero until source containers exist, then one baseline hauler; two or three only when source containers or sustained destination demand show clear logistics pressure.
  5. upgrader: one baseline upgrader; two or three only when stored energy is healthy and active builder demand is not competing for energy.
  6. builder: zero when there are no construction sites or builder-managed repairs; one to three as construction and road/container repair backlog grows.
  7. scout: no scout before RCL 3; from RCL 3 onward, one low-priority [MOVE] scout when adjacent room intelligence is missing, stale, or needs a metadata refresh. Per-room planning still counts living scouts assigned by memory.homeRoom, even when those scouts are outside the owned room; final spawn admission prevents those plans from producing more than one active or spawning scout across the colony.

debug.defense(roomName?)

Returns the latest persisted owned-room defensive assessment without recalculating combat policy.

debug.defense()
debug.defense("W1N1")

The output includes posture and reason, actionable hostile pressure, shared tower target and adequacy, desired/active defenders, critical-rampart minimum hits and exposure, and safe-mode policy, recommendation, eligibility, and retry state. Hostile usernames and free-form reasons are intentionally kept out of numeric stats.

Planned Debug Helpers

Potential future helpers include deeper stale-memory/task/source inspection, room visual toggles, and more granular workflow profiling. Do not document a planned helper as an available command until it is implemented and registered in debug.help().

Adding New Debug Commands

Debug documentation is part of the codebase, not optional notes.

Whenever a new console helper, global command, inspect function, profiler, visualization helper, or debugging utility is added, both of the following must happen in the same pull request:

  1. Implement the feature.
  2. Update docs/DEBUG.md.

The documentation update should include:

  • command name
  • parameters
  • return value
  • example usage
  • sample output where useful
  • any status, emoji, memory, visual, or console-log behavior it introduces

If a helper is removed or renamed, update this file in the same pull request.