diff --git a/usim/_primitives/context.py b/usim/_primitives/context.py
index f27581b..7d86ea6 100644
--- a/usim/_primitives/context.py
+++ b/usim/_primitives/context.py
@@ -148,11 +148,16 @@ class Scope:
         which must exit without ``await``\ ing or ``yield``\ ing anything.
         """
         assert after is None or at is None,\
-            "schedule date must be either absolute or relative"
+            "start date must be either absolute or relative"
+        # resolve "now" to what the event loop expects
+        if after == 0:
+            after = None
+        elif at == __LOOP_STATE__.LOOP.time:
+            at = None
         assert after is None or after > 0,\
-            "schedule date must not be in the past"
+            "start date must not be in the past"
         assert at is None or at > __LOOP_STATE__.LOOP.time,\
-            "schedule date must not be in the past"
+            "start date must not be in the past"
         child_task = Task(payload, self, delay=after, at=at)
         __LOOP_STATE__.LOOP.schedule(
             child_task.__runner__,
diff --git a/usim/_primitives/timing.py b/usim/_primitives/timing.py
index ea750a2..d4a9d5d 100644
--- a/usim/_primitives/timing.py
+++ b/usim/_primitives/timing.py
@@ -12,7 +12,7 @@ time can represent more than 285 million years of time accurately.
        it is still not possible to reach :py:class:`Eternity`.
        This may change in the future.
 """
-from typing import Coroutine, Generator, Any, AsyncIterable
+from typing import Coroutine, Generator, Any, AsyncIterable, Union
 
 from .._core.loop import __LOOP_STATE__, __HIBERNATE__, Interrupt as CoreInterrupt
 from .notification import postpone, Notification
@@ -258,13 +258,20 @@ class Delay(Notification):
         delay = time + 20
         await delay      # delay for 20
         await delay      # delay for 20 again
-        print(time.now)  # gives 40
+        print(time.now)  # prints 40
 
-    The expression ``time + duration`` is equivalent to ``Delay(duration)``.
+    Because :py:class:`~.Delay` represents a time duration it is not a
+    :py:class:`~.Condition` that becomes true at some point in time.
+    If a Condition is required, use ``time == time.now + duration`` instead.
+
+    The expression ``time + duration`` is equivalent to ``Delay(duration)``
+    if ``duration`` is positive. If ``duration`` is ``0``, ``time + duration``
+    is equivalent to an :py:class:`~.Instant`.
     """
     __slots__ = ('duration',)
 
     def __init__(self, duration: float):
+        assert duration > 0, "delay must point at the future"
         super().__init__()
         self.duration = duration
 
@@ -278,6 +285,42 @@ class Delay(Notification):
     def __str__(self):
         return 'usim.time + {}'.format(self.duration)
 
+    # NOTE: Python objects *always* have __bool__, which defaults
+    # to True. We could debug-protect against misuse of bool(time + 3)
+    # but that would lead to observably different behaviour.
+    # If we always raise an error, that is inconsistent with other objects.
+
+    if __debug__:
+        def __and__(self, other):
+            raise TypeError((
+                "Operator & not supported for delays\n\n"
+                "Delays (time + delay) are measured from the ongoing current time,\n"
+                "They do not provide a Condition, but a Notification. Operators of\n"
+                "Condition are not supported.\n"
+                "\n"
+                "If a Condition is required, use 'time == time.now + delay' instead"
+            ))
+
+        def __or__(self, other):
+            raise TypeError((
+                "Operator | not supported for delays\n\n"
+                "Delays (time + delay) are measured from the ongoing current time,\n"
+                "They do not provide a Condition, but a Notification. Operators of\n"
+                "Condition are not supported.\n"
+                "\n"
+                "If a Condition is required, use 'time == time.now + delay' instead"
+            ))
+
+        def __invert__(self):
+            raise TypeError((
+                "Operator ~ not supported for delays\n\n"
+                "Delays (time + delay) are measured from the ongoing current time,\n"
+                "They do not provide a Condition, but a Notification. Operators of\n"
+                "Condition are not supported.\n"
+                "\n"
+                "If a Condition is required, use 'time == time.now + delay' instead"
+            ))
+
 
 class Time:
     r"""
