Explain what a semaphore is.
Implement a nonblocking counting semaphore simulator that processes task acquire and release operations. Use FIFO ordering for blocked tasks, and record whether each task acquires, blocks, releases, or is awakened.
The function receives an initial permit count and valid operations shaped as { "action": "acquire"|"release", "task": "id" }. Return one event string per operation. A release always belongs to a task currently holding a permit.
def simulate_semaphore(initial_permits, operations):