44#include < algorithm>
55#include < cassert>
66#include < cmath>
7+ #include < iostream>
78#include < stdexcept>
89#include < string>
910#include < unordered_map>
@@ -250,6 +251,12 @@ class Assembler : public GridObject, public Usable {
250251 // Chest search distance - if > 0, assembler can use inventories from chests within this distance
251252 unsigned int chest_search_distance;
252253
254+ // Per-agent cooldown duration - number of timesteps before an agent can use this assembler again
255+ unsigned int agent_cooldown;
256+
257+ // Per-agent cooldown tracking - maps agent_id to timestep when cooldown ends
258+ std::vector<unsigned int > agent_cooldown_ends;
259+
253260 Assembler (GridCoord r, GridCoord c, const AssemblerConfig& cfg, StatsTracker* stats)
254261 : protocols(build_protocol_map(cfg.protocols)),
255262 unclip_protocols (),
@@ -266,6 +273,7 @@ class Assembler : public GridObject, public Usable {
266273 obs_encoder(nullptr ),
267274 allow_partial_usage(cfg.allow_partial_usage),
268275 chest_search_distance(cfg.chest_search_distance),
276+ agent_cooldown(cfg.agent_cooldown),
269277 clipper_ptr(nullptr ) {
270278 GridObject::init (cfg.type_id , cfg.type_name , GridLocation (r, c), cfg.tag_ids , cfg.initial_vibe );
271279 }
@@ -287,9 +295,36 @@ class Assembler : public GridObject, public Usable {
287295 }
288296
289297 // Initialize the per-agent tracking array (call after knowing num_agents)
290- // Note: This is a no-op for now since we removed per-agent cooldown, but kept for API compat
291- void init_agent_tracking (unsigned int /* num_agents*/ ) {
292- // No-op - per-agent cooldown was removed
298+ void init_agent_tracking (unsigned int num_agents) {
299+ if (agent_cooldown > 0 ) {
300+ agent_cooldown_ends.resize (num_agents, 0 );
301+ }
302+ }
303+
304+ // Check if a specific agent is on cooldown
305+ bool is_agent_on_cooldown (unsigned int agent_id) const {
306+ if (agent_cooldown == 0 || agent_id >= agent_cooldown_ends.size ()) {
307+ return false ;
308+ }
309+ return current_timestep_ptr && agent_cooldown_ends[agent_id] > *current_timestep_ptr;
310+ }
311+
312+ // Get remaining cooldown for a specific agent
313+ unsigned int get_agent_cooldown_remaining (unsigned int agent_id) const {
314+ if (agent_cooldown == 0 || agent_id >= agent_cooldown_ends.size () || !current_timestep_ptr) {
315+ return 0 ;
316+ }
317+ if (agent_cooldown_ends[agent_id] <= *current_timestep_ptr) {
318+ return 0 ;
319+ }
320+ return agent_cooldown_ends[agent_id] - *current_timestep_ptr;
321+ }
322+
323+ // Set cooldown for a specific agent
324+ void set_agent_cooldown (unsigned int agent_id) {
325+ if (agent_cooldown > 0 && agent_id < agent_cooldown_ends.size () && current_timestep_ptr) {
326+ agent_cooldown_ends[agent_id] = *current_timestep_ptr + agent_cooldown;
327+ }
293328 }
294329
295330 // Get the remaining cooldown duration in ticks (0 when ready for use)
@@ -439,6 +474,11 @@ class Assembler : public GridObject, public Usable {
439474 return false ;
440475 }
441476
477+ // Check per-agent cooldown
478+ if (is_agent_on_cooldown (actor.agent_id )) {
479+ return false ;
480+ }
481+
442482 // Check if on cooldown and whether partial usage is allowed
443483 unsigned int remaining = cooldown_remaining ();
444484 if (remaining > 0 && !allow_partial_usage) {
@@ -497,6 +537,9 @@ class Assembler : public GridObject, public Usable {
497537 cooldown_duration = static_cast <unsigned int >(protocol_to_use.cooldown );
498538 cooldown_end_timestep = *current_timestep_ptr + cooldown_duration;
499539
540+ // Set per-agent cooldown
541+ set_agent_cooldown (actor.agent_id );
542+
500543 // If we were clipped and successfully used an unclip protocol, become unclipped. Also, don't count this as a use.
501544 if (is_clipped) {
502545 become_unclipped ();
@@ -515,6 +558,14 @@ class Assembler : public GridObject, public Usable {
515558 features.push_back ({ObservationFeature::CooldownRemaining, static_cast <ObservationType>(remaining)});
516559 }
517560
561+ // Add per-agent cooldown remaining if the observer is a valid agent
562+ if (observer_agent_id != UINT_MAX && agent_cooldown > 0 ) {
563+ unsigned int agent_remaining = std::min (get_agent_cooldown_remaining (observer_agent_id), 255u );
564+ if (agent_remaining > 0 ) {
565+ features.push_back ({ObservationFeature::AgentCooldownRemaining, static_cast <ObservationType>(agent_remaining)});
566+ }
567+ }
568+
518569 // Add clipped status to observations if clipped
519570 if (is_clipped) {
520571 features.push_back ({ObservationFeature::Clipped, static_cast <ObservationType>(1 )});
0 commit comments