neckcord 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neckcord-1.0.0/LICENSE +21 -0
- neckcord-1.0.0/MANIFEST.in +11 -0
- neckcord-1.0.0/PKG-INFO +263 -0
- neckcord-1.0.0/README.md +225 -0
- neckcord-1.0.0/csrc/Makefile +20 -0
- neckcord-1.0.0/csrc/neckcord.c +1082 -0
- neckcord-1.0.0/csrc/neckcord.h +169 -0
- neckcord-1.0.0/csrc/tools/neckcord_selftest.c +398 -0
- neckcord-1.0.0/neckcord/__init__.py +32 -0
- neckcord-1.0.0/neckcord/__main__.py +6 -0
- neckcord-1.0.0/neckcord/allocator.py +326 -0
- neckcord-1.0.0/neckcord/cervical.py +253 -0
- neckcord-1.0.0/neckcord/cli.py +339 -0
- neckcord-1.0.0/neckcord/cpg.py +481 -0
- neckcord-1.0.0/neckcord/py.typed +1 -0
- neckcord-1.0.0/neckcord.egg-info/PKG-INFO +263 -0
- neckcord-1.0.0/neckcord.egg-info/SOURCES.txt +21 -0
- neckcord-1.0.0/neckcord.egg-info/dependency_links.txt +1 -0
- neckcord-1.0.0/neckcord.egg-info/entry_points.txt +2 -0
- neckcord-1.0.0/neckcord.egg-info/requires.txt +10 -0
- neckcord-1.0.0/neckcord.egg-info/top_level.txt +1 -0
- neckcord-1.0.0/pyproject.toml +67 -0
- neckcord-1.0.0/setup.cfg +4 -0
neckcord-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amandeep Singh Bains
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
neckcord-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neckcord
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Descending cervical 4-byte command bottleneck, self-healing hexapod VNC CPG, and presynaptic-shunting fault-tolerant thruster allocator
|
|
5
|
+
Author: Amandeep Singh Bains
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/indian-aman/neckcord
|
|
8
|
+
Project-URL: Repository, https://github.com/indian-aman/neckcord
|
|
9
|
+
Project-URL: Releases, https://github.com/indian-aman/neckcord/releases
|
|
10
|
+
Project-URL: Issues, https://github.com/indian-aman/neckcord/issues
|
|
11
|
+
Project-URL: Documentation, https://github.com/indian-aman/neckcord#readme
|
|
12
|
+
Keywords: neckcord,necklink,drosophila,connectome,descending-neurons,ventral-nerve-cord,cpg,kuramoto,control-allocation,fault-tolerant-control,usv,rov,hexapod,robotics,embedded-c99
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: C
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
25
|
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: numpy>=1.24
|
|
30
|
+
Provides-Extra: nltp
|
|
31
|
+
Requires-Dist: pynltp>=1.2.0; extra == "nltp"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
34
|
+
Requires-Dist: matplotlib>=3.7; extra == "dev"
|
|
35
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
36
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
<div align="center">
|
|
40
|
+
|
|
41
|
+
# NeckCord (`neckcord` v1.0.0)
|
|
42
|
+
|
|
43
|
+
**4-byte descending cervical command bottleneck, self-healing hexapod VNC phase CPG, and millisecond presynaptic-shunting thruster control allocation — in Python and zero-heap, zero-float ANSI C99.**
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/neckcord/)
|
|
46
|
+
[](https://github.com/indian-aman/neckcord/releases/tag/v1.0.0)
|
|
47
|
+
[](https://pypi.org/project/neckcord/)
|
|
48
|
+
[](csrc/)
|
|
49
|
+
[-10B981?style=flat-square)](neckcord/cervical.py)
|
|
50
|
+
[](tests/test_neckcord.py)
|
|
51
|
+
[](LICENSE)
|
|
52
|
+
|
|
53
|
+
[**Quick Start**](#-30-second-quick-start) ·
|
|
54
|
+
[**4-Byte Cervical Codec**](#1-4-byte-descending-cervical-command-frame-neckcordcervical) ·
|
|
55
|
+
[**Self-Healing Hexapod CPG**](#2-segmental-vnc-hexapod-cpg-neckcordcpg) ·
|
|
56
|
+
[**Shunting Thruster Allocator**](#3-presynaptic-shunting-thruster-cord-neckcordallocator) ·
|
|
57
|
+
[**Zero-Float ANSI C99**](#-zero-heap-zero-float-ansi-c99-core-csrc) ·
|
|
58
|
+
[**Benchmarks**](#-empirical-benchmarks) ·
|
|
59
|
+
[**When to Use**](#-when-to-use-neckcord--and-when-not-to)
|
|
60
|
+
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<p align="center">
|
|
64
|
+
<img src="https://raw.githubusercontent.com/indian-aman/neckcord/main/assets/neckcord_benchmarks.png" alt="NeckCord v1.0.0 USV Thruster Fault Reallocation and Hexapod Leg-Loss Benchmarks" width="100%">
|
|
65
|
+
</p>
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Biological Architecture: Brain → Cervical Bottleneck → Self-Healing VNC
|
|
70
|
+
|
|
71
|
+
In *Drosophila melanogaster*, roughly ~1,300 descending neurons cross a tiny cervical neck connective to coordinate thousands of thoracic motor units (`BANC` / `MANC` connectomes; Braun et al., *Nature* 2024; Lesser et al., *Nature* 2024). Rather than streaming high-rate per-muscle waveforms over the neck bottleneck, the brain sends compact behavioral intent (`DNa02` bilateral steering, `DNp09` forward drive, `MDN` reverse walk, `Giant Fiber` emergency freeze), while the **Ventral Nerve Cord (`VNC`)** handles local rhythm generation, **campaniform sensilla** load-error monitoring, and **presynaptic shunting** injury adaptation.
|
|
72
|
+
|
|
73
|
+
**NeckCord** (Product #5 in the [**NeckLink**](https://github.com/indian-aman/necklink) fruit-fly brain ecosystem) brings this exact split architecture to marine USVs, ROVs, multirotors, and legged robots:
|
|
74
|
+
|
|
75
|
+
1. **4-Byte Cervical Command Frame (`neckcord.cervical`)**: Packs `drive_fwd`, `turn_yaw`, `strafe_lat`, `gait_mode`, `freeze_reflex`, a `2-bit` rolling sequence counter, and `4-bit` bus-fault-hardened `CRC-4` into **`32 bits` (`400 B/s` at `100 Hz`)** — `6.0x` smaller than raw `4×float32 + header` actuator streams and ready to ride directly inside [`pynltp`](https://pypi.org/project/pynltp/) frames over lossy marine radio or cellular CGNAT links.
|
|
76
|
+
2. **Segmental 6-Limb VNC CPG (`neckcord.cpg.SegmentalCPGCord`)**: Phase-coupled Kuramoto oscillator network with contralateral commissural anti-phase inhibition and ipsilateral intersegmental lags. Smoothly transitions across **Metachronal Wave**, **Tetrapod**, and **Tripod** gaits from a single scalar descending drive, and automatically re-indexes into a **5-limb pentapod ring (`Δφ = 72°`, duty `0.82`) + ipsilateral foothold gap-closure reflex** (`100.0%` static CoM support-polygon stability) when any leg jams or loses load.
|
|
77
|
+
3. **Presynaptic-Shunting Thruster Allocator (`neckcord.allocator.ShuntingThrusterCord`)**: Online fault-tolerant wrench allocator (`W H W^T + λI`) for `4`–`8` actuators. Detects weed-fouling, propulsor shear, or ESC brownout from campaniform effort-vs-feedback discrepancy in **`~42 ms`**, shunts drive away from the fouled thruster (`0.02 N` wasted effort), and rebalances `surge`, `sway`, and `yaw` across surviving thrusters with yaw/sway-prioritized desaturation.
|
|
78
|
+
|
|
79
|
+
```mermaid
|
|
80
|
+
flowchart LR
|
|
81
|
+
A["Shore / Brain Planner"] -->|"4-Byte DescendingFrame (400 B/s @ 100 Hz, CRC-4)"| B["Cervical Bottleneck (UART / CAN / pynltp)"]
|
|
82
|
+
B --> C["SegmentalCPGCord (6-Limb Tripod / Tetrapod / Wave / 5-Leg Heal)"]
|
|
83
|
+
B --> D["ShuntingThrusterCord (4..8 Ch W*H*W^T + λI Reallocator)"]
|
|
84
|
+
E["Campaniform Load / ESC Feedback"] -->|"Effort Discrepancy (42 ms)"| C
|
|
85
|
+
E -->|"Presynaptic Shunting (s_i -> 0)"| D
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## ⚡ 30-Second Quick Start
|
|
91
|
+
|
|
92
|
+
### Install
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install neckcord
|
|
96
|
+
# Optional NLTP encrypted radio/UDP transport integration:
|
|
97
|
+
pip install "neckcord[nltp]"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Run from the Terminal
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
neckcord demo
|
|
104
|
+
neckcord benchmark-quick
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Python API
|
|
110
|
+
|
|
111
|
+
### 1. 4-Byte Descending Cervical Command Frame (`neckcord.cervical`)
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from neckcord import DescendingFrame, GaitMode, encode_descending_4b, decode_descending_4b
|
|
115
|
+
|
|
116
|
+
tx = DescendingFrame(
|
|
117
|
+
drive_fwd=0.82, # DNp09 forward (+1) / MDN Moonwalker reverse (-1)
|
|
118
|
+
turn_yaw=-0.25, # DNa02 bilateral steering [-1, +1]
|
|
119
|
+
strafe_lat=0.10, # Lateral sway / crab walk [-1, +1]
|
|
120
|
+
gait_mode=GaitMode.AUTO,
|
|
121
|
+
freeze_reflex=False,
|
|
122
|
+
seq=2,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
raw = encode_descending_4b(tx) # b'\x68\xe0\x0c\x20' (4 bytes = 32 bits)
|
|
126
|
+
rx = decode_descending_4b(raw, expected_seq=2)
|
|
127
|
+
wrench = rx.to_wrench(max_surge=60.0, max_sway=40.0, max_yaw=25.0)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
| Bits | Field | Encoding | Resolution / Role |
|
|
131
|
+
| :--- | :--- | :--- | :--- |
|
|
132
|
+
| `[31:24]` | `drive_fwd` | `8-bit` symmetric `-127..+127` | `0.0079` step, exact `0.0` & `±1.0` |
|
|
133
|
+
| `[23:16]` | `turn_yaw` | `8-bit` symmetric `-127..+127` | `0.0079` step, exact `0.0` & `±1.0` |
|
|
134
|
+
| `[15:9]` | `strafe_lat` | `7-bit` symmetric `-63..+63` | `0.0159` step, exact `0.0` & `±1.0` |
|
|
135
|
+
| `[8:7]` | `gait_mode` | `2-bit` unsigned `0..3` | `0=AUTO`, `1=TRIPOD`, `2=TETRAPOD`, `3=WAVE` |
|
|
136
|
+
| `[6]` | `freeze_reflex` | `1-bit` flag | `Giant Fiber` instant stance lock / zero wrench |
|
|
137
|
+
| `[5:4]` | `seq` | `2-bit` rolling counter | `0..3` packet-loss & reorder tracking |
|
|
138
|
+
| `[3:0]` | `crc4` | `x^4 + x + 1` (`init=0x9, xor=0x5`) | Rejects `100%` of 1-bit flips, `0x00000000` & `0xFFFFFFFF` |
|
|
139
|
+
|
|
140
|
+
### 2. Segmental VNC Hexapod CPG (`neckcord.cpg`)
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from neckcord import SegmentalCPGCord
|
|
144
|
+
|
|
145
|
+
cpg = SegmentalCPGCord(adapt_to_damage=True)
|
|
146
|
+
|
|
147
|
+
# Fast forward command -> Tripod gait; low speed -> Metachronal Wave
|
|
148
|
+
state = cpg.step(raw, dt=0.01)
|
|
149
|
+
|
|
150
|
+
# Simulate sudden middle-left leg (L2) jam / amputation mid-stride:
|
|
151
|
+
cpg.inject_limb_fault("L2")
|
|
152
|
+
for _ in range(100):
|
|
153
|
+
state = cpg.step(0.85, dt=0.01)
|
|
154
|
+
|
|
155
|
+
assert state.active_gait == "adaptive_pentapod"
|
|
156
|
+
assert state.is_statically_stable is True # CoM strictly inside >=3-leg polygon!
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 3. Presynaptic-Shunting Thruster Cord (`neckcord.allocator`)
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import numpy as np
|
|
163
|
+
from neckcord import ShuntingThrusterCord
|
|
164
|
+
|
|
165
|
+
# Standard 4-thruster X-vectored marine USV / ROV (Port-Fore, Stbd-Fore, Port-Aft, Stbd-Aft)
|
|
166
|
+
cord = ShuntingThrusterCord.usv_vectored_4(lx=0.45, ly=0.35, cant_deg=45.0, u_max=35.0)
|
|
167
|
+
|
|
168
|
+
w_des = np.array([35.0, 0.0, 0.0]) # Pure forward surge [Fx, Fy, Tz]
|
|
169
|
+
alloc = cord.step(w_des, dt=0.02)
|
|
170
|
+
|
|
171
|
+
# Port-Aft thruster (index 2) gets fouled by kelp (90% loss of thrust):
|
|
172
|
+
true_effectiveness = np.array([1.0, 1.0, 0.10, 1.0])
|
|
173
|
+
for _ in range(4):
|
|
174
|
+
measured_thrust = true_effectiveness * alloc.commands
|
|
175
|
+
alloc = cord.step(w_des, feedback_thrust=measured_thrust, dt=0.02)
|
|
176
|
+
|
|
177
|
+
# T2 is shunted to 0.0 N; T0 & T1 rebalance pure forward surge with zero yaw spin!
|
|
178
|
+
assert alloc.isolated_mask[2] and alloc.commands[2] == 0.0
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 🔧 Zero-Heap, Zero-Float ANSI C99 Core (`csrc/`)
|
|
184
|
+
|
|
185
|
+
[`csrc/neckcord.h`](csrc/neckcord.h) and [`csrc/neckcord.c`](csrc/neckcord.c) implement the complete `NeckCord` stack in pure fixed-point `Q15` / `Q14` / `16-bit BAM` integer arithmetic for bare-metal `STM32`, `ESP32`, `RP2040`, and safety MCUs without an FPU:
|
|
186
|
+
|
|
187
|
+
- **Zero dynamic allocation**: No `malloc`, `calloc`, `realloc`, or `free`.
|
|
188
|
+
- **Zero floating-point instructions**: Quarter-wave `65`-entry `Q15` sine lookup table (`130 B` ROM) + exact `int64_t` `3×3` Gram cofactor solver and integer cross-product CoM convex-hull verifier.
|
|
189
|
+
- **`304 bytes` total runtime state**: Combined `sizeof` all 5 state/result structs (`< 10%` of the `3 KB` static RAM budget).
|
|
190
|
+
- **100% wire-compatible with Python**: Encodes and decodes identical 4-byte frames (`0x68E00C20`).
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
cd csrc && make clean all test
|
|
194
|
+
# Compiles cleanly under: gcc -O2 -std=c99 -Wall -Wextra -pedantic -Werror (10/10 passed)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 📊 Empirical Benchmarks
|
|
200
|
+
|
|
201
|
+
Reproduce all numbers and regenerate `assets/neckcord_benchmarks.png` and `results/benchmark_summary.json` locally:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
python scripts/benchmark_neckcord.py
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 1. 3-DOF X-Vectored 4-Thruster USV Corridor Tracking (`dt = 0.02 s`, `30 s`, `10` Irregular Wave Seeds)
|
|
208
|
+
|
|
209
|
+
Both architectures use the **exact same Fossen 3-DOF hull model, wave seeds, and outer LOS + PID controller**:
|
|
210
|
+
|
|
211
|
+
| Scenario & Metric | `Static Pseudo-Inverse + Raw Float32` | `NeckCord (4B Cervical + Shunting VNC)` | Outcome |
|
|
212
|
+
| :--- | :---: | :---: | :--- |
|
|
213
|
+
| **Command Link Bandwidth (`100 Hz`)** | `2,400 B/s` (`24 B/frame`) | **`400 B/s` (`4 B/frame`)** | **`6.0x` lower wire bandwidth** |
|
|
214
|
+
| **Intact Fleet (`100%` Health) — RMS XTE** | `0.108 m` (`0.233 m` max) | `0.109 m` (`0.233 m` max) | **Identical nominal tracking (`1 mm`)** |
|
|
215
|
+
| **Intact Fleet (`100%` Health) — Peak Yaw** | `4.84° ± 0.94°` | `4.81° ± 0.79°` | **No quantization degradation** |
|
|
216
|
+
| **`90%` Port-Aft Loss (`t = 8 s`) — Peak Yaw** | `24.19° ± 2.28°` | **`4.77° ± 0.65°`** | **`5.07x` less heading spin-out** |
|
|
217
|
+
| **`90%` Port-Aft Loss (`t = 8 s`) — RMS XTE** | `0.498 m` (`0.735 m` max) | **`0.112 m` (`0.224 m` max)** | **`4.45x` tighter corridor tracking** |
|
|
218
|
+
| **Fault Isolation Latency** | Never isolated | **`42.0 ms`** | **Shunted in ~2 control ticks** |
|
|
219
|
+
| **Mean Drive Sent to Fouled `T2` Thruster** | `22.70 N` | **`0.02 N`** | **Protects jammed ESC / windings** |
|
|
220
|
+
|
|
221
|
+
### 2. Hexapod Single-Leg Jam (`L2` Middle-Left Lost at Fast `0.85` Drive)
|
|
222
|
+
|
|
223
|
+
| Condition | `%` Cycle in `>=3`-Leg CoM Static Support Polygon | Min CoM Stability Margin |
|
|
224
|
+
| :--- | :---: | :---: |
|
|
225
|
+
| **Intact 6-Leg Tripod** | `100.0%` | `+3.90 cm` |
|
|
226
|
+
| **`L2` Jammed — Unadapted Tripod CPG** | `55.0%` *(tips out of polygon every half-stride)* | `< 0 cm` *(collinear 2-leg phase)* |
|
|
227
|
+
| **`L2` Jammed — NeckCord Adaptive VNC** | **`100.0%`** | **`+2.24 cm`** |
|
|
228
|
+
| **All 6 Single-Leg Faults (`L1..R3` Mean) — NeckCord** | **`100.0%`** | **`+1.89 to +2.24 cm`** |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 🎯 When to Use NeckCord — and When Not To
|
|
233
|
+
|
|
234
|
+
**Reach for `NeckCord` when:**
|
|
235
|
+
- Controlling **4-to-8 thruster vectored USVs, ROVs, or multirotors** exposed to kelp/weed fouling, propulsor damage, or ESC brownouts where losing one actuator would otherwise spin the vehicle off heading and burn the stalled motor.
|
|
236
|
+
- Streaming real-time operator or autonomy commands across **narrow, lossy acoustic, LoRa, marine UHF, or `4G`/`5G` links** (`400 B/s` at `100 Hz` — pairs directly with `pynltp`).
|
|
237
|
+
- Running **hexapod robots or bare-metal C99 motor cords** (`304 B` RAM, zero FPU/heap) that need smooth single-parameter gait transitions (`Wave ↔ Tetrapod ↔ Tripod`) and instant 5-legged self-healing on leg failure.
|
|
238
|
+
|
|
239
|
+
**Stick with standard tools when:**
|
|
240
|
+
- **Underactuated 2-thruster differential hulls with no bow thruster or rudder**: If 1 of 2 fixed parallel thrusters dies completely, no allocator can produce forward surge without net yaw moment (`rank(W_surviving) = 1 < 2`).
|
|
241
|
+
- **Clean local wired buses on healthy, un-faulted vehicles**: As shown in our intact-fleet benchmark above, a 3-line static Moore-Penrose pseudo-inverse achieves identical nominal tracking (`0.108 m` vs `0.109 m` XTE) when all actuators remain `100%` healthy.
|
|
242
|
+
- **Asymmetric unidirectional thrusters (`u_i >= 0` only, e.g. non-reversible quadrotor props in 6-DOF)**: `ShuntingThrusterCord` assumes bidirectional control authority (`[-u_max, +u_max]`) with yaw/sway-prioritized scaling; strictly non-negative polyhedral actuator bounds with `> 6` coupled DOFs are better served by an active-set QP solver (`OSQP` / `proxqp`).
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 🗂️ Repository Layout
|
|
247
|
+
|
|
248
|
+
| Path | Description |
|
|
249
|
+
| :--- | :--- |
|
|
250
|
+
| [`neckcord/cervical.py`](neckcord/cervical.py) | `DescendingFrame`, `encode_descending_4b`, `decode_descending_4b`, `CRC-4`, `CervicalBottleneckLink` |
|
|
251
|
+
| [`neckcord/cpg.py`](neckcord/cpg.py) | `SegmentalCPGCord` 6-limb Kuramoto CPG, gait transitions, pentapod + gap-closure self-healing |
|
|
252
|
+
| [`neckcord/allocator.py`](neckcord/allocator.py) | `ShuntingThrusterCord` online health estimation, presynaptic shunting, `W H W^T + λI` allocator |
|
|
253
|
+
| [`neckcord/cli.py`](neckcord/cli.py) | `neckcord` CLI (`demo`, `benchmark-quick`, `--json`) |
|
|
254
|
+
| [`csrc/`](csrc/) | Zero-heap, zero-float `Q15`/`Q14` ANSI C99 core (`neckcord.h`, `neckcord.c`, `10/10` `neckcord_selftest.c`) |
|
|
255
|
+
| [`scripts/benchmark_neckcord.py`](scripts/benchmark_neckcord.py) | 10-seed 3-DOF USV + 6-limb hexapod benchmark & 3-panel hero chart generator |
|
|
256
|
+
| [`results/benchmark_summary.json`](results/benchmark_summary.json) | Reproducible empirical benchmark output JSON |
|
|
257
|
+
| [`tests/test_neckcord.py`](tests/test_neckcord.py) | `22` `pytest` unit, edge-case, CLI, and C99 source-audit tests |
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
Released under the [MIT License](LICENSE). Copyright (c) 2026 Amandeep Singh Bains.
|
neckcord-1.0.0/README.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# NeckCord (`neckcord` v1.0.0)
|
|
4
|
+
|
|
5
|
+
**4-byte descending cervical command bottleneck, self-healing hexapod VNC phase CPG, and millisecond presynaptic-shunting thruster control allocation — in Python and zero-heap, zero-float ANSI C99.**
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/neckcord/)
|
|
8
|
+
[](https://github.com/indian-aman/neckcord/releases/tag/v1.0.0)
|
|
9
|
+
[](https://pypi.org/project/neckcord/)
|
|
10
|
+
[](csrc/)
|
|
11
|
+
[-10B981?style=flat-square)](neckcord/cervical.py)
|
|
12
|
+
[](tests/test_neckcord.py)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
|
|
15
|
+
[**Quick Start**](#-30-second-quick-start) ·
|
|
16
|
+
[**4-Byte Cervical Codec**](#1-4-byte-descending-cervical-command-frame-neckcordcervical) ·
|
|
17
|
+
[**Self-Healing Hexapod CPG**](#2-segmental-vnc-hexapod-cpg-neckcordcpg) ·
|
|
18
|
+
[**Shunting Thruster Allocator**](#3-presynaptic-shunting-thruster-cord-neckcordallocator) ·
|
|
19
|
+
[**Zero-Float ANSI C99**](#-zero-heap-zero-float-ansi-c99-core-csrc) ·
|
|
20
|
+
[**Benchmarks**](#-empirical-benchmarks) ·
|
|
21
|
+
[**When to Use**](#-when-to-use-neckcord--and-when-not-to)
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<p align="center">
|
|
26
|
+
<img src="https://raw.githubusercontent.com/indian-aman/neckcord/main/assets/neckcord_benchmarks.png" alt="NeckCord v1.0.0 USV Thruster Fault Reallocation and Hexapod Leg-Loss Benchmarks" width="100%">
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Biological Architecture: Brain → Cervical Bottleneck → Self-Healing VNC
|
|
32
|
+
|
|
33
|
+
In *Drosophila melanogaster*, roughly ~1,300 descending neurons cross a tiny cervical neck connective to coordinate thousands of thoracic motor units (`BANC` / `MANC` connectomes; Braun et al., *Nature* 2024; Lesser et al., *Nature* 2024). Rather than streaming high-rate per-muscle waveforms over the neck bottleneck, the brain sends compact behavioral intent (`DNa02` bilateral steering, `DNp09` forward drive, `MDN` reverse walk, `Giant Fiber` emergency freeze), while the **Ventral Nerve Cord (`VNC`)** handles local rhythm generation, **campaniform sensilla** load-error monitoring, and **presynaptic shunting** injury adaptation.
|
|
34
|
+
|
|
35
|
+
**NeckCord** (Product #5 in the [**NeckLink**](https://github.com/indian-aman/necklink) fruit-fly brain ecosystem) brings this exact split architecture to marine USVs, ROVs, multirotors, and legged robots:
|
|
36
|
+
|
|
37
|
+
1. **4-Byte Cervical Command Frame (`neckcord.cervical`)**: Packs `drive_fwd`, `turn_yaw`, `strafe_lat`, `gait_mode`, `freeze_reflex`, a `2-bit` rolling sequence counter, and `4-bit` bus-fault-hardened `CRC-4` into **`32 bits` (`400 B/s` at `100 Hz`)** — `6.0x` smaller than raw `4×float32 + header` actuator streams and ready to ride directly inside [`pynltp`](https://pypi.org/project/pynltp/) frames over lossy marine radio or cellular CGNAT links.
|
|
38
|
+
2. **Segmental 6-Limb VNC CPG (`neckcord.cpg.SegmentalCPGCord`)**: Phase-coupled Kuramoto oscillator network with contralateral commissural anti-phase inhibition and ipsilateral intersegmental lags. Smoothly transitions across **Metachronal Wave**, **Tetrapod**, and **Tripod** gaits from a single scalar descending drive, and automatically re-indexes into a **5-limb pentapod ring (`Δφ = 72°`, duty `0.82`) + ipsilateral foothold gap-closure reflex** (`100.0%` static CoM support-polygon stability) when any leg jams or loses load.
|
|
39
|
+
3. **Presynaptic-Shunting Thruster Allocator (`neckcord.allocator.ShuntingThrusterCord`)**: Online fault-tolerant wrench allocator (`W H W^T + λI`) for `4`–`8` actuators. Detects weed-fouling, propulsor shear, or ESC brownout from campaniform effort-vs-feedback discrepancy in **`~42 ms`**, shunts drive away from the fouled thruster (`0.02 N` wasted effort), and rebalances `surge`, `sway`, and `yaw` across surviving thrusters with yaw/sway-prioritized desaturation.
|
|
40
|
+
|
|
41
|
+
```mermaid
|
|
42
|
+
flowchart LR
|
|
43
|
+
A["Shore / Brain Planner"] -->|"4-Byte DescendingFrame (400 B/s @ 100 Hz, CRC-4)"| B["Cervical Bottleneck (UART / CAN / pynltp)"]
|
|
44
|
+
B --> C["SegmentalCPGCord (6-Limb Tripod / Tetrapod / Wave / 5-Leg Heal)"]
|
|
45
|
+
B --> D["ShuntingThrusterCord (4..8 Ch W*H*W^T + λI Reallocator)"]
|
|
46
|
+
E["Campaniform Load / ESC Feedback"] -->|"Effort Discrepancy (42 ms)"| C
|
|
47
|
+
E -->|"Presynaptic Shunting (s_i -> 0)"| D
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## ⚡ 30-Second Quick Start
|
|
53
|
+
|
|
54
|
+
### Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install neckcord
|
|
58
|
+
# Optional NLTP encrypted radio/UDP transport integration:
|
|
59
|
+
pip install "neckcord[nltp]"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Run from the Terminal
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
neckcord demo
|
|
66
|
+
neckcord benchmark-quick
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Python API
|
|
72
|
+
|
|
73
|
+
### 1. 4-Byte Descending Cervical Command Frame (`neckcord.cervical`)
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from neckcord import DescendingFrame, GaitMode, encode_descending_4b, decode_descending_4b
|
|
77
|
+
|
|
78
|
+
tx = DescendingFrame(
|
|
79
|
+
drive_fwd=0.82, # DNp09 forward (+1) / MDN Moonwalker reverse (-1)
|
|
80
|
+
turn_yaw=-0.25, # DNa02 bilateral steering [-1, +1]
|
|
81
|
+
strafe_lat=0.10, # Lateral sway / crab walk [-1, +1]
|
|
82
|
+
gait_mode=GaitMode.AUTO,
|
|
83
|
+
freeze_reflex=False,
|
|
84
|
+
seq=2,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
raw = encode_descending_4b(tx) # b'\x68\xe0\x0c\x20' (4 bytes = 32 bits)
|
|
88
|
+
rx = decode_descending_4b(raw, expected_seq=2)
|
|
89
|
+
wrench = rx.to_wrench(max_surge=60.0, max_sway=40.0, max_yaw=25.0)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| Bits | Field | Encoding | Resolution / Role |
|
|
93
|
+
| :--- | :--- | :--- | :--- |
|
|
94
|
+
| `[31:24]` | `drive_fwd` | `8-bit` symmetric `-127..+127` | `0.0079` step, exact `0.0` & `±1.0` |
|
|
95
|
+
| `[23:16]` | `turn_yaw` | `8-bit` symmetric `-127..+127` | `0.0079` step, exact `0.0` & `±1.0` |
|
|
96
|
+
| `[15:9]` | `strafe_lat` | `7-bit` symmetric `-63..+63` | `0.0159` step, exact `0.0` & `±1.0` |
|
|
97
|
+
| `[8:7]` | `gait_mode` | `2-bit` unsigned `0..3` | `0=AUTO`, `1=TRIPOD`, `2=TETRAPOD`, `3=WAVE` |
|
|
98
|
+
| `[6]` | `freeze_reflex` | `1-bit` flag | `Giant Fiber` instant stance lock / zero wrench |
|
|
99
|
+
| `[5:4]` | `seq` | `2-bit` rolling counter | `0..3` packet-loss & reorder tracking |
|
|
100
|
+
| `[3:0]` | `crc4` | `x^4 + x + 1` (`init=0x9, xor=0x5`) | Rejects `100%` of 1-bit flips, `0x00000000` & `0xFFFFFFFF` |
|
|
101
|
+
|
|
102
|
+
### 2. Segmental VNC Hexapod CPG (`neckcord.cpg`)
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from neckcord import SegmentalCPGCord
|
|
106
|
+
|
|
107
|
+
cpg = SegmentalCPGCord(adapt_to_damage=True)
|
|
108
|
+
|
|
109
|
+
# Fast forward command -> Tripod gait; low speed -> Metachronal Wave
|
|
110
|
+
state = cpg.step(raw, dt=0.01)
|
|
111
|
+
|
|
112
|
+
# Simulate sudden middle-left leg (L2) jam / amputation mid-stride:
|
|
113
|
+
cpg.inject_limb_fault("L2")
|
|
114
|
+
for _ in range(100):
|
|
115
|
+
state = cpg.step(0.85, dt=0.01)
|
|
116
|
+
|
|
117
|
+
assert state.active_gait == "adaptive_pentapod"
|
|
118
|
+
assert state.is_statically_stable is True # CoM strictly inside >=3-leg polygon!
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. Presynaptic-Shunting Thruster Cord (`neckcord.allocator`)
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import numpy as np
|
|
125
|
+
from neckcord import ShuntingThrusterCord
|
|
126
|
+
|
|
127
|
+
# Standard 4-thruster X-vectored marine USV / ROV (Port-Fore, Stbd-Fore, Port-Aft, Stbd-Aft)
|
|
128
|
+
cord = ShuntingThrusterCord.usv_vectored_4(lx=0.45, ly=0.35, cant_deg=45.0, u_max=35.0)
|
|
129
|
+
|
|
130
|
+
w_des = np.array([35.0, 0.0, 0.0]) # Pure forward surge [Fx, Fy, Tz]
|
|
131
|
+
alloc = cord.step(w_des, dt=0.02)
|
|
132
|
+
|
|
133
|
+
# Port-Aft thruster (index 2) gets fouled by kelp (90% loss of thrust):
|
|
134
|
+
true_effectiveness = np.array([1.0, 1.0, 0.10, 1.0])
|
|
135
|
+
for _ in range(4):
|
|
136
|
+
measured_thrust = true_effectiveness * alloc.commands
|
|
137
|
+
alloc = cord.step(w_des, feedback_thrust=measured_thrust, dt=0.02)
|
|
138
|
+
|
|
139
|
+
# T2 is shunted to 0.0 N; T0 & T1 rebalance pure forward surge with zero yaw spin!
|
|
140
|
+
assert alloc.isolated_mask[2] and alloc.commands[2] == 0.0
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 🔧 Zero-Heap, Zero-Float ANSI C99 Core (`csrc/`)
|
|
146
|
+
|
|
147
|
+
[`csrc/neckcord.h`](csrc/neckcord.h) and [`csrc/neckcord.c`](csrc/neckcord.c) implement the complete `NeckCord` stack in pure fixed-point `Q15` / `Q14` / `16-bit BAM` integer arithmetic for bare-metal `STM32`, `ESP32`, `RP2040`, and safety MCUs without an FPU:
|
|
148
|
+
|
|
149
|
+
- **Zero dynamic allocation**: No `malloc`, `calloc`, `realloc`, or `free`.
|
|
150
|
+
- **Zero floating-point instructions**: Quarter-wave `65`-entry `Q15` sine lookup table (`130 B` ROM) + exact `int64_t` `3×3` Gram cofactor solver and integer cross-product CoM convex-hull verifier.
|
|
151
|
+
- **`304 bytes` total runtime state**: Combined `sizeof` all 5 state/result structs (`< 10%` of the `3 KB` static RAM budget).
|
|
152
|
+
- **100% wire-compatible with Python**: Encodes and decodes identical 4-byte frames (`0x68E00C20`).
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
cd csrc && make clean all test
|
|
156
|
+
# Compiles cleanly under: gcc -O2 -std=c99 -Wall -Wextra -pedantic -Werror (10/10 passed)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 📊 Empirical Benchmarks
|
|
162
|
+
|
|
163
|
+
Reproduce all numbers and regenerate `assets/neckcord_benchmarks.png` and `results/benchmark_summary.json` locally:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
python scripts/benchmark_neckcord.py
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 1. 3-DOF X-Vectored 4-Thruster USV Corridor Tracking (`dt = 0.02 s`, `30 s`, `10` Irregular Wave Seeds)
|
|
170
|
+
|
|
171
|
+
Both architectures use the **exact same Fossen 3-DOF hull model, wave seeds, and outer LOS + PID controller**:
|
|
172
|
+
|
|
173
|
+
| Scenario & Metric | `Static Pseudo-Inverse + Raw Float32` | `NeckCord (4B Cervical + Shunting VNC)` | Outcome |
|
|
174
|
+
| :--- | :---: | :---: | :--- |
|
|
175
|
+
| **Command Link Bandwidth (`100 Hz`)** | `2,400 B/s` (`24 B/frame`) | **`400 B/s` (`4 B/frame`)** | **`6.0x` lower wire bandwidth** |
|
|
176
|
+
| **Intact Fleet (`100%` Health) — RMS XTE** | `0.108 m` (`0.233 m` max) | `0.109 m` (`0.233 m` max) | **Identical nominal tracking (`1 mm`)** |
|
|
177
|
+
| **Intact Fleet (`100%` Health) — Peak Yaw** | `4.84° ± 0.94°` | `4.81° ± 0.79°` | **No quantization degradation** |
|
|
178
|
+
| **`90%` Port-Aft Loss (`t = 8 s`) — Peak Yaw** | `24.19° ± 2.28°` | **`4.77° ± 0.65°`** | **`5.07x` less heading spin-out** |
|
|
179
|
+
| **`90%` Port-Aft Loss (`t = 8 s`) — RMS XTE** | `0.498 m` (`0.735 m` max) | **`0.112 m` (`0.224 m` max)** | **`4.45x` tighter corridor tracking** |
|
|
180
|
+
| **Fault Isolation Latency** | Never isolated | **`42.0 ms`** | **Shunted in ~2 control ticks** |
|
|
181
|
+
| **Mean Drive Sent to Fouled `T2` Thruster** | `22.70 N` | **`0.02 N`** | **Protects jammed ESC / windings** |
|
|
182
|
+
|
|
183
|
+
### 2. Hexapod Single-Leg Jam (`L2` Middle-Left Lost at Fast `0.85` Drive)
|
|
184
|
+
|
|
185
|
+
| Condition | `%` Cycle in `>=3`-Leg CoM Static Support Polygon | Min CoM Stability Margin |
|
|
186
|
+
| :--- | :---: | :---: |
|
|
187
|
+
| **Intact 6-Leg Tripod** | `100.0%` | `+3.90 cm` |
|
|
188
|
+
| **`L2` Jammed — Unadapted Tripod CPG** | `55.0%` *(tips out of polygon every half-stride)* | `< 0 cm` *(collinear 2-leg phase)* |
|
|
189
|
+
| **`L2` Jammed — NeckCord Adaptive VNC** | **`100.0%`** | **`+2.24 cm`** |
|
|
190
|
+
| **All 6 Single-Leg Faults (`L1..R3` Mean) — NeckCord** | **`100.0%`** | **`+1.89 to +2.24 cm`** |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 🎯 When to Use NeckCord — and When Not To
|
|
195
|
+
|
|
196
|
+
**Reach for `NeckCord` when:**
|
|
197
|
+
- Controlling **4-to-8 thruster vectored USVs, ROVs, or multirotors** exposed to kelp/weed fouling, propulsor damage, or ESC brownouts where losing one actuator would otherwise spin the vehicle off heading and burn the stalled motor.
|
|
198
|
+
- Streaming real-time operator or autonomy commands across **narrow, lossy acoustic, LoRa, marine UHF, or `4G`/`5G` links** (`400 B/s` at `100 Hz` — pairs directly with `pynltp`).
|
|
199
|
+
- Running **hexapod robots or bare-metal C99 motor cords** (`304 B` RAM, zero FPU/heap) that need smooth single-parameter gait transitions (`Wave ↔ Tetrapod ↔ Tripod`) and instant 5-legged self-healing on leg failure.
|
|
200
|
+
|
|
201
|
+
**Stick with standard tools when:**
|
|
202
|
+
- **Underactuated 2-thruster differential hulls with no bow thruster or rudder**: If 1 of 2 fixed parallel thrusters dies completely, no allocator can produce forward surge without net yaw moment (`rank(W_surviving) = 1 < 2`).
|
|
203
|
+
- **Clean local wired buses on healthy, un-faulted vehicles**: As shown in our intact-fleet benchmark above, a 3-line static Moore-Penrose pseudo-inverse achieves identical nominal tracking (`0.108 m` vs `0.109 m` XTE) when all actuators remain `100%` healthy.
|
|
204
|
+
- **Asymmetric unidirectional thrusters (`u_i >= 0` only, e.g. non-reversible quadrotor props in 6-DOF)**: `ShuntingThrusterCord` assumes bidirectional control authority (`[-u_max, +u_max]`) with yaw/sway-prioritized scaling; strictly non-negative polyhedral actuator bounds with `> 6` coupled DOFs are better served by an active-set QP solver (`OSQP` / `proxqp`).
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 🗂️ Repository Layout
|
|
209
|
+
|
|
210
|
+
| Path | Description |
|
|
211
|
+
| :--- | :--- |
|
|
212
|
+
| [`neckcord/cervical.py`](neckcord/cervical.py) | `DescendingFrame`, `encode_descending_4b`, `decode_descending_4b`, `CRC-4`, `CervicalBottleneckLink` |
|
|
213
|
+
| [`neckcord/cpg.py`](neckcord/cpg.py) | `SegmentalCPGCord` 6-limb Kuramoto CPG, gait transitions, pentapod + gap-closure self-healing |
|
|
214
|
+
| [`neckcord/allocator.py`](neckcord/allocator.py) | `ShuntingThrusterCord` online health estimation, presynaptic shunting, `W H W^T + λI` allocator |
|
|
215
|
+
| [`neckcord/cli.py`](neckcord/cli.py) | `neckcord` CLI (`demo`, `benchmark-quick`, `--json`) |
|
|
216
|
+
| [`csrc/`](csrc/) | Zero-heap, zero-float `Q15`/`Q14` ANSI C99 core (`neckcord.h`, `neckcord.c`, `10/10` `neckcord_selftest.c`) |
|
|
217
|
+
| [`scripts/benchmark_neckcord.py`](scripts/benchmark_neckcord.py) | 10-seed 3-DOF USV + 6-limb hexapod benchmark & 3-panel hero chart generator |
|
|
218
|
+
| [`results/benchmark_summary.json`](results/benchmark_summary.json) | Reproducible empirical benchmark output JSON |
|
|
219
|
+
| [`tests/test_neckcord.py`](tests/test_neckcord.py) | `22` `pytest` unit, edge-case, CLI, and C99 source-audit tests |
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
Released under the [MIT License](LICENSE). Copyright (c) 2026 Amandeep Singh Bains.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
CC ?= gcc
|
|
2
|
+
CFLAGS ?= -O2 -std=c99 -Wall -Wextra -pedantic -Werror
|
|
3
|
+
BUILD := build
|
|
4
|
+
TARGET := $(BUILD)/neckcord_selftest
|
|
5
|
+
|
|
6
|
+
.PHONY: all clean test
|
|
7
|
+
|
|
8
|
+
all: $(TARGET)
|
|
9
|
+
|
|
10
|
+
$(BUILD):
|
|
11
|
+
mkdir -p $(BUILD)
|
|
12
|
+
|
|
13
|
+
$(TARGET): neckcord.c neckcord.h tools/neckcord_selftest.c | $(BUILD)
|
|
14
|
+
$(CC) $(CFLAGS) -I. neckcord.c tools/neckcord_selftest.c -o $(TARGET)
|
|
15
|
+
|
|
16
|
+
test: $(TARGET)
|
|
17
|
+
./$(TARGET)
|
|
18
|
+
|
|
19
|
+
clean:
|
|
20
|
+
rm -rf $(BUILD)
|