@@ -344,7 +387,10 @@ class Time:
         """The current simulation time"""
         return __LOOP_STATE__.LOOP.time
 
-    def __add__(self, other: float) -> Delay:
+    def __add__(self, other: float) -> Union[Delay, Instant]:
+        assert other >= 0, "delay must point at the future"
+        if other == 0:
+            return Instant()
         return Delay(other)
 
     def __ge__(self, other: float) -> After:

Allow delays of 0
As discussed on #27, it would be sensible for the user interface to allow delays of 0. This seems prudent when calculating delays and for usability.

The event loop currently special-cases ``None`` to mean "now" and so disallows other ways of expressing this. Ideally, we handle this in the user interface implementations - these are already doing much more.

There are currently two user-facing cases:
* ``time + 0``
* ``scope.do(after=0)``
diff --git a/usim_pytest/test_types/test_scope.py b/usim_pytest/test_types/test_scope.py
index aefbdd8..e003659 100644
--- a/usim_pytest/test_types/test_scope.py
+++ b/usim_pytest/test_types/test_scope.py
@@ -2,7 +2,7 @@ import pytest
 
 from usim import Scope, time, instant, Concurrent
 
-from ..utility import via_usim
+from ..utility import via_usim, assertion_mode
 
 
 async def async_raise(exc: BaseException, after=0):
@@ -12,6 +12,10 @@ async def async_raise(exc: BaseException, after=0):
     raise exc
 
 
+async def async_pass():
+    pass
+
+
 class TestExceptions:
     @via_usim
     async def test_fail_scope(self):
@@ -131,3 +135,43 @@ class TestExceptions:
                     scope.do(async_raise(TypeError(), 0))
                     scope.do(async_raise(KeyError(), 0))
                     raise exc_type
+
+
+class TestScoping:
+    @via_usim
+    async def test_do_after(self):
+        async with Scope() as scope:
+            apass = scope.do(async_pass(), after=10)
+            assert time.now == 0
+            await apass
+            assert time.now == 10
+            # zero delay is well-defined
+            apass = scope.do(async_pass(), after=0)
+            assert time.now == 10
+            await apass
+            assert time.now == 10
+
+    @via_usim
+    async def test_do_at(self):
+        async with Scope() as scope:
+            apass = scope.do(async_pass(), at=10)
+            assert time.now == 0
+            await apass
+            assert time.now == 10
+            # zero delay is well-defined
+            apass = scope.do(async_pass(), at=10)
+            assert time.now == 10
+            await apass
+            assert time.now == 10
+
+    @assertion_mode
+    @via_usim
+    async def test_do_misuse(self):
+        fail_pass = async_pass()
+        with pytest.raises(AssertionError):
+            async with Scope() as scope:
+                scope.do(fail_pass, at=-1)
+        with pytest.raises(AssertionError):
+            async with Scope() as scope:
+                scope.do(fail_pass, after=-1)
+        fail_pass.close()
diff --git a/usim_pytest/test_types/test_time.py b/usim_pytest/test_types/test_time.py
index 009d84c..27487d6 100644
--- a/usim_pytest/test_types/test_time.py
+++ b/usim_pytest/test_types/test_time.py
@@ -4,7 +4,7 @@ import pytest
 
 from usim import time, until, eternity, instant, each
 
-from ..utility import via_usim
+from ..utility import via_usim, assertion_mode
 
 
 class TestTime:
@@ -19,6 +19,18 @@ class TestTime:
             await time
         with pytest.raises(TypeError):
             await (time <= 100)
