# python-delay-chain

`pySource -> delay1 -> delay2 -> pySink`, four Python components over
`python2DtGenericPayload`. The two delays are instances of one reusable module,
so the chain generalises to any number of instances back to back.

| file | role |
|---|---|
| `python-delay-chain.vsi.cmd` | the twin: four components, one int signal per link |
| `vsi_delay.py` | `VariableDelay`: a FIFO of N simulation steps; N per instance from env var `VSI_DELAY_<NAME>`, else `delay_config.json`, else a default; changeable at run time by `set_steps()` or a `{"at_step": new_steps}` schedule. No VSI dependency. |
| `delay_config.json` | `delay1` = 2 steps; `delay2` = 3 steps, lengthening to 5 at its 40th step |
| `apply_glue.py` | writes the behaviour into the generated skeletons **by region name** and copies the module into `<twin>\src`; re-run it after every `generate -overwrite` |
| `test_delay_chain.py` | plain-Python proof of the arithmetic, with a negative control |

## Build

```powershell
. D:\InnexisVSI\innexis_home\vsi_2026.2\env_vsi.ps1
mkdir D:\vsi_work\templates\python-delay-chain; cd D:\vsi_work\templates\python-delay-chain
copy C:\Users\chris\Documents\Siemens\innexis-vsi\templates\python-delay-chain\python-delay-chain.vsi.cmd .
vsiBuild -c -f .\python-delay-chain.vsi.cmd
C:\Python313\python.exe C:\Users\chris\Documents\Siemens\innexis-vsi\templates\python-delay-chain\apply_glue.py .\workspace\PyDelayChain
```

Result 2026-09-05: generated and built in 55 s (four gateway extensions
`VsiPythonGateways.cp313-win_amd64.pyd`, one per component, plus
`FabricServer.exe`); glue applied to all four skeletons; every patched file
parses; the module imports from `src`.

What the glue is, in full (everything else in the skeletons is vsiBuild's):

- every component, *Global Variables & Definitions*: put `..` on `sys.path`,
  `from vsi_delay import VariableDelay`
- `pySource`, *Before sending the packet*: `self.mySignals.tick += 1`
- `delay1` / `delay2`, *Constructor*: `self.delay = VariableDelay.from_config('<name>')`
- `delay1` / `delay2`, *Before sending the packet*:
  `self.mySignals.tick_out = self.delay.step(self.mySignals.tick_in)`
- `pySink`, *After sending the packet*: append `time_ns,tick` to `sink_trace.csv`

The skeleton receives `tick_in` in its packet callback (from component id
`n - 1`) and sends `tick_out` to component `n + 1` right after that region, so
one line per delay is the whole behaviour.

## Proof without the fabric

```
python test_delay_chain.py
single delay 2                                       PASS
delay 2 then delay 3 = 5                             PASS
delay 0 then delay 1 = 1                             PASS
four instances 1+2+0+3 = 6                           PASS
schedule 1 -> 3 at step 4                            PASS
shorten 3 -> 1 keeps newest                          PASS
json config for delay1                               PASS
env var overrides json                               PASS
default when unconfigured                            PASS
negative control (must read FAIL)                    FAIL
0 failure(s) in the real cases
```

## Running it

Blocked, like every twin here, on the fabric server's `VeloceStratoOS`
request (needs `mentorall_s` or `VeloceStratoOS` issued for vendor `saltd`; see
`../../LICENCE-REQUEST.md`). When it runs: `mingw32-make sim` from the twin
directory, bounded, then read `vsi.sim\_logs\check.*.log` and `sink_trace.csv`.
Expected: the sink's tick lags the source's by 2 + 3 = 5 steps **plus whatever
transport latency the fabric adds per hop**, which is not modelled in the module
and is the first thing to measure. Change a delay without regenerating:
`$env:VSI_DELAY_DELAY1 = 4` before `make sim`, or edit `src\delay_config.json`.

## Adding a third delay

Add a component and port, its two signals, and rewire the three `connect
signals` lines into four; add an entry to `GLUE` in `apply_glue.py` (copy the
`delay2` block, change the name) and to `delay_config.json`. Regenerate, apply
glue, build. The module needs no change.
