Blog › ICP guides

Simula developer on retainer: PROCESS coroutines, HOLD/PASSIVATE/ACTIVATE, CLASS hierarchy, discrete event simulation, and Simula engineering on monthly retainer

November 2, 2026 · ~18 min read

A Simula discrete event simulation was losing five customer activations per run. The system modeled a single-server queue: a QUEUE PROCESS subclass managed the service discipline, pulling CUSTOMER PROCESS instances from a waiting list, activating them for service, then holding for the service duration before activating the next customer. The QUEUE process implemented service in a WHILE loop that checked whether the waiting list was non-empty, executed ACTIVATE customer to schedule the customer for service, then executed HOLD(service_time) to suspend the QUEUE for the service duration. On simulation runs where the queue was empty when the QUEUE process first checked, the QUEUE process executed PASSIVATE to suspend itself indefinitely. A separate GENERATOR PROCESS created CUSTOMER instances and placed them in the waiting list, then attempted to reactivate the QUEUE by executing ACTIVATE queue. The bug: the GENERATOR executed ACTIVATE queue before the QUEUE had passivated itself — the QUEUE was still in mid-execution at the moment GENERATOR called ACTIVATE. Simula’s scheduling semantics place the newly activated process at a specific point in the Sequencing Queue Set (SQS); when the QUEUE later executed PASSIVATE, it was removed from the SQS without ever re-executing. Five customers per run were permanently dropped because their GENERATOR called ACTIVATE before PASSIVATE completed. The Simula developer on retainer diagnosed the sequencing violation: the ACTIVATE call must occur after the PASSIVATE executes, which means either the GENERATOR must wait (using HOLD) before calling ACTIVATE, or the QUEUE must check the waiting list again before passivating. The fix restructured the QUEUE loop to check for waiting customers immediately before each PASSIVATE call, ensuring ACTIVATE calls from GENERATOR always target a passivated QUEUE. Lost activations per run: 5 → 0.

The work log entry read “fixed simulation queue activation bug, 13h.” It names the symptom and the duration. It cannot explain to a client why the sequencing of ACTIVATE-before-PASSIVATE creates a scheduling race in Simula’s coroutine model (Simula’s SQS is a single sorted list of event notices; ACTIVATE places a notice in the SQS at the specified simulation time; if the target process executes PASSIVATE after being placed in the SQS as the next event, the PASSIVATE removes the notice and the process never executes), why ACTIVATE-after-PASSIVATE is the correct ordering (the PASSIVATE makes the process dormant; the subsequent ACTIVATE from another process places it back in the SQS for future execution), why Simula’s TIME variable represents simulation time rather than wall-clock time (Simula simulates time discretely; HOLD(t) advances simulation time by t units without consuming real time), or why the SQS trace was the diagnostic tool that revealed the dropped activations (the SQS trace shows all scheduled event notices; the absence of a CUSTOMER notice in the SQS after GENERATOR called ACTIVATE indicated the notice was placed and immediately removed). The 13 hours of SQS trace analysis, ACTIVATE/PASSIVATE ordering design, WHILE loop restructuring, simulation run validation, and statistical output verification are not visible in the diff beyond a restructured loop body.

Simula’s PROCESS coroutine system: HOLD, PASSIVATE, ACTIVATE, and the SQS

Simula was designed by Ole-Johan Dahl and Kristen Nygaard at the Norwegian Computing Center in the 1960s as a simulation extension of ALGOL 60. Its central innovation was the CLASS, the first formal object-oriented construct, combined with coroutine-based quasi-parallel execution for modeling concurrent discrete events. Simula’s PROCESS class provides coroutine semantics: a PROCESS instance has its own execution stack and can be suspended and resumed at arbitrary points. The fundamental PROCESS lifecycle: a PROCESS is created with NEW PROCESS_CLASS, placed into the simulation by calling ACTIVATE p, and runs until it either completes or suspends. Suspension mechanisms: HOLD(t) suspends the current process for t time units of simulation time, placing it in the SQS to be resumed after the specified duration; PASSIVATE suspends the current process indefinitely, removing it from the SQS; the process will not resume until another process executes ACTIVATE targeting it.

