Skip to content

⏱ Crafting — Runtime / Time

This folder contains the runtime implementations of IWallClockProvider and time-related helpers used by the Crafting system.

Crafting intentionally separates gameplay time from real-world wall-clock time:

  • Game Time — via ITimeProvider (scaled / unscaled / fixed), used while the app is running
  • Wall-Clock Time — via IWallClockProvider (UTC Unix seconds), used only for offline logic

This separation keeps the Crafting runtime deterministic, testable, and independent of Unity’s static time APIs.

Truth-pass note: Everything documented here reflects behaviour implemented by CraftingServiceCore in the current runtime.


🎯 Purpose

Crafting jobs may continue while the game is closed. To support this safely, the service relies on two distinct notions of time.

1️⃣ Game time (app running)

Provided via ITimeProvider (from Common).

Used for: - Job ticking - Progress calculation - Duration tracking

Game time is selected by CraftTimeMode: - Scaled - Unscaled - Fixed

2️⃣ Wall-clock time (app closed)

Provided via IWallClockProvider.

Used only for: - Capturing acceptedAtUtc - Computing elapsed offline time - Determining offline completion - Restoring remaining duration on load

Even when offline progress is disabled, acceptedAtUtc is still recorded to preserve snapshot integrity and consistent restore behaviour.


⚙️ Implementations

SystemWallClock

internal sealed class SystemWallClock : IWallClockProvider
{
    public long UtcNowSeconds => System.DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}

Behaviour: - Default wall-clock provider when none is supplied - Uses the local system UTC time - Returns Unix epoch seconds - Allocation-light and stateless

This provider is sufficient for single-player and local projects. Server-authoritative games may supply their own implementation.

The implementation in this folder is an internal default. Consumers should provide their own IWallClockProvider if different behaviour is required.


🧩 Wall-clock contract

public interface IWallClockProvider
{
    long UtcNowSeconds { get; }
}

Runtime behaviour: - Assigned via wallClockComponent on CraftingServiceCore - If omitted, Crafting falls back to SystemWallClock - Providers should return non-negative UTC seconds (negative values will break offline calculations)

Crafting never mutates or caches wall-clock state.


⏳ Offline progress flow (exact runtime behaviour)

1️⃣ Job accepted - acceptedAtUtc is captured using the wall-clock provider

2️⃣ Game exits

3️⃣ Game reloads → RestoreJobs() - elapsedOffline = nowUtc − acceptedAtUtc

If offline progress is disabled: - No offline ticking occurs - Job resumes using stored remainingSeconds

If offline progress is enabled: - Elapsed time is capped by offlineCompleteCapHours (if > 0) - If elapsedOffline >= totalDuration: - Offline delivery is attempted immediately - Delivery-time modifiers are re-applied - Otherwise: - Job resumes with reduced remaining duration

4️⃣ Offline delivery failure (target container cannot be resolved) - Currency is refunded - Inputs cannot be refunded (no container handle) - Job is marked Failed

5️⃣ Legacy snapshots (acceptedAtUtc <= 0) - Resume using stored remainingSeconds - No offline ticking applied - Warning logged in Editor / Development builds

Paused jobs

  • Never tick offline
  • Resume with the exact stored pausedRemainingSeconds

🕒 Game time helpers

TimeExtensions

public static class TimeExtensions
{
    public static float Now(this ITimeProvider t, CraftTimeMode mode) =>
        mode switch
        {
            CraftTimeMode.Unscaled => t.UnscaledTime,
            CraftTimeMode.Fixed    => t.FixedTime,
            _                      => t.ScaledTime
        };
}

Purpose: - Normalises access to Unity time sources - Allows the service to switch time modes without static UnityEngine.Time calls - Keeps time selection explicit and testable


💡 Usage tips

  • For server-authoritative time, implement IWallClockProvider using server UTC.
  • For deterministic tests, supply a fixed or scripted wall-clock provider.
  • To pause live craft progress without affecting offline logic:
  • Swap the ITimeProvider
  • Leave the wall-clock provider unchanged
  • To simulate long absences, use a test wall-clock provider that jumps forward in time.

These time abstractions ensure Crafting remains robust across live gameplay, pauses, shutdowns, and offline restoration — without mixing real-world time into gameplay logic.