Porphyrophobia
Implement an event-driven simulation engine.
The engine maintains a global clock and a set of pending callbacks. Callbacks are scheduled to fire at specific future ticks. The engine processes commands from an input stream and prints output as specified.
Input Format
num_commands
command_1
command_2
...
command_num_commands
Each command is one of the following:
SCHEDULE <id> <tick> <purple> [num_side_effects]
CANCEL <id>
TICK
QUERY <id>
idis a unique integer identifier for a callback. No twoSCHEDULEcommands in the input will use the sameid.tickis the tick at which the callback is scheduled to fire. It is guaranteed thattickis strictly greater than the current clock value at the time of the command.purpleis either1(purple-tier) or0(non-purple).num_side_effectsis an optional integer. If present and greater than0, the nextnum_side_effectslines are side-effect commands belonging to this callback. If omitted or0, the callback has no side effects.CANCELandQUERYmay reference anidthat does not exist or has already fired or been cancelled.
Side-effect commands are themselves SCHEDULE, CANCEL, or QUERY commands (with their own possible nested side effects for SCHEDULE). TICK is not permitted as a side effect.
Output Format
The engine produces output only in response to FIRE events and QUERY commands. All output is one line per event, printed to stdout.
Engine State
The engine maintains:
- A clock, initialized to
0. - A set of pending callbacks, each with an
id, a scheduledtick, apurpleflag, and a registration order (the order in which it was added viaSCHEDULE, starting from0and incrementing globally across allSCHEDULEcommands — including those issued as side effects). - A set of fired callback ids — callbacks that have executed.
- A set of cancelled callback ids — callbacks that were cancelled before firing.
Commands
SCHEDULE <id> <tick> <purple> [num_side_effects]
Adds a new callback to the pending set. Prints nothing.
If num_side_effects is present and greater than 0, the following num_side_effects lines define the side effects that will execute when this callback fires. See Callback Side Effects.
CANCEL <id>
If id is currently pending, removes it from the pending set and adds it to the cancelled set. Prints nothing.
If id is not pending (already fired, already cancelled, or never scheduled), this command is a no-op. Prints nothing.
TICK
Advances the clock by 1. Then fires all pending callbacks whose scheduled tick equals the new clock value, according to the execution rules below.
QUERY <id>
Prints the current status of callback id:
PENDING <id>
FIRED <id>
CANCELLED <id>
UNKNOWN <id>
PENDING— the callback is in the pending set.FIRED— the callback has fired.CANCELLED— the callback was cancelled.UNKNOWN—idwas never scheduled.
Callback Side Effects
When a callback fires, its side effects execute in order, immediately, as part of firing that callback. Each side effect is a command (SCHEDULE, CANCEL, or QUERY). A SCHEDULE side effect may itself have nested side effects — these are not executed until that newly scheduled callback fires.
Side effects complete fully before the next callback in the tick's firing order is considered.
Tick Execution
When TICK is processed:
- Increment the clock by
1. - Collect all pending callbacks whose scheduled tick equals the new clock value. Sort them into a fire queue: purple callbacks first, then non-purple. Within each tier, order by registration order (ascending). This fire queue is fixed at this point and does not change.
- Process the fire queue front to back:
- If the callback is still pending (it may have been cancelled by a prior callback's side effect), fire it: remove from pending, add to fired, print
FIRE <id>, then execute its side effects in order. - If the callback is no longer pending (cancelled mid-tick), skip it silently.
- If the callback is still pending (it may have been cancelled by a prior callback's side effect), fire it: remove from pending, add to fired, print
TICKis complete.
Callbacks scheduled by side effects during this tick are not added to the current fire queue, even if their scheduled tick equals the current clock. They are effectively dead — the clock will never revisit this tick.
Behavioral Requirements
- The clock starts at
0and increments by exactly1perTICKcommand. - Registration order is global and monotonically increasing. Every
SCHEDULEcommand — whether top-level or a side effect — increments the global counter. - A callback scheduled as a side effect is valid. If its tick is in the future, it will fire when the clock reaches it. If its tick equals the current clock, it will never fire.
CANCELon an unknown, already-fired, or already-cancelled id is a no-op.QUERYon an unknown id printsUNKNOWN <id>.- A callback cancelled mid-tick by another callback's side effect does not fire, even if it was already in the fire queue.
Notes
- The engine is fully deterministic. No randomness, no timeouts.
- The engine does not skip ticks.
TICKalways advances by exactly1, even if no callbacks are pending. - Side effects can be arbitrarily nested: a side effect can schedule a callback whose own side effects schedule further callbacks. Registration order and all state transitions apply normally.
4
SCHEDULE 1 1 0
TICK
QUERY 1
TICKFIRE 1
FIRED 1