The Sequencing Queue Set (SQS) is Simula’s central scheduling data structure. It is a sorted list of event notices, each containing a reference to a scheduled PROCESS and the simulation time at which that process should resume. The simulation kernel always executes the PROCESS at the head of the SQS — the one with the earliest scheduled time. TIME is a global variable that always holds the current simulation time; it advances automatically as the kernel picks the next event notice from the SQS. ACTIVATE variants: ACTIVATE p AT t schedules process p to resume at absolute simulation time t; ACTIVATE p DELAY d schedules p to resume d time units after the current TIME; ACTIVATE p BEFORE q and ACTIVATE p AFTER q insert p’s event notice immediately before or after q’s notice in the SQS at the same simulation time. REACTIVATE p reschedules a process that is already in the SQS, removing its existing event notice and placing a new one. The distinction between ACTIVATE (for passivated processes) and REACTIVATE (for already-scheduled processes) is the most common source of double-scheduling errors in Simula retainer code.

The MAIN program in Simula is itself a coroutine. When the simulation begins, MAIN runs first, creating and activating the initial set of PROCESS instances. When MAIN calls HOLD or PASSIVATE, the simulation kernel picks the next event from the SQS and resumes it. MAIN typically holds for a finite simulation duration and then terminates the simulation by reading statistics. The DETACH procedure is a lower-level coroutine primitive that transfers control back to the calling coroutine (the one that created or last activated the current process) without scheduling a specific future resume time. DETACH is used to implement producer-consumer patterns and coroutine pipelines where one coroutine produces values for another to consume. The PROCESS class abstracts over DETACH by providing the higher-level HOLD/PASSIVATE/ACTIVATE scheduling vocabulary. Retainer work frequently involves diagnosing code that mixes DETACH with PASSIVATE/ACTIVATE, creating scheduling state that the SQS no longer correctly tracks.

A correct single-server queue simulation in Simula: the GENERATOR PROCESS creates CUSTOMER instances at exponentially distributed inter-arrival times (using HOLD with a random variate), adds each CUSTOMER to a waiting list, then checks if the SERVER PROCESS is passivated (using the IDLE boolean procedure on the SERVER reference) and, if so, calls ACTIVATE server. The SERVER PROCESS loops: if the waiting list is empty, executes PASSIVATE; otherwise, removes the first CUSTOMER from the list, calls ACTIVATE customer DELAY 0 to schedule the customer for immediate post-service execution, then calls HOLD(service_time) to model the service duration. The critical ordering invariant: the SERVER’s PASSIVATE must execute before the GENERATOR’s ACTIVATE reaches the SERVER. The standard idiom for checking this invariant: use the IDLE predicate (true when a process is passivated and not in the SQS) rather than a boolean flag variable, since IDLE reflects the actual SQS state.

Simula’s CLASS system: INNER, VIRTUAL, inheritance, and reference types

Simula’s CLASS mechanism is the historical origin of object-oriented programming. A CLASS declaration: CLASS vehicle; INTEGER speed; PROCEDURE describe; BEGIN ... END; defines a class named vehicle with an integer attribute speed and a procedure attribute describe. A subclass: vehicle CLASS truck; INTEGER payload; PROCEDURE describe; BEGIN ... END; declares truck as a subclass of vehicle. An instance: REF(truck) t; t :- NEW truck; creates a new truck and assigns the reference to t. The :- operator is Simula’s reference assignment; := is used only for value types (integers, reals, booleans, characters). The distinction between :- and := is the most common syntax error in Simula code written by programmers coming from ALGOL or C-family languages.

The INNER statement in a base class procedure body designates the point where the subclass body executes. When a procedure is called on an instance of a subclass, the base class procedure body executes until it reaches INNER; at that point, the subclass body executes; when the subclass body completes, control returns to the base class body after INNER. This is Simula’s mechanism for defining template method patterns: the base class defines setup and teardown code before and after INNER, and the subclass provides the middle step. If a CLASS declaration has no INNER statement, the subclass body simply executes after the base class body completes. VIRTUAL procedures are declared in the base class as VIRTUAL: PROCEDURE compute_speed; without an implementation body. Each concrete subclass must provide the implementation. Simula dispatches virtual procedure calls to the implementation in the most-specific class. A base class can also provide a default VIRTUAL body with VIRTUAL PROCEDURE describe; BEGIN default implementation; INNER; END; — the INNER inside the VIRTUAL body is where the subclass override executes if one exists. The combination of VIRTUAL and INNER creates a flexible dispatch system that is historically the predecessor of virtual method tables in C++ and method lookup in Smalltalk.