+        with pytest.raises(TypeError):
+            ~(time + 3)
+        with pytest.raises(TypeError):
+            (time + 3) & (time == 3)
+        with pytest.raises(TypeError):
+            (time + 3) | (time == 3)
+
+    @assertion_mode
+    @via_usim
+    async def test_debug_misuse(self):
+        with pytest.raises(AssertionError):
+            await (time + -1)
 
     @via_usim
     async def test_delay(self):
@@ -27,6 +39,9 @@ class TestTime:
             assert start + (delay * seq) == time.now
             await (time + delay)
         assert start + (delay * 5) == time.now
+        # allow for 0 delay
+        await (time + 0)
+        assert start + (delay * 5) == time.now
 
     @via_usim
     async def test_moment(self):

pip install -e .[contrib]
parse_log_pytest
pytest
pytest pytest-timeout pytest-cov pytest-xdist pytest-mock pytest-asyncio
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
certifi @ file:///croot/certifi_1671487769961/work/certifi
coverage==7.2.7
execnet==2.0.2
flake8==5.0.4
flake8-bugbear==23.3.12
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
importlib-metadata==4.2.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
mccabe==0.7.0
packaging @ file:///croot/packaging_1671697413597/work
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pycodestyle==2.9.1
pyflakes==2.5.0
pytest==7.1.2
pytest-asyncio==0.21.2
pytest-cov==4.1.0
pytest-mock==3.11.1
pytest-timeout==2.3.1
pytest-xdist==3.5.0
sortedcontainers==2.4.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
-e git+https://github.com/MaineKuehn/usim.git@dff719c209ba8d275cd1133a0c447a361fce4486#egg=usim
zipp @ file:///croot/zipp_1672387121353/work

name: usim
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.2.7
      - execnet==2.0.2
      - flake8==5.0.4
      - flake8-bugbear==23.3.12
      - importlib-metadata==4.2.0
      - mccabe==0.7.0
      - pycodestyle==2.9.1
      - pyflakes==2.5.0
      - pytest-asyncio==0.21.2
      - pytest-cov==4.1.0
      - pytest-mock==3.11.1
      - pytest-timeout==2.3.1
      - pytest-xdist==3.5.0
      - sortedcontainers==2.4.0
prefix: /opt/conda/envs/usim

usim_pytest/test_types/test_scope.py::TestScoping::test_do_after usim_pytest/test_types/test_scope.py::TestScoping::test_do_at usim_pytest/test_types/test_time.py::TestTime::test_delay
usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_scope usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_child usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_children usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_specialisation usim_pytest/test_types/test_scope.py::TestExceptions::test_fail_privileged usim_pytest/test_types/test_scope.py::TestScoping::test_do_misuse usim_pytest/test_types/test_time.py::TestTime::test_representable usim_pytest/test_types/test_time.py::TestTime::test_misuse usim_pytest/test_types/test_time.py::TestTime::test_debug_misuse usim_pytest/test_types/test_time.py::TestTime::test_moment usim_pytest/test_types/test_time.py::TestTime::test_previous_moment usim_pytest/test_types/test_time.py::TestTime::test_after usim_pytest/test_types/test_time.py::TestTime::test_previous_after usim_pytest/test_types/test_time.py::TestTime::test_before usim_pytest/test_types/test_time.py::TestTime::test_previous_before usim_pytest/test_types/test_time.py::TestTime::test_infinity usim_pytest/test_types/test_time.py::TestTime::test_infinity_ge usim_pytest/test_types/test_time.py::TestTimeCondition::test_after usim_pytest/test_types/test_time.py::TestTimeCondition::test_before usim_pytest/test_types/test_time.py::TestTimeCondition::test_moment usim_pytest/test_types/test_time.py::TestTimeCondition::test_extremes usim_pytest/test_types/test_time.py::TestTimeIteration::test_delay usim_pytest/test_types/test_time.py::TestTimeIteration::test_interval usim_pytest/test_types/test_time.py::TestTimeIteration::test_misue
usim/_primitives/timing.py usim/_primitives/context.py
usim/_primitives/timing.py usim/_primitives/context.py