amer224 commited on
Commit
ea693bf
·
verified ·
1 Parent(s): 8d46921

Add brains/sovereign-v3/data_adapters.py

Browse files
Files changed (1) hide show
  1. brains/sovereign-v3/data_adapters.py +451 -0
brains/sovereign-v3/data_adapters.py ADDED
@@ -0,0 +1,451 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ data_adapters.py — shard → SOVEREIGN v3 training samples.
3
+
4
+ Reads amer224/territorial-bot-data `rl/shards_v2/*.npz`:
5
+ rgb (N, 256, 256, 3) uint8 frames (recorded every 2 ticks)
6
+ lab (N, 256, 256) int {0 water, 1 neutral, 2 me, 3 enemy}
7
+ nums (N, 8) float context vector
8
+ kind (N,) {0 expand, 1 attack, 2 bank}
9
+ cell (N,) int 32x32 grid idx of the click
10
+ pct (N,) float in [0,1] troops committed
11
+ logp (N,) float behavior logprob (unused)
12
+ reward(N,) float farmer shaping (unknown scale)
13
+ alive (N,) bool still alive at this frame
14
+ lens (list of int) per-episode frame counts
15
+
16
+ v3 extras (all derived from FREE labels already in the shards):
17
+
18
+ 1. RETURN CONDITIONING (RPO): rtg = z-scored remaining return; rtg_next
19
+ = the shifted target (econ forecaster predicts ctx_full = nums+rtg).
20
+ 2. OPPONENT-INTENT MIRRORS: lab[t] vs lab[t+1] deltas give
21
+ threat = pixels that BECOME enemy next frame (where they strike)
22
+ expand = pixels that BECOME mine next frame (where I grow)
23
+ These are the training targets for the model's threat/expand heads —
24
+ implicit opponent modeling from consecutive frames.
25
+ 3. NEXT-SEGMENTATION: lab64_next = downsampled lab[t+1] (one-step spatial
26
+ dynamics supervision; the seed of a future MCTS world model).
27
+ 4. WIN LABEL: 1 if the episode survived to timeout, 0 if it died.
28
+ 5. SEQUENCE BUILDER for the GRU memory core (truncated-BPTT windows).
29
+
30
+ No exotic dependencies (numpy + torch).
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from dataclasses import dataclass
36
+ from pathlib import Path
37
+ from typing import Iterator, List, Optional, Sequence, Tuple
38
+
39
+ import numpy as np
40
+ import torch
41
+ from torch.utils.data import Dataset, WeightedRandomSampler, DataLoader
42
+
43
+ GAMMA = 0.995
44
+ GRID = 32
45
+ SURVIVOR_MULT = 4.0
46
+ KILL_MULT = 2.0
47
+ KILL_WINDOW = 2
48
+ TAU = 1.0
49
+ W_CLIP = (0.2, 8.0)
50
+ SURVIVE_BONUS = 2.0
51
+ WIN_BONUS = 5.0
52
+ WIN_SPIKE = 3.0
53
+ GATE_SIZE = 64
54
+
55
+
56
+ # --------------------------------------------------------------------------
57
+ # Episode bookkeeping
58
+ # --------------------------------------------------------------------------
59
+
60
+ def episode_bounds(lens: Sequence[int]) -> np.ndarray:
61
+ """Cumulative start index of each episode, length len(lens)+1."""
62
+ return np.concatenate([[0], np.cumsum(lens)])
63
+
64
+
65
+ def episode_of(lens: Sequence[int], idx: np.ndarray) -> np.ndarray:
66
+ bounds = episode_bounds(lens)
67
+ return np.searchsorted(bounds[1:], idx, side="right")
68
+
69
+
70
+ # --------------------------------------------------------------------------
71
+ # Returns, RTG, weights
72
+ # --------------------------------------------------------------------------
73
+
74
+ def compute_returns(reward: np.ndarray, alive: np.ndarray,
75
+ lens: Sequence[int], gamma: float = GAMMA,
76
+ survive_bonus: float = SURVIVE_BONUS,
77
+ win_bonus: float = WIN_BONUS,
78
+ win_spike: float = WIN_SPIKE) -> np.ndarray:
79
+ """Return-to-go per frame, then z-scored per shard (shaping-robust).
80
+
81
+ Terminal handling per episode:
82
+ - died -> no bonus
83
+ - survived -> +survive_bonus on the last frame
84
+ - likely win (last reward >= win_spike) -> +win_bonus
85
+ """
86
+ R = np.zeros_like(reward, dtype=np.float32)
87
+ bounds = episode_bounds(lens)
88
+ for e in range(len(lens)):
89
+ s, t = bounds[e], bounds[e + 1]
90
+ if t <= s:
91
+ continue
92
+ r = reward[s:t].astype(np.float32)
93
+ if alive[t - 1]:
94
+ r = r.copy()
95
+ r[-1] += win_bonus if r[-1] >= win_spike else survive_bonus
96
+ acc = 0.0
97
+ for i in range(t - 1, s - 1, -1):
98
+ acc = r[i - s] + gamma * acc
99
+ R[i] = acc
100
+ mu, sd = R.mean(), R.std()
101
+ if sd < 1e-6:
102
+ return np.zeros_like(R)
103
+ return (R - mu) / max(sd, 1e-6)
104
+
105
+
106
+ def shift_within_episode(v: np.ndarray, lens: Sequence[int]) -> np.ndarray:
107
+ """v[t] <- v[t+1] within each episode (last frame repeats)."""
108
+ out = v.copy()
109
+ bounds = episode_bounds(lens)
110
+ for e in range(len(lens)):
111
+ s, t = bounds[e], bounds[e + 1]
112
+ if t > s:
113
+ out[s:t - 1] = v[s + 1:t]
114
+ return out
115
+
116
+
117
+ def frame_weights(Rz: np.ndarray, alive: np.ndarray, lens: Sequence[int],
118
+ kills: Optional[np.ndarray] = None,
119
+ tau: float = TAU, clip: Tuple[float, float] = W_CLIP,
120
+ survivor_mult: float = SURVIVOR_MULT,
121
+ kill_mult: float = KILL_MULT,
122
+ window: int = KILL_WINDOW) -> np.ndarray:
123
+ """AWR-style soft filtering weights, normalized to mean 1."""
124
+ w = np.clip(np.exp(Rz / tau), clip[0], clip[1]).astype(np.float32)
125
+ bounds = episode_bounds(lens)
126
+ for e in range(len(lens)):
127
+ s, t = bounds[e], bounds[e + 1]
128
+ if t > s and alive[t - 1]:
129
+ w[s:t] *= survivor_mult
130
+ if kills is not None:
131
+ dk = np.diff(kills, prepend=kills[:1] - 0.0)
132
+ hit = np.where(dk > 1e-3)[0]
133
+ for i in hit:
134
+ lo, hi = max(0, i - window), min(len(w), i + window + 1)
135
+ w[lo:hi] *= kill_mult
136
+ return w / (w.mean() + 1e-8)
137
+
138
+
139
+ def win_labels(alive: np.ndarray, lens: Sequence[int]) -> np.ndarray:
140
+ """1 if the episode survived to timeout, 0 if it died."""
141
+ lab = np.zeros(len(alive), dtype=np.int64)
142
+ bounds = episode_bounds(lens)
143
+ for e in range(len(lens)):
144
+ s, t = bounds[e], bounds[e + 1]
145
+ if t > s and alive[t - 1]:
146
+ lab[s:t] = 1
147
+ return lab
148
+
149
+
150
+ # --------------------------------------------------------------------------
151
+ # Label-derived supervision (gate / seg / dynamics / intent mirrors)
152
+ # --------------------------------------------------------------------------
153
+
154
+ def _downscale_labels(lab: np.ndarray, size: int) -> np.ndarray:
155
+ """(N,H,W) int labels -> (N,size,size) majority-vote labels."""
156
+ N, H, W = lab.shape
157
+ gh, gw = H // size, W // size
158
+ out = np.zeros((N, size, size), dtype=np.int16)
159
+ for gy in range(size):
160
+ for gx in range(size):
161
+ blk = lab[:, gy * gh:(gy + 1) * gh, gx * gw:(gx + 1) * gw]
162
+ flat = blk.reshape(N, -1)
163
+ # majority vote via bincount along axis 1
164
+ vals = np.argmax(
165
+ np.apply_along_axis(lambda r: np.bincount(r, minlength=4),
166
+ 1, flat.astype(np.int64)), axis=1)
167
+ out[:, gy, gx] = vals
168
+ return out
169
+
170
+
171
+ def gate_masks_from_lab(lab64: np.ndarray, adj: int = 2
172
+ ) -> Tuple[np.ndarray, np.ndarray]:
173
+ """Per-kind mask targets at 64x64 for kind 0..2 from downsampled labels.
174
+
175
+ Returns (masks (N,3,64,64) float in [0,1], valid (N,3) bool).
176
+ Kind 2 (bank) has no spatial target -> flagged invalid.
177
+ """
178
+ N, S, _ = lab64.shape
179
+ masks = np.zeros((N, 3, S, S), dtype=np.float32)
180
+ valid = np.zeros((N, 3), dtype=bool)
181
+ valid[:, 0] = True
182
+ valid[:, 1] = True
183
+ me = lab64 == 2
184
+ enemy = lab64 == 3
185
+ neutral = lab64 == 1
186
+ # expand mask: dilate me by adj and intersect neutral
187
+ pad = adj
188
+ mp = np.pad(me, ((0, 0), (pad, pad), (pad, pad)), mode="constant")
189
+ d = np.zeros_like(me)
190
+ for dy in range(-pad, pad + 1):
191
+ for dx in range(-pad, pad + 1):
192
+ d |= mp[:, pad + dy:pad + dy + S, pad + dx:pad + dx + S]
193
+ masks[:, 0] = (d & neutral).astype(np.float32)
194
+ masks[:, 1] = enemy.astype(np.float32)
195
+ return masks, valid
196
+
197
+
198
+ def intent_mirrors(lab64: np.ndarray, lab64_next: np.ndarray
199
+ ) -> Tuple[np.ndarray, np.ndarray]:
200
+ """threat = pixels that BECOME enemy next frame; expand = BECOME mine."""
201
+ threat = ((lab64_next == 3) & (lab64 != 3)).astype(np.float32)
202
+ expand = ((lab64 == 1) & (lab64_next == 2)).astype(np.float32)
203
+ return threat, expand
204
+
205
+
206
+ # --------------------------------------------------------------------------
207
+ # Frame stacks + arena filter
208
+ # --------------------------------------------------------------------------
209
+
210
+ def build_stacks(rgb: np.ndarray, lens: Sequence[int],
211
+ gap: int = 2, K: int = 4) -> Tuple[np.ndarray, np.ndarray]:
212
+ """rgb (N,H,W,3) -> (N,3K,H,W) gap-spaced stacks (memory ablation)."""
213
+ N, H, W, C = rgb.shape
214
+ out = np.zeros((N, C * K, H, W), dtype=np.float32)
215
+ valid = np.zeros(N, dtype=bool)
216
+ rgb_f = rgb.astype(np.float32) / 255.0
217
+ bounds = episode_bounds(lens)
218
+ eps = episode_of(lens, np.arange(N))
219
+ for i in range(N):
220
+ s = bounds[eps[i]]
221
+ ch = []
222
+ ok = True
223
+ for k in range(K):
224
+ j = i - k * gap
225
+ if j < s:
226
+ j = s
227
+ ok = False
228
+ ch.append(rgb_f[j].transpose(2, 0, 1))
229
+ out[i] = np.concatenate(ch, axis=0)
230
+ valid[i] = ok
231
+ return out, valid
232
+
233
+
234
+ def arena_eps_of(lab: np.ndarray, lens: Sequence[int],
235
+ water_frac: float = 0.85) -> np.ndarray:
236
+ """Per-episode True if the episode looks like the leaky arena map."""
237
+ bounds = episode_bounds(lens)
238
+ flags = []
239
+ for e in range(len(lens)):
240
+ s, t = bounds[e], bounds[e + 1]
241
+ frac = float((lab[s:t] == 0).mean()) if t > s else 0.0
242
+ flags.append(frac > water_frac)
243
+ return np.array(flags, dtype=bool)
244
+
245
+
246
+ # --------------------------------------------------------------------------
247
+ # Sequence windows (GRU memory-core training)
248
+ # --------------------------------------------------------------------------
249
+
250
+ def build_sequences(lens: Sequence[int], T: int = 8,
251
+ stride: int = 4) -> List[Tuple[int, int]]:
252
+ """Return (start, length=T) windows fully inside episodes."""
253
+ bounds = episode_bounds(lens)
254
+ windows: List[Tuple[int, int]] = []
255
+ for e in range(len(lens)):
256
+ s, t = bounds[e], bounds[e + 1]
257
+ i = s
258
+ while i + T <= t:
259
+ windows.append((i, T))
260
+ i += stride
261
+ return windows
262
+
263
+
264
+ # --------------------------------------------------------------------------
265
+ # Prepper + Dataset
266
+ # --------------------------------------------------------------------------
267
+
268
+ @dataclass
269
+ class PreppedShard:
270
+ rgb: np.ndarray # (N,3,256,256) float32 in [0,1]
271
+ nums: np.ndarray # (N,8)
272
+ rtg: np.ndarray # (N,1) z-scored return conditioning
273
+ rtg_next: np.ndarray # (N,1)
274
+ nums_next: np.ndarray # (N,9) cat(nums[t+1], rtg[t+1]) — econ target
275
+ kind: np.ndarray # (N,)
276
+ cell: np.ndarray # (N,)
277
+ pct: np.ndarray # (N,)
278
+ ret: np.ndarray # (N,) z-scored returns (value target)
279
+ w: np.ndarray # (N,) sample weights (mean 1)
280
+ win_lab: np.ndarray # (N,) 0/1
281
+ lab64: np.ndarray # (N,64,64) int64
282
+ lab64_next: np.ndarray # (N,64,64) int64 (dynamics target)
283
+ threat: np.ndarray # (N,64,64) float
284
+ expand: np.ndarray # (N,64,64) float
285
+ gate_mask: np.ndarray # (N,3,64,64)
286
+ gate_valid: np.ndarray # (N,3) bool
287
+
288
+
289
+ class ShardPrepper:
290
+ """Loads + preps one npz shard. Cache the result per epoch."""
291
+
292
+ def __init__(self, path: Path, stack: Optional[Tuple[int, int]] = None,
293
+ skip_arena: bool = True):
294
+ self.path = Path(path)
295
+ self.stack = stack
296
+ self.skip_arena = skip_arena
297
+
298
+ def prepare(self) -> Optional[PreppedShard]:
299
+ d = np.load(self.path, allow_pickle=True)
300
+ rgb, lab = d["rgb"], d["lab"]
301
+ nums = d["nums"].astype(np.float32)
302
+ kind = d["kind"].astype(np.int64)
303
+ cell = d["cell"].astype(np.int64)
304
+ pct = d["pct"].astype(np.float32)
305
+ reward = d["reward"].astype(np.float32)
306
+ alive = d["alive"].astype(bool)
307
+ lens = [int(l) for l in d["lens"]]
308
+
309
+ if self.skip_arena:
310
+ ae = arena_eps_of(lab, lens)
311
+ if ae.all():
312
+ return None
313
+
314
+ if self.stack is not None:
315
+ gap, K = self.stack
316
+ rgb_f, _ok = build_stacks(rgb, lens, gap=gap, K=K)
317
+ else:
318
+ rgb_f = rgb.astype(np.float32).transpose(0, 3, 1, 2) / 255.0
319
+
320
+ # returns / rtg / weights / win labels
321
+ ret = compute_returns(reward, alive, lens)
322
+ rtg = ret[:, None].astype(np.float32)
323
+ rtg_next = shift_within_episode(rtg, lens)
324
+ nums_next8 = shift_within_episode(nums, lens)
325
+ nums_next = np.concatenate([nums_next8, rtg_next], axis=1) # (N,9)
326
+ w = frame_weights(ret, alive, lens, kills=nums[:, 7])
327
+ win_lab = win_labels(alive, lens)
328
+
329
+ # label-derived supervision at 64x64
330
+ lab64 = _downscale_labels(lab, GATE_SIZE).astype(np.int64)
331
+ lab_next = np.empty_like(lab)
332
+ lab_next[:-1] = lab[1:]
333
+ lab_next[-1] = lab[-1]
334
+ # fix episode boundaries: last frame of an episode repeats itself
335
+ bounds = episode_bounds(lens)
336
+ for e in range(len(lens)):
337
+ t = bounds[e + 1]
338
+ if t < len(lab):
339
+ lab_next[t - 1] = lab[t - 1]
340
+ lab64_next = _downscale_labels(lab_next, GATE_SIZE).astype(np.int64)
341
+ threat, expand = intent_mirrors(lab64, lab64_next)
342
+ gmask, gvalid = gate_masks_from_lab(lab64)
343
+
344
+ keep = np.ones(len(rgb_f), dtype=bool)
345
+ if self.skip_arena:
346
+ ae = arena_eps_of(lab, lens)
347
+ eps = episode_of(lens, np.arange(len(rgb_f)))
348
+ keep = ~ae[eps]
349
+ if not keep.any():
350
+ return None
351
+
352
+ return PreppedShard(
353
+ rgb=rgb_f[keep], nums=nums[keep], rtg=rtg[keep],
354
+ rtg_next=rtg_next[keep], nums_next=nums_next[keep],
355
+ kind=kind[keep], cell=cell[keep], pct=pct[keep], ret=ret[keep],
356
+ w=w[keep], win_lab=win_lab[keep], lab64=lab64[keep],
357
+ lab64_next=lab64_next[keep], threat=threat[keep],
358
+ expand=expand[keep], gate_mask=gmask[keep],
359
+ gate_valid=gvalid[keep])
360
+
361
+
362
+ class StageADataset(Dataset):
363
+ """Concatenated prepped shards; sampled with WeightedRandomSampler."""
364
+
365
+ def __init__(self, prepped: Sequence[PreppedShard]):
366
+ def cat(key):
367
+ return np.concatenate([getattr(p, key) for p in prepped])
368
+ self.rgb = cat("rgb")
369
+ self.nums = cat("nums")
370
+ self.rtg = cat("rtg")
371
+ self.rtg_next = cat("rtg_next")
372
+ self.nums_next = cat("nums_next")
373
+ self.kind = cat("kind")
374
+ self.cell = cat("cell")
375
+ self.pct = cat("pct")
376
+ self.ret = cat("ret")
377
+ self.w = cat("w")
378
+ self.win_lab = cat("win_lab")
379
+ self.lab64 = cat("lab64")
380
+ self.lab64_next = cat("lab64_next")
381
+ self.threat = cat("threat")
382
+ self.expand = cat("expand")
383
+ self.gmask = cat("gate_mask")
384
+ self.gvalid = cat("gate_valid")
385
+
386
+ def __len__(self) -> int:
387
+ return len(self.rgb)
388
+
389
+ def __getitem__(self, i: int) -> dict:
390
+ t = torch.from_numpy
391
+ return dict(
392
+ rgb=t(self.rgb[i]), nums=t(self.nums[i]), rtg=t(self.rtg[i]),
393
+ rtg_next=t(self.rtg_next[i]), nums_next=t(self.nums_next[i]),
394
+ kind=torch.tensor(self.kind[i]), cell=torch.tensor(self.cell[i]),
395
+ pct=torch.tensor(self.pct[i]), ret=torch.tensor(self.ret[i]),
396
+ w=torch.tensor(self.w[i]), win_lab=torch.tensor(self.win_lab[i]),
397
+ lab64=t(self.lab64[i]), lab64_next=t(self.lab64_next[i]),
398
+ threat=t(self.threat[i]), expand=t(self.expand[i]),
399
+ gate_mask=t(self.gmask[i]), gate_valid=t(self.gvalid[i]))
400
+
401
+
402
+ def make_stage_a_loader(shard_paths: Sequence[Path], batch_size: int = 16,
403
+ num_workers: int = 4, shuffle_shards: bool = True,
404
+ stack: Optional[Tuple[int, int]] = None,
405
+ seed: int = 0) -> Iterator[dict]:
406
+ """Shard-at-a-time prepping + weighted sampling (memory-efficient)."""
407
+ paths = list(shard_paths)
408
+ rng = np.random.default_rng(seed)
409
+ if shuffle_shards:
410
+ rng.shuffle(paths)
411
+ for sp in paths:
412
+ prepped = ShardPrepper(sp, stack=stack).prepare()
413
+ if prepped is None:
414
+ continue
415
+ ds = StageADataset([prepped])
416
+ sampler = WeightedRandomSampler(ds.w, num_samples=len(ds) // 2,
417
+ replacement=True)
418
+ dl = DataLoader(ds, batch_size=batch_size, sampler=sampler,
419
+ num_workers=num_workers, drop_last=True)
420
+ for batch in dl:
421
+ yield batch
422
+
423
+
424
+ if __name__ == "__main__":
425
+ # quick sanity run on fake data
426
+ rng = np.random.default_rng(0)
427
+ N, lens = 120, [40, 40, 40]
428
+ lab = np.zeros((N, 256, 256), dtype=np.uint8)
429
+ lab[:, 10:200, 10:200] = 1 # neutral
430
+ lab[:, 20:80, 20:80] = 2 # me
431
+ lab[:, 100:180, 100:180] = 3 # enemy
432
+ lab_next = lab.copy()
433
+ lab_next[:, 180:190, 100:180] = 3 # enemy advances (threat)
434
+ lab_next[:, 80:90, 20:80] = 2 # I expand
435
+ rgb = (lab[:, :, :, None].astype(np.float32) * 40).astype(np.uint8)
436
+ rgb = np.concatenate([rgb] * 3, axis=-1)
437
+ alive = np.ones(N, dtype=bool)
438
+ alive[75] = False
439
+ ret = compute_returns(np.zeros(N, dtype=np.float32), alive, lens)
440
+ w = frame_weights(ret, alive, lens, kills=np.zeros(N))
441
+ lab64 = _downscale_labels(lab, 64)
442
+ lab64_next = _downscale_labels(lab_next, 64)
443
+ threat, expand = intent_mirrors(lab64, lab64_next)
444
+ masks, valid = gate_masks_from_lab(lab64)
445
+ print("returns z-range:", ret.min(), ret.max())
446
+ print("weights mean:", w.mean(), "max:", w.max())
447
+ print("threat frac:", threat.mean(), "expand frac:", expand.mean())
448
+ print("masks:", masks.shape, "valid per kind:", valid.sum(0))
449
+ print("windows (T=8, stride=4):", len(build_sequences(lens, 8, 4)))
450
+ print("win labels:", np.bincount(win_labels(alive, lens)))
451
+ print("adapter smoke OK")