Reference types in Simula are declared with REF(ClassName). A reference variable can hold either a reference to an instance of the named class (or any subclass) or the null value NONE. Testing for NONE: IF r IS NONE THEN ... tests whether reference r is null; IF r ISNT NONE THEN ... tests whether it is non-null. Note that IS and ISNT are reference equality predicates in Simula, not just NONE checks: IF r IS s THEN ... tests whether r and s point to the same object. This dual use of IS/ISNT for both null-checking and reference equality is a common source of bugs when programmers assume IS is only used for NONE. Downcasting: r QUA truck treats reference r (declared as REF(vehicle)) as a reference to a truck instance. The QUA expression signals a runtime error if r does not actually refer to a truck instance. Simula does not have a safe downcast operator that returns NONE on failure; all QUA expressions must be guarded by explicit type-checking code. The INSPECT statement provides a pattern for safe dispatch: INSPECT r WHEN truck DO ... WHEN car DO ... OTHERWISE DO ... executes the appropriate branch based on the actual class of the referent without explicit QUA downcasts.

Simula’s attribute access uses the dot notation: t.speed accesses the speed attribute of the object referenced by t. Procedure calls: t.describe calls the describe procedure on the truck instance. The THIS ClassName expression inside a procedure body returns a reference to the current object treated as an instance of the named class — Simula’s equivalent of self or this in modern OOP languages. Inner class instances in Simula can access attributes of their enclosing class directly by name; this scoping rule is a frequent source of confusion in Simula retainer code review when inner class attributes shadow enclosing class attributes with the same name. The retainer audit task for Simula inheritance hierarchies: for each VIRTUAL procedure declaration in a base class, verify that all concrete subclasses provide an implementation; for each CLASS with a NONE-typed ref() attribute, verify that all access paths guard the dereference with an IS NONE check; for each QUA downcast, verify that a preceding INSPECT or type check makes the downcast safe.

Simula I/O, simulation statistics, and Simula’s design legacy

Simula’s I/O system uses file-based classes. InFile and OutFile are classes for reading from and writing to files; instances are created with NEW InFile("filename") and opened with the Open procedure. InImage reads a line from an input file into the Image buffer (a TEXT value); OutImage writes the current output buffer. GetInt, GetReal, GetBool, GetChar extract typed values from the Image buffer; PutInt, PutReal, PutChar, PutText add values to the output buffer. SYSIN and SYSOUT are the standard input and output streams; they are always available without explicit file creation. Simula’s TEXT type is a mutable sequence of characters: T :- COPY(some_text) creates a copy; T.GetChar reads the next character from the text; T.Sub(pos, len) extracts a substring; T.Length returns the character count. Comparison: T1 = T2 compares TEXT values by content (not by reference, which uses IS/ISNT). The TEXT comparison-by-content vs reference-equality-by-IS distinction is a common retainer bug in string-handling Simula code.

Simulation statistics collection in Simula: queue length over time is captured by recording the queue length at each event point (arrival and departure) weighted by the time duration of that queue length; the time-weighted average is computed at simulation end. Waiting times are collected by recording the TIME at which each CUSTOMER enters the queue and subtracting from the TIME at which service begins. Server utilization: total service time divided by total simulation duration. Simula programs collect these statistics using global accumulator variables updated in each PROCESS’s execution body. The retainer task of simulation statistics design involves: identifying the event points that define state transitions; verifying that the weighting factor (duration between events) is correctly computed; ensuring that statistics are not collected during the initial warm-up period (before the simulation reaches steady state); and validating that the computed statistics match analytical results for simple distributions.

Simula’s design legacy in programming language history is unmatched: it introduced the CLASS concept (1967, Simula67), the first formal object-oriented system; it introduced coroutine quasi-parallel execution for simulation; and it introduced reference semantics with null references (NONE) and runtime type checking (QUA, INSPECT). Bjarne Stroustrup explicitly named Simula as the primary inspiration for C++ classes. Alan Kay cited Simula as an influence on Smalltalk’s message-passing model. Java’s class hierarchy, C#’s virtual methods, and every object-oriented language since the 1970s derives from concepts Simula introduced. Retainer work with Simula today typically occurs in three contexts: university programming language research (studying the origins of OOP); simulation science (using Simula for discrete-event system modeling in domains where legacy Simula codebases exist); and compiler research (building on or extending the Simula language). The SIMULA Standard (Simula-87) is the current formal specification; implementations include the GNU Simula compiler (cim) and the Simula Research Laboratory’s reference implementation.

