Find the owning system first

Start in the system that owns the behavior you are changing. This keeps a small fix from becoming a patch in the wrong place or a workaround that another system cannot maintain.

  • Systems/Jail/ owns booking, custody, sentences, release, doors, and jail-state behavior.
  • Systems/Parole/ and Systems/ParoleManager.cs own conditions, check-ins, violations, and completion.
  • Systems/NPCs/ owns the officer, guard, and inmate behavior classes that drive the player-facing flows.
  • Systems/CrimeTracking/ owns crime history, witness handling, rap sheets, and parole records.
  • UI/ owns player-facing UI; Harmony/ is for the game patches that need to feed those systems.
  • Utils/ holds shared helpers and logging, while Tests/ contains manual and in-game test scaffolding.

Use the real manager and state-machine path. A direct teleport, a manager-only completion, or a static NPC can help isolate a crash, but is not the final implementation for a player-facing flow.

Make IL2CPP-safe changes

IL2CPP is the primary target for this worktree. Keep Mono-only code behind #if MONO, keep the IL2CPP path in the matching #else branch, and use the helper that already exists before introducing reflection or a new interop wrapper.

  • Register injected behaviors and their dependencies during startup, before a spawn or lookup can reach them.
  • For injected component types, use the existing helpers in Utils/Helpers.cs. Do not call generic AddComponent<T>, GetComponent<T>, FindObjectOfType<T>, or generic ScriptableObject.CreateInstance<T> directly on IL2CPP.
  • Keep delegate-heavy Mono-only APIs out of injected type surfaces. Gate them with #if MONO, and use [HideFromIl2Cpp] for helpers that are not part of the exposed type.
  • Use explicit Il2Cpp namespaces and types when the direct API calls for them.

Treat an IL2CPP class-pointer or MethodInfoStoreGeneric error as a blocking defect. Fix the registration or type surface instead of replacing the affected behavior.

Keep NPC flows canonical

Intake officers, release officers, parole officers, guards, and inmates must run their actual behavior classes in the final IL2CPP path. The player should see the same state-machine journey that the system was designed to run.

  • Register the behavior before the first spawn or lookup.
  • Follow the canonical escort, booking, release, and parole state machines rather than completing a step from a manager.
  • Keep listener references stable; pair every subscription with a lifecycle-safe unsubscribe through Utils/EventHelper where applicable.
  • Check for an existing runtime component before adding one, and validate Unity-object lifetime before a callback uses it.
  • Confirm a normal run has no fallback or static-guard logging.

Build and verify the changed flow

Open Behind Bars.sln in Visual Studio 2022 or later, or run both debug builds from the solution root.

dotnet build "Behind Bars.sln" -c "Debug IL2CPP"
dotnet build "Behind Bars.sln" -c "Debug Mono"

Build each target against its matching Schedule I installation. Different IL2CPP game assemblies can produce different Behind Bars DLLs from the same source, so do not reuse an IL2CPP DLL across mismatched installs.

Manual IL2CPP checks

  1. The intake officer responds and completes the full booking flow.
  2. The release officer spawns and completes the full escort and release flow.
  3. Parole officer systems follow the canonical flow.
  4. Inmates follow the canonical behavior flow.
  5. Normal flow contains no fallback/static-guard logging.
  6. The loader log has no canonical behavior class-pointer or generic-method-store failures.

Test the path you changed. A jail-door fix needs a booking-to-release run; a parole change needs its check-in or violation flow. A successful build does not show that the player journey worked.

Finish a change cleanly

  1. Keep the diff inside the owning system unless a dependency genuinely needs to move with it.
  2. Build Debug IL2CPP and Debug Mono.
  3. Install the matching runtime-specific DLL and run the player flow that changed.
  4. Check the fresh loader log for canonical behavior registration and runtime errors.