How HourTab tracks Simula developer retainer hours

Simula retainer work shares the invisible-work problem common to all simulation language retainers, with the additional challenge that Simula’s most important retainer tasks — ACTIVATE/PASSIVATE sequencing repair, VIRTUAL procedure hierarchy design, ref()/NONE/QUA reference type audit, SQS scheduling analysis, and simulation statistics design — produce diffs whose surface area is small relative to the analytical work required. Restructuring a QUEUE process loop to check for waiting customers before each PASSIVATE call is a diff with four lines; the value is correct simulation behavior with no dropped activations, a PROCESS lifecycle that correctly models the single-server queue discipline, and an activation sequencing invariant that holds under all simulation scenarios including empty-queue startup. Adding a VIRTUAL procedure override to a TRUCK subclass is a diff with three lines; the value is correct dispatching behavior for all TRUCK instances, elimination of wrong-method invocations that silently returned base-class results for the wrong entity type, and a CLASS hierarchy that correctly implements the intended polymorphic dispatch model. Adding NONE checks before all ref(CUSTOMER) dereferences is a diff with a guard condition at each access site; the value is elimination of runtime dereference errors on all empty-queue code paths.

HourTab gives Simula developers a public retainer-hours URL they send to clients — typically simulation science researchers working with legacy Simula codebases, university programming language courses using Simula to teach the historical origins of OOP and discrete event simulation, and compiler research teams studying or extending the Simula language implementation — at the start of an engagement. For Simula retainers, each work log entry should name the mechanism (HOLD(t) time-delay design; PASSIVATE indefinite suspension; ACTIVATE p AT t/DELAY d/AFTER q scheduling design; REACTIVATE rescheduling; SQS event notice ordering audit; ACTIVATE-before-HOLD sequencing repair; CLASS declaration with INNER slot design; VIRTUAL procedure declaration and override; REF(ClassName) typed reference; NONE null reference audit; QUA ClassName downcast design; IS/ISNT reference-equality predicate; INSPECT pattern dispatch; InFile/OutFile with InImage/OutImage I/O; TEXT type string handling), the specific PROCESS or CLASS name and the sequencing or dispatch problem, and the before/after observable metric. Simula retainers are often compared to Smalltalk developer retainers for OOP inheritance hierarchy work (both languages were critical early OOP systems), to CLU developer retainers for abstract type and module system design (CLU was designed in the same 1970s era as Simula-87 and shares the structured exception philosophy), and to Dylan developer retainers for method dispatch design in academic OOP language contexts. The distinction from Smalltalk is Simula’s discrete event simulation kernel: Simula’s most common retainer work involves PROCESS coroutine scheduling analysis and ACTIVATE/PASSIVATE/HOLD sequencing, not message-passing and metaclass design as in Smalltalk. HourTab’s work log makes the SQS trace analysis and ACTIVATE sequencing design visible to clients who would otherwise see only the symptom — customers missing from simulation output or wrong statistical results — and not understand why the fix required understanding Simula’s SQS ordering semantics and the ACTIVATE-before-PASSIVATE invariant.

Track Simula developer retainer hours without the status emails

HourTab gives Simula developers a public URL per client retainer. One link, no login, live burn-down. Your clients stop asking “how many hours do I have left?” and your work log becomes the proof of value that gets the retainer renewed.

See HourTab pricing →

FAQ: Simula developer retainers

What does a Simula developer on retainer typically do?

A Simula developer on monthly retainer covers four principal service areas: PROCESS coroutine and simulation kernel design (HOLD(t) time-delay design; PASSIVATE indefinite suspension; ACTIVATE p AT t/DELAY d/AFTER q/BEFORE q scheduling; REACTIVATE rescheduling; SQS event notice ordering audit; permanently-passivated PROCESS diagnosis; ACTIVATE-before-HOLD sequencing repair; IDLE predicate for queue-is-empty checks); CLASS hierarchy and INNER/VIRTUAL design (CLASS declaration with INNER slot; VIRTUAL procedure declaration and override; REF(ClassName) typed reference; NONE null reference audit; QUA ClassName downcast; IS/ISNT reference-equality predicate; INSPECT pattern dispatch; THIS ClassName self-reference); simulation entity and queue design (queue data structure as linked list of REF(PROCESS); arrival-process distribution modeling; service-time distribution modeling; statistics collection; TIME current simulation time access); and Simula I/O and text handling (InFile/OutFile with InImage/OutImage; SYSIN/SYSOUT; TEXT type string handling; GetChar/PutChar character operations; Sub/Length/Copy TEXT operations).

What Simula work is most commonly underlogged in a retainer?

ACTIVATE sequencing repair (QUEUE PROCESS ACTIVATE placed after conditional HOLD; CUSTOMER permanently passivated when GENERATOR called ACTIVATE before PASSIVATE completed; 5 lost activations/run; restructured QUEUE loop to check waiting list before each PASSIVATE; lost activations: 5/run → 0; 13–24 hrs invisible in SQS trace analysis, ACTIVATE-before-PASSIVATE invariant design, and simulation validation), VIRTUAL procedure hierarchy design (VIRTUAL PROCEDURE not overridden in TRUCK subclass; Simula dispatched to base CLASS body with wrong default behavior; wrong speed values: 6/run → 0; 10–18 hrs invisible in VIRTUAL dispatch analysis, CLASS hierarchy audit, and INNER slot design), and REF() NONE null reference audit (REF(CUSTOMER) dereferenced without NONE check; runtime error on empty-queue code path; errors: 4/run → 0; 8–15 hrs invisible in NONE propagation analysis, ISNT predicate design, and reference audit).

What are typical Simula developer retainer rates?

Entry-level Simula developers (1–2 years, CLASS declaration, basic PROCESS coroutine usage, HOLD/PASSIVATE/ACTIVATE primitives, REF()/NONE patterns) bill at $65–$115/hr. Mid-level Simula engineers (2–4 years, ACTIVATE-before-HOLD sequencing analysis, VIRTUAL procedure hierarchy design, SQS event ordering, simulation statistics collection, queue entity design) bill at $110–$195/hr. Senior Simula architects (4–8 years, complete simulation system architecture, complex coroutine scheduling networks, CLASS hierarchy design for large entity type systems, Simula I/O pipeline design, Simula compiler toolchain work) bill at $160–$285/hr. Monthly retainer ranges: $2,000–$4,500/mo advisory (15–25 hrs), $6,000–$16,000/mo for full Simula platform engagements.

What should a Simula developer retainer agreement include?

A Simula developer retainer agreement should specify: PROCESS simulation scope (HOLD(t)/PASSIVATE/ACTIVATE/REACTIVATE scheduling design; SQS event notice ordering audit; permanently-passivated PROCESS diagnosis; ACTIVATE-before-PASSIVATE invariant design; IDLE predicate usage); CLASS hierarchy scope (CLASS/INNER/VIRTUAL design; REF(ClassName) typed reference; NONE/IS/ISNT null audit; QUA downcast; INSPECT pattern dispatch; THIS self-reference); simulation entity scope (queue data structure; arrival/service distribution modeling; statistics collection; TIME access); I/O scope (InFile/OutFile/InImage/OutImage; SYSIN/SYSOUT; TEXT type operations); and hour logging format (PROCESS or CLASS name; operation type; before/after error metric; Simula variant: Simula67 or Simula-87).

How should Simula developer retainer hours be logged?

Log each Simula retainer session with: advisory category (HOLD(t) time-delay design; PASSIVATE indefinite suspension; ACTIVATE p AT t/DELAY d/AFTER q scheduling; REACTIVATE rescheduling; SQS event notice ordering audit; CLASS declaration with INNER slot; VIRTUAL procedure declaration and override; REF(ClassName) typed reference; NONE null reference audit; QUA ClassName downcast; IS/ISNT reference-equality predicate; INSPECT pattern dispatch; queue data structure design; simulation statistics collection; InFile/OutFile I/O; TEXT string handling), the specific PROCESS or CLASS name and the sequencing or dispatch problem (QUEUE PROCESS ACTIVATE placed after HOLD; 5 lost customer activations/run; moved ACTIVATE sequencing to ensure PASSIVATE executes first; lost activations: 5/run → 0), and the before/after metric (lost activations/run: 5 → 0; wrong dispatch results/run: 6 → 0; runtime NONE errors/run: 4 → 0). Include Simula variant (Simula67 or Simula-87), operating system, and whether the fix required ACTIVATE sequencing repair, VIRTUAL override addition, NONE reference check addition, or CLASS hierarchy redesign.