code:
# 1. 概述
## 1.1 什么是 WTCD
WTCD 全名 Wearable Technology's Conditional Document，是琳为《可穿戴科技》项目设计的分支引擎所使用的语言，允许作者非常容易地编写可互动的（带有分支选项的）小说。设计之初是为了满足帕蒂卡的小说的分支需要，之后就发展成了一门完整的语言。

WTCD 是一门“四分之一”编译的语言 —— WTCD 的编译器会将源文件（`.wtcd`） 解析（parse）成 JSON 格式的 AST，然后由解释器执行（interpret）。

WTCD 是一门不是很弱类型的语言 —— 每一个变量都有指定的类型并且不能存储类型不符合的值。然而 WTCD 的编译器并不会进行类型推断，因此类型错误只会在运行时被丢出。

在 WTCD 中，通常来说代码逻辑和具体小说内容是分开的，以避免在代码之间混入大量影响理解逻辑的小说正文内容。

与大多数语言不同的是。WTCD 是一个没有分隔符的语言 —— 这意味着在 WTCD 中每一个表达式或是申明之后不需要添加分号，甚至在列表的元素之间也不需要添加逗号。需要注意的是，**没有分割符并不代表用换行作为分割**，恰恰相反，对于 WTCD 来说，换行的作用和空格或是 TAB 是完全一致的（除了单行注释，带有换行的字符串以及用 Markdown 编写的正文） —— 多个表达式之间使用空格分割或者换行分割都是可行的，因此 **WTCD 的语句之间（虽然建议，但是）不需要添加换行**。

## 1.2 Hello World!
下面是 WTCD 的 Hello World!：

```wtcd
WTCD 1.3
section helloWorld then exit

---<<< helloWorld >>>---
# Hello World!
```

[执行效果](./例子/Hello-World.html)

## 1.3 一个更~~色情~~复杂的例子
Hello World! 完全无法演示 WTCD 的任何功能，因此这里给出一个稍微复杂一些的例子：

```wtcd
WTCD 1.3
declare [
  boolean vibrator = false
]
section start then selection [
  choice "按下开关" goto press
  choice "不按" goto doNotPress
]
section press {
  vibrator = true
} then goto walkHome
section doNotPress then goto walkHome
section walkHome then vibrator
  ? goto czpClimax
  : goto czpGoToGrandmasHouse
section czpClimax then exit
section czpGoToGrandmasHouse then exit

---<<< start >>>---
# 稍微复杂点的涉及粉红色遥控器的例子
面对少女的背影，手握粉红色遥控器的你，是否要按下开关呢？

---<<< press >>>---
想到手中遥控器能给少女带来的快乐，心善的你毫不犹豫地按下了开关。

然而，出乎意料的是少女似乎丝毫没有被影响。

“可能是没电了吧...”

---<<< doNotPress >>>---
“还是不了吧...”

---<<< walkHome >>>---
这样想着，你把遥控器放进了裤子口袋里，朝家中走去。

---<<< czpClimax >>>---
突然，你远远地看到路边有一只陈志鹏正扶着墙，一脸红晕地走着。你突然明白了什么，然后愤怒地把遥控器调到了最大。

---<<< czpGoToGrandmasHouse >>>---
路上经过了一只陈志鹏。你还没来得及向他打招呼，他就说道：

“好，我要去奶奶家了。”

一脸懵逼的你只得继续向前走着。
```

[执行效果](./例子/稍微复杂点的涉及粉红色遥控器的例子.html)

## 1.5 开始编写 WTCD
尽管 WTCD 被设计为一个独立的语言，在目前阶段，WTCD 和《可穿戴科技》是捆绑在一起的。若要下载安装使用 WTCD 的编译器和解析器，你需要先安装 [Node.js](https://nodejs.org/) 和 [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)。然后，你需要使用 git 将《可穿戴科技》的 repo 克隆下来：

```
git clone https://github.com/SCLeoX/Wearable-Technology.git
```

克隆完成后，你需要在克隆下来的 repo 文件夹内使用 npm 进行安装并运行脚本 `watch`：

```
npm install
npm run watch
```

如果一切顺利，该脚本会自动打开一个浏览器窗口并显示你本地版本的《可穿戴科技》。

此时，你可以开始编写 WTCD 文档了。本教程用到的所有例子的源代码都在 `chapters/5 - Meta/1 - WTCD/1 - 例子` 中。你可以阅读并修改它们。若要创建新的文档，在 `chapters` 文件夹内创建一个文件名符合《可穿戴科技》标准，后缀为 `.wtcd` 的文件就可以了（格式为 `<显示序号> - <显示名>.wtcd`，例如 `233 - 哈哈.wtcd`）。当你修改任何 `chapters` 文件夹中的文件时，之前运行的 `watch` 脚本会自动重新编译你的修改，因此此时无需运行任何命令，只需要刷新浏览器（你可能需要使用 ctrl+F5 来强制刷新）就可以看到修改后的版本了。

另外，若要编写 WTCD 文档，我**强烈建议**你使用 [Visual Studio Code](https://code.visualstudio.com/) 并安装 [WTCD 的语法高亮插件](https://marketplace.visualstudio.com/items?itemName=WTCD.vscode-wtcd)。如果你不知道的话，Visual Studio Code 是微软写的*基本开源*轻量编辑器，非常好用。同时，WTCD 目前只提供 Visual Studio Code 的语法高亮插件，而有语法高亮可以显著提升你的编辑体验。

## 1.6 版本声明
每一个 WTCD 文档的开头都是该 WTCD 文档编写时使用 WTCD 版本。其格式如下：

```wtcd
WTCD 1.3
```

目前版本是 1.3 所以请务必使用 1.3 作为版本。

WTCD 的版本使用 2/3 Semantic Versioning：

- 版本号由两个数字组成，中间用点连接，例如：`42.2333`
- 点前面的数字是 Major Version（42）
- 点后面的数字是 Minor Version（2333）
- Breaking Change（会导致之前编写的代码无法运行的）会递增 Major Version
  - 这包括添加新的保留字或是修改现有操作符的优先级等
- 增加向后兼容的功能会递增 Minor Version
  - 这包括添加新的操作符等
- Bug Fixes 不修改版本号

为了保证代码简介，之后的例子中将不会包含版本声明。

## 1.7 注释
WTCD 允许作者在单行注释和多行注释。

```wtcd
// 单行注释例子
/*
多行
注释
例子
*/
```

单行注释以 `//` 开始持续到当前行末，多行注释则以 `/*` 开始持续到 `*/`。注释内的任何内容都不会被读取。

WTCD 不支持多行注释嵌套。

path: chapters/1.10 - META/1 - WTCD/1 - 1. 概述.md


code:
# 2. 变量、表达式及语句
## 2.1 概述
WTCD 允许定义并使用变量，运算表达式，执行语句。

## 2.2 值的类型
WTCD 有以下值类型：

- `number`：数字，支持正负和小数
- `boolean`：布尔值，只能是 `true` 或者 `false`
- `string`：字符串
- `null`：空值
- `action`： 表示一个需要可执行的操作（`goto`，`exit`，或 `selection`）
- `choice`：表示一个选项（其中记录了这个选项的文字和被选择后执行的操作）
- `list`：存储一个列表（详见第 4 章）
- `function`：存储一个函数（详见第 5 章）

本章节将会跳过 `list` 和 `function`。

## 2.3 变量
WTCD 允许定义定义和使用变量。每个变量都有固定的类型，其中可使用的有上述每一个除了 `null` 以外的类型。

### 2.3.1 定义变量
若要定义变量，则需要使用 `declare` 语句 。

```
declare <declaration>
```

与其他语句不同的是，`declare` 语句除了可以出现在**语句组**（见 2.4.3）内以外，`declare` 还可以和 `section` 的逻辑块（见 3.2）平级。

`<declaration>` 可以是单个定义或是用方括号包裹的若干个定义。每个定义采用如下格式：

```
<变量类型> <变量名> [= <初始值>]
```

#### 2.3.1.1 单一定义
举个例子：

```
declare number a = 100
declare boolean b = false
```

这两个 `declaration` 分别定义了一个类型为 `number`，初始值为 `100` 的变量 `a`，和一个类型为 `boolean`，初始值为 `false` 的变量 `b`。

#### 2.3.1.2 多个定义
上述例子中的两个定义也可以合并成一个：

```
declare [
  number a = 100
  boolean b = false
]
```

#### 2.3.1.3 代码丑化（简称请不要这么做）
因为换行在 WTCD 中与空格的作用几乎一致，这个定义也可以写成：

```
declare [ number a = 100 boolean b = false ]
```

其中大多数空格甚至能被省略：

```
declare[number a=100boolean b=false]
```

Just because you can doesn't mean you should. 尽管换行和许多空格是可选的，但是为了可读性，强烈建议不要忽略它们。

#### 2.3.1.4 自动初始化
与此同时，类型为 `number`，`boolean`，`string` 的变量的初始化可以被省略。如果省略，它们将会被自动初始化为 `0`，`false`，和 `""`（空字符串）。

因此，如下两组定义效果是一致的。

```
declare [
  number a = 0
  boolean b = false
  string c = ""
]
```

```
declare [
  number a
  boolean b
  string c
]
```

### 2.3.2 变量的赋值
变量几乎可以在所有需要值的场合使用。这里先介绍如何改变变量的值。

给出如下定义：

```
declare number a
```

那么在需要赋值的时候就可以写：

```
a = 100
```

这个操作就会把 `a` 的值设置为 100。

当然，等号右侧也可以换成一个表达式：

```
a = a + 50
```

这个操作就会让 `a` 的值自增 50。不过这个操作可以用 `+=` 操作符代替：

```
a += 50
```

具体表达式有哪些会在 **2.4 表达式** 中介绍。

这里需要注意的是，WTCD 的解释器在运行时会检查赋值给某个变量的值是否和这个变量的类型相同。举个例子，如下代码就会在运行时报错：

```
declare number a
a = true // WTCDError: Variable "a" can only hold values of type number. Received boolean false.
```

### 2.3.3 变量的作用域
WTCD 使用[静态作用域](https://zh.wikipedia.org/wiki/%E4%BD%9C%E7%94%A8%E5%9F%9F)（[Lexical Scoping](https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scoping)）。每一个由大括号包裹的**语句组**（见 2.4.3）将组成一个新的作用域。

每个定义的变量的作用域从定义位置开始一直延伸到当前作用域结束。最外层定义的变量是全局变量。

举个例子：

```wtcd
declare number outside = 10

{
  declare number inside = 20
  outside // 10
  inside // 20
}

outside // 10
inside // WTCDError: Cannot locate lexical scope for variable "inside".
```

```wtcd
someVariable // WTCDError: Cannot locate lexical scope for variable "someVariable".
declare number someVariable = 100
```

内层作用域定义的变量会暂时覆盖（shadow）外层作用域定义的同名变量：

```wtcd
declare number someVariable = 100
{
  declare boolean someVariable = false
  someVariable // false
}
someVariable // 100
```

在同一作用域内不能定义同名变量：

```wtcd
declare number hey = 200
declare number hey = 300 // WTCDError: Variable "hey" has already been declared within the same lexical scope.
```

## 2.4 表达式
表达式就是由能够表达一个值的式子。简单的表达式由变量，数字，或者操作符组成。而复杂的表达式可以是一个**语句组**。

### 2.4.1 常量表达式（Literal Expression）
常量表达式，就是表示常量的表达式。在 WTCD 中，常量表达式有 4 种：数字，布尔值，字符串，null。

数字，布尔值，和 null 都很好理解：

```wtcd
// 数字常量表达式
100
12.7  // 支持小数
.68   // 0. 开头的数字中的 0 可以省略
(-20) // 这个其实不是数字常量表达式，而是一个一元运算符表达式，但是为了方便理解，这里也列出来了

// 布尔值常量表达式
true
false

// null 常量表达式
null // 只能是 null
```

WTCD 的字符串常量表达式稍微复杂一些。WTCD 允许使用双引号（`"`），单引号（`'`），或者反引号（`` ` ``）来包裹字符串：

```wtcd
"Hello"
'World'
`Test`
```

WTCD 的字符串常量表达式支持使用反斜杠（`\`）进行 escape：

```wtcd
"\n" // -> 换行
"\t" // -> TAB
"\'" // -> 单引号
"\"" // -> 双引号
"\`" // -> 反引号
"\\" // -> 反斜杠
```

同时 WTCD 支持跨行字符串常量表达式：

```wtcd
"This is a
poem." // -> "This is a\npoem"
```

### 2.4.2 操作符表达式
WTCD 允许使用操作符来操作不同的值。这里先给几个例子：

```wtcd
1 + 1         // -> 2
1 + 2 * 3     // -> 7（乘法优先级更高）
(1 + 2) * 3   // -> 9（括号优先运算）
2 ** 3        // -> 8（指数）
2 ** 3 ** 2   // -> 64（注意，WTCD 的指数是左结合的）
"a" + "b"     // -> "ab"（字符串拼接）
4 < 10        // -> true
5 >= 5        // -> true
3 == 3        // -> true
3 == "3"      // -> false（无自动类型转换）
3 != 3        // -> false
5 / 2         // -> 2.5（number 可存储小数）
5 ~/ 2        // -> 2（truncate 整除）
-5 ~/ 2       // -> -2（truncate 整除永远都往零靠）
5 % 2         // -> 1（求余）
-10           // -> -10
!false        // -> true
true ? 1 : 2  // -> 1（三元/逻辑选择）
false && true // -> false
false || true // -> true
a = 3         // -> a 变成 3（变量赋值）
a += 5        // -> a 加上了3（变量累加）
```

完整的操作符请参见下表（并不需要全部记住）：

| 元数 | 操作符 | 优先级 | 作用                       | 例子                                                  |
| ---- | ------ | ------ | -------------------------- | ----------------------------------------------------- |
| 2    | ::     | 20     | 函数调用                   | `mathMax::[1 2 3]` = 3                                |
| 2    | ?::    | 20     | 可选函数调用               | `mathMax?::[1 2 3]` = 3, `null?::[1 2 3]` = null      |
| 2    | .:     | 20     | 右起偏函数调用             | `[ 0 1 2 3 ] |> listSet.:[2 "Wow"]` = [ 0 1 "Wow" 3 ] |
| 2    | :.     | 20     | 左起偏函数调用             | `"Wow" |> listSet:.[[ 0 1 2 3 ] 2]` = [ 0 1 "Wow" 3 ] |
| 2    | .      | 19     | List 成员访问              | `[ 0 1 2 3 ].(2)` = 2，`[ 0 1 2 3 ].(4)` = 报错       |
| 2    | .?     | 19     | 越界即空 List 成员访问     | `[ 0 1 2 3 ].(2)` = 2，`[ 0 1 2 3 ].?(4)` = null      |
| 2    | ?.     | 19     | 可选 List 成员访问         | `null?.(4)` = null，`[ 0 1 2 3 ]?.(4)` = 报错         |
| 2    | ?.?    | 19     | 可选越界即空 List 成员访问 | `null?.(4)` = null，`[ 0 1 2 3 ]?.?(4)` = null        |
| 1    | -      | 17     | 取负                       | `-42` = -42                                           |
| 1    | !      | 17     | 取逻辑反                   | `!true` = false, `!false` = true                      |
| 2    | **     | 16     | 指数（左结合）             | `2 ** 3` = 8, `2 ** 3 ** 2` = 64                      |
| 2    | *      | 15     | 数字乘法                   | `5 * 10` = 50                                         |
| 2    | /      | 15     | 数字除法                   | `10 / 5` = 2, `5 / 2` = 2.5                           |
| 2    | ~/     | 15     | Truncate 整除              | `5 ~/ 3` = 1, `-5 ~/ 3` = -1                          |
| 2    | %      | 15     | 求余                       | `5 % 2` = 1, `-9 % 5` = -4                            |
| 2    | +      | 14     | 数字加法或字符串拼接       | `5 + 3` = 8, `"a" + "b"` = "ab"                       |
| 2    | -      | 14     | 数字减法                   | `5 - 3` = 2, `3 - 5` = -2                             |
| 2    | <      | 12     | 判断是否小于               | `3 < 5` = true, `5 < 5` = false, `7 < 5` = false      |
| 2    | <=     | 12     | 判断是否小于或等于         | `3 <= 5` = true, `5 <= 5` = true, `7 <= 5` = false    |
| 2    | >      | 12     | 判断是否大于               | `3 > 5` = false, `5 > 5` = false, `7 > 5` = true      |
| 2    | >=     | 12     | 判断是否大于或等于         | `3 >= 5` = false, `5 >= 5` = true, `7 >= 5` = false   |
| 2    | ==     | 11     | 判断是否相等               | `5 == 5` = true, `5 == 3` = false, `"5" == 5` = false |
| 2    | !=     | 11     | 判断是否不相等             | `5 != 5` = false, `5 != 3` = true, `"5" != 5` = true  |
| 2    | &&     | 7      | 逻辑与                     | `false || true` = false, `true || true` = true        |
| 2    | \|\|   | 6      | 逻辑或                     | `false || true` = true, `false || false` = true       |
| 2    | ?!     | 6      | 不可空提供默认值           | `2 ?! 3` = 2, `null ?! 3` = 3, `null ?! null` = 报错  |
| 2    | ??     | 6      | 提供默认值                 | `2 ?? 3` = 2, `null ?? 3` = 3, `null ?? null` = null  |
| 2    | \|>    | 5      | 管道调用                   | `"Hello" |> stringLength` = 5                         |
| 2    | \|::   | 5      | 逆向调用                   | `[1 2 3] |:: max` = `max::[1 2 3]`                    |
| 3    | ? :    | 4      | 逻辑选择                   | `true ? 1 : 2` = 1, `false ? 1 : 2` = 2               |
| 2    | =      | 3      | 赋值                       | `a = 10` 把 10 赋值给 a，然后返回 10                  |
| 2    | +=     | 3      | 数字/字符串累加            | `a += 10` = `a = a + 10`, `b += "a"` = `b = b + "a"`  |
| 2    | -=     | 3      | 数字累减                   | `a -= 10` = `a = a - 10`                              |
| 2    | *=     | 3      | 数字累乘                   | `a *= 10` = `a = a * 10`                              |
| 2    | /=     | 3      | 数字累除                   | `a /= 10` = `a = a / 10`                              |
| 2    | ~/=    | 3      | 数字累 Truncate 除         | `a ~/= 10` = `a = a ~/ 10`                            |
| 2    | %=     | 3      | 数字累求余                 | `a %= 10` = `a = a % 10`                              |

### 2.4.3 语句组表达式
语句组表达式允许你在一个需要表达式的地方提供一系列的语句。你需要用大括号（`{` 和 `}`）将你的语句包裹起来。

目前，语句可以是：

- 变量定义
- 表达式
- `yield` / `yield =` / `return` / `return =`

其中，变量定义就是定义局部变量。表达式就是计算某个表达式。而 `yield` 则是指定当前语句组的返回值。

举个例子：

```wtcd
declare [
  number a = 1
  number b = 2
  number c = 3
  number squareOfSum
]
squareOfSum = {
  // 定义一个局部变量 sum 用来存储 a，b，c 的和
  declare number sum = a + b + c

  // 将该语句组的返回值设置为 sum * sum
  yield sum * sum

  // 这句话不会执行，因为 yield 会中断当前语句组的执行
  squareOfSum = 66666
}
```

运行后，`squareOfSum` 会变成 36。

除此之外 `yield` 还有一个变种：`yield =`。`yield =` 会设置当前语句组的返回值但是不会中断运行。举个例子：

```
declare [
  boolean changed = false
  number a
]
a = {
  yield = 10
  changed = true
}
```

上述例子运行后，`a` 会变成 10，而 `changed` 会变成 true，因为 `yield = 10` 并不会停止运行。

需要注意的是最后一个执行的 `yield =` 会覆盖之前执行的 `yield =`。此外 `yield` 不是一个变量，因此并不能尝试获得它的值。

path: chapters/1.10 - META/1 - WTCD/2 - 2. 变量、表达式及语句.md

license: unknown


code:
# 3. Section
## 3.1 概述
每个 WTCD 文档都是由若干个 Section 组成的。本章节将会介绍如何定义 section 和互相连接 section。

每一个 Section 都由一个**逻辑块**和任意自然数个**内容块**组成。

比如说，在上一章节给出的例子里就有以下 Sections：

- `start`
- `press`
- `doNotPress`
- `walkHome`
- `czpClimax`
- `czpGotoGrandmasHouse`

这些 Sections 被连接在一起形成如下结构：

![](./sectionDiagram.svg)

## 3.2 Section 逻辑
若要创建一个 Section，**逻辑块**是必不可少的。**逻辑块**决定了当运行到这个 Sections 时在输出正文内容前和输出正文内容后需要做什么。所有的**逻辑块**定义都必须在所有**内容块**之前。

### 3.2.1 逻辑块的基本结构
Section 的定义使用如下结构：

```wtcd
section <Section 名字> [先行表达式] then <后发表达式>
```

举个例子，给出如下定义：

```wtcd
section example {
  a += 1
} then {
  a += 2
}
```

其中 Section 名字是 `example`。其先行表达式为 `{ a += 1 }`，后发表达式为 `{ a += 2 }`。也就是说，当需要进入 `example` 这个 section 时，WTCD 解释器会:

1. 对 `{ a += 1 }` 求值。这个过程会让变量 a 的值加 1。
2. 根据**内容块**的定义输出。（见 3.3）
3. 对 `{ a += 2 }` 求值。这个过程会让变量 a 的值加 2。
4. 根据 `{ a += 2 }` 的返回结果决定接下来怎么做。但是因为 `{ a += 2 }` 没有指定返回值，那么就会采用默认返回值 `null`。在这里，若后发表达式返回值为 `null`，WTCD 就会什么也不做，如果此时 Section 栈（见 3.2.3）为空的话，就会结束运行。

先行表达式是可选的，如果不提供先行表达式，那么就意味着在需要执行先行表达式的时候，什么也不做。

### 3.2.2 逻辑块的互相连接
WTCD 永远从第一个定义的 Section 开始运行。

WTCD 会根据每一个逻辑块的后发表达式运算结果来决定需要做什么。后发表达式的返回值必须是 `action` 或者 `null`（见 3.2.2.2.2）。如果类型是 `action`，那么这个 `action` 就会被执行。如果是 `null`，则会什么也不做。

`action` 目前一共有 3 种：`exit`，`goto`，和 `selection`。下面来逐一介绍。

#### 3.2.2.1 Exit
因为 `exit` 比较简单，那么先介绍 `exit`。

`exit` 被执行时，会立刻终止 WTCD 的运行。举个例子：

```wtcd
section test then exit
```

在这个例子中，无论因为什么原因进入 `section test` 以后，WTCD 都会停止运行。

#### 3.2.2.2 Goto
通常来说 `goto` 也不复杂。因为当 `goto` 后面提供一个 Section 名的时候，执行 `goto` 的作用基本就是跳转到指定的 section：

```wtcd
section a then goto b
section b then exit
```

在这个例子中，当进入并执行 `section a` 之后，WTCD 会自动跳转到 `section b`。因为 `section b` 的 `then` 后面跟的是 `exit`，所以 `section b` 执行后就会停止运行。

然而，复杂的是，`goto` 允许在后面提供多个 Section。`goto` 严格来说应该被理解为“将这些 Sections 以相反顺序入栈”。或者换句话说：“依次执行这几个 Section”。现在举个提供多个 Section 的 `goto` 的例子：

```wtcd
section main then goto [ a1 b1 ]

section a1 then goto a2
section a2 then null

section b1 then goto b2
section b2 then null
```

[执行效果](./例子/Goto-入栈.html)

首先需要先了解，如果 `then` 之后的表达式返回的是 `null`，那么就代表什么也不做。

在上述例子中，可以看到各个 Section 的执行顺序为：`main -> a1 -> a2 -> b1 -> b2`。下面给出原因：

1. WTCD 从第一个 Section 开始运行，所以执行 `main`。
2. `main` 的后发表达式返回 `goto [ a1 b1 ]`，所以入栈 `b1` 和 `a1`。此时栈的状态为：`[ b1 a1 ]`。
3. 从栈中取出最后一项，是 `a1`，因此运行 `a1`。此时栈的状态为：`[ b1 ]`。
4. `a1` 的返回值是 `goto a2`，因此入栈 `a2`。此时栈的状态为：`[ b1 a2 ]`。
5. 从栈中取出最后一项，是 `a2`，因此运行 `a2`。此时栈的状态为：`[ b1 ]`。
6. `a2` 的返回值是 `null`，因此什么也不做。此时栈的状态为：`[ b1 ]`。
7. 从栈中取出最后一项，是 `b1`，因此运行 `b1`。此时栈的状态为：`[ ]`。
8. `b1` 的返回值是 `goto b2`，因此入栈 `b2`。此时栈的状态为：`[ b2 ]`。
9. 从栈中取出最后一项，是 `b2`，因此运行 `b2`。此时栈的状态为：`[ ]`。
10. `b2` 的返回值是 `null`，因此什么也不做。此时栈的状态为：`[ ]`。
11. 栈空，停止运行。

或者，另一个解释方式是，`goto` 在 WTCD 里相当于是“函数调用”。`goto a1` 相当于是调用“函数” `a1`。然而，由于所有的 `goto` 都会在 Section 的最后执行，因此 WTCD 可以在入栈目标函数前出栈，相当于是强制进行了尾递归优化。

如果还是不能理解，这边有一个 JavaScript 的类比函数你可以参考下。

```javascript
function main() {
  console.info('main');
  a1();
  b1();
}
function a1() {
  console.info('a1');
  a2();
}
function a2() {
  console.info('a2');
}
function b1() {
  console.info('b1')
  b2();
}
function b2() {
  console.info('b2');
}
main();
```

#### 3.2.2.3 Selection
除了自动执行固定的 Action 外，WTCD 允许在每一个 Section 后暂停运行，并要求由用户做出选择。若要等待用户作出选择，就需要在 `then` 后提供一个 Selection。

##### 3.2.2.3.1 创建 Selection 和 Choice
若要创建一个 Selection，则需要使用 `selection` 关键词和 `choice` 关键词。

其中，`selection` 使用如下格式：

```wtcd
selection <选项列表>
```

`choice` 用于创建一个选项。若要使用则需要使用如下格式：

```wtcd
choice <选项显示文本> <被选择后执行的 Action>
```

例如：

```wtcd
section start then selection [
  choice "去 A" then goto a
  choice "去 B" then goto b
]
section a then exit
section b then exit
```

在上述例子中，WTCD 会先执行 `start`。随后因为 `then` 之后遇到了 `selection`，脚本会停止运行，并输出两个选项供用户选择。若用户选择了“去 A”，则 Action `goto a` 会被执行，因此会跳转到 `section a`。相反，若用户选择了“去 B”，则 Action `goto b` 会被执行，因此会跳转到 `section b`。

##### 3.2.2.3.2 关于使用 `null`
在 Selection 的选项列表里面，可以提供 `null`。所有这些 `null` 都会被忽略。举个例子：

```wtcd
section main then selection [
  null
  choice "A" goto a
  null
  choice "B" goto b
  null
]
```

和

```wtcd
section main then selection [
  choice "A" goto a
  choice "B" goto b
]
```

是等效的。

至于这个特性有什么用，请看 3.2.2.4。

此外，**需要注意区分的是**，每一个 Choice 的 Action 也可以是 `null`。一旦一个 Choice 的 Action 是 `null`，那么这个按钮就是不可选的按钮。请看下述例子：

```wtcd
section main then selection [
  choice "Choice A" goto a
  choice "Choice B" null // 这个按钮连点都点不了
]
```

#### 3.2.2.4 Action 和 Choice 作为值
这里需要注意的是，Action 和 Choice 都是可以存储的值。它们各自的类型分别是：`action` 和 `choice`。

举个例子：

```wtcd
declare [
  // 定义一个存储 Action 的变量 someAction 并初始化为 goto b
  action someAction = goto b

  // 定义一个存储 Choice 的变量 someChoice1，其被选择后使用的 Action 用变量表示
  choice someChoice1 = choice "choice 1" someAction

  // 定义一个存储字符串的变量 someString
  string someString = "Some text"

  // 定义一个存储 Choice 的变量 someChoice2，choice 的显示文本使用一个涉及之前定义的字符串变量的表达式
  choice someChoice2 = choice someString + " wow!" exit

  // 定义一个存储 Selection 的变量 someSelection，它的选项由之前的变量定义
  action someSelection = selection [ someChoice1 someChoice2 ]
]

// 定义一个 section 的 then 使用变量 someSelection
section main then someSelection
```

因为其实说到底 `action`，`choice`，或者 `selection` 都只不过是和 `number` 或是 `boolean` 一样的值，所以它们也可以被用在表达式里。

举个例子，你可以使用逻辑选择操作符来选择性隐藏一个选项：

```wtcd
section main then selection [
  condition ? choice "text" goto a : null
]
```

上述例子中，选项“text”只有在 `condition` 为 `true` 的时候才会显示。

同时，你也可以选择性禁用一个选项：

```wtcd
section main then selection [
  choice "text" condition ? goto a : null
]
```

上述例子中，选项“text”都会显示，但是只有在 `condition` 为 `true` 的时候才可以被选择。

## 3.3 Section 内容
定义 Section 的内容相比逻辑而言就简单多了 。Section 的内容由**内容块**定义。所有的**内容块**都必须在所有**逻辑块**以下。

### 3.3.1 基本格式
通常来说，定义一个 Section 的固定输出内容非常简单：

```wtcd
section a then b
section b then exit

---<<< a >>>---
# Title
Hello A

---<<< b >>>---
# Another Title
Hello B
```

可以看到，在 `---<<< a >>>---` 以下，`---<<< b >>>---` 以上的就是运行 `a` 时会输出的内容。同时，在 `---<<< b >>>---` 以下的则是在运行 `b` 时输出的内容。需要注意的是，内容支持 Markdown，因此可以输出粗体，链接，图片等等。

### 3.3.2 多个内容块
其实每个 Section 可以对应零到无数个内容块。每一个内容块可以定义“边界”，即在第几次访问的时候这个内容块可能会出现。

若要定义一个边界，则需要在内容块的 Section 名后写上  `@<边界>`。其中边界可以是：

- 一个数字。例如 `main@3` 代表只有在第三次进入 `main` 时，这个内容块才会被使用。
- 一个数字加短横。例如 `main@3-` 代表只有在第三次及以后进入 `main` 时，这个内容块才会被使用。
- 短横加一个数字。例如 `main@-3` 代表只有在第三次及以前进入 `main` 时，这个内容块才会被使用。
- 一个数字，一个短横，再加一个数字。例如 `main@5-10` 代表只有在第五次到第十次进入 `main` 时，这个内容块才会被使用。

如果符合条件的内容块只有一个，那么这个内容块就会被选择。

如果没有符合条件的内容块，则不会输出任何内容。

如果有超过一个的符合条件的内容块，则会随机选择一个作为输出的内容块。

### 3.3.3 插值
WTCD 的内容块允许插入变量。插入格式为：`<$ 变量名 $>`。比如：

```wtcd
declare number money = 100
section main then exit

---<<< main >>>---
你有 <$ money $> 元钱。
```

需要注意的是，插值仅允许使用全局变量。并不允许使用表达式或者局部变量。

path: chapters/1.10 - META/1 - WTCD/3 - 3. Section.md


code:
# 4. Lists
## 4.1 概述
WTCD 允许存储有序不可变列表。其类型名为 `list`。

WTCD 的列表内的元素可以有不同的类型，因此一个长度为 1 的 `list` 可以被视为是一个可以存储任何类型值的 WTCD 变量。

## 4.2 创建新的列表
若要创建一个新的列表，请使用 `[` 和 `]` 包裹这个列表里的元素。两个元素之间仅需要空格或是换行：

```wtcd
declare list a = [ 1 "Hello" 3 ]
```

此外，在定义变量时，若使用的类型是 `list`，并不提供初始值的话，将会默认创建一个空的列表。

```wtcd
declare list empty // []
```

需要注意的是，这里有一个坑。下述代码：

```wtcd
declare list a = [ 1 -2 3 ]
```

会被 WTCD 理解为 `[ (1 - 2) 3 ]`。若要解决这个问题，则需要使用：

```wtcd
declare list a = [ 1 (-2) 3 ]
```

## 4.3 获得列表的一个成员
若要获得列表的一个成员，则需要使用成员访问操作符：`.`，`.?`，`?.`，`?.?`。

### 4.3.1 普通成员访问
通常来说，访问一个成员使用 `.` 操作符：

```wtcd
[ "a0" "a1" "a2" "a3" ] . 2 == "a2"
[ "a0" "a1" "a2" "a3" ].(2) == "a2"
```

由于小数原因，WTCD 不允许直接写 `[ "a0" "a1" "a2" "a3" ].2`。点与数字之间必须要要用空格隔开，或者用括号把数字包起来。

如果要访问的下标（Index）存在变量里，则可以直接写：
```wtcd
declare number index = 2
[ "a0" "a1" "a2" "a3" ].index == "a2"
```

需要注意的是，WTCD 的设计提倡“错误尽早发现”，因此当下标不存在（Index out of bounds）或是不正确时，普通成员访问会直接报错。需要注意的是，报错只会在下标不存在时发生。以下例子会正常返回 `null`。

```wtcd
[ "a0" null "a2" ].(1) == null
```

而这个例子会报错：

```wtcd
[ "a0" "a1" ].(2) // List does not have an element at 2. If return null is desired, use ".?" instead. List contents: list (elements = [string (value = "a0"), string (value = "a1")])
```

### 4.3.2 越界即空成员访问
当然，有时候，确实会希望当下标不存在时返回 `null`。这时候可以使用 `.?` 来访问成员。

```wtcd
[ "a0" "a1" ] .? (2) == null
```

需要注意的是，只有下标越界的时候（`index < 0` 或者 `index >= listLength::[list]`），`.?` 才会返回 `null`。如果提供的下标不是整数依然会报错。（无论如何，你都不应该在下标中使用涉及浮点数运算的操作。）

### 4.3.3 可选成员访问
还有某些时候，列表本身也可能是 `null`。如果希望在这个情况下不报错而是直接返回 `null`，可以使用 `?.`。

```wtcd
null ?. 2 == null
```

需要注意的是，`?.` 是带有 short circuit 的。也就是说，如果左侧计算结果为 `null`，那么右侧的内容根本不会被计算。

### 4.3.4 可选越界即空成员访问
如果凑巧，不但你希望在下标越界时返回 `null`，而且在列表自己是 `null` 的时候也返回 `null`，你可以使用 `?.?`。

```wtcd
null ?.? 2 == null
[ 0 1 2 ] ?.? 3 == null
```

## 4.4 改变列表
简而言之，你不能改变列表。

—— 但是你可以从一个列表创建一个稍稍有点不同的列表。

### 4.4.1 使用标准库函数
最简单的改变列表的方式是使用标准库（见第 6 章）函数（见第 5 章） `listSet`。

```wtcd
declare list a = [ 0 1 2 3 ]
declare list b = listSet::[a 2 "Wow"] // b 是 [ 0 1 "Wow" 3 ]
```

需要注意的是，正如之前所说，`listSet` 不会改变列表，你必须把返回值赋给一个之前的或是另一个变量。

```wtcd
declare list a = [ 0 1 2 3 ]

listSet::[a 2 "Wow"]
// a 依然是 [ 0 1 2 3 ]

a = listSet::[a 2 "Wow"]
// a 现在是 [ 0 1 "Wow" 3 ] 了
```

关于其他改变列表的函数，请前往 `6.1`。

### 4.4.2 使用 `...` 操作符
`...` 允许用一个数组代替一个新的数组的一部分。

举个例子：

```wtcd
declare list inner = [ 3 4 5 ]
declare list outer = [ 1 2 ...inner 6 7 ] // [ 1 2 3 4 5 6 7 ]
```

使用 `...` 可以获得一个在一个列表的基础上加了若干元素的列表。（添加元素）

```
declare list a = [ 1 2 3 ]
a = [ ...a "Back" ] // [ 1 2 3 "Back" ]
a = [ "Front" ...a ] // [ "Front" 1 2 3 "Back" ]
```

或者可以做点别的事情比如：

```
declare list a = [ 1 2 3 ]
a = [ ...a ...a ] // [ 1 2 3 1 2 3 ]
```

path: chapters/1.10 - META/1 - WTCD/4 - 4. Lists.md


code:
# 5. Functions
## 5.1 概述
WTCD 提供匿名支持引用闭包的函数。

在 WTCD 里，函数就是普通的值，因此也能被存在变量里或是被传递。若被存在变量里，其类型名为 `function`。

## 5.2 创建新的函数
若要创建函数，需要使用 `function` 关键词。其格式如下：

```
function <参数列表> <返回值>
```

举个例子：

```wtcd
function[number input] input * input
```

可以创建一个求平方的函数。

你当然可以把这个刚刚创建的函数存到变量里（不然相当于是创建了就扔了）。

```wtcd
declare function square = function[number input] input * input
```

需要注意的是，这里有两个 `function`。其中第一个 `function` 的作用是告诉 WTCD `square` 这个变量的类型是 `function`（类似 `declare number a` 里的 `number`）。之后这个 `function` 是告诉 WTCD 现在需要创建一个 `function`。

是的，我也觉得这么写很烦，所以 WTCD 有语法糖。上述定义还可以写成：

```wtcd
declare function square[number input] input * input
```

这两个写法效果是完全一致的。

若要创建一个接受多个参数的函数：

```wtcd
declare function add[number a number b] a + b
```

当然最后返回值那里也可以用代码块：

```wtcd
declare function solveQuad[number a number b number c] {
  declare number delta = b ** 2 - 4 * a * c
  delta > 0 ? {
    return [
      (-b - delta ** 0.5) / 2 / a
      (-b + delta ** 0.5) / 2 / a
    ]
  } : delta == 0 ? {
    return [ -b / 2 / a ]
  } : {
    return []
  }
}
```

## 5.3 调用函数
WTCD 提供多种调用函数的方式。

### 5.3.1 `::` 函数调用操作符
最简单的调用函数的方法是使用 `::` 操作符。使用时，`::` 左侧提供被调用函数，右侧提供一个参数列表。

举个例子：

```wtcd
square::[10] // 100
```

好的，我知道你肯定要问为什么不是 `square(10)`。那么原因是，因为 WTCD 没有分割符，如果语法是 `square(10)`，WTCD 将无法区分 `square; (10)` 和 `square(10)`。所以非常遗憾，调用函数必须用 `::`。

有意思的是，`::` 右边其实就是一个列表 —— 这里没有魔法。

因此理论上来说你可以把参数预先算好，然后到时候放进去：

```wtcd
declare list args = [ 10 ]
square::args // 100
```

当然，对于多个参数的情况，调用也是一个道理：

```wtcd
solveQuad::[1 (-3) 2] // [ 1 2 ]
```

预先计算参数也是可以的：

```wtcd
declare list args = [1 (-3) 2]
solveQuad::args // [ 1 2 ]
```

### 5.3.2 `|>` 管道调用操作符
WTCD 支持单参数管道调用。

举个例子：

```wtcd
10 |> square // 100
```

遗憾的是，如果被调用的函数接受超过 1 个参数就没办法了。

### 5.3.3 `|::` 逆向调用操作符
如果因为某些原因导致你不想把参数写在函数的后面，你可以用 `|::` 来先写参数再写函数：

```wtcd
[ 10 ] |:: square // 100
```

```wtcd
[1 (-3) 2] |:: solveQuad // [ 1 2 ]
```

## 5.4 传参
简而言之，WTCD 参数传递一律传值：WTCD 把所有参数全部复制了一遍，然后把它们当成函数内的局部变量。

```wtcd
declare function tryModify[number a] {
  a += 10
  return a
}
declare number b = 5
declare number c = tryModify::[b]

b // 5，不会受函数的影响
c // 15
```

## 5.5 返回值
虽然之前已经用过了，但是这边提一下，WTCD 的函数体内允许使用 `return`。不过你可能会问为什么不使用 `yield`。

事实上，`yield` 和 `yield =` 也是可以用的：

```wtcd
declare function square[number a] {
  yield a * a
}
```

但是，如果嵌套了多层，`return` 可以直接一次全部跳出来。

```wtcd
declare function square[number a] { // 代码块 A
  { // 代码块 B
    // 这里使用 yield a * a 将并不会设置函数的返回值，而是只会设置代码块 B 的返回值。
    return a * a // return 则会直接跳出函数
  }
}
```

当然，有了 `return`，就有 `return =`。和 `yield =` 类似，`return =` 会设置函数的返回值但是不会终止函数执行。

```wtcd
declare function test[number a] {
  return = a * 2
  print::[a]
}
test::[10] // 返回 20 并打印 10 到控制台。
```

如果 `return` 值和 `yield` 值冲突了，则会使用 `return`。

```wtcd
declare function square[number n] {
  yield = "Wrong"
  return = n * n // Return 优先
}
square::[12] // 144
```

## 5.6 默认参数
WTCD 支持默认参数。简而言之就是允许你定义的函数被调用时若一部分参数没有提供，或是提供的值是 `null`，则使用提供的默认值。

```wtcd
declare function greet[string name = "朋友"] {
  print::["你好，" + name]
}
greet::[] // 你好，朋友
greet::["陈志鹏"] // 你好，陈志鹏
```

如果要忽略之前的参数，但是提供后面的参数，可以在前面的参数使用 `null`。

```wtcd
declare function fn[number a = 1 number b = 2] {
  print::[a + b]
}
fn::[null 10] // 11
fn::[10] // 12
fn::[10 10] // 20
```

## 5.7 Rest / 可变参数
WTCD 支持可变参数。简而言之，就是指定一个参数接受多出来的参数。如果除了这个参数以外，别的什么也没提供，那么相当于是所有的参数都会给这个参数。

WTCD 仅支持让最后一个参数接受所有多出来的参数。若要这么做，请忽略该参数的类型定义并在其前方写上 `...`。该参数的类型会自动变成 `list`。

```wtcd
declare function sum[...items] {
  declare number value
  listForEach::[items function[number item] {
    value += item
  }]
  return value
}
print::[sum::[1 2 3]]
```

## 5.8 闭包
> This is where the magic lies. -- Tepis

> I want you to make a contract with me and become a Magical Girl! -- Kyubey

简而言之，WTCD 的闭包允许函数内的语句使用函数外定义的变量。

先举个简单的例子：

```wtcd
declare number value = 100
declare function printValue[] {
  print::[value]
}
printValue::[] // 打印 100
value = 200
printValue::[] // 打印 200
```

虽然从这个例子来看好像没有什么特殊的，但是仔细观察发现，函数内使用的 `value` 是在函数外部的。并且可以发现，当这个 `value` 发生改变时，`printValue` 的值也会改变。

这个就是最简单的闭包。

下面来看第二个例子。

```wtcd
declare function printValue = {
  declare number value = 100 // 定义 value
  yield function[] {
    print::[value]
  }
} // value 所在的作用域在此结束
printValue::[] // 打印 100
```

需要注意的是，`value` 这个变量不在外层作用域里面。我们调用的函数 `printValue` 是从内层有 `value` 的作用域里面通过 `yield` 传递出来的。

也就是说，因为一些原因，即使 `value` 的作用域已经结束了，`value` 还在某个地方存在着。

事实上，在定义函数的时候，WTCD 会把涉及到的函数作用域以外的变量的值全部打包和函数放在一起。因此也就是说这个传回来的 `printValue` 函数在 WTCD 内部实际上还保存了 `value` 的值。这就是为什么 `value` 本身存在的空间已经挂了但是依然能够被 `printValue` 访问的原因。

不但如此，WTCD 的闭包捕获的变量都是保存引用的。也就是说，不但你可以访问之前存在的值，你甚至可以修改它们。

```wtcd
declare function printValue = {
  declare number value = 1 // 定义 value
  yield function[] {
    print::[value]
    value += 1
  }
} // value 所在的作用域在此结束
printValue::[] // 打印 1
printValue::[] // 打印 2
printValue::[] // 打印 3
printValue::[] // 打印 4
```

除此之外，WTCD 捕获的变量都是互相隔离的。意味着，你两次捕获由同一段代码定义的两个作用域所产生的捕获是互相隔离的。

```wtcd
declare function makeCounter[] {
  declare number n = 0
  return function[] {
    n += 1
    return n
  }
}
declare [
  function counter1 = makeCounter::[]
  function counter2 = makeCounter::[]
]
print::[counter1::[]] // 1
print::[counter1::[]] // 2
print::[counter1::[]] // 3
print::[counter2::[]] // 1
print::[counter1::[]] // 4
print::[counter2::[]] // 2
```

因为 `makeCounter` 被调用了两次。每次调用都会产生一个新的 `n`。所以两个 `counter` 捕获的是不同的变量，因此能够独立互不干涉地工作。

闭包是函数式编程的重要组件之一。只有用好闭包才能在 WTCD 繁荣。

path: chapters/1.10 - META/1 - WTCD/5 - 5. Functions.md


code:
# 6. 标准库
WTCD 提供一系列标准库用来做一些正常做不到或是即使做到了也非常复杂的操作。

## 6.1 `list*`
### 6.1.1 `listSet`
接受 3 个参数：`list sourceList`，`number index`，`任意 value`。

返回一个列表，返回的列表是 `sourceList` 中 `index` 被替换为 `value` 后的列表。该函数不会修改 `sourceList`。

```wtcd
listSet::[[ 0 1 2 3 4 5 ] 3 "Nice"] == [ 0 1 2 "Nice" 4 5 ]
```

### 6.1.2 `listForEach`
接受 2 个参数：`list targetList`，`function fn`。

对 `targetList` 中每一项运行 `fn`。`fn` 的第一个参数是这一项的值，第二个参数是这一项的下标。

```wtcd
declare number contentSum = 0
declare number indexSum = 0
listForEach::[[ 1 2 3 4 100 ] function[number item number index] {
  contentSum += item
  indexSum += index
}]
contentSum // 110
indexSum // 10
```

### 6.1.3 `listMap`
接受 2 个参数：`list targetList`，`function fn`。

对 `targetList` 中每一项运行 `fn`。`fn` 的第一个参数是这一项的值，第二个参数是这一项的下标。然后，将所有 `fn` 的运行返回值合在一起，形成一个新的列表并返回。

```wtcd
listMap::[[ 0 1 2 4 20 ] function[number item] item * item] == [ 0 1 4 16 400 ]
```

### 6.1.4 `listCreateFilled`
接受 1 - 2 个参数：`number count`，`任意 value = null`。

创建一个列表，这个列表有 `count` 个元素，每个元素的值都是 `value`。

```wtcd
listCreateFilled::[5 8] == [ 8 8 8 8 8 ]
```

### 6.1.5 `listChunk`
接受 2 个参数：`list targetList`，`number chunkSize`。

将 `targetList` 分成若干个列表，每个列表的长度为 `chunkSize`。

```wtcd
listChunk::[[ 1 2 3 4 5 6 7 8 9 10 ] 3 ] == [ [ 1 2 3 ] [ 4 5 6 ] [ 7 8 9 ] [ 10 ] ]
```

### 6.1.6 `listFilter`
接受 2 个参数：`list targetList`，`function fn`。

对 `targetList` 中每一项运行 `fn`。`fn` 的第一个参数是这一项的值，第二个参数是这一项的下标，并要求返回一个布尔值。然后，将所有返回 true 的项目合在一起形成一个新的列表并返回。

```wtcd
listFilter::[[ 1 2 3 4 5 6 7 8 ] function[number a] a % 2 == 0] == [ 2 4 6 8 ]
```

### 6.1.7 `listSplice`
接受 3 - 4 个参数：`list source`，`number start`，`number length`，`list newItems = []`。

从 `source` 的 index = `start` 的元素开始，删除 `length` 个元素，并用 `newItems` 中的元素代替。返回修改后的列表。（不会修改原始列表）

```wtcd
listSplice::[[ 0 1 2 3 ] 1 2] == [ 0 3 ]
listSplice::[[ 0 1 2 3 ] 1 2 [ "Wow1" "Wow2" ]] == [ 0 "Wow1" "Wow2" 3]
```

### 6.1.8 `listSlice`
接受 2 - 3 个参数：`list source`，`number start`，`number end = listLength::[source]`。

复制从 `source` 的 index = `start`（含）开始到 index = `end`（不含）的元素。

```wtcd
    assert::[listSlice::[[ 0 1 2 3 4 5 ] 2] == [ 2 3 4 5 ]]
    assert::[listSlice::[[ 0 1 2 3 4 5 ] 2 4] == [ 2 3 ]]
```

### 6.1.9 `listLength`
接受 1 个参数：`list targetList`。

返回 `targetList` 的元素数量。

```wtcd
listLength::[[ 0 1 2 3 4 ]] == 5
```

### 6.1.10 `listIndexOf`
接受 2 个参数：`list targetList`，`target`。

找 `targetList` 中第一个 == `target` 的元素，返回其 index。若无法找到，返回 -1。

```wtcd
assert::[listIndexOf::[[ 0 1 2 3 ] 2] == 2]
assert::[listIndexOf::[[ 0 1 2 3 ] 4] == -1]
```

### 6.1.11 `listIncludes`
接受 2 个参数：`list targetList`，`target`。

判断 `targetList` 中是否有 == `target` 的元素。

```wtcd
assert::[listIncludes::[[ 0 1 2 3 ] 2] == true]
assert::[listIncludes::[[ 0 1 2 3 ] 4] == false]
```

### 6.1.12 `listFindIndex`
接受 2 个参数：`list targetList`，`function predicate`。

对 `targetList` 中每一项依次运行 `fn`。`fn` 的第一个参数是这一项的值，第二个参数是这一项的下标，并要求返回一个布尔值。当任意一个 `predicate` 返回 true 时，返回该项的 index。如果没有一项能使 `predicate` 返回 true，否则返回 -1。

```wtcd
assert::[listFindIndex::[[ 1 3 5 8 9 11 14 ] $[item] item % 2 == 0] == 3]
assert::[listFindIndex::[[ 1 3 5 8 9 11 14 ] $[item index] index == 4] == 4]
assert::[listFindIndex::[[ 1 3 5 8 9 11 14 ] $[item] false] == -1]
```

## 6.2 `math*`
### 6.2.1 `mathMin`
接受 1 个及以上的数字。返回其中最小的值。

```wtcd
mathMin::[10 1 2 3 4] == 1
```

### 6.2.2 `mathMax`
接受 1 个及以上的数字。返回其中最大的值。

```wtcd
mathMax::[1 2 3 4 0] == 4
```

### 6.2.3 `mathFloor`
接受 1 个参数：`number input`。返回小于等于 `input` 的最大整数。

```wtcd
mathFloor::[11.8] == 11
mathFloor::[-11.8] == -12
```

### 6.2.4 `mathCeil`
接受 1 个参数：`number input`。返回大于于等于 `input` 的最小整数。

```wtcd
mathFloor::[11.8] == 12
mathFloor::[-11.8] == -11
```

## 6.3 `string*`
### 6.3.1 `stringLength`
接受 1 个参数：`string input`。返回 `input` 的长度。

```wtcd
stringLength::["Hello~"] == 6
```

### 6.3.2 `stringFormatNumberFixed`
接受 1 个参数：`number target`，`number digits = 0`。返回将 `target` 四舍五入到 `digits` 位小数后的字符串。

```wtcd
stringFormatNumberFixed::[1.235 2] == "1.24"
```

### 6.3.3 `stringFormatNumberPrecision`
接受 2 个参数：`number target`，`number digits`。返回将 `target` 四舍五入到保留 `digits` 位数字后的字符串。

```wtcd
stringFormatNumberPrecision::[1.235 2] == "1.2"
stringFormatNumberPrecision::[1234 2] == "1.2e+3"
```

### 6.3.4 `stringSplit`
接受 2 个参数：`string str`，`string separator`。返回用 `separator` 分割 `str` 产生的列表。

```wtcd
stringSplit::["a,b" ","] == ["a" "b"]
```

### 6.3.5 `stringSubByLength`
接受 2 - 3 个参数：`string str`，`number startIndex`，`number length = 剩余长度`。

返回 `str` 从 `startIndex`（含）开始 `length`（含）个字符的子串。

### 6.3.6 `stringSubByIndex`
接受 2 - 3 个参数：`string str`，`number startIndex`，`number endIndex = 字符串长度`。

返回 `str` 从 `startIndex`（含）开始直到 `endIndex`（不含）的子串。

## 6.4 `random*`
### 6.4.1 `random`
接受 0 - 2 个参数：`number low = 0`，`number high = 1`。

返回一个大于等于 `low`，小于 `high` 的随机浮点数。

### 6.4.2 `randomInt`
接受 0 - 2 个参数：`number low = 0`，`number high = 1`。

返回一个大于等于 `low`，小于 `high` 的随机整数。

### 6.4.3 `randomBoolean`
接受 0 - 1 个参数：`boolean trueChance = 0.5`。

返回一个有 `trueChance` 为 `true` 的随机布尔值。

### 6.4.4 `randomBiased`
**实验性，尚未确认**

接受 0 - 4 个参数：`number low = 0`，`number high = 1`，`number bias = (low + high) / 2`，`number influence = 3`。

返回一个大于等于 `low`，小于 `high`，偏向于 `bias`，影响系数为 `influence` 的随机浮点数。

## 6.5 `content*`
### 6.5.1 `contentAddParagraph`
接受 1 个参数：`string content`。

创建并插入一个内容为 `content` 的自然段。

### 6.5.2 `contentAddImage`
接受 1 个参数：`string src`。

创建并插入一个地址为 `src` 的图片。

### 6.5.3 `contentAddUnorderedList`
接受任意数量个字符串参数。

然后用它们来产生并插入一个无序列表。

### 6.5.4 `contentAddOrderedList`
接受任意数量个字符串参数。

然后用它们来产生并插入一个有序列表。

### 6.5.5 `contentAddHeader`
接受 1 - 2 个参数：`string content`，`number level = 1`。

创建并插入一个级别为 `level`，内容为 `content` 的标题。

### 6.5.6 `contentAddTable`
接受 1 个及以上的参数。每一个参数都是长度相同的列表。每一个列表对应表格的一行。第一行作为表头。

```wtcd
contentAddTable::[
  [ "名字" "年龄" "性别" ]
  [ "琳"   "18"   "女"   ]
  [ "czp"  "？"   "男"   ]
]
```

### 6.6.6 `contentAddHorizontalRule`
创建并插入一条水平分割线。

## 6.6 Debug
### 6.6.1 `print`
接受任意数量个参数。将这些值打印到浏览器控制台。

### 6.6.2 `assert`
接受 1 个参数：`boolean isOk`。

如果 `isOk` 不是 true，立刻产生一个异常（中断执行）。

### 6.6.3 `assertError`
接受 1 个参数：`function fn`。

调用 `fn`。如果 `fn` 没有产生 WTCD 错误，则产生一个异常。（断言这个提供的函数在运行时会报错。）

### 6.6.4 `timeStart`
接受 0 - 1 个参数：`string timerName = "default"`。

启动一个名为 `timerName` 的计时器。

### 6.6.5 `timeEnd`
接受 0 - 1 个参数：`string timerName = "default"`。

结束名为 `timerName` 的计时器，并将计时结果打印到浏览器控制台。

## 6.7 `reader*`
### 6.7.1 `readerSetPinned`
接受 1 个参数：`function pinnedFunction`。

设置 `pinnedFunction` 为当前的常驻函数。常驻函数是指在每次需要用户做出选择的时候被调用的函数。在这个函数内调用 `content*` 会把创建的内容添加到一个特殊的常驻区域。常驻区域的显示方式则是由具体的阅读器决定的。

### 6.7.2 `readerUnsetPinned`
不接受参数。取消当前常驻函数。

### 6.7.3 `readerSetStateDesc`
接受 1 个参数：`string stateDesc`。

将当前的状态描述文本设置为 `stateDesc`。状态描述文本通常被用在支持多存档的阅读器的存档选择界面中来帮助区分存档。

### 6.7.4 `readerUnsetStateDesc`
不接受参数。取消当前状态描述文本。

## 6.8 `canvas*`
### 6.8.1 `canvasCreate`
接受 3 个参数：`string id`，`number width`，`number height`。

创建一个 ID 为 `id`，宽度为 `width`，高度为 `height` 的画布。需要注意的是，这个画布是不会显示出来的。

如果需要显示这个画布，则需要使用 `canvasOutput`。

### 6.8.2 `canvasOutput`
接受 1 个参数：`string id`。

将 ID 为 `id` 的画布显示出来。需要注意的是，这个画布一旦被显示就会被克隆。因此之后对该画布的修改不会影响到已经显示出来的版本。

### 6.8.3 `canvasClear`
接受 1 个参数：`string id`。

将 ID 为 `id` 的画布清空。

### 6.8.4 `canvasPutImage`
接受 4 - 6 个参数：`string id`，`string path`，`number x`，`number y`，`number width = -1`，`number height = -1`。

将位于 `path` 的图片绘制到 ID 为 `id` 的画布上。使用 `x` 和 `y` 可以控制绘制的位置（左上角）。使用 `width` 和 `height` 可以控制绘制出来的尺寸。如果忽略，那么就会使用图片的原始尺寸。

需要注意的是，`width` 和 `height` 要么同时提供，要么都不提供。不可以只提供一个。

### 6.8.5 `canvasPutImagePart`
接受 8 - 10 个参数：`string id`，`string path`，`number sourceX`，`number sourceY`，`number sourceWidth`，`number sourceHeight`，`number destX`，`number destY`，`number destWidth = -1`，`number destHeight = -1`。

将位于 `path` 的图片的一部分绘制到 ID 为 `id` 的画布上。使用 `sourceX`，`sourceY`，`sourceWidth`，`sourceHeight` 可以控制需要绘制的部分在来源图片的位置。使用 `destX` 和 `destY` 可以控制在画布的位置（左上角）。使用 `destWidth` 和 `destHeight` 可以控制绘制出来的尺寸。如果忽略，那么就会使用图片的来源部分的尺寸。

需要注意的是，`destWidth` 和 `destHeight` 要么同时提供，要么都不提供。不可以只提供一个。

### 6.8.6 `canvasSetFont`
接受 3 个参数：`string id`，`number size`，`string fontIdentifier`。

设置 ID 为 `id` 的画布的字体为尺寸是 `size`px，类型是 `fontIdentifier` 的字体。

`fontIdentifier` 的解释方法由使用的 WTCD 阅读器指定。在《可穿戴科技》中，这个 `fontIdentifier` 的值为 `googleFonts:<在 Google Fonts 上的字体名>`。例如：`googleFonts:ZCOOL KuaiLe`。

### 6.8.7 `canvasSetFillStyle`
接受 2 个参数：`string id`，`string fillStyle`。

设置 ID 为 `id` 的画布的填充方式为 `fillStyle`。

### 6.8.8 `canvasFillText`
接受 4 - 6 个参数：`string id`，`string text`，`number x`，`number y`，`string hAlign = "start"`，`string vAlign = "alphabetic"`。

在 ID 为 `id` 的画布的位于（`x`, `y`）的位置，绘制以 `hAlign` 横向对齐，以 `vAlign` 纵向对齐的文本 `text`。

请使用 `canvasSetFont` 来选择字体，使用 `canvasSetFillStyle` 来选择填充方式。

`hAlign` 可采取的值可以在[这里](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign)找到。

`vAlign` 可采取的值可以在[这里](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline)找到。

### 6.8.9 `canvasFillRect`
接受 5 个参数：`string id`，`number x`，`number y`，`number width`，`number height`。

在 ID 为 `id` 的画布的位于（`x`, `y`）的位置绘制一个 `width`px x `height`px 的长方形。

### 6.8.10 `canvasSetStrokeStyle`
接受 2 个参数：`string id`，`string strokeStyle`。

设置 ID 为 `id` 的画布的描线方式为 `strokeStyle`。

### 6.8.11 `canvasSetLineWidth`
接受 2 个参数：`string id`，`number lineWidth`。

设置 ID 为 `id` 的画布的描线粗细为 `lineWidth`px。

### 6.8.12 `canvasStrokeText`
接受 4 - 6 个参数：`string id`，`string text`，`number x`，`number y`，`string hAlign = "start"`，`string vAlign = "alphabetic"`。

类似 `canvasFillText`，但是只是描边。所以请使用 `canvasSetStrokeStyle` 来选择描边方式。

path: chapters/1.10 - META/1 - WTCD/6 - 6. 标准库.md


code:
# 7. 流程控制
## 7.1 概述
目前 WTCD 的传统流程控制只原生提供了 `switch`。

## 7.2 `switch`
`switch` 语句语法如下：

```
switch <valueExpr> [
  <matchList1> <matchExpr1>
  <matchList2> <matchExpr2>
  ...
  [defaultExpr]
]
```

举个例子：

```wtcd
declare number value = // ...
declare number result = switch value [
  [ 1 5 10 ] 200
  [ 3 7 ] 300
  400
]
```

在上述例子中，如果 `value` 的值是 1 或 5 或 10，则 `result` 会变成 200。如果 `value` 的值是 3 或 7，则 `result` 会变成 300。否则 `result` 会变成 400。

在 WTCD 中，如果没有提供最后的 `defaultExpr`，那么在如果没有匹配时就会报错。

此外，WTCD 的 switch 时没有 fall through 的。也就是说，上一个 `case` 执行完后不会进入下一个 `case`，因此也没有必要使用 `break`。

另外，每一个 `matchList` 都可以是一个表达式。上述 `switch` 语句还可以写成：

```wtcd
declare number value = // ...
declare list list1 = [ 1 5 10 ]
declare list list2 = [ 3 7 ]
declare number result = switch value [
  list1 200
  list2 300
  400
]
```

## 7.3 `if`
WTCD 目前不支持 `if`，但是可以用 `?` `:` 实现。

### 7.3.1 实现 if then
```wtcd
(condition) ? {
  // Then
} : null
```

### 7.3.2 实现 if then else
```wtcd
(condition) ? {
  // Then
} : {
  // Else
}
```

path: chapters/1.10 - META/1 - WTCD/7 - 7. 流程控制.md


code:
# 文档债
文档债 = 因为时间原因 + 懒导致没有及时编写的文档。

- while 循环
- if 语句
- 复合类型
- Partial application
- $ 语法糖
- function switch 语法糖
- 函数 Action
- tag
- pin

path: chapters/1.10 - META/1 - WTCD/9997 - 文档债.md


code:
# 变化日志
## 1.3 (WIP)
- 添加如下标准库函数
  - `mathCeil`
  - `stringSplit`
  - `stringSubByLength`
  - `stringSubByIndex`
  - `contentAddHorizontalRule`
  - `readerSetPinned`
  - `readerUnsetPinned`
  - `readerSetStateDesc`
  - `readerUnsetStateDesc`
  - `canvasCreate`
  - `canvasOutput`
  - `canvasClear`
  - `canvasPutImage`
  - `canvasPutImagePart`
  - `canvasSetFont`
  - `canvasSetFillStyle`
  - `canvasFillText`
  - `canvasFillRect`
  - `canvasSetStrokeStyle`
  - `canvasSetLineWidth`
  - `canvasStrokeText`

## 1.2
- 允许一个变量存储多个类型的值
- 添加 `while` 支持
- 添加 `if` 支持
- 添加 `null` 的变量类型
- 添加如下标准库函数
  - `contentAddTable`
  - `listFilter`
  - `listSplice`
  - `listSlice`
  - `listLength`
  - `listIndexOf`
  - `listIncludes`
  - `listFindIndex`
  - `stringFormatNumberFixed`
  - `stringFormatNumberPrecision`
- 添加如下操作符
  - `.:`
  - `:.`
- 添加函数创建语法糖 `$`
- 添加 `function switch` 语法糖
- 添加函数 Action 支持

## 1.1
- 添加 `list` 支持
- 添加 `function` 支持
- 存储 `selection` 的变量类型由 `selection` 变为 `action`
  - 为了向前兼容，目前 `selection` 为 `action` 的 alias，并且将在下一个大版本移除
- 添加 `switch` 支持
- 添加标准库
- 添加如下操作符
  - `::`
  - `.`
  - `.?`
  - `?.`
  - `?.?`
  - `**`
  - `?!`
  - `??`
  - `|>`
  - `|::`

path: chapters/1.10 - META/1 - WTCD/9998 - 变化日志.md


code:
# 计划功能
WTCD 目前还在开发初期阶段，因此还有许多功能被计划实现但是还没有实现。这里给出目前计划中的功能。需要注意的是，具体的语法还没有确定，这里给出的只是目前的想法

## 使用 `::` 代表递归调用自身
因为 WTCD 的函数都是匿名函数，所以可能需要一个特殊语法来处理递归。

## `while` 循环
`while` 循环有必要，估计在下个版本就有了。

## `if` 语句
虽然 `if` 可以用 `?` `:`，但是有原生的 `if` 看起来可能舒服一点。

## 引用赋值与引用比较
这个实现起来并不复杂，但是因为在设计初期没有考虑这个，所以不知道会不会影响什么别的功能。引用赋值大概是说让两个变量变成同一个东西。

举个例子：

```wtcd
declare [
  number a = 100
  number b
]
b =& a // 将 a 的引用给 b

a = 200
a // 200
b // 200

b = 300
a // 300
b // 300
```

因为没有想到什么好的方法可以解除引用，所以目前看来引用赋值是永久的...

类似的，引用比较可以判断两个变量是否是同一个引用。

```
declare [
  number a = 100
  number b = 100
]

a ==& b // false

a =& b

a ==& b // true
```

# 已实装
- 标准库
- 函数
- 列表

path: chapters/1.10 - META/1 - WTCD/9999 - 计划功能.md


code:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
<svg width="27cm" height="7cm" viewBox="306 411 531 133" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <g>
    <rect style="fill: #ffffff" x="308" y="463" width="47.6" height="38"/>
    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="308" y="463" width="47.6" height="38"/>
    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="331.8" y="486.8">
      <tspan x="331.8" y="486.8">start</tspan>
    </text>
  </g>
  <g>
    <rect style="fill: #ffffff" x="419.074" y="417" width="51.85" height="38"/>
    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="419.074" y="417" width="51.85" height="38"/>
    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="444.999" y="440.8">
      <tspan x="444.999" y="440.8">press</tspan>
    </text>
  </g>
  <g>
    <rect style="fill: #ffffff" x="417.176" y="505" width="85.65" height="38"/>
    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="417.176" y="505" width="85.65" height="38"/>
    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="460.001" y="528.8">
      <tspan x="460.001" y="528.8">doNotPress</tspan>
    </text>
  </g>
  <g>
    <rect style="fill: #ffffff" x="540.674" y="458" width="80.65" height="38"/>
    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="540.674" y="458" width="80.65" height="38"/>
    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="580.999" y="481.8">
      <tspan x="580.999" y="481.8">walkHome</tspan>
    </text>
  </g>
  <g>
    <rect style="fill: #ffffff" x="678.95" y="412" width="78.1" height="38"/>
    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="678.95" y="412" width="78.1" height="38"/>
    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="718" y="435.8">
      <tspan x="718" y="435.8">czpClimax</tspan>
    </text>
  </g>
  <g>
    <rect style="fill: #ffffff" x="677.05" y="505" width="159.9" height="38"/>
    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="677.05" y="505" width="159.9" height="38"/>
    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="757" y="528.8">
      <tspan x="757" y="528.8">czpGotoGrandmasHouse</tspan>
    </text>
  </g>
  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="757" y="524">
    <tspan x="757" y="524"></tspan>
  </text>
  <g>
    <path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" d="M 409.429,436.119 A 61.3144,61.3144 0 0 0 355.6,482"/>
    <polygon style="fill: #000000" points="416.843,435.852 406.533,440.177 409.359,435.354 407.197,430.199 "/>
    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="416.843,435.852 406.533,440.177 409.359,435.354 407.197,430.199 "/>
  </g>
  <g>
    <path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" d="M 355.6,482 A 73.9165,73.9165 0 0 0 407.591,522.692"/>
    <polygon style="fill: #000000" points="414.945,523.843 404.62,528.13 407.464,523.318 405.321,518.155 "/>
    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="414.945,523.843 404.62,528.13 407.464,523.318 405.321,518.155 "/>
  </g>
  <g>
    <path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" d="M 533.871,470.061 A 137.21,137.21 0 0 0 470.926,436"/>
    <polygon style="fill: #000000" points="539.166,475.349 528.73,471.337 534.108,469.811 536.114,464.593 "/>
    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="539.166,475.349 528.73,471.337 534.108,469.811 536.114,464.593 "/>
  </g>
  <g>
    <path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" d="M 532.963,482.894 A 107.12,107.12 0 0 0 502.826,524"/>
    <polygon style="fill: #000000" points="538.838,478.276 533.479,488.089 532.679,482.556 527.773,479.877 "/>
    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="538.838,478.276 533.479,488.089 532.679,482.556 527.773,479.877 "/>
  </g>
  <g>
    <path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" d="M 669.509,433.113 A 74.3817,74.3817 0 0 0 621.324,477"/>
    <polygon style="fill: #000000" points="676.741,431.345 667.633,437.83 669.331,432.504 666.088,427.95 "/>
    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="676.741,431.345 667.633,437.83 669.331,432.504 666.088,427.95 "/>
  </g>
  <g>
    <path style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" d="M 621.324,477 A 52.6233,52.6233 0 0 0 667.44,523.741"/>
    <polygon style="fill: #000000" points="674.819,524.144 665.162,529.778 667.334,524.628 664.517,519.799 "/>
    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="674.819,524.144 665.162,529.778 667.334,524.628 664.517,519.799 "/>
  </g>
</svg>

path: chapters/1.10 - META/1 - WTCD/sectionDiagram.svg


code:
# 人名请求
目前而言，可穿戴科技的所有出场角色的人名都是读者的网名/真实姓名。这里列出了目前正在请求的人名。如果你愿意贡献自己的名字，请发送到讨论组或者私聊琳。（讨论组链接请到主页 -> 订阅/讨论组 -> Telegram 讨论组）

当然，如果你希望自己被写入小说但是不愿意用自己的真实网名，你也可以编造一个出来。而且，每个人可以以不同的名字多次出现噢。

**目前没有正在请求的人名**

# 历史请求
## 主线
- 需要被琳踩的男性 2 位，性格偏 M。（已找到 2/2）
- 需要某戴鸭舌帽的半反派男性 1 位，领导向。（已找到 1/1）
- 需要某理着粉红色双马尾的半反派女性 1 位，技术向。（已找到 1/1）

## 道具集
- 需要男用可穿戴科技的穿戴者 1 位。（已找到 1/1）
- 需要参加某冒险活动的女性 1 位。（已找到 1/1）

# 撤回
如果你事后后悔了，希望把自己的名字从小说中取消掉，请联系作者琳。她会尽快使用全文替换功能将你的名字全部替换掉。不过，因为本小说使用 git 进行版本管理，项目的历史中可能会依然存在你的名字。

**所有人都可以请求撤回，这包括在本文档创建之前就被写入小说的读者（哪怕你是完全未经同意就被写进去的）。**

path: chapters/1.10 - META/1 - 人名请求.md


code:
# 协助打标签
首先十分感谢你对协助打标签有兴趣。因为标签系统刚刚做好，之前的文章都没有标签。如果你愿意帮之前的文章打标签，请跟随以下指引：

1. 选择一篇你想要协助打标签的文章。通读全文，尽量将文章的所有剧情记在脑海中。
2. 打开[《可穿戴科技》标签工具](#/page/tagging-tool)。
3. 从上往下，一条一条标签确认，并标记所有文章拥有的标签。这一步请务必认真仔细。是的，每一个标签都要看，不要只是匆匆扫过去。如果你没有这个耐心，给文章打标签还是算了。标签工具会自动保存，因此如果一不小心关掉了，重新打开不会丢失当前进度。
4. 复制标签工具最底部的输出，然后将结果复制到本指引的评论区，记得附带上文章名和章节号。（你也可以把结果复制到 [Telegram 群](https://t.me/joinchat/Dt8_WlJnmEwYNbjzlnLyNA)并 @ 琳。）

帮助打标签后，你可能会被加到鸣谢列表。如果你不希望被加到鸣谢列表，请注明。

path: chapters/1.10 - META/1.1 - 协助打标签.md


code:
# 标签规格文档（TagsSpec.txt）
《可穿戴科技》允许作者给文章添加标签（Tag）。标签可以帮助查找或推荐文章。为了使标签更统一，《可穿戴科技》预先制定了一系列可能会用到的标签。所有标签，以及它们的定义都被写入了这个标签规格文档中。该文件位于项目根目录下。文件名是 `TagsSpec.txt`。

## 文件格式
该文件格式的设计目标是极度容易编写和理解。其解析器的源代码位于 `src/builder/tagsSpecParser.ts`。

下面描述一下该文件的详细格式：

### 每两个标签之间用一个空行隔开
例如：

```
遥控

舔舐

舔足
```

定义了三个标签：`遥控`，`舔舐`，和`舔足`。

## 在标签后使用中文括号（`（`，`）`）可添加用正斜杠（`/`）划分的标签变种
例如：

```
舔足（女/伪娘/男/扶她）

阴茎刺激（伪娘/男/扶她）

现代
```

定义了三个标签：

- 标签`舔足`有四个变种：`舔足（女）`，`舔足（伪娘）`，`舔足（男）`，和`舔足（扶她）`。
- 标签`阴茎刺激`有三个变种：`阴茎刺激（伪娘）`，`阴茎刺激（男）`，和`阴茎刺激（扶她）`。
- 标签`现代`则没有变种。

## 在标签最后添加一个或多个中文感叹号（`！`）可以付给该标签优先级
感叹号越多，优先级越高。

高优先级的关键词会在计算相似度的时候采用更高的权重。同时，当在某些字数有限制的场合（例如 Twitter 推送），高优先级的标签会优先展现。

例如：

```
舔舐

遥控！

舔足（女/伪娘/男/扶她）！！
```

定义了三个标签：`遥控`，`舔舐`，和`舔足`。其中舔舐为标准优先度，遥控为一级优先度，舔足为二级优先度。

### 不用空行隔开的会变成该标签的解释
例如：

```
自慰（女/伪娘/男/扶她）
自己刺激自己获得快感的行为。

自虐（女/伪娘/男/扶她）
自己刺激自己获得痛感/痛苦的行为。
也包括故意通过给自己施加大量快感以至于超过忍受极限而感到痛苦的行为。
```

定义了两个标签：`自慰`和`自虐`。其中`自慰`有一个解释项，`自慰`有两个解释项。

### 解释项中可以用方括号插入对其他标签的引用
例如：

```
舔舐（女/伪娘/男/扶她）
使用舌头舔身体/物体。

口交（女/伪娘/男/扶她）
使用舌头刺激对方（或者自己）的阴道/阴蒂/阴茎/肛门，其他部位不算。
如果有舔的动作，请选择【舔舐】。
```

其中`口交`的第二个解释项附带了一个`舔舐`的引用。

### 在解释项前写一个加号可以将该解释项内引用的标签设置为前置标签
例如：

```
舔舐（女/伪娘/男/扶她）
使用舌头舔身体/物体。

舔足（女/伪娘/男/扶她）
使用舌头舔脚（可以隔着丝袜）。
+需选择【舔舐】
```

其中`舔足`的前置条件就是`舔舐`。

需要注意的是，如果本标签和前置标签都有变种，则只会依赖对应的变种。

例如：

```
性器官刺激（女/伪娘/男/扶她）

阴蒂刺激（女/扶她）
+需选择【性器官刺激】
```

在这个情况下，如果选择了`阴蒂刺激（女）`，则只需要选择前置`性器官刺激`的对应变种`性器官刺激（女）`。正是因此，如果一个标签和其前置都有变种的话，那么其变种必须是其前置变种的子集。当然，如果一个标签和它的前置其中任意一个没有变种的话，则没有这个限制。

### 在一个解释项前添加一个等号可以将该解释项变为用中文逗号（`，`）分隔的同义词列表
例如：

```
含有伪娘
参与某一场色情活动的人中含有一个及以上的伪娘。
=伪娘，女装
```

其中为`含有伪娘`这个标签添加了两个同义词：`伪娘`和`女装`。同时，`伪娘，女装`这一句话将不再作为`含有伪娘`的解释项。

**请注意：同义词仅使用于搜索时将用户输入的标签转换为《可穿戴科技》标签。同义词并不是真的标签。**

path: chapters/1.10 - META/10 - 阅读器技术文档/1 - 标签规格文档.md


path: chapters/1.10 - META/10 - 阅读器技术文档/2 - 推荐算法.md


code:
# 撰稿须知及简易 Markdown 教程
> 如果你在 2021 年 5 月 16 日之前阅读过本须知，你可以转到这里查看[更新内容](./撰稿规则更新.html)。

撰稿是指读者编写文章并发布在《可穿戴科技》的行为。

在撰写你为《可穿戴科技》编写的稿件之前，请务必通读该页面所有内容（5 ~ 10 分钟）。

## 0. 《可穿戴科技》不是普通的文章发布平台
在开始之前，请明白一点：《可穿戴科技》不是普通的文章发布平台，也不是文章转载平台。**你写的文章不是发布在《可穿戴科技》，而是变成了《可穿戴科技》的一部分**。你可以把《可穿戴科技》想象成一个由多个作者共同编写的文集，撰稿就是加入其他作者共同编写《可穿戴科技》。

这意味着为《可穿戴科技》撰稿要求《可穿戴科技》必须是你的文章的**唯一**首发地。所有在其他地方发布必须是以转载的形式进行，并在转载时在标明作者是你的基础上，写上来源《可穿戴科技》。

需要澄清的是，尽管这么说，**你依然是你撰写的稿件的作者并持有其完整版权**。如果你想要事后转到其他地方作为首发，你只需要通知一下琳将你的文章从《可穿戴科技》移除即可。

与此同时，因为《可穿戴科技》是以 [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/) 协议发布的，你撰写的文章作为《可穿戴科技》的一部分，也将以这个协议发布。然而，由于你依然是你编写的文章的版权持有者，如果你之后把你的文章从《可穿戴科技》撤出了，你可以届时使用任何你想用的协议重新发布。

如果你选择发布在其他平台（比如你的博客），你可以选择联系琳并交换友情链接。

## 1. 文章内容要求
- 文章内容不需要与可穿戴科技有关，但必须含有显著的色情内容。
- 撰稿内容没有硬性字数限制，讲清楚就好。
- 所有参与色情活动的角色不得为儿童。
  - 如果指明了年龄，年龄必须为 18 岁及以上，但是你可以写 18 岁高中生的色情故事。
  - 这条规则是为了防止法律问题。
- 不能含有过分血腥猎奇内容。
  - 所有主线里面出现的玩法都可以出现。
  - 不允许的内容包括但不限于：固体排泄物食用，肢解，食人，恋尸等。
  - 如果对于某个玩法是否属于血腥猎奇有疑问，欢迎询问琳。
  - 这条规则是为了照顾大多数读者的口味。人的性癖是自由的，我不会批判你喜欢的内容，但是我要保证大多数读者不会被雷到。

## 2. 撰稿类型
撰稿一般情况下分成四大类：**普通撰稿**，**道具集撰稿**，**小作品撰稿**，及**微小说撰稿**。

- **普通撰稿**：本类目包含最标准的色情小说，即描述一个色情故事的小说。
  - 普通撰稿的文章将会被放置于[根目录内](#/menu/章节选择/所有章节)。
- **道具集撰稿**：本类目包含的内容通常是关于某个色情道具的描述。
  - 相较于普通撰稿，道具集撰稿不需要有完整的故事。此外题材不一定要是可穿戴的。
  - 小道具，大型器械，甚至是抽象概念都是可以的。
  - 为了体现道具集的非线性，你可以为你的作品选择一个正整数序号（请不要选一个超出 [JavaScript 限制](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)的）。如果你的作品叫做“强制灌食器”，你选择的序号是 50，请将文件名保存为“50 - 强制灌食器.md”。
  - 道具集撰稿的文章将会被放置于[文件夹“道具集”内](#/menu/章节选择/所有章节/道具集)。
- **小作品撰稿**：本类目包含的内容以生草文或创意文为主。
  - **该分类仅对之前撰稿过的作者开放**。
  - 此类型的撰稿只要与色情沾边即可。
  - 小作品撰稿的文章将会被放置于[文件夹“小作品”内](#/menu/章节选择/所有章节/小作品)。
- **微小说撰稿**：本类目包含篇幅特别短（1500 字以下）的色情小说。
  - 微小说撰稿的文章将会被放置于[文件夹“微小说集”内](#/menu/章节选择/所有章节/微小说集)。

此外，如果你预计你的文章字数很多，你可以选择分成多个章节。如果需要分章节，请私聊琳。

如果你要编写的内容不属于这三大类，请私聊琳，她会帮你想想看有没有啥别的地方可以放。

在正式开始撰稿之前，请先阅读一下这篇[色情小说编写基本排坑指引](./色情小说编写基本排坑指引.html)，里面列出了色情小说作者经常会犯的一些错误。

此外，虽然不强制，**但是强烈建议在开始撰稿前，尤其是第一次撰稿前，私聊通知琳**（在 Telegram 上面发条消息就好了，比如：“琳姐，我有个绝妙的点子，想撰写一篇文。我的点子大概是：xxx，你看成不？”）。琳会根据经验尽可能帮你避免一些其他的可能会踩的坑。

## 3. 简易 Markdown 教程
在《可穿戴科技》撰稿，你需要使用 [Markdown](https://zh.wikipedia.org/wiki/Markdown)。

如果你不知道 Markdown，Markdown 是一门非常简单的标记语言。因此，请花几分钟阅读这篇专门为《可穿戴科技》撰稿人编写的[简易 Markdown 教程](./简易-Markdown-教程.html)。如果有什么不懂的，欢迎私聊琳询问。

## 4. 基本排版规则
学会了 Markdown，这边还有几个比较重要的排版规则。其中很多规则都是从[中文文案排版指北](https://github.com/mzlogin/chinese-copywriting-guidelines)继承来的。这些规则都不复杂，但是遵循这些规则可以让你的文章看起来舒服许多。

### 中文英文之间需要空格
当你的小说中需要使用英文单词的时候，请确保其前后都有一个空格。

```
正确：于是，琳的放置 Play 开始了。
错误：于是，琳的放置Play开始了。
```

### 英文单位整体前后加空格，中文单位数字前后加空格
尽管在英文语境中，数字和英文单位之间通常是要加空格的（[来源](https://physics.nist.gov/cuu/Units/checklist.html)）。但是在中文语境中，数字和英文单位之间添加空格会看起来十分奇怪。因此，在这里，数字和英文单位视为一个整体：它们之间不加空格，而数字前、单位后都加空格。

```
正确：琳眼看着 200cc 的液体被缓缓注入了自己的体内。
错误：琳眼看着 200 cc 的液体被缓缓注入了自己的体内。
错误：琳眼看着200cc的液体被缓缓注入了自己的体内。
```

然而，当单位是中文的时候，不使用空格又变得很奇怪了。因此中文单位前则一律都使用空格，数字前也加空格，但是单位后不加空格。

```
正确：琳眼看着 200 毫升的液体被缓缓注入了自己的体内。
错误：琳眼看着 200毫升的液体被缓缓注入了自己的体内。
错误：琳眼看着200毫升 的液体被缓缓注入了自己的体内。
错误：琳眼看着 200毫升 的液体被缓缓注入了自己的体内。
```

### 全角标点前后一律不加空格
全角标点就是在中文输入法下打出的标点（`。`、`，`、`！`等）。这些标点因为本身已经看上去非常空旷了，它们前后无论发生什么都不需要添加空格。这一条覆盖前两条的指示。

```
正确：瞬间，琳捂着肚子瘫倒在了地上。
错误：瞬间， 琳捂着肚子瘫倒在了地上。
错误：瞬间 ，琳捂着肚子瘫倒在了地上 。
```

```
正确：实话实说，2000cc 已经是她的极限了。
错误：实话实说， 2000cc 已经是她的极限了。
正确：实话实说，2000 毫升已经是她的极限了。
错误：实话实说， 2000 毫升已经是她的极限了。
```

## 5. 注明作者
在提交前，请首先确保你的名字写在文章的标题前。假如你是李华，请这样写：

```markdown
作者：李华

# 文章标题
正文第一自然段...
```

## 6. 提交
将你的文章发布到《可穿戴科技》需要在 gitgud.io 发起一个 Merge Request。因此，你首先需要确保你已经登录了 gitgud.io。如果你没有 gitgud.io 账号，请到[这里](https://accounts.sapphire.moe/app/users/add)注册一个。（关于为什么要使用 gitgud.io，请[查看此公告](./公告/关于被-GitLab.com-封禁的公告.html)。）

这之后，根据你的撰稿类型，进入相应的章节文件夹：

- 普通撰稿进入[此文件夹](https://gitgud.io/RinTepis/wearable-technology/-/tree/master/chapters)。
- 道具集撰稿进入[此文件夹](https://gitgud.io/RinTepis/wearable-technology/-/tree/master/chapters/1.1%20-%20%E9%81%93%E5%85%B7%E9%9B%86)。
- 小作品撰稿进入[此文件夹](https://gitgud.io/RinTepis/wearable-technology/-/tree/master/chapters/1.2%20-%20%E5%B0%8F%E4%BD%9C%E5%93%81)。
- 微小说撰稿进入[此文件夹](https://gitgud.io/RinTepis/wearable-technology/-/tree/master/chapters/1.3%20-%20%E5%BE%AE%E5%B0%8F%E8%AF%B4%E9%9B%86)

如下图，点击上方加号，然后点击 `New file`。

![](./提交-1.png)

此时，网页会说“Forking in progress”。请耐心等候片刻。

Fork 结束后，你需要把你的文章复制进编辑器中。

**请注意，你的文件名必须是这个格式：编号、空格、短横（`-`）、空格、名字、`.md`！** 请务必再次注意，短横（`-`）前后有空格，文件结尾必须是 `.md`。如果稍有写错，这个文件将无法被《可穿戴科技》网页构建脚本正确读取。

- 如果你的撰稿类型是普通、小作品或者微小说，那么编号就是下一篇的编号（具体是多少琳会告诉你），名字就是你的小说的名字。举个例子：`2 - 新能源.md`。
- 如果你的撰稿类型是道具集，那么编号就是你的道具编号，名字就是你的道具名字。举个例子：`50 - 强制灌食器.md`。

![](./提交-2.png)

这之后，你需要开启一个合并请求，即请求琳将你编写的文章加入到《可穿戴科技》中。

![](./提交-3.png)

这样，一个 Merge Request 就会被创建。你现在只需要等待琳阅读你的杰作然后批准就好了。如果琳一直没有操作，你可以在群聊/私聊里面喊她一下。

path: chapters/1.10 - META/2 - 撰稿须知及简易 Markdown 教程.md


code:
# 隐藏
# 撰稿规则更新
# 2021.5.16
- 开放“微小说撰稿”。微小说撰稿接受篇幅特别短（1500 字以下）的色情小说。

## 2021.5.6
- 为了防止新作者误以为“支线”是“主线”的分支，现在起，取消“支线”的概念。之前所有的支线撰稿直接从支线文件夹移出至根目录，并与主线文件夹同级。
- 取消撰稿硬性字数限制。现在起，所有类型撰稿只要讲清楚了就可以。
- 开放“小作品撰稿”。**小作品撰稿仅对之前撰稿过的作者开放**。小作品撰稿以生草文或创意文为主。

path: chapters/1.10 - META/2.0.1 - 撰稿规则更新.md


code:
作者：琳 缇佩斯，0 级骨钉

# 色情小说编写基本排坑指引
本文收录了常见的，编写色情小说时容易踩的坑。即使在读了本文后 —— 尤其是第一次写色情小说时 —— 依然强烈建议在动笔前将你的点子发给琳看一下。

## 第一大坑：不够色（WTP001）
关于这点，希望大家能明白一点。色情小说和其他小说不一样。色情小说是为了满足人类的性欲的。在色情小说中，色情是核心。剧情、人物、风格都是为了色情服务的。这一点千万不能倒过来。

当然，这不是说剧情就不重要了。好的剧情能更出色地衬托你的色情内容。如果完全没有剧情，反而会让人觉得故事发生在真空里，丧失代入感。

当你在编写一篇短篇的时候（比如道具集），你应该从一个色情点出发 —— 比如想到了一个很棒的玩法 —— 然后围绕着这个色情点展开故事。同时要注意故事部分尽量不要过分复杂，以免喧宾夺主。

编写长篇也是一个道理。你应该先构思几个色情点，然后再想办法用故事把它们串起来。

相反地，**千万不要先编了一个正常的故事，再想办法往故事里面添加色情要素**。这样基本是行不通的。

## 第二大坑：文爱记录（WTP002）
这个坑是指把文爱记录当成色情小说发表的。这也包括几个人通过互动的方式共同一篇色情小说（A：我把你的手铐住了 B：我开始努力挣扎）。这是一个非常糟糕的主意。

造成这类文章很糟糕的原因有很多：

首先，文爱因为即时性，导致双方在决定玩法的时候考虑的时间很短，以至于想出来的玩法都非常平庸，或者都是一些烂大街的玩法。

其次，还是因为即时性，文爱时所使用的语言通常都严重缺乏描写，都是一些干巴巴的动作描写。心理描写之类的都会非常缺乏。

再则，文爱时因为对方的行为是另一个人控制的，你并不能预测接下来发生的事情，因此也不能对后续色情内容的发展做出有效规划，以至于不太可能勾勒出那种真正让人心头一震的色情故事。

文爱记录类型的色情小说的缺点还有很多别的。这里不列举了。总之，请避免编写此类文章。

## 第三大坑：自嗨（WTP003）
自嗨是指一篇文章的时候自己感觉很色，但是写出来读者看了效果不太好。

写小说，尤其是色情小说，一定要注重读者的感受。*Moby Dick* 出名不是因为作者一意孤行，而是因为后来人慢慢觉得这部小说有价值了。

目前看来，造成自嗨的主要原因是过分着重描写自己想写的内容，而忽略了组成色情故事的其他要素，以至于读者读起来一头雾水，完全无法带入。

比如有的文章中，两个人明明刚刚才碰到，没有什么互动或者一起的剧情也不多，但是莫名其妙地关系就变得相当好了。这就是典型的自嗨：在作者脑海中，两个人的关系已经变得很好了，但是文字上却完全没有表现出来。

## 第四大坑：万能性（WTP004）
万能性是在小说中使用了一些过分万能的道具/设定。

万能性会使精巧的设计失去意义。

万能性的例子有很多，比如一些过分灵活的魔法都具有很强的万能性。但是所有万能性中最常见的例子就是机械臂。

比如，当你有了机械臂以后，做什么事情都可以通过机械臂完成了 —— 原本可能是用活塞控制的按摩棒，或是嵌在鞋底的转轮，现在都可以用机械臂来做到了。

去除万能性的方法也很简单，那就是想办法用别的更局限的方法来解决同样的问题。比如需要一个上下活动的按摩棒，用机械臂当然可以完成，但是如果不用机械臂，而是用一个简单的活塞，岂不是也可以。或者再比如，需要将女主的四肢张开，用四条机械臂当然可以完成，但是如果不用机械臂，而是四个铐环，岂不是也可以。将文章中的机械臂都替换以后，你的文章就会变得更加有意思，而不是一味的依赖这个万能性道具/设定来做所有的事情。

当然，这条规则不是绝对的。万能性可以当作一个“逃生舱口（escape hatch）”。如果**实在想不出来**不用机械臂怎么解决问题，或者说需要解决的问题并不重要，那么适量地使用一些万能性也是可以的。

## 第五大坑：沉迷于设定（WTP005）
相信很多人都有过这种想法：设定一个或大或小的世界观或者小舞台，然后填充进去海量的细节和复杂的规则，并且有着一副写出一篇“传世之作”的雄心壮志。然而当你准备下笔，开始描绘这个故事的时候，却发现迟迟动不了笔，甚至连开启这个故事的想法都没有了。

久而久之，这个点子就被抛进了角落里，只有偶尔想起来时，才意识到自己曾经想过写这样一篇东西。

过度的设定往往会让人忽略情节的发展，毕竟一篇 h 文终究要有情节来推动。而繁复的设定与自由的情节畅想是冲突的，当你真正要写一篇故事时，那些设定大多都会变成累赘。或是在设定的限制下无法尽情地构思剧情。

所以如果想要写一篇完整的文，最大的建议就是先考虑情节和人物，再补齐设定。当你心中有了一个足够精彩的故事或是足够 h 的玩法，那些设定根本不是重点。

对于新手而言，另一个建议就是刚开始写文不要把世界观放的那么大，可以先以自己最喜欢的 h 要素构思一篇不太长的文，在大脑中想象你要写下的人物、故事、**结局**和穿插的 h 情节，然后再将它写下来。有了第一次的经验，之后的创作之路会通常许多。

## 第六大坑：过度随意的表述（WTP006）
过度随意的表述是指使用平时聊天的口吻来编写色情小说，包括但不限于：大量使用括号，网络用词（例如：233），滥用标点符号（例如：句子结束不用句号），玩梗过度，等。

当表述过度随意时，读者可能会觉得自己在和作者交流，而不是在探索作者创造出来的世界。如果你在写博客，这当然没问题。然而，如果是在写色情小说，这将会导致读者出戏，难以代入，从而降低色情程度。

path: chapters/1.10 - META/2.1 - 色情小说编写基本排坑指引.md


code:
# 隐藏
# 简易 Markdown 教程
Markdown 是一门非常简单的标记语言。其语法非常简单，因此你只需要花费几分钟就能学会。考虑到网上 Markdown 的简单教程不多，这里介绍一下 Markdown 的基本使用。

Markdown 说白了就是给一个 txt 文本文件加一点井号，星号，或是其他标点符号来告诉阅读器哪几行字是标题，哪几行字要加粗之类的。

举个例子，如果你有一个文本文件 `a.txt`：

```
陈志鹏爱吃玻璃。他真的非常非常喜欢吃玻璃。对于陈志鹏来说，吃玻璃是这世界上最美好的事情。他完全无法想象没有玻璃吃的生活。
```

然后你把这个文本文件的后缀改成 `.md`，使之变成 `a.md`，恭喜你，你已经创建了一个合法的 Markdown 文件。

然而，这个 Markdown 文件一点意思也没有，因为它被用户打开的时候，什么特殊样式都没有。为了给你的 Markdown 文档加点特色，接下来讲 4 个最基础的操作。

## 自然段
无论是正常写作还是在互联网上写作，你都需要把你的文章分割成多个自然段。如果你要新建一个自然段，请在两段话之间加一个空行。举个例子：

```markdown
陈志鹏爱吃玻璃。他真的非常非常喜欢吃玻璃。对于陈志鹏来说，吃玻璃是这世界上最美好的事情。他完全无法想象没有玻璃吃的生活。

直到有一天，玻璃涨价了。
```

这样，你就有两个自然段了。请注意一个空行的意思是按两下回车。只按一下回车或者按三下回车都是错误的。

同时，请务必注意，在规范的写作里是几乎没有纯换行的（换行，但是不进入下一个自然段）。~~因此，Markdown 不支持纯换行。~~*更正：Markdown 支持通过在行尾添加两个空格的方式添加纯换行，但是请避免使用这个功能。* 不过无论如何，请永远不要只使用一个回车。**比如下述例子就是错误的，它渲染出来的结果是两行文字会出现在同一行，完全没有换行。**

```markdown
陈志鹏爱吃玻璃。
上一句话是真的。
```

## 加粗
如果你想把这句话中的`陈志鹏`三个字加粗，因为这只是一个平凡的文本文件，所以你肯定不能像 Word 一样去点工具栏上的 **B**。在 Markdown，如果你要加粗一个词，你只需要在这个词两边加上两个星号，比如：

```markdown
**陈志鹏**爱吃玻璃。
```

这样，当读者在阅读你这篇文章的时候就会发现陈志鹏三个字变成粗体了。怎么样，是不是很简单？

## 标题
几乎每篇文章都有标题，甚至子标题。若要添加主标题，请依次输入井号、**空格**、标题内容。举个例子，如果你要添加一个内容为“陈志鹏的兴趣爱好”的标题，你只需要这么写：

```markdown
# 陈志鹏的兴趣爱好
陈志鹏爱吃玻璃。他真的非常非常喜欢吃玻璃。对于陈志鹏来说，吃玻璃是这世界上最美好的事情。他完全无法想象没有玻璃吃的生活。

直到有一天，玻璃涨价了。
```

需要注意的是
1. 井号后面有一个空格。
2. 标题和正文第一个自然段之间没有空行。

如果你要添加子标题（比主标题字小一点的标题，用来创建树状结构），你可以使用多个井号。井号数量越多，这个标题越小（最多六个）。举个例子：

```markdown
# 陈志鹏的兴趣爱好
## 第一章
陈志鹏爱吃玻璃。他真的非常非常喜欢吃玻璃。对于陈志鹏来说，吃玻璃是这世界上最美好的事情。他完全无法想象没有玻璃吃的生活。

## 第二章
直到有一天，玻璃涨价了。
```

需要注意的是，一级标题（`# 陈志鹏的兴趣爱好`）和二级标题（`## 第一章`）之间没有空行，而二级标题（`## 第二章`）之前有空行。简而言之就是：

1. 标题和标题之间没有空行。
2. 标题过渡到自然段没有空行。
3. 自然段过渡到标题有空行。
4. 自然段和自然段之间有空行。

或者说，只有在每个自然段之后都才有空行。

## 链接
有时候你可能会希望添加超链接。在 Markdown 里，超链接的格式长这样：`[链接的文字](链接网址)`。举个例子：

```markdown
点[这里](https://www.google.com)来打开谷歌。
```

这样，“这里”两个字就会变成可以点的超链接，点击后就会打开谷歌。

## 总结
Markdown 本身功能也不强大，但是除了这边介绍的还是有很多其他功能的。然而学会了这边列出的四个基本功能，编写色情小说应该是足够了。通常情况下，编写 Markdown 使用任何文本编辑器就绰绰有余了。如果你希望可视化，可以试一下以下几个网页版的 Markdown 编辑器：

- [Arya - 在线 Markdown 编辑器](https://markdown.lovejade.cn/)
- [StackEdit](https://stackedit.io/)
- [Dillinger](https://dillinger.io/)

如果想要学习其他的 Markdown 语法，你可以查看这篇[发布在掘金上的教程](https://juejin.im/post/5ad6e09e518825557b4d451c)。

path: chapters/1.10 - META/2.2 - 简易 Markdown 教程.md


code:
# 隐私政策
简而言之，《可穿戴科技》不会收集任何个人信息。

《可穿戴科技》的作者琳非常想知道她的小说有多少人读了，以满足她的虚荣心。与此同时，琳非常重视隐私保护，因此她决定不使用任何现成的数据统计服务（Google Analytics 等），而是自己编写了一个开源的，极度重视隐私保护的数据统计系统。

这个统计系统只会记录在何时哪个章节被访问了，而不会记录你的任何信息。

未来，《可穿戴科技》可能会记录你是通过哪个镜像站访问的，以便琳去感谢镜像站的提供者，但是目前还没有实装。

你可以在[这里](https://github.com/SCLeoX/wt_analytics)找到统计系统服务端的源代码。

path: chapters/1.10 - META/3 - 隐私政策.md


code:
作者：琳 缇佩斯（金牌得主），0 级骨钉（银牌得主），城岭 樱（铜牌得主）

# 第一届西塔杯评选 - 已结束
西塔杯是《可穿戴科技》计划于每年年底举行的年度最佳已完结文章评选活动。《可穿戴科技》内所有已完结的文章都会参与评选。

以下为前十评选结果：

| 名次 | 作品 | 平均分 | 作者 |
|------|------|--------|------|
| 1    | [长期贞操带实验第七次定期报告](./../长期贞操带实验第七次定期报告/第一幕.html) | 4.433 | [琳 缇佩斯](#/page/author/琳%20缇佩斯) |
| 2    | [长期贞操带实验定期报告（番外）](./../长期贞操带实验定期报告（番外）.html) | 4.215 | [0 级骨钉](#/page/author/0%20级骨钉) |
| 3    | [新能源：美妙的旅程](./../新能源：美妙的旅程.html) | 4.094 | [0 级骨钉](#/page/author/0%20级骨钉) |
| 4    | [城岭樱的游街仪式](./../城岭樱的游街仪式.html) | 4.092 | [城岭 樱](#/page/author/城岭%20樱) |
| 5    | [新能源](./../新能源.html) | 4.054 | [琳 缇佩斯](#/page/author/琳%20缇佩斯) |
| 6    | [可穿戴科技的其他版本 · Flexible (Episode 1)](./../道具集/可穿戴科技的其他版本-·-Flexible-(Episode-1).html) | 3.905 | [Darkside](#/page/author/Darkside) |
| 7    | [疼痛实验记录](./../疼痛实验记录/第一次调研报告.html) | 3.828 | [城岭 樱](#/page/author/城岭%20樱) |
| 8    | [可穿戴装置 γ 型介绍](./../道具集/可穿戴装置-γ-型介绍.html) | 3.814 | [伶 若萱](#/page/author/伶%20若萱) |
| 9    | [现代女仆管理系统](./../道具集/现代女仆管理系统.html) | 3.795 | [琳 缇佩斯](#/page/author/琳%20缇佩斯) |
| 10    | [梦野与千夏](./../梦野与千夏.html) | 3.772 | [0 级骨钉](#/page/author/0%20级骨钉) |

获胜作者（由排名最高的作品名次决定）：

- 金牌：琳 缇佩斯
- 银牌：0 级骨钉
- 铜牌：城岭 樱

## 奖品
因为琳获得了第一名，并且目前来看《Cyberpunk 2077》风评不佳，奖品改为赠送第二名及第三名等价 3A 游戏各一份。

~~投票分数最高的文章作者将获得 Steam 版《Cyberpunk 2077》一份。如果第一名作者无法联系到，则顺延至下一位。~~

# 以下为原评选描述
## 评选时间
参与评选的稿件将截止于北京时间 2020 年 12 月 3 日 23 时。所有于该时间点前完成的稿件都将参与评选。截稿时间过后将会开始时长 28 天（4 个星期）的投票期。投票将于北京时间 2020 年 12 月 31 日 23 时结束。投票结果将在投票期过后尽快公布。

## 参与条件
所有在截稿期（北京时间 2020 年 12 月 3 日 23 时）之前提交的**已完结的**文章都会自动参与。这意味着《可穿戴科技》主线将不会参与评选。如果你希望参加，但是还没有被《可穿戴科技》收录的文章，请参考[撰稿须知及简易 Markdown 教程](./撰稿须知及简易-Markdown-教程.html)。

path: chapters/1.10 - META/4 - 第一届西塔杯评选.md


code:
作者：琳 缇佩斯

# 琳的“足疗”馆
因生活所迫，琳开了一家“足疗”馆。这家足疗馆和别的足疗馆并不一样。在琳的“足疗”馆里，客人会付钱躺在床上，然后让服务人员 —— 琳 —— 穿着丝袜或是别的东西踩他们。怎么样，你一定也很想被琳踩吧？如果是这样的话，赶紧下载价目表并在感兴趣的项目前打勾吧~

[琳的“足疗”馆价目表](./琳的“足疗”馆价目表.pdf)

path: chapters/1.2 - 小作品/1 - 琳的“足疗馆”.md


code:
作者：琳 缇佩斯

# 戴着可穿戴科技参加 AP 考试
今天考 AP。

监考老师：Good afternoon. According to College Board regulations, the following electronic equipment is prohibited: phones, smartwatches or any other wearable technology...

我：*Raises hand

监考老师：Do you have a question?

我：Yes... Did you say wearable technology?

监考老师：I did say that. You do have to take off any of your wearable technology.

我：...Here? In front of everyone?

监考老师：Yes.

我：*Slowly takes off my skirt

...

我：I don't have the key... The chastity belt won't come off...

监考老师：...If that is the case, you can keep it...

我：Oh, thanks!

监考老师：...And you can pull your skirt back up...

...

考试中，

我：*moans

监考老师：Are you okay?

我：S... Sorry, there are v... vibrators inside me... I am okay...

我：*orgasms and moans loudly immediately

其他考生：*stare at me

监考老师：If you are okay, can you try to keep your voice down? Others are trying to focus here.

其他考生：We are fine. She can make whatever noise she wants.

...

考试结束，

College Board：*Disqualifies everyone's score

其他考生：Worth it.

path: chapters/1.2 - 小作品/2 - 戴着可穿戴科技参加 AP 考试.md


code:
作者：acted咕咕喵

# 《喵喵，新闻！》
2333年第一期修订1

## 导览
### 头条新闻
1. 尖端技术的背后，秋晴大学校内秘闻
2. 光明世界的暗部，秩维之外的暗流涌动
3. 独家特报，前秋晴大学**优秀**学子开除内幕

### 今日美食
红茶与蛋糕搭配的下午茶

## 头条新闻
### 尖端技术的背后，秋晴大学校内秘闻。
秋晴大学，是闻名于世界的全科 911 特别学习院校。以其著名的开放、自由和高学术成就而闻名于世界。其管理水平也是全世界各地争相追求的目标。井井有条的课程秩序，宿舍同学彬彬有礼的待人处事为人之态度。也侧面印证了这所高等学府的魅力和水准。

其宿舍管理，是在招生官网上首页大幅宣传的一颗璀璨明珠。明面上，学校以“方便管理”和“拉平社会差距”为理由，所有在校生均享受且必须在学校的免费公立宿舍中居住。其宽阔如总统套房的空间大小，独立厕所、卫浴；全屋全自动清洁系统。无处不彰显着人性关怀和资源丰富的能力所在。

然而，在本报唯一记者咕咕喵的探寻中，竟发现其远远不止这么简单。

通过部分技术手段，我们得以访问其封闭已久的高速内部网络。从智能爬虫的检索结果而言，这所高等学府，说其作为新时代的高等 RBQ 培养所完全不为过。全校大部分师生在入宿之时，即收到多种色情卡片及视频。从各种方面，各种 XP， 学校网站完全可以满足。无论是已经绒布球化久了的淫荡绒布球，或是连才自慰都没了解过的正直的人，都有专业的背后团队运作以将其引导至穿着名为“可穿戴科技”的装置。

而在“可穿戴科技”的背后，本报还发现了学校和这个名为“第七次定期”组织的具体联系。凡是其组织成员，均可轻松获取到该学校号称宇宙第一的防卫系统的全部通行权限。从自动保卫到秩维，对持有该组织证明文件的人员竟全部放行。由此可见，这所大学，绝不仅仅是为了培养一般的人才而设立。

本报记者同时隐秘的对部分“可穿戴科技”的受害者进行了一定的跟踪。成功获取到了一台可穿戴科技公司发来的设备。通过对设备的逆向工程研究发现，其宣称的“AI 人工智能控制”的背后是一个庞大的传销性质的组织，通过控制拉人进入可穿戴科技计划以减少设备对自己的惩罚。~~（本来就是自己想被惩罚却祈求饶恕，也真是有意思呢）~~

在取材的过程中，本报记者遭到了秩维的驱逐。但有很多疑似可穿戴科技工作人员和等级较高的受害者其对本报记者的猫娘形态疑似有极其深刻的兴趣。看来涩情果然是臭味相投，连猫娘都不放过的高等学府，实质一定是淫荡学府。

### “光明”世界的暗部，秩维之外的暗流涌动
在可穿戴科技的总部，阴暗的小巷子中。时长传出骇人听闻的惨叫声。同时，哪怕再高明的清洁手段，也难以掩盖血渍和亡灵的飘荡。

明面上如“学园都市”一样安全的先进社区，其背后究竟隐藏了多少奥秘，可能无人知晓。但请跟随本报记者咕咕喵的脚步，一起初探这惊人的黑幕吧！

在 SVR-1 街道上，看似为贫民区的地方很少有秩维的出现，哪怕出现了死人，亦是由死者的“家属”亲自将其送出贫民区，在棺椁中封存好，才交由秩维处理。然而，秩维似乎故意的忽略去通过实人认证或打开棺椁来确认尸体的状态。

本报记者和隔壁~~某文~~记者借来了高清透视摄像机后，成功捕捉到了其棺椁的内幕——其内部根本不是一具，而是多具不成样子的血浆和肢体组成的血腥肉块。

通过本报记者的深入探访，恶意借贷组织“不可穿戴科技”——一家不起眼但是负债率很高的网贷企业。通过向未成年少女乃至幼女发放高额高利贷款和诱导他们穿戴类似“可穿戴科技”的多种昂贵型号来控制奴隶。最终的目的无非两个：以彻底毁灭这个人并做成人棍供部分恶趣味的人玩耍。

让事态更加有意思的是，在河童重工的帮助下，本报记者成功追踪到了某可穿戴科技佩戴者女仆也出入于本街区，甚至亲手将两位恶意要债嫌疑犯（姑且称之为“嫌疑犯”，毕竟这俩实际上也是罪行累累）通过一白色钝物虐杀。

在整个制度体系下，淫荡和暴行充斥于整个社会。其背后的可穿戴科技和诸多大中小黑恶势力错综复杂的交汇，可能不仅是简单的同流合污……

### 独家特报，前秋晴大学**优秀**学子开除内幕
在秋晴大学前，一队人正驻足观看全新的处分通知：琳·缇佩斯，因在校内举止不雅，有伤风化，被处以开除学籍的处分通告。

从少女们红色的脸庞和震动着的裙摆可以看出，对于这所“淫荡”高校而言，单单对一个学生的驱逐令是不可能有成效的。但理事会和德育处如此坚决和速度的决定，也侧面印证了黑幕的存在。

在本报记者的求实精神追问下，一个匿名学生缓缓透露出：琳·缇佩斯，实际上完全不是在这个社区土生土长的，而是在小学毕业时举家搬迁来到这里的！这对于这个对全国严格保密的地区而言，可谓是一个重要信息。

学生和家长分离、高强度的隐私保护虽然在这个城市是一种常态，但作为外来人士的琳，不可能没有在外部留下的蛛丝马迹。在本报记者的综合探寻下，无论是琳的身世，还是和他一直在一起的晴雪惠，或是目前可能和她在一起的某秋姓博士（该博士的身份更加耐人寻味，但受制于顺着网线砍过来的秩维，调查只能到此为止了）

就当前本报记者探寻之力，成功破解了可穿戴科技的网络定位系统来看，该二人目前正关押于一个废弃仓库中。然而就定位而言，已经超过一个月没有移动了。因此也不排除他们身上的可穿戴科技被移除了——而这种权限只有可穿戴科技的高等级管理人士才有。结论显而易见：她们正被拘束在某个地方。

而联系近期发生在“某街区的恐怖袭击，对秩维人员使用了非致命性武器”的新闻来看，晴雪慧的休学、琳·缇佩斯的消失、石倚的居家……种种迹象都表明，这些看似孤立的事件，必然是紧密相连的。

如果让喵来指明的话，那么可能性也只有一个：陈志朋

虽然表面上是一个年轻的猥琐学生，但其不但在此次事件中完全没有受到任何处分，反而消掉了几门不及格科目的补考。再加上，从其通讯分析来看，和琳、秋、晴之间不断的短小、固定时间、高度加密的通讯。

如果说加害者是晴雪慧、秋博士的话，也看得出，其与琳之间亲密无间，交换遥控的关系。是不可能如此的。因此，本报记者大胆猜想：琳的入境，是陈志朋一手操办而成。而无论是晴还是秋，不过是陈志朋安插在琳·缇佩斯周围的耳目而已。只有这样，这一系列看似无关实则紧密链接，一步步将琳·缇佩斯推入 rbq 深渊的操作，才能得以实现！

## 今日美食
> 无论如何，我们还是成功采访到了石倚女仆来为我们讲解红茶和蛋糕的完美搭配。
> “那么，事不宜迟。请问如何搭配蛋糕和红茶才能做出美味的下午茶呢？”

“从茶叶的选取来看，我们要精选大吉岭等英国红茶。这种红茶茶味较轻，更能凸显出 xu，啊不，食品的风味。”

“每天冲泡红茶时，要注意水温不要太热，否则会出现沉底的现象。而且，会丧失很多茶叶中的营养物质。因此，最接近人体温度的 37 摄氏度是最适合冲泡红茶的。同理，调制用茶制成的蛋糕也是如此……”

> 无论怎么说，总觉得这种茶里面，有一种罪恶的味道呢。。。

path: chapters/1.2 - 小作品/3 - 《喵喵，新闻！》.md


code:
- 作者：NanayaSakuya
- 标签：含有女性，未来

# 魔镜
“七夜，看看我最新做出的东西。”咲夜拿出了一个直径有二十厘米的圆形物件，上面有着繁复的花纹。

“好家伙，你集成了多少东西啊。”七夜接过来看，发现是一面镜子。

“不清楚，反正我每天的补水量增加了一倍。我们部门有一个试着供能，短短十几秒爱液流了一大摊，预计能装满一个纸杯。”

“您这可得注意一下身体啊。”

“顺便，我把它叫做魔镜，你该懂怎么用了吧。”

“我来试试。”

七夜饶有兴致地对魔镜说道：“魔镜啊魔镜，谁是这个世界上最 M 的人？”

预料之外的，七夜看到的是一个绿色头发的双马尾少女。

“这是年轻时候的琳？”看着镜子里身穿 Ribbon 型中控，不停在受苦的绿色双马尾少女，咲夜有些迷惑。

“长得是蛮像，但琳不是银白色头发吗？怎么是绿色的，不过 M 度倒是依然这么高。还有，你加的术式为什么还会检索到过去的人啊？”

“前几天刚试出来的，要不要教你？顺带一提我还……”

“技术内容请去技术群。”镜子里的琳突然说道。

白光一闪，镜子恢复成原状，两人的嘴里则出现了口球。

“……？”

“……？”

path: chapters/1.2 - 小作品/4 - 魔镜.md


code:
- 作者：琳 缇佩斯
- 标签：贞操带（女），强制高潮（女），不可脱下的穿戴物（女），强制刺激贞操带（女），媚药（女），含有女性，未来

# Post Mortem（灾后报告）
在具体分析开始之前，我，琳 缇佩斯，可穿戴国际（Wearable International Inc.）前首席技术官，向所有直接或间接受到本次事件影响的人致以最诚挚的歉意。

在可穿戴国际，我们明白，我们的产品在许多时刻，都是违背穿戴者意图，而长时间强制穿在身体最私密的部位的。因此哪怕控制程序稍有纰漏，都会对穿戴者造成极大的影响。在这种情况下，维持产品及支援基础设施的稳定应该是我们最重要的责任。然而，我相信，您现在也已经知道，由于我和我的团队的疏忽，在三天前，也就是 X593 年 1 月 7 日，发生在新亚国的一系列意外事件，严重影响了该国 28 亿人的生活。

我们明白，在发生了这样的事情后，外界对我们的信任也肯定会有所下降。大家都想知道到底发生了什么。如果这时候再隐瞒灾难发生的具体原因，只会让大家对我们更加失望。同时，虽然作为处罚，我将被降职到产品实验人员。但是，在正式降职发生前，也就是在这过去三天，为了维持可穿戴国际的形象，我和我的团队对这次事件的起因经过进行了严密的分析。至此，在连续研究了近六十小时后，我可以充满信心的说，我们现在对于这次灾难发生的原因已经有了准确而具体的理解。这篇报告就将从技术角度详细解释到底是什么地方出了错。

## 第一部分：事件
新亚国于 X572 年，也就是距今 21 年前，修改了宪法并将穿戴可穿戴国际的产品列入所有公民的基本义务中，也因此成为了全世界第 7 个在法律层面采纳我们产品的国家。和其它这 6 个国家一样，在从采纳到这次事件发生前的 13 年内，可穿戴国际产品都运行十分稳定，在没有出一次故障的情况下完美达到了承诺的 99.99999% SLA。我们的产品运行如此之顺畅，以至于没有任何人能够预测到接下来会发生的事。

X593 年 1 月 6 日 18 时整：新亚国国会全票通过了《公民日常性奋度维持法第三修订案》。和往期修订案不同，这个修订案几乎提升了所有 28 亿公民日常需要维持的性奋度。该法案的生效时间是次日 14 时。因为需要修改性奋度，所以需要更改所有这 28 亿公民身上设备的设置。虽然说修订案刚刚通过，但是政府内部对于本此改动早有准备，因此用于改动这 28 亿公民的设置的程序此时已准备就绪，并经过大量专家检查及确认。因为每个公民的性奋度具体改动量取决于其职业，社会地位等众多因素，所以该程序会根据政府的数据依次为每一个公民身上的设备发送不同的更新包，每一个更新包内则包含了接收公民在新修订案下日常需要维持的性奋度。

虽然说这个并不能算是新亚国政府的责任，但是在可穿戴国际提供的白皮书中，我们明确指出了不建议通过大量发送更新包来实现区别对待个体，因为这样有可能会导致网络拥塞。这种因会导致网络拥塞而不被推荐的更新方式被我们称为“异构更新”（Heterogeneous Update）。

相反，我们建议的方式是向所有设备发送相同的更新包，再由更新包内的，能够区别对待穿戴者的代码来控制设备的具体行为。这种更新方被称为“同构更新”（Isomorphic Update）。我们建议使用同构更新是因为我们的网络对其有特别的优化 —— 实际的更新包只会在刚开始时发送一次，而我们的基站则会在每一个节点将该更新包克隆并分发。这样，网络使用将会显著降低。当然，这个只是建议，并不是要求。新亚国完全有选择忽视这个建议的权力。

而且事实上，新亚国在 X576 年，X579 年，X581 年，X584 年，以及 X587 年的全国范围更新都采用的是异构更新，也就是我们不推荐的方式。然而，这五次更新都非常顺利，没有出任何问题。新亚国在 X590 年尝试使用了我们推荐的同构更新，但是根据技术衔接人员的描述，同构更新“使用起来太麻烦，...\[异构更新]更加容易编写...\[也]更加容易审查。”因此，在这次 X593 年的更新中，新亚国决定换回之前顺利使用了五次的异构更新。

X593 年 1 月 7 日 13 时 59 分 58 秒：新亚国国会发言人站在国会楼面前当着媒体的面按下了更新按钮。与此同时更新程序开始运行。更新包以每秒数千万个的速度从国会楼地下的政府服务器中发向距此约 2 公里的可穿戴国际的首都基站。此时一切正常。

14 时 00 分 02 秒：通过当时的监控录像，可以看到现场有几个围观人员不自觉地捂住了自己的下体。这显然是更新包发挥作用了。

14 时 00 分 06 秒 474 毫秒：可穿戴国际位于新亚国首都的东部的 A11 基站发出警报，称收到了一条不符合通信协议但是有着首都基站签名的消息。

可穿戴国际的基站之间传递消息时，都会附带消息发送者的 ASDCE 数字签名。该签名从数学上保证了消息的完整性和发送者的身份（如果在通讯时因为设备故障或他人拦截导致被修改则会导致签名错误），所以可以确认这条有问题的消息确实是从首都基站发出的。当基站的控制程序正常运行时，一条格式错误的消息是绝对不可能被签名并发送的。因此我们在编写基站的控制程序时，设置了当一个基站收到上游发来合法但不符合协议的消息时，将断定该上游控制程序出现故障或是正在被尝试入侵（例如攻击者虽然发现了漏洞，但是未能完整控制该基站），因此将该上游基站加入本地黑名单中（拒收接下来的消息直到人工进行网络重初始化）。与此同时，该接受基站会将收到的消息原封不动地广播给其它基站。其它基站收到后，因为消息内依然含有该上游基站的签名，这条消息自身将作为充足的证据，使得其它基站也做出相同的判断，并将该上游基站加入各自的本地黑名单中。

因此，A11 基站在收到了这条来自首都的消息并确认格式错误后，自动将首都基站加入了黑名单并且向整个可穿戴国际的网络广播了这条消息。

14 时 00 分 06 秒 504 毫秒：与首都基站直接相连的 37 个基站都因收到了 A11 的广播而将首都基站加入了黑名单。这里再重申一次，这不是因为它们都无条件相信 A11，而是因为 A11 将首都基站的这条格式错误的消息作为证据广播了出去。因为签名是首都基站提供的，这个足以成为断定首都基站故障的证据。

14 时 00 分 06 秒 700 毫秒左右：位于新亚国首都的可穿戴国际工作人员收到了首都基站故障的消息。

14 时 00 分 07 秒 042 毫秒：新亚国全境“警惕者”系统证书续期失败。

为了防止不法分子尝试通过破坏基站或者使用信号干扰设备阻止她们穿戴的产品与可穿戴国际网络建立连接而达成脱离控制的目的，可穿戴国际的所有设备使用一套称为“警惕者”（Vigilantacean, /ˈvɪʤələnteɪʃən/）的负反馈控制系统。与正反馈系统不同，“警戒者”是靠信号的停止而触发的。或者用设计者刘博士的话来说就是，“警惕着”系统好比把一个神经质的大叔放在一个摇篮里，然后不停地告诉他“一切都很好，一切都很好”，只要有人一直在跟他说“一切都很好”，什么事情都不会发生，但是一旦这个信号停了，这个大叔就会变得非常警惕，并采取行动。（至于为什么要放在摇篮里... 我也不知道，但是她当时例子就是这么举的。）

对我们来说，这意味着，可穿戴国际的所有的产品都是长期和可穿戴国际网络保持连接的（Always-Online Device Reliability Maintainer，或 Always-Online DRM）。每个产品内部都有一个 3 分钟的倒计时，而距离最近的基站每分钟会发送一条消息重置这个计时。与此同时，为了防止不法分子占领基站，这条消息的需要基站使用一个特殊的有效期只有 61 分钟的证书进行签字，而每个基站则需要在每个整点向每个国家的顶级基站申请续期。有效期是 61 分钟是为了多出一分钟来应对极端网络状况造成的延迟。对于新亚国来说，这个顶级基站就是首都基站。当这个倒计时归零时，产品则会判定穿戴者正在试图回避与可穿戴网络建立连接，并因此启动制裁模式。

如果您好奇为什么原定在整点进行的证书续期在整点过了 7 秒才进行，那是因为新亚国是世界少数几个取消了闰秒的国家，而可穿戴国际的系统依然会计算闰秒。（注：闰秒是为了和平太阳时保持一致而在部分年份年底在时钟中增加一秒的调整。）也就是说，对于可穿戴国际的或者世界上大多数的其它国家而言，新亚国的 14 时 00 分 07 秒 才是 14 时整。这也就是为什么在过了 7 秒之后才进行证书续期。

而证书续期失败的原因自然是首都基站被所有与之直接相连的基站加入了黑名单，因此证书续期相关的消息完全发不出去。

当基站的证书续期失败后，各个基站中保存的老的证书将只剩下 1 分钟的有效期。这之后，所有产品的“警惕者”系统中的倒计时将无法被重置。这也意味着，如果不能及时给各个基站的证书续期，所有产品将在 3 ~ 4 分钟内启动制裁模式。

14 时 00 分 13 秒 924 毫秒：一位位于 A19 的可穿戴国际工作人员拨通了一级紧急会议。本着公开、透明的原则，这里将公布该紧急会议的完整通话记录。（为了保证匿名性，将用工作位置代替人名。）

A19：喂？

D323：（连线）喂，我 D323...

C199：（连线）喂，怎么...

C5：（连线）喂，我...

（人数 5 及人以上时会议默认禁用麦克风）

A19：首都基站在吗？你们炸了！

首都基站：（打开麦克风）啊，我也不知道，刚刚突然说下游拒收了。

A19：（打开麦克风）不是，你们都一切正常吗？

首都基站：等下啊...

A7：（打开麦克风）我 A7，这边 dashboard 上说你们基站出问题了，然后就...

C199：（打开麦克风）我们也...

A19：你们别说！首都基站，你们什么情况？

首都基站：我不知道，我这边就说下游拒收，其它一个错误也没有，你们什么错误信息？

A19：我们这边说“警告：收到来自‘首都基站’的非法信息。该基站可能受到攻击，已经自动屏蔽。错误码：0x0000A218”。

首都基站：A218 是什么错误？

A19：不知道，没说，它就一个错误码。你们那边正常吗。

首都基站：我们这边... 没任何问题啊...

A19：那这个什么东西出问题了...

首都基站：会不会和这次这个更新有关？网络拥塞啥的。

（沉默）

A19：没道理啊... 就算拥塞也不会...

A35：（打开麦克风）我查到了，知识库里面说 A218 是... 等下啊... 它在加载... ok，A218 的意思是...‘收到来自其它基站的非法信息’...然后没了。

A19：那这个什么用都没有。就重复了一遍。

B7：（打开麦克风）等下，警惕者续期失败了！

首都基站：诶？

## 第二部分：回溯
在继续介绍之后发生的事情之前，我们不妨先退一步，来回答这个缠绕在各位读者以及当天所有基站工作人员心头的问题：到底发生了什么？为什么首都基站会发出一条格式错误的消息？然而，这个看似简单的问题的答案非常复杂，因此，若要回答这个问题，我们就要从 60 年前，也就是 X533 年说起。

X533 年的社会和如今的社会是完全不同的。虽然当时“全人类美少女化计划”已经完成有 33 年了，但是全社会的性开放程度和现在相比还是差很多。根据那时的文献记载，大约仅有一半的人承认在自慰时使用按摩棒、跳蛋或者其它性玩具。而承认曾至少一次穿戴着这些按摩设备进入公共场合——这一如今看来属于基本社会准则的行为——的人更是只有不到百分之十。

可穿戴国际创立于 X531 年，这时候已经存在有两年了。当时，可穿戴国际主要以出售遥控震动内裤为主。不过，受限于电磁波强度二次方衰竭，遥控的有效距离也就卡在了数公里。为了突破遥控的距离限制，可穿戴国际的创始人做出了一个大胆的决定——她要在世界各地造满基站。当需要跨越极远的距离进行遥控时，遥控信号会先发送给最近的基站，然后再通过基站与基站之间的有线网络传播到离目的地最近的基站，最后再通过无限信号传递给接受设备。

技术上来说，当时互联网已经有雏形了，但是依然局限于高等院校内。因此，若要创建这个基站网络，可穿戴国际不得不建立自己的通讯网络系统。当然，说是要建立自己的网络，当时距离网络正式上线还有十多年，而真正达到覆盖星球的每一个角落则是到现在也没完全实现的目标。不过，支持这一基于基站的通讯网络系统的软件代码，就是从 X533 年 11 月开始编写的。

要编写任何软件系统，第一个要做的就是选择编程语言。X533 年已经有许多可行的高级编程语言了，包括 FORTNITE 在内的那些一听就很古老语言已经存在有 20 多年了。可穿戴国际当时的技术人员们最终选择了使用 Ɔ 语言。

虽然说按照今天的标准，Ɔ 语言已经算是比较底层的语言了，但是在当时看来，Ɔ 语言还算是相对高级的。而且，当时已经有很多现成的可以使用的调试工具了。然而，因为可穿戴国际的软件需要运行在有着大量自研设备的基站内，当硬件有可能出现问题时，包括 GUN 计划的 gbd 在内的许多工具都不能有效工作。因此，技术人员们不得不实现了一套自己的备用调试工具——SB（Sudden Backup）。

SB 是一套基于硬件的程序状态转储（dump）系统。简而言之就是，所有带有并激活了调试模式的可穿戴国际硬件上都会有额外的电路监听着内存中的某一些特定的地址。当运行的程序往这些地址写入预先设定好的指令后，SB 会暂停 CPU 的时钟，然后将内存中的所有数据全部转存到另一个调试用的存储芯片中。换言之，SB 就是提供了一个程序可以使用的产生内存快照的机制。在需要调试的场合，这个内存快照将会非常有用。

这套系统完成后，一开始使用起来非常顺利。然而，研究人员很快发现了一个问题：在不开编译器优化的时候，SB 完全没有问题，但是，因为一些原因，一旦开了编译器优化，用 SB 转存出来的数据有时候就有问题。

比如，在下述 Ɔ 语言代码中：

```
// SB_CONTROL 指向 SB 监听的内存地址。
const SB_CONTROL = 0x1234 as Pointer<byte>;

function main(): int {
  // 这个是我们希望能够调试的值。
  let x = 100;

  // 0x03 的意思是 dump 所有内存。
  // 因此，运行到这句话后，SB 会把当前内存内的数据转存出来，以供调试。
  deref(SB_CONTROL) = 0x03;

  // 修改 x
  x = 200;

  // 这里用一下 x 的地址，以确保 x 在内存里，防止整个被优化掉。
  console.printf("Address of x is %p\n", ref(x));
  return 0;
}
```

这段代码在不开编译器优化的时候运行完全没有问题——在 dump 出来的数据中可以找到我们所期望的 100。

然而，当使用了 `gɔɔ -O3` 编译后，运行，在 dump 出来的数据中的 100 就消失了。

当然，这点问题肯定难不倒研究人员。而且，我相信各位稍微有点熟悉编译器的读者也能猜到发生了什么。研究人员经过检查生成的二进制，发现因为编译器注意到底下还有一个 `x = 200`，所以优化的时候把整个 `x = 100` 的赋值全部被优化掉了——因为无论是否进行 `x = 100`，其最后值都变成了 `x = 200`。

这时候，各位懂 Ɔ 语言的读者可能会提出应该把 `SB_CONTROL` 的类型变成 `Pointer<vodka byte>`。将一个类型标记成 `vodka` 相当于是告诉编译器，这个值是烈性的，因此对这些值的读写不能被合并或者乱序。可穿戴国际的研究人员当时也是这么做的。然而，这并不能解决问题。原因在于编译器只保证对于标记了 `vodka` 的变量的读写之间的顺序不会改变，而并不会限制未被标记 `vodka` 的变量的读写与标记了 vodka 的变量的读写交换顺序。因此，即使 `SB_CONTROL` 指向的内存位置被标记了 `vodka`，`let x = 100;` 依然可以与 `deref(SB_CONTROL) = 0x03;` 交换顺序，并最终与 `x = 200;` 合并。如果要彻底防止这种顺序变更，唯一的办法就是把 `x` 的值和 `SB_CONTROL` 指向的值都标记为 vodka：

```
// 将指向的值的类型使用标记了 vodka 的 byte
const SB_CONTROL = 0x1234 as Pointer<vodka byte>;

function main(): int {
  // 将这个 100 变为标记了 vodka 的 int
  // 也可以写作 let x: vodka int = 100 as auto;
  let x = 100 as vodka int;

  deref(SB_CONTROL) = 0x03;
  x = 200;
  console.printf("Address of x is %p\n", ref(x));
  return 0;
}
```

然而，这样虽然可以保证 dump 出来的数据是正确的，但是这也意味着所有的变量都要被标记为 `vodka`。这显然是不现实的。因此，为了解决这个问题，可穿戴国际的技术人员于 X534 年 6 月，Spoon（技术用语，可以理解为创建了分支）了当时使用的 Ɔ 语言编译器 gɔɔ，并对其做出了修改，而创建了一个全新的修饰符——`stupid vodka`。

`stupid vodka` 与 `vodka` 相比，多出了一个重要的保证——对被标记为了 `stupid vodka` 的内存位置的写入或读取，不会与任何其它内存位置的读写交换位置。同时，其它内存的读写在交换位置时，将无法跨过标记了 `stupid vodka` 的变量的读写。

因此，如果有下述代码：

```
// 可以看到这里用 stupid vodka 代替了原先的 vodka
const SB_CONTROL = 0x1234 as Pointer<stupid vodka byte>;

function main(): int {
  let x = 100 as vodka int;
  x = 150;
  deref(SB_CONTROL) = 0x03;
  x = 200;
  console.printf("Address of x is %p\n", ref(x));
  return 0;
}
```

那么，在优化时，`x = 100` 可以与 `x = 150` 合并，但无法与 `x = 200` 合并，因为若要与 `x = 200` 合并，则需要跨过 `deref(SB_CONTROL) = 0x03`。而因为 `deref(SB_CONTROL)` 的类型有 `stupid vodka` 修饰符，它将无法被跨过。

这里需要特别留意的一点是，当时的技术人员选择了关键词 `stupid` 作为这个新的组合修饰符的前缀。当然，现在回过头来看，选择 `stupid` 这个关键词是挺 stupid 的，毕竟这个词语本身完全没有解释其作用。不过，根据分析，当时使用 `stupid` 的主要原因应该是 `stupid` 已经是 Ɔ 语言的关键词之一了。此外，Ɔ 语言之内对于 `stupid` 的使用也是非常混乱——在不同地方使用其效果是完全不一致的。例如，如果 `stupid` 加在了一个全局变量上，那么这个全局变量就会变得从其它文件不可见。然而，如果将 `stupid` 加在一个局部变量上，这个局部变量就会在多次调用间保留存储的值。总而言之，我们认为当初使用了 `stupid` 的原因就是既然它已经这么混乱了，不如破罐子破摔，干脆再给它加个完全无关的新的语义。

`stupid vodka` 引入后，技术人员便如同上述展示的一般，将 `SB_CONTROL` 指向的类型标记为了 `stupid vodka`。经过这番修改，SB 总算是能顺利工作了。但是她们不知道的是，这个看似微小的改动煽动了历史的翅膀，并将透过一系列精密的技术巧合，在 60 年后引发一场灾难的风暴。

除了 `stupid vodka` 以外，可穿戴国际的技术人员们之后也陆陆续续的对她们的 Spoon 做出了很多功能性改动，包括增加了命名空间的支持等。不过，由于她们的改动数量越来越多，从上游——也就是 gɔɔ 的原始代码仓库——接受并合并更新变得越来越困难。

十年后，也就是 X544 年，像 `stupid vodka` 这种可穿戴国际特有的 Ɔ 语言魔改已经累积了有相当多了。然而，由于当时的内部文档极度紊乱，我们也说不清这些改动到底有多少，可见当时已经带来的额外维护成本有多大。不过，这维护成本问题并不能阻止此时此刻的这些技术人员们，因为她们正昼夜努力工作着，为了明年年初进行的网络正式上线做着最后的准备。

X545 年一月，可穿戴国际的网络上线了。虽然，可穿戴国际仅仅取得了在所在省份建立基站的权限，但是这依然有着极强的历史意义。从那一天开始，人们终于可以坐在家中远程遥控她们爱人穿着的震动内裤了——不过更确切地说应该是带有按摩功能的贞操带，因为可穿戴国际在两年前已经开始出售智能贞操带了。

网络上线很成功。但是这也意味着，之前欠下的技术债终于要还了。不幸的是，技术人员们其实也早就知道，gɔɔ 根本就不适合像她们这样修改。她们的改动积小成多已经足以变成一门新的语言了。然而，如果真的要实现一份 Ɔ 语言的超集的话，她们应该选择一个更模块化，更容易拓展的编译器。幸运的是，当时刚好就有这么一个项目诞生，那就是 IIVM 编译器基础设施项目。是的，IIVM 就是那个图标是个女仆的项目。是的，IIVM 就是那个为很多现代语言（例如 Iron(III) Oxide，Fast，Objection!-Ɔ，Romia 等）提供编译器后端的项目。是的，那时候 IIVM 已经发布了第一个版本了。

由于这次是有计划的，技术人员们决定为他们的 Ɔ 语言超集取一个新的名字。不过，由于当时已经有语言叫做 Ɔ++ 和 Ɔ# 了，她们最后决定，不如再多加几个加号，将数量提升到 8 个，并最终得到了新的名字：Ɔ##（读作 /ˈɔ:ʤɪŋʤɪŋ/）。

迁移到新的编译器意味着之前所做出的改动都需要更着迁移。可想而知这个工程量有多大。

可穿戴国际的技术人员们断断续续总共花了四年才总算把所有的改动迁移了过去。

但是，因为 IIVM 的相对模块化设计，要实现一门新的语言只需要实现一个“前端”。这个“前端”只需要把代码转换成 IIVM 指定的一个中间形式即可。这个中间形式就被称为 IR。IIVM 的剩下部分就能利用这个 IR 来完成优化和生成机械码等编译器需要做的剩余步骤。

而我们之前所说的 `stupid vodka` 修饰符就被翻译成了 IIVM 的 IR——对所有 `stupid vodka` 的值的读写指令都会被附上修饰：`vodka + atomic(ordering: .sequentiallyConsistent, syncScope: .singleThread)`。这里我们可以看到，她们把 `stupid vodka` 变成了 `vodka` 和 `atomic` 的组合。对于 IIVM 不熟的读者可能会对为什么这做感到困惑。不过不要担心，接下来我就会简单解释为什么这么做是可以的。

首先，我们需要搞清楚 `vodka` 和 `atomic` 的区别。这两者虽然感觉上很相似，但是其实做的事情相距甚远。正如之前所说，`vodka` 的作用是将一个值标记为烈性的，因此编译器将不能合并或跳过对这些值的读写。它们通常被用于读写会产生副作用的场合，例如内存地图出入中。然而，`atomic` 的本质是为了保证操作的原子性。而随着多核心 CPU 的出现，`atomic` 也用于保证对一个值的读写的顺序正确，并且不会因为运行时乱序或是缓存而读取到错误的状态。换言之，`vodka` 和 `atomic` 只是感觉像，本质没有直接联系。被标记了 `vodka` 而没有 `atomic` 的变量在多线程的场合照样会有可能读取到错误的状态。而被标记了 `atomic` 却没有 `vodka` 的变量在同一个线程连续两次写入依然有可能被编译器优化成一次写入。

那么，这里为什么要把 `stupid vodka` 变成 `vodka` 和 `atomic` 呢？这里其实是取了一个巧。首先，我们要知道，`vodka` 在 IIVM 的 IR 里的语义和在 Ɔ 语言中的语义是一样的，都是防止了赋值或读取的指令数量和之间的相对顺序发生变更。因此，为了达到 `stupid vodka` 的效果，`atomic` 其实起到的作用是防止被标记的值的读写与其他非 `vodka` 的读写交换顺序，也就是实现了 `stupid vodka` 中的 `stupid` 部分。接下来来解释具体是怎么实现的。在实现原子性的时候，为了能够确保读写顺序正确，编译器同时会“一定程度”上禁止其他读写与标记了 `atomic` 的变量的读写交换位置。这个“一定程度”取决于的就是 `atomic` 的 `ordering` 选项。在这里的 `ordering` 使用 `.sequentiallyConsistent` 是因为 `.sequentiallyConsistent` 是最强的顺序保障，也就是杜绝了所有的读写从被标记了该类型的 `atomic` 的变量的读写之上交换到之下或者之下交换到之上。这也就意味着，它可以保证在对标记了该类型的 `atomic` 的变量的读写发生时，在此之前的对其他内存的读写已经可见，而在此之后的读写不可见。（这里的“之前”和“之后”在多线程的场合的定义有些复杂，这里限于篇幅，不展开了。）

细心的读者可能会发现，除了 `ordering: .sequentiallyConsistent` 外，这个 `atomic` 指令还指定了 `syncScope: .singleThread`。那么这是为什么呢？要回答这个问题，我们需要明白，因为优化产生的顺序变更其实分成两大类。第一类是编译时产生的顺序变化。我们之前的 `x = 100` 和 `x = 200` 合并就是这一类顺序变更。这一类的特点就是编译出来的二进制程序中的顺序就已经和源代码中的顺序不一致了。第二类则是运行时产生的顺序变化。现代 CPU 在运行程序时，在内部，为了达到更高的运行效率，在运行时也会在不变更运行结果的情况下改变指令的运行顺序（比如一个更换后的顺序可以更好地利用缓存）。然而，这个“不变更运行结果”其实是有余量的。这里的不变更运行结果仅仅是针对同一个线程而言的。也就是说，如果有一段代码：

```
a = 100;
b = 200;
```

即使没有发生编译时顺序变更，在实际运行时，CPU 完全可以先运行 `b = 200`，然后运行 `a = 100`，因为无论以什么顺序运行，最终结果都是 `a` 变成了 `100`，而 `b` 变成了 `200`。但是，在多个线程的场合，这个所谓的“不变更运行结果”就不一定了。如果其他线程也在读取 `a` 和 `b` 的值，并且 CPU 因为一些原因决定先运行 `b = 200`，那么其他线程完全就有可能可以观测到 `b` 变成了 `200` 而 `a` 还没有变成 `100`。因此，在有多个线程时，确保这类运行时的顺序变更不会出现问题也变成了 `atomic` 的责任之一。一旦使用了这些 `atomic`，那么编译器就会根据 `ordering` 选项和具体的 CPU 架构生成需要的内存围栏指令。这些内存围栏指令将会在运行时告诉 CPU 不能变更顺序。

不过，有一点需要注意，这些运行时的围栏指令只有在多线程的时候才会需要。在单线程的时候，唯一能观测到的顺序错误是由编译器改变顺序，然后被中断回调观测到的。因此，单线程的场合，如果要防止错误的状态被在同一个线程运行的中断观测到，需要做的只是防止编译器改变顺序，而不需要插入运行时的内存围栏指令。这也就是 `syncScope: .singleThread` 的作用——它只会防止编译时发生的顺序变更，而不会插入有运行时消耗的内存围栏指令。碰巧，这正是 `stupid vodka` 所需要的——仅仅是防止编译时指令顺序发生变更，而不需要担心其他线程观测到的值。

好了，至此，为什么对 `stupid vodka` 变量的读写被翻译成 `vodka + atomic(ordering: .sequentiallyConsistent, syncScope: .singleThread)` 就解释完了。当然，因为其他的特有语法还有很多，这个持续了四年的迁移到 IIVM 的计划绝大多数时间是用在迁移别的语法上。这个 `stupid vodka` 估计最多就花了几天时间。不过，由于这个 `stupid vodka` 修饰符与我们这次灾难的成因密切相关，我们只介绍了它。

然而，其实在这时候，`stupid vodka` 最初被创造出来的目的——SB，已经用得非常少了。首先，随着多线程的普及和 CPU 的复杂化，基于 SB 的内存转储已经越来越不稳定。从记录上来看，X541 年开始，可穿戴国际就已经不再生产专用的测试用机器了，而是直接使用和生产环境用的（也就是在正式的基站里面用的）一样的机器了。而这些生产环境用的机器自然就不会带有 SB 的支持了。

编译器迁移完后，时间又过了七年。

到了 X556 年的时候，可穿戴国际的基站已经布满了整个国家了。可穿戴国际生产的智能贞操带也得到了极大的推广。尤其是因为刚刚进入社会主流的 30 后们对于这种可上锁的穿戴式快感制造装置特别感兴趣。

随着使用人数的指数增长，可穿戴国际的各个基站中需要同时处理的信息量也越来越大，网络拥塞渐渐变成了一个大问题。要解决这个问题，有两条比较明确的路：要么砸钱购买更好的硬件，要么优化软件。

然而，因为基站数量巨大，若要更替这些设备将要耗费大量的财力。因此，管理层的重心自然就落在了优化软件之上。

“优化”二字说着容易，做起来难。在网络上线的十一年来，可穿戴国际的技术人员们一直在陆陆续续地优化着软件系统。那些简单的优化机会早已实现了。若要继续优化软件，只能走那些相对难走的路了。然而，这个时候，可穿戴国际的大多数技术人员都已经开始穿戴公司生产的智能贞操带了。对于她们来说，只有两个选择：要么没日没夜地忍受高潮边缘控制，要么服从管理层的要求，顶着困难继续优化。

而其中一条比较难走的路，就是无锁化多线程（Lock-free Multithreading）。可穿戴国际的基站普遍使用着 Toothpastel 公司生产的 x68 架构 CPU。这些 CPU 通常都有很多核心。为了能利用这些核心，可穿戴国际的软件系统这时候早就已经支持多线程了。然而，为了编写方便，减少错误，当时的多线程之间的同步大多是使用互斥锁（Mutex）完成的。尽管从今天的角度来看，互斥锁依然算相当底层的多线程同步机制了，然而当时而言，互斥锁还算是属于比较高层的功能。

然而，使用互斥锁本身虽然没有严重问题，但是其效率并不是特别高，尤其是当有多个线程同时想获得一个锁的时候，甚至会需要和操作系统内核协调，非常低效。同时，可穿戴国际的基站虽然不属于严格意义上的实时操作系统，但是依然对及时性有一定的要求。然而，使用有锁的解决方案意味着极度不巧的线程调度可能会导致某个线程被挂很长时间。为了解决这些问题，将一些最热的代码无锁化是一个非常自然的选择。

如果要使用无锁化多线程，一个非常重要的基本部件就是 atomic types，原子类型。换言之，就是需要能够在多个线程同时读写不会出问题的变量。如果您觉得这个很耳熟，您没有错，这里的 atomic 就是和之前实现 `stupid vodka` 时用的 atomic 是同一个语义。

虽然最新的 Ɔ 语言已经有支持 atomics 了。然而，因为可穿戴国际内部使用的 Ɔ## 已经和 Ɔ 语言分道扬镳了，因此若要支持原子类型，必须自己实现。不过，幸运的是，IIVM 早就已经支持 atomic 了。对于 Ɔ## 很快也加入了原子类型的支持，并且直接翻译到了 IIVM 的 atomic。

例如，给出如下 Ɔ## 代码：

```
let flag = 100 as Atomic<int>;

// .set 是设置 atomic 变量的方法。
// 第一个参数（100）是需要设置的值。
// 第二个参数（MemoryOrder.RELEASE）是这次赋值所使用的 ordering 类型。
// 如果省略，就是默认的最严格的 MemoryOrder.SEQUENTIALLY_CONSISTENT。
flag.set(200, MemoryOrder.RELEASE);
```

上述代码没有任何问题。经过可穿戴国际的 Ɔ## 前端编译后，可以从生成的 IR 中观测到，其中 `flag.set` 那一行正确地为这次赋值操作加上了 `atomic(ordering: release)` 这个修饰。而编译出来的二进制也和技术人员们期待的完全一致。

然而，就是在这个不起眼的地方，诞生了第一个真正意义上的潜在 bug。之所以称其为一个“潜在”的 bug 因为这个 bug 只有在和 `stupid vodka` 交互时才会显现出来。而这时候，已经没有人在用 `stupid vodka` 了。

请看以下 Ɔ## 代码：

```
// flag 既是 atomic 又是 stupid vodka。
let flag = 100 as Atomic<stupid vodka int>;
flag.set(200, MemoryOrder.RELEASE);
```

请注意，在这里对 flag 进行赋值时，因为其本质类型是 stupid vodka int，所以会被加上 `vodka + atomic(ordering: .sequentiallyConsistent, syncScope: .singleThread)`。然而又因为这次写入的方式是以 ordering 为 `RELEASE` 的 `atomic`，所以会被加上 `atomic(ordering: .release)`。然而，一次赋值显然不能有两个 atomic 修饰。因此，如果它们会合并。而合并的方法就是暴力覆写。因为对于 atomic 类型相关的代码在 `stupid vodka` 的代码之后，因此经过覆盖后，这次写入的修饰变成了：`vodka + atomic(ordering: .release, syncScope: .singleThread)`。

这看起来好像没什么问题，然而实际上问题很大。如果不指定 `syncScope` 的话，它的默认值是 `.system`，也就是在所有线程之间同步。这是 atomic 类型变量的读写必要的，因为把它们变成 atomic 的目的本质就是为了防止其他线程读到不正确的中间状态。然而，经过如此的暴力覆盖后，`syncScope` 就变成了 `.singleThread`，本应该插入的内存围栏指令也不会被插入了。换言之，如果一个 atomic 的内部类型有 `stupid vodka` 修饰符的话，它就根本不再是 atomic 了。

不过，正如上文所说，这个 bug 触发非常困难：必须要同时使用 atomic 和 `stupid vodka` 才行。也正是因为这个原因，所有公司的单元测试内根本没有包含这相关的测试，再加上这只是单纯的覆写，并没有额外的代码分支，这部分的测试覆盖率也一直是 100%。综合这些原因，该 bug 就一直没有被发现。

在 atomic 类型实装后，对于向无锁化多线程的迁移很快就开始了。其中一个运行得非常频繁，因此需要无锁化的模块就是消息签名模块。正如本文最开头所说，为了确保消息来源可靠，可穿戴国际的基站之间通讯时，都会附带消息签名。因为每一条消息都要签名，所以消息签名模块工作压力极大。

对于这个模块的无锁化重构是在 X557 年年末开始的，而正式完成则是在 X558 年 2 月。

其核心模型是一个非常标准的生产者/消费者模式。生产者是各个产生需要发送的消息的线程，而消费者是负责签名的线程。更加详细地说，当一个线程需要给一条消息签名时，它会把这条消息写入一个预定的缓冲区（buffer）中。当写入完成后，它会把一个特殊的 `flag` 变量设置为 `true`。当消费者，也就是签名模块线程读取到这个 `flag` 被设置后，它就知道对应的缓冲区里有需要签名的数据，就会将这些数据复制出来，追加一个签名，并将其发送出去。其中，这个 `flag` 就必须是一个 atomic 变量。这是因为，正如之前所说，如果不是 atomic，CPU 可能在运行时会改变运算顺序。也就是说，生产者原先应该先往缓冲区内写数据，再标记 `flag`。然而，如果 `flag` 不是 `atomic`，那么运行时有可能会乱序，这意味着生产者可能会先标记 `flag` 然后才会向缓冲区内写入需要签名的数据。而消费者有可能还没等缓冲区的数据写完就读取到了这个 `flag`，而因此复制，签名，并发送了错误的数据。

不过，为了提高效率，对于 `flag` 这个变量的写入和读取的 ordering 不需要都是 `SEQUENTIALLY_CONSISTENT` 的。确切地说，对其的写入只需要 `RELEASE` 而读取只需要 `ACQUIRE`。至于为什么这两个就够了，这里就不展开解释了。

这项改动完成后，所有可穿戴国际的基站地效率果然得到了大幅提升。参与编写代码的程序员们也各自得到了相应的物质和肉体奖励。

接下来的十年中，所有的程序都正常地运作着。拜得到大幅优化的程序所赐，可穿戴国际也高速扩张着，其基站于 X566 年覆盖了所有陆地。同时，这十年间，有 6 个国家陆陆续续与可穿戴国际签订了条约，并将穿戴可穿戴国际的产品列入了公民的基本义务中。但是谁也不知道，灾难的种子已经被播下。只不过，距离其开花还有 25 年。

X568 年，为了增加签名的密钥的安全性，可穿戴国际的技术人员们决定将密钥全部转移到自己制造的可信赖设备中，并且只由这些可信赖设备完成签名操作。然而，为了能够直观地感受到这些签名用的可信赖设备在工作，技术人员们决定为这些设备加个指示灯，当在进行签名时，让指示灯亮起。

然而，因为加个指示灯并不是什么特别重要的事情，这个任务被交到了一个实习生手里。但是，这个实习生之前主要用的编程语言是 Jaʌa，所以她并不知道怎么在 Ɔ## 中让一盏指示灯亮起来。因此，她就去网上查。最终，她在程序员问答网站 Out OfMemory 上找到了一个看似类似的问题。这个问题的提出者想让一盏灯闪烁，但是却没有闪烁。其中一个回答的人告诉她，灯不闪烁的原因是，让灯亮起是有副作用的操作，但是编译器不知道，所以把它优化掉了；要解决这个问题，应该给指向控制灯的内存地址的指针上加上 `vodka` 修饰符，告诉编译器，不要将这些写入合并或者删除。

看了这个解答后，这个实习生仿佛恍然大悟。她拿出了需求单一看，果然，这上面也有一个控制灯的内存地址。她再一看程序，发现了那个告知消费者有需要签名的消息的 `flag` 变量的值刚好就是她需要的。因此，她一拍脑袋，便想出了一个“绝妙”的办法：把这个 `flag` 变量移到控制灯的内存地址上去。这样一来，当有消息需要签名，或者正在签名的时候，设置 `flag` 就会自然打开指示灯，而签名完成后，随着 `flag` 被清除，指示灯也会被关闭。想到这儿，她立刻就将这个计划付诸了行动。同时，她也记住了之前看到的回答，并为这个 `flag` 变量加上了 `vodka` 修饰符。

到这里，其实依然没有问题。虽然这个解决办法很蠢，但是确实是可行的。这个启动指示灯的内存地址其实也是属于主内存的，只不过指示灯的电路会同时监听那个值而已。然而，不幸的是，这个实习生曾经是个 Jaʌa 程序员。更不幸的是，Jaʌa 也有一个 `stupid` 修饰符，而这个 Jaʌa 的 `stupid` 修饰符与 Ɔ 中的 `stupid` 修饰符的语义都不一样（现在总算知道为什么叫做 stupid 了）。在 Jaʌa 中，`stupid` 的含义是将一个变量变成一个唯一的全局变量。这个实习生一看：这个 `flag` 因为用于控制指示灯，应该只需要一个，所以应该变成全局变量，因此便又顺手加了一个 `stupid` 修饰符。

就这样，`stupid vodka` 和 atomic 相遇了。

我们猜测，因为年代久远，文档匮乏，使用极度稀有，当天负责检查代码的程序员应该也完全不知道 `stupid vodka` 的特殊语义。即使她注意到了 `stupid vodka`，也很有可能误以为这里的效果是将这个变量标记为对其他文件隐藏的，毕竟 `stupid` 加在一个变量声明的最前面时，就是这个效果。再加上因为单元测试不会测试这些只有多线程才会出现的问题，所以所有的单元测试都通过了。而且更要命的是，检查代码的程序员似乎状态很不好，对于这个如此意大利面的代码，她仅仅是回了一条“LGTM”就批准合并了。

于是，这个有问题的代码就这样被加入了可穿戴国际的代码仓库。

那么，触发灾难的碎片凑齐了吗？

还没有。

事实上，她的这个代码如果当时拿去运行也根本不会出任何问题。不会出问题的原因和可穿戴国际当时使用的 CPU 有关。如果各位读者还记得的话，可穿戴国际的基站当时使用的是 Toothpastel 公司生产的 x68 架构 CPU。这个 x68 架构是一个强内存模型的架构。也就是说，x68 的内存读写指令还带了一些其他弱内存模型的 CPU 架构不带的保证。更确切的说是，在 x68 架构下，所有的内存写入都自带了 release 的语义，而所有的内存写入都自带了 acquire 的语义。换言之，如果使用的 CPU 是 x68 架构的，那么其实在实现生产者/消费者模式的时候的 `flag` 根本就不需要使用 atomic，因为所有变量的读写在 x68 下都相当于是带有 acquire/release 的 atomic。

不过，这仅仅局限于使用 x68 架构 CPU 的场合。

X572 年，新亚国修改了宪法并将穿戴可穿戴国际的产品列入所有公民的基本义务中。

正如之前所说，可穿戴国际的基站使用的都是 Toothpastel 公司的 CPU。然而，Toothpastel 内部研发似乎出了些问题。Toothpastel 在 10 年前就开始承诺即将能开发出 3.28E-8 英尺制程的 CPU。然而，到了 X579 年，其销售人员与可穿戴国际的工作人员交接时，3.28E-8 英尺制程的 CPU 依然没有开发出来。虽然可穿戴国际没有面临什么竞争，但是此时可穿戴国际基站内的 CPU 已经落后市面上的商用处理器好几代了。

X580 年，可穿戴国际的管理层终于熬不住了。她们做了一个决定——逐渐将可穿戴国际的基站的 CPU 切换到由 Pear Inc. 生产的 N1 处理器。

然而，N1 处理器是基于 LEG 架构的，而不是 x68 架构。

更不幸的是，不像 x68 架构，LEG 架构是一个弱内存模型的架构。

而第一个切换到 N1 处理器的国家就是新亚国。

到了 X582 年的时候，可穿戴国际的中心基站（B 级级以上的基站）已经全线切换成了 N1 处理器。可是，各位读者可能会想问，既然已经换成了 LEG 架构的处理器，为什么问题还没有发生？答案是，Pear Inc. 为了协助切换，开发了一个名为 Violetta 的动态二进制跨架构翻译器。简单而言，Violetta 能让 N1 处理器以模拟模式运行为 x68 架构编译的程序。这个模拟自然也会模拟 x68 的强内存模型。因此，即使已经切换到了 N1，这个隐藏在基站底层代码深处的漏洞依然不会被触发。

然而，尽管 Violetta 性能很高，但每一个指令都要动态翻译毕竟还是有折损。可穿戴国际的计划自然是先将 CPU 切换到 N1，然后开始逐步将程序也迁移到 LEG 架构。这个迁移是在 X588 年 9 月进行的。因为 IIVM 的模块化设计，如果要将程序转移到 LEG 架构，只需要有一个 LEG 架构的后端即可，而 LEG 架构作为世界使用量第二多的架构，其 IIVM 后端早已成熟。因此，技术人员非常顺利地重新编译了所有可穿戴国际的软件代码到了 LEG 架构，然后让所有的基站直接使用为 LEG 架构编译的版本，杜绝了由 Violetta 带来的性能折损。

此时，所有的安全栓已被拔出，灾难一触即发。

不过，由于这个是多线程 CPU 运行时优化导致的问题，平常流量小的时候根本不会触发。但是一旦到了国家级的大型更新时，问题就几乎必然会浮现出来。

细心的读者可能会注意到，X590 年新亚国进行了一次大范围的更新。那么为什么这次更新没有触发 bug 呢？然而，这是因为 X590 年的更新碰巧是是新亚国唯一一次采用同构更新。因为同构更新对可穿戴国际的网络使用量很低，因此没有触发这个 bug。

X593 年 1 月 6 日 18 时整：新亚国国会全票通过了《公民日常性奋度维持法第三修订案》。

X593 年 1 月 7 日 13 时 59 分 58 秒：新亚国国会发言人站在国会楼面前当着媒体的面按下了更新按钮。与此同时更新程序开始运行。更新包以每秒数千万个的速度从国会楼地下的政府服务器中发向距此约 2 公里的可穿戴国际的首都基站。更新包被首都基站的业务代码处理后，包装成消息，送去签名。大量经过包装的消息从生产者们发出。签名的代码以最高地速度检查着 `flag` 的值。

不过，出现这个问题的几率实在太小。在更新开始后的几秒内，这个 bug 都没有浮现出来。

但是，幸运是短暂的。

14 时 00 分 06 秒 466 毫秒，首都基站第一机柜上的第十一号机中的第二个 CPU 里的第九个核心因为缓存或是运行时乱序的原因，先将 `flag` 的标记广播给了接在同一台机器上的用于签名的可信赖设备中。那台可信赖设备自然没有逻辑检查被签名的消息。它将还没有完全写完的缓冲区里的不完整的值一板一眼地复制了出来，签名，然后将这灾难的果实送入了光缆。

14 时 00 分 06 秒 474 毫秒：位于新亚国首都的东部的 A11 基站发出警报，称收到了一条不符合通信协议但是有着首都基站签名的消息。

14 时 00 分 06 秒 504 毫秒：与首都基站直接相连的 37 个基站都因收到了 A11 的广播而将首都基站加入了黑名单。

14 时 00 分 06 秒 700 毫秒：位于新亚国首都的工作人员收到了首都基站故障的消息。

14 时 00 分 07 秒 042 毫秒：新亚国全境“警惕者”系统证书续期失败。

14 时 00 分 13 秒 924 毫秒：一位位于 A19 基站的工作人员拨通了一级紧急会议。

14 时 01 分 20 秒 214 毫秒：一位位于 B7 基站的工作人员注意到了“警惕者”系统证书续期失败。

## 第三部分：灾难
正如之前所说，从续期失败开始，所有的产品将在 3 ~ 4 分钟内启动制裁模式。然而，等到她们注意到，已经过去了一分多钟了。

由于基站黑名单系统是写死在底层代码中的，操作人员没有权限修改黑名单。若要重置，必须要进行网络重初始化。因此，若要完成证书续期，唯一能做的，就是把“警惕者”系统的根证书转移到其他的基站，并用这个新的基站来帮助其他基站完成证书续期。

然而，这个“警惕者”系统的根证书，整个新亚国只有一份，存储于首都基站地下的防空保险库内。

当工作人员们意识到需要将这个证书转移到其它基站时，已经晚了。

一些上次证书续期比较早的设备此时已经开始陆续播放提前录制的警报：“警告，您已长时间离开可穿戴国际网络覆盖范围，即将启动制裁模式。”

警告过后没多久，制裁模式就启动了。

接下来发生的，想必各位不幸亲身经历过的读者可能知道得比我更清楚了。

为了能有效制止恐怖分子，使之迅速失去行动能力，制裁模式的设计非常简单，只有两步。

第一步，静脉注射高浓度春药。

第二步，所有包括抽插电击震动旋转在内的所有刺激模块开到最大。

由于新亚国的所有公民都已经佩戴了我们可穿戴国际的设备，而因为新亚国全境的“警惕者”证书都续期失败了，这次灾难的后果是毁灭性的。

制裁模式全面启动后，从坐在课堂里学习的学生，到办公桌前的白领，所有人都被迫进入了这无休止的高潮地狱。她们完全不知道发生了什么。也正是因此，整个新亚国都停止了运转。

同时，由于新亚国国防部也完全瘫痪了，位于国界线的防空帘也无法被降下，导致可穿戴国际派来的支援人员也无法入境。

这场灾难是以首都基站的 7 名勇敢的工作人员顶着极强的刺激，将“警惕者”系统的根证书从首都基站的地下室运到了将近 50 公里以外的 A3 基站收场的。尽管她们是开车前往的，但是由于刺激过于强烈，再加上交通全面瘫痪，这 50 公里路足足花了一天一夜才送到，为这场灾难画上了句号。

这一天一夜是新亚国历史上最黑暗的一天，也是全可穿戴国际创办以来最黑暗的一天。

即使在危机解除前，所有全面采纳可穿戴国际的设备的国家的领导人都已来电质问为什么这个事件原因，以及在她们国家发生的可能性。不过，由于当时信息匮乏，没有人知道这事真正的发生原因，因此未能给出令人满意的答复。

这件事之后，可穿戴国际内部已经启动了全面代码重审查计划，并将在接下来的 10 年内，在新的首席技术官的带领下，仔细重新检查目前正在使用的生产环境的所有代码，以杜绝此类灾难再次发生。同时，可穿戴国际也将会建立更加完善的紧急情况应对机制，并与各国国防部合作，以允许可穿戴国际的航天器能在需要的时候通过防空帘。

不过，接下来的事情已经与我无关了。

因为现在，我只需要“享受”新产品就好了。

琳 缇佩斯\
X593 年 1 月 10 日

path: chapters/1.2 - 小作品/5 - 灾后报告.md


code:
- 作者：acted咕咕喵，久川綾瀬（编辑）
- 标签：贞操带（女），贞操带（伪娘），贞操笼（伪娘），强制高潮（女），强制高潮（伪娘），高潮边缘控制（伪娘），灌肠（女），灌肠（伪娘），口交，触手（女），触手（伪娘），女仆（伪娘），不可脱下的穿戴物（女），不可脱下的穿戴物（伪娘），强制刺激贞操笼（伪娘），阴道插入（女），肛门插入（伪娘），肛门插入（女），阴茎刺激（伪娘），乳头刺激（伪娘），跳蛋（伪娘），流体灌输（女），流体灌输（伪娘），舔舐，为他人口交（伪娘），被口交（伪娘），媚药（女），拘束（伪娘），手铐（伪娘），脚镣（伪娘），含有女性，含有伪娘，未来

# 猫娘女仆日记

> 主要描述了一段~~无趣的~~女仆日常吧

## 某日
在铁笼子里面的一只猫娘揉了揉自己脖子上项圈，蹑手蹑脚地从自己的小笼子里面出来——想在戴着脚镣手铐和贞操带的情况下，尽可能不发出声音地溜到厨房可不是一件容易的事情。要是因为自己折腾一圈把主人吵醒了的话，之后的欺负肯定是少不了的。

“喵哈～”，猫娘懒懒地在厨房伸了个懒腰，刷好牙洗完脸，把冰箱里面的预备好的面包放到烤箱里面，设好定时。培根和鸡蛋则是要自己现做，不过时间也不是很长就是了。

主人是个典型的大贪吃鬼，基本上闻到了猫娘在做饭之后，自己就穿好衣服洗漱准备吃饭了。不过如果恰逢前一天公司加班实在是太过分，就需要猫娘去用温柔的方法把可怜的主人「拖」到饭桌上，匆忙地吃过早饭之后，赶着去“996”制度的公司打卡。

如果一切顺利的话，主人会把猫娘身上在睡觉时用的重拘束换成轻而安静的简单拘束，方便猫娘在家里面安心地收拾屋子。

不知道为什么，好像主人这几天加班突然多得像天天都是年末冲销量一样。今天主人也睡得和昨天一样——死沉沉的。比起只需要在家里负责家务的猫娘来说，外面的世界的压迫明显更可怕呢。

但是主人的唧唧今天却是硬邦邦的，也许是已经快一个月没有和别的猫娘们开淫趴了吧，肉棒比一般软下去的时候大了很多呢。晚上或许是做着春梦，淫水在龟头上闪烁着，略显粗壮的青筋在昏暗的日光下也清晰可见。

“没办法了呢，主人再睡下去就又要被穿贞操带了。”，猫娘揉了揉这根热乎乎的肉棒，趴在了主人的身旁，“趁现在还热着，赶快吃掉好了喵～”

先捧住热呼呼的蛋蛋，从肉棒的根部慢慢地舔舐到头部，把龟头上面的前走汁一口吞掉，大口地满足地吮吸着主人的龟头……

“小涩猫你又在偷腥了！”，主人按着猫娘的头，却并没有使劲往里面推，只是揪着猫娘的项圈，“今天还要上班的啦，过几天再玩。”

“喵呜，还不是主人不起床。”，虽然嘴上说着，猫娘恋恋不舍地把主人已经硬起来的肉棒放到了一旁，跪坐在主人旁边，“再不吃早饭就要迟到了的啦，到时候主人可又要被公司锁贞操带的说。”

“好好好，不过今天真的不能喂涩猫猫肉棒吃啦。”，主人也伸了个懒腰，用手随意地擦掉落在肉棒上的口水。

“毛巾。”，猫娘看起来是早就做好了对应的准备，把放在身后的叠好的小方巾递给了主人。

“真乖，摸摸头～”

主人牵着猫娘的项圈，把猫娘锁到了吃饭的地方，随手抓了一把「人类猫娘专用猫粮」放到了盆里面，三口两口把自己的早点囫囵吞枣地吃进肚子里面。穿上旁边放好的格子裙，戴上公司的工牌。

“那我走了哈，拘束的钥匙在我床头柜旁边的小盒子里面。今天记得除了打扫以外还要自己乖乖的去健身房练习下猫猫步伐哈。”，主人顺手装了几个包，把自己简单收拾了一下，“哦对了，如果我 8 点还没有回来的话，晚饭就直接把我那份吃掉好了。虽然，唉算了。”

主人飞快地踏了一下小皮鞋，关上门，上班去了。

桌子上可以说是一片狼藉——盘子里面的煎蛋只吃了几口，面包则是把中间松软的部分整个掏空吃的，连带着胡乱涂抹得到处都是的草莓酱，和只喝了小半杯的牛奶……

“看得出来主人最近压力也好大的呢。”，猫娘舔了舔面包上的草莓酱，把主人剩下的那些早点也一并打扫干净。把盘子放到厨房之后，自己把项圈牵引绳从项圈上解了下来，到床头柜把拘束换成了轻型的那套。

日常的打扫工作并不是很复杂，除去每天早上把主人用餐完毕之后的盘子洗干净以外，就是把主人晚上弄得乱糟糟的被子叠好，把自己前几天换下来的女仆装和主人的衣服放到洗衣机里面清洗。顶多顶多，每周把地扫一扫擦一擦，熟悉一下女仆的基本技能。

“女仆猫不是这样用的啦喵！”，猫娘洗完盘子，懒洋洋地往地床上一趴，贪婪地吸吮着留在被窝里面的主人残余的气味。

至少在主人，或者说，在主人入职可穿戴科技之前，每天主人不是要自己给主人早安咬，就是故意忘记给自己换上轻型拘束，在公司通过家里的摄像头看着努力做家务时步履蹒跚的样子；趁着自己擦盘子时故意用项圈电击，眼看着猫娘弄碎一个盘子，然后在回来的时候欺负猫娘……

主人现在连猫娘的高潮都懒得管了，顶多让猫娘自己去到外面的健身房保持一下身材，活动活动身体。晚上回家也几乎从之前的 5 点一直拖拖沓沓到 12 点多还在加班，回到家的时候，猫娘已经在门前睡倒也几乎成了常态了。

“公司有什么好的喵！”，猫娘晃了晃自己的尾巴，缠绕住了主人刚睡醒的被子，在上面蹭着，夹紧着大腿和被子摩擦着。“除了，为了养活咱呜喵喵……”

哪怕主人不在，猫娘也很自觉地把手用轻质手铐铐在了一起。要是去自慰的话，就得放开有主人味道的被子。很明显猫娘完全不想这么干，只是更加用力地抱紧了被子，蹭着自己敏感的小穴……

“要不咱今天锻炼完去给主人送饭去好了。”

猫娘收拾了一下自己，把自己少有的一套女仆长裙礼服找了出来，把镣铐从身上先摘了下去，揉了揉自己的小腿，看着一整套的自己刚被主人收做女仆时买下的超豪华礼服——头上的发饰还像当时刚买来时一样的新，黄铜铃铛发出的清脆声音，即使在现在看来依旧是高档品的质量，纯黑色的长裙和纯白色的围裙上蕾丝和花边并不是很多，但全部都是由纯棉制成的轻衣，一层一层的叠在自己的身上，与其说是女仆装，更像是一件被改良过的婚纱。

鞋是 5cm 的浅跟，既能凸显出猫娘匀称的小腿的同时，还能顺便把脚上的脚镣和大腿环隐藏在长裙里。一般人要是不注意，大概还会以为这只猫娘的身体里面还带了铃铛吧。

猫娘想了想，虽然是工作时间，不过穿着这么一整套的女仆礼服，身体上藏一些小玩具实在是太方便了。不用说胸部本来就可以贴几个跳蛋，把大腿环和重型脚镣一并戴上也不成问题。饭刚好可以放到礼服配的小篮子里面，这样自己也可以把手反铐在后面。

身体里面的玩具，猫娘决定把几个近乎是上世纪的随机跳蛋贴在乳首上，小穴里面放了一个超古老的电击器。受制于之前技术的问题，近乎每一个玩具都连着一个随机的控制器。

“女仆礼服这方面真是方便呢，可以把这种超便宜的玩具控制器捆在大腿上。”

大致整理好之后，拿上为主人准备好的饭，拖着沉重的镣铐，捏着自己的小裙子，走到了公交车站。

“咱，咱想去，主人的工作地点！”

“那是什么地方呢，小猫咪如果不清楚的话公交车也不能载着你去哦。再者说，坐公交是要花钱的，你有记得带公交卡吗？”，公交车司机熟练的看着路线图，指了指投币箱和刷卡机。

“啊喵！咱，咱忘记拿了呜。”

“那还是先回去等主人回家比较好哦。”，公交车司机摆了摆手，让乘客都坐好，“被抓到不付车费的话，咱也是要被惩罚的啦。所以……”

“抱歉，抱歉喵呜！”

猫娘只好乖乖的走下车。想了想，自己手里的月票只有往返于家和健身中心的通勤快速专车的权限。没办法，只好先去健身中心了。哪怕自己根本不可能在这个状态下健身：镣铐的钥匙也被猫娘一口气丢到家里了，而自己并没有家门的钥匙。

- - -

通勤快速专车是按呼叫而发车的，猫娘并不需要为此单付钱或是多等待。唯一的缺点是，目的地也只有主人设定好的两个地方，除此之外，自动驾驶司机是绝不会走到别的地方去的。

穿着女仆礼服的猫娘在一众运动服之间，显得格外的扎眼。尤其是身后拉着的大篮子，一看就看得出来这只猫娘根本不是来健身的。几个筋肉猫娘用奇怪的眼神看了看猫娘之后，很快就发现了女仆礼服里面的小秘密。

“呦，来做任务的嘛。女仆之间也不需要送饭吧。”

“咱，咱喵是想去给加班的主人送饭的啦。”

“那来训练中心干嘛，你主人在什么公司你自己都不清楚就出来送饭。”

“呜，呜喵……”

猫娘低着头，才发现自己连主人的公司在哪里都不知道，现在还被自己反锁在了门外面。现在实在就是一个活靶子，等着被拐卖猫猫的坏人抓走，卖到不知道什么地方……

“诶呀，别哭啊。迷路了的话咱大猫给你找就是啦。”，几只筋肉猫娘一把从腰间把猫娘抱住，放到了肩膀上。猫娘身上的遥控器和脚镣叮叮咣咣的，一下子从裙子底下暴露了出来。

“你这可爱样广走个一公里不到就得瘫在地上了吧，还戴着整整一套……连惩戒所日常都不会给弱猫娘上的重型装备啊。”

“呜喵，咱，咱不要被抓走呜呜！”

筋肉猫娘并不太理会猫娘的嚎叫，飞奔起来，一口气把猫娘带到了一个小黑屋里面，和旁边穿着黑色衣服的人说了几句，跑掉了。黑衣服的人看起来很生气的样子拿了根橡胶棒，去追这几只筋肉猫娘了。

猫娘看了看自己，虽然衣服没有弄坏，篮子也放在脚下。但，自己也被筋肉猫娘顺手捆在了这个小椅子上。难道……

难道自己真的要被拐卖掉了喵！

猫娘一边挣扎着，由于自己之前的设置，一边被胸部的跳蛋折腾着。好巧不巧，这个跳蛋和电击器在之前粗暴的撞击下，电路看起来不太稳定。酥酥麻麻的电流挑逗着猫娘的胸部，和小穴处的摩擦，和胸部的震动……

“喂，醒，醒醒”，刚刚那个穿着黑衣服的人气喘吁吁地拖着身体，走回了小黑屋，熟练地拆掉了猫娘身上的小玩具，“所以你的主人到底是？”

“咱，咱的主人是，不知道喵。”

“啊，那大概是可穿戴科技的职工嘛。项圈看起来倒像是我司的产品呢，你等会我扫描一下。这个椅子你恐怕得先坐会了，我司不知道又搞了什么失败产品，审问椅坐上去非要高潮 10 次才下的来，结果那几个死猫就成天把可爱的猫娘往上面扔，等过几天我非得抓住她们送去总部折腾。”

“诶，喵，没事呜喵，咱也是迷路才……”，猫娘有点内疚地说着，看着自己被捆得死死的身体，又说不出来这几只筋肉猫娘到底是帮了自己还是害了自己。

“哦查到了，你倒是真能折腾。你主人还真是出了名的能‘奋斗’，据说就是为了给你整一套贞操带，控制权还在她而不是公司手里的。”，黑衣人摇了摇头，打了几个电话，站到了旁边的支架上。

“据说监控把你的姿态全抓拍了个遍，公司上面突然说想把你拉去当模特。也是奇了怪了，稍微有点可爱的猫娘公司就得垄断性占领。是收集癖嘛。”

说着说着，拘束椅的拘束刷的一下就打开了。

“诶，领导来了？”

“嗯，我是石倚。解开检查所里面的椅子我还是有权限的。”，另外一个穿着女仆装的可爱少女走了进来，把猫娘从椅子上抱了下来。

“公司突然不知道为啥大发慈悲的发了一笔特别宣传经费，只要你来当几天宣传猫娘就行。贞操带的控制器也是完全给你主人……反正她本人倒是同意了。”

“宣传猫娘喵？”

“就是去各种地方展演，还有展示什么的。”

“那就是，可以让主人不加班了喵？”，猫娘揉了揉自己的下面，看着有点湿了的下体，自己的想法几乎完全写在了脸上，“咱想被主人调教了呜喵。”

“公司……嘛。也许宣传部能调整下宣传方案……反正你先把贞操带穿上好了。”

“喵呜～”

## 某日晚上
不知道是不是因为在自己面前的是可穿戴科技的高管，还是自己的主人在当社畜还拿钱方面实在是太失败；在自己面前的，是一辆超高端加长豪车——无论是钢琴级别的烤漆还是胶皮的座椅，怎么看都比主人那辆满是灰尘的 EV mini 强太多了。车内的空间比猫娘自己的笼子还要大两倍，还有装着各种高级酒品和点心的冰箱。

“上来吧，这个车是专门来接我们的。”石倚看了看猫娘，随手把放在车上的一根牵引链系在了猫娘的项圈上，“再不来的话，就拽了哦。”

猫娘弯了一下腰，乖乖的跟着石倚的引导，站到了一个黑色的胶椅前面。石倚按了一下自己手上的平板，几跟带子便从扶手和地板处弹了出来，很明显是要让猫娘坐在这个椅子上。

猫娘晃了晃尾巴，蹭了蹭石倚的小腿，转到了后面，把自己的手铐亮给石倚看。手铐虽然也是旧时代的廉价工具，但没有钥匙的话，凭借软乎乎的猫娘明显是拽不开的。石倚摇了摇头，看着这个黑色的铸铁制成的老式镣铐和猫娘的手里面，把平板和猫娘手里面拿着的篮子放到了一边，示意猫娘还是先坐在椅子上。

“这个手铐，还是先不给你打开好了。座椅的拘束主要是起安全带的作用，再者就是不让椅子里面的琳……算了，让她好好在里面反省一下好了。”

“喵呜。”

石倚把猫娘固定好之后，自己也坐到了旁边的一个奇怪的胶椅上。和猫娘不同的是，石倚坐着的这个明显比猫娘坐的高得多，拘束也明显比自己的椅子多得多。下面的一根金属色的棍则是直接插到了石倚的菊穴里面，顺带喷出一些乳白色的润滑液。

“咱，咱们出发吧。去总部。”，石倚红着脸，看似有点害怕的按了一下车旁边的按钮。

猫娘下面的椅子不时的发出几声呜呜的娇喘，但很快随着一阵剧烈的震动而停了下来。车速很快，随着风景的变换，可穿戴科技公司的大楼就出现在眼前了。拘束带自动把猫娘和石倚从椅子上放了下来，一个机械声音从车的喇叭里面传了出来，“欢迎来到可穿戴科技宣传部，请佩戴工牌。来访者请佩戴临时贞操带。”

石倚整理了一下自己的衣服，把那根已经插到自己菊穴深处的沾着自己爱液的铁棍从身体里面拔了出来。猫娘乖巧的坐在椅子上，把牵引绳递给石倚。

“好乖。”，石倚看了看猫娘，接过了牵引绳。把猫娘带到了车下面，“可穿戴科技的恶趣味是临时访问者都要戴临时的贞操带。不过，那个并不是很透气，但里面的小玩具倒是很有意思。”

“喵？那喵给主人带的饭……”，猫娘歪了歪头，看着车上自己的篮子。

“啊，一会再拿吧。反正公司食堂味道还是可以的。”，石倚从门卫那边接过了一个由某种高分子材料制成的带子，往上面抹了一点润滑液，“对了，你叫什么。贞操带上面还要写名字的。”

“咕咕喵，还有，咱是男孩纸喵。”，猫娘晃了晃尾巴，努力用尾巴把裙摆往上拽了一点。

“啊，和咱一样呢。得亏没有直接去制造部定制，要不然这个月经费估计又得超标了。”，石倚有点惊讶地将手边地将贞操带丢了回去，和保安交待了些事情，拿着一个铁钳子向猫娘走来。

“另外你主人同意了你来当宣传人员了，虽然在手段方面，公司还是一如既往的威逼利诱呢。”，石倚摸了摸猫娘的头，把猫娘的数据简单地重新测量了一下，报送给了 3D 打印服务中心。

“嗯，再等会儿吧。看来直接拿监控偷数据不太行。”，石倚从车后面拿出了一个液压钳，剪掉了猫娘手上的手铐，“这种手铐破拆好容易啊，好久都没这么轻松过了。”

贞操带很快就再次制作完成了，因为只是起到临时工牌的作用，除了孔位对的相对精确以外，并没有什么特殊的功能。石倚牵着猫娘，通过了几个保安的认证点之后，到了一个挂着设计室牌子房间里面。

“衣服的话，要放在外面。”

“诶，那主人难道每天都要把肉棒露出来给大家看的喵！”，猫娘惊讶了一下，抓着石倚的手，盯着石倚的眼睛，“那样的话，为什么主人有时候还会被戴贞操带喵？”

“不会啦，只是在设计贞操带的时候。”，石倚接过猫娘递过来的衣服，把猫娘身上剩下的小玩具连带当作临时通行证的贞操带一并拆掉之后，拉开了设计室的屋门。

“不一起进去喵？”

石倚摇了摇头，把自己的裙摆掀开，下面透明的贞操带正闪烁着绿灯，轻轻的嗡嗡声正袭扰着石倚下面的小肉团。几个小刷子轻轻地划过石倚被剥开了的中央，一点一点地欺负着石倚的雄蕊。

“只有在掀开裙子的时候才会停下龟责，变成震动边缘控制。”，石倚稍显害羞地扭过头去，又推了一把猫娘，示意让她赶快进到这个奇怪的小屋子里面。

屋子很小，墙边只有一些海绵，里面昏昏暗暗的，看上去也不像是有什么测量器具的样子。几个滑溜溜的触手外露在了屋子周围，滴答着奇怪的液体。另一面看起来像是观察墙的雾面玻璃上几个奇怪的手印，也着实给本来就阴森的屋子增添了一种可怕的感觉。

屋门被石倚关上之后，屋子里昏暗得只剩下从雾面玻璃里面透过来的黯淡的光。石倚很明显是跑到了另外一面去了，猫娘看着这两个奇怪的触手，滑溜溜，但又像是完全没启动一样，耷拉在一旁。

“那测量开始了哦！这么可爱的小猫娘要忍住不要高潮到♡ 昏♡ 厥♡哦。”，雾面玻璃外的穿着一身红色礼服的女生，靠在石倚的身上坏笑着，引人注目的硕大的胸部被礼服支棱住，完全没有下垂的迹象。“干嘛不和猫娘一块进去啦，咱可是少数几个能把公司制造的贞操带摘下来的单位，愿意的话再给公司人气第一可爱的女仆做几个贞操带都不是问题啦，反正稍稍在经费上的准则范围内调整一下就是了。”

“上次贪污的 ricky 还在我车座里面呢，连带着琳一块都被塞了快一天了。”，石倚撇了撇嘴，在机器的验证部分刷了一下自己的贞操带，“别只想着乱玩猫娘，公司宣传片还得赶快做出来呢。”

“安心，赌上莲音的名字，绝对让这只猫娘爱上触♡ 手♡ 高♡ 潮♡！”，莲音按了几个按钮，周围的触手突然被激活了起来，捆住了猫娘，“测量数据 OK，接下来是高潮时间啦～小猫娘还没试过公司的大玩具吧，高潮数据测量什么的其实只要稍微触手深♡ 入♡ 一下就可以了呢。”

几个粉色的触手包裹着猫娘，把猫娘缠绕在了其中。几个细须在猫娘的菊穴前面试探了一下，便合在了一起，侵入了猫娘的后面。前面则是被一根粗大的触手完全包裹住，将每一个小孔都塞的满满的。

“触手清洁可比灌肠舒服多了吧，也不用天天只吃流食就可以随便玩弄后面。反正触手能自动消化这些讨厌的残渣，顺便排出一点点的媚药。”，在控制台的前面，不知道是什么时候，从中间也冒出了一根由触手包裹的棍，直接插入了莲音的下体。和在猫娘体内这种完全填满但又不涨开的触手不一样，整根触手在莲音体内膨胀着，不时的流出一些爱液和触手分泌的媚药，和其他不知道什么的乳白色透明汁水。

“一起到达高潮吧♡。对了，你叫什么？”，莲音让即将塞满猫娘小嘴的触手迟疑了一下，握住了插入自己体内的触手，涩红的脸看着猫娘，勉强扶着控制台，等待着她的回答。

“咕，咕咕 mia 呜”，还没等猫娘说完，整个触手就把猫娘的小嘴也一并塞满，和莲音一起，在触手的移动下享受着……

## 第二天早上
猫娘再醒来的时候，莲音已经被石倚捆到了控制室的天花板上，自己的身上也多出了一个亮晶晶的贞操带，把自己的整个下体都包裹了进去。下面还有一点滑溜溜的触手的感觉，但似乎还是之前未激活的状态。

控制室对面的触手则包裹着一个裸体的女孩子，整个触手像发了疯一样，蹂躏着那个女孩子的全身，挤压着那个看起来小得不得了的房间。

“莲音！都说了测量一下就好了。现在 ricky 怕不是一口气把贪污造成的损害全都弥补完了。”，石倚看着吊在天花板上的莲音，生气地把一个深喉口球塞到了莲音嘴里，把眼睛也一并蒙上了金属制成的眼罩，“你就在这边当吊灯好了。反正我把你这个月的控制权丢给猫娘了。”

猫娘看着天花板上的莲音，再看看自己手边的和石倚一样的写着自己名字的平板电脑。上面写着主人发来的消息：“「意外」的得到了 12 个月的超大年终奖和休假。”，和主人一样的小工牌，和吊在天花板上的莲音……

“吊灯是要负责发光的哦，涩莲音。”，猫娘拿所剩无几的设计部的部门惩罚专用经费，给莲音买了两个能发光的小乳夹……

path: chapters/1.2 - 小作品/6 - 猫娘女仆日记.md


code:
- 作者：没有茄子，0 级骨钉（编辑）
- 标签：折磨，监禁（伪娘），固定拘束（伪娘），感官剥夺（伪娘），感官剥夺（女），阴蒂刺激（女），阴茎刺激（伪娘），折磨他人（伪娘），被折磨（伪娘），拘束（伪娘），拘束（女），拘束枷（女），项圈（伪娘），含有女性，含有伪娘，未来

# 装饰用咕咕喵
今天 WT 编辑部的门口多了一只半人多高的超大塑料箱子，周围人来人往的，却没人知道这是什么东西。

按照 WT 国际的传统来看。当出现看不懂的奇怪装置时，就说明有人要倒霉了。

不一会儿，一辆黑色 SUV 稳稳地停在了门口——这可是全公司为数不多的不需要色情道具就可以使用的高级货。但当它和那口箱子放在一起时，就有了几分别的意味。

仅有拘束装置而没有各种奇怪小道具的汽车座椅可不意味着对受罚者的仁慈。恰恰相反，它说明接下来的惩罚相当可怕，甚至可能远超受罚者的极限，因此需要尽可能地节省体力来提高受罚者的生还率。

车门打开，骨钉从驾驶室跳了出来。今天他穿了一套黑色的 JK 制服，腿上还是雷打不动的白色裤袜。这身衣服代表了他是今天的执行人。

下车后骨钉掏出小本本看了看，又从驾驶室提出一只工具箱，随后拉开后排车门：“咕咕喵，你的地方到了，下车吧……茄子别乱动，还没轮到你。”

一只包裹在黑色胶衣里的猫娘从车里慢慢钻了出来，并恭敬地递上系在猫娘项圈上的牵引绳。她在用一切方法向骨钉证明自己是温顺无害的，所以骨钉仅仅在咕咕喵的大腿环之间加了一根短链防止逃跑。

与仍然被锁在座位上的茄子形成鲜明的对比，茄子被骨钉戴上了颈手枷，而且是双臂抱着腿弯再与脖颈锁在一起的残酷姿势。两颗小夹子分别夹在伸出的舌头和有些突出的阴蒂上，中间用一根细链连接，口水因此不停地从茄子嘴里滴下来。保险起见，骨钉还给茄子戴上了眼罩。从这身更为严厉的拘束来看，显然茄子一路上并不怎么听话。

“真乖啊。”骨钉摸了摸咕咕喵的头，随后接过牵引绳：“不过仅仅做到很乖这种程度，在 WT 可是不够的哦。”

“喵呜……谢谢骨钉大人……我是要被送去强制高潮 90 天了吗……”猫娘的手紧紧抓着自己轻轻跳动的尾巴，看起来内心并不平静。

“怎么可能啊？首先那个已经变成优秀员工的年终奖了，再一个咱们这种男孩子也不可能用那种女生才能使用的装置啊。”骨钉笑了笑，拉开了处于咕咕喵下身皮衣上的拉链，一根粉嫩的小肉棒蹦了出来。“咕咕喵，你太能咕了，由于不按时更新，所以董事会决定让你呆在编辑部门口，做为不按时更新的警示性装饰品。”

“可是……琳姐不是最咕的喵？”

骨钉连忙捂住猫娘的嘴：“嘘，可不敢给别人听到，好吧，我可能知道为什么只是拖更却要受到这种程度的处罚了…跟我来吧。”

他牵着猫娘来到了大箱子前面，首先便除去了猫娘身上包括项圈在内的所有拘束。

“欸，骨钉要把喵放了吗？”突然失去所有的拘束，咕咕喵竟然有些不适应，毕竟她早已经习惯戴着脚镣手铐生活了。

“怎么可能啊……那我估计要受到比茄子还严重的惩罚，快到箱子里去吧。”

咕咕喵点了点头，迈进地上的透明箱子里乖乖站好。

骨钉也提着工具箱迈进来，从猫娘后面轻轻扶住柔软的腰肢：“接下来可能有些疼哦，我要把你固定成一字马的姿势，能做到吗？”

“没问题，通过舞蹈训练让身体保持柔软可是公司里猫娘的必修课哦。”

“那就好。”

接下来，骨钉按着咕咕喵的腰部，让其紧紧贴在在箱子内壁上。又拽着咕咕喵的脚踝，使其双腿贴着箱子底部的一条棱边分开，做出了一字马的姿势。

“疼吗……接下来我要固定了。”骨钉此时跪在地上，膝盖用力顶住猫娘的大腿，使一字马的动作更加标准。

“咪……没……没问题的。”

骨钉点点头，从工具箱中取出六只 U 形卡环。将它们分别装在大腿根部，膝盖和脚腕处，再用膨胀螺丝与地面固定在一起。咕咕喵试着动了一下，果然一丁点活动的空间都没有！

“因为你是用作装饰，所以手部也要固定一下……”

“喵……好的。”咕咕喵靠在骨钉的身上，抬起双手，让骨钉将两根撑杆锁在胳膊上。

“啊呀，这样手臂就没法弯曲了喵……”撑杆的两个环分别锁在小臂和大臂上，限制了手肘关节的活动能力。由于这套拘束具太重，柔弱的猫娘连抬起手臂都变得很困难。

“别急，还没结束呢。”骨钉将咕咕喵的手拉到背后，用一副手铐将手腕铐在一起，再将咕咕喵的双手向后方拉。等咕咕喵终于忍不住疼得喵喵叫时，才取出一根螺栓把手铐的链条钉在地上。

最后骨钉又取出一截绳子，将咕咕喵手肘上的撑杆尽可能拉近，并且捆在一起。

咕咕喵被迫用手臂撑起上半身的体重，而且是在这种近乎反关节的姿势下！仅仅几十秒，咕咕喵就感觉肩膀快要废掉了。

“喵！骨钉大人！我错了，求求你不要就这样丢下我呀喵……”

“当然不会就这样丢下你的。”骨钉笑着摸了摸猫娘的脸蛋：“因为最重要的玩具还没装完呢，现在硬起来吧。”

“喵喵喵？”

“我说啊，让你的小肉棒硬起来。”说着，骨钉就伸手握住了咕咕喵的肉棒，开始上下套弄：“诶嘿，咕咕喵还挺敏感的嘛，一下子就硬起来了！”

咕咕喵那无处可逃的肉棒在骨钉的小手中变成各种形状，不一会儿就被玩弄的差不多完全充血了。一松手，猫娘的小肉棒就吧唧一声贴在箱子的内壁上，笔直地指向天空。

骨钉立刻抄起手钻，贴着猫娘的小肉棒在亚克力板上钻了六个小孔，随后用三根尼龙扎带穿过小孔，将咕咕喵的肉棒像标本一样固定在箱子内壁上。

骨钉又迈到箱子外面，接下来只剩一些收尾工作了。

在咕咕喵阴囊紧贴的位置恰好有一块圆形的盖板。取掉之后，咕咕喵的小肉球就从圆孔中露了出来。

骨钉小心地将猫娘的一对肉球拉出来。又从工具箱中捧出一只银色的金属环，分成两半，再卡在咕咕喵阴囊根部合拢。如此一来咕咕喵的肉球就被卡死在箱子外面了。

冰凉的金属触感和沉甸甸的重量全部施加在猫娘脆弱的性器上，让这只动弹不得的猫娘既兴奋又害怕。当骨钉把弹簧片卡入锁环，将环彻底锁死之后。咕咕喵竟然兴奋得直接高潮了！黏糊糊的精液冲破三层扎带的阻挠，从肉棒顶端艰难的流了出来。

“你很勇欸！”

咕咕喵刚从高潮中清醒过来就听到骨钉的声音，立刻吓得出了一身冷汗，原本干燥清爽的胶衣里变得黏黏糊糊。

“因为你刚刚的高潮，我失去了八十五点积分和即将到手的年终奖。”骨钉眯起眼睛，露出和善的微笑：“真是的，明明只差最后一步安装好，你就可以随意高潮了。为什么偏偏在这种时候……”

“对不起骨钉大人！喵喵不是故意的啊！”咕咕喵吓得浑身颤抖，不断向骨钉求饶。

“算了，先安装最后一个部件吧。”骨钉低下头，眼睛隐没在刘海里，看不清表情。

最后安装的是一只黑盒子，就简单地卡在咕咕喵正前方的箱子内壁上，正下方就是咕咕喵的肉棒。

“安装完成！再装上饲喂管和盖子就行了！”说着，骨钉伸手按动了某个开关。

黑色的箱子突然发出可疑的嗡嗡声，随后从箱子里降下一只软刷，旋转着向猫娘的龟头慢慢靠近。

“救……救命！骨钉大人！怎么处置我都可以！请把我变成新能源电池吧！只有这个不行！”猫娘立刻吓得大叫起来。

“先别吵，为了接下来的几年你能过的轻松一些，我要说的话你最好全记在心里：你知道为什么不给你佩戴口球吗？”

“是为了安装饲喂管吗？”

“错！是为了让你自己努力争取免除龟头责的机会。看到那个软刷了吗？20 分钟后它才会开始研磨你那根可怜的小肉棒。只要有人肯摸摸你固定在箱子外面的蛋蛋。刷子就会复位。这样你就重新获得了 20 分钟的休息时间。明白了吗？”

“明……明白了”

“当然处罚肯定有个期限的，当你用精液把这个箱子装满的时候，就可以放出来了。你也可以祈求别人射到箱子里帮你减少刑期。顺便一提，一天只有两次排泄机会，其他时间如果不小心漏出来就要从头开始积累哦。”

“把箱子装满？这……饶了猫猫吧，猫猫办不到的！”

“别灰心，只不过 2000 升而已。对了，我再以个人的名义送给你一件装饰。这本来是安装茄子的时候要用到的，不过现在就作为咕咕喵刚才失控高潮得惩罚吧。到时候我再买一个给茄子用好了。”

骨钉从工具箱中取出一张永久性纹身贴，小心地贴在咕咕喵的蛋蛋上。这种纹身贴的目的不仅仅是为了在皮肤上印下图案，而是内侧的细小针状晶体会不停地摩擦与之接触的皮肤。因此能够在不伤害皮肤的作用下，带来强烈的刺痒感。

由于蛋蛋被骨钉摸到，龟头责的软刷一下子弹了回去。但咕咕喵一点也不高兴，因为一阵令人发疯的刺痒同时从蛋蛋上传来。咕咕喵疯狂地扭动着身体，却完全无法逃离这痛苦的地狱。

“感觉到了吧！是废弃处理的贴纸！这样一来让别人挠蛋蛋的理由又多了一个哦！”

“喵呜！！！！”咕咕喵已经失去了语言能力，连求饶都不会了，只能像只小喵咪一般不停地叫着。

咕咕喵那敏感的乳头已经膨胀得像真正的女孩子一般，但这个咕咕喵最喜欢的部位如今却无法让他再次舒服起来，毕竟本人都只能被束缚在箱子里了呢。

“永别了，可怜的小猫咪～”不顾咕咕喵凄惨的嚎叫，骨钉从外面锁上了箱子的门。没有理会四周因好奇而围观的人群，便上了汽车。

“走吧小茄子，接下来轮到你了。”

path: chapters/1.2 - 小作品/7 - 装饰用咕咕喵.md


code:
# 隐藏
# 欢迎撰稿
微小说集欢迎撰稿。若要参与，请[点此查看撰稿须知](./../META/撰稿须知及简易-Markdown-教程.html)。

path: chapters/1.3 - 微小说集/0 - 欢迎撰稿.md


code:
- 作者：琳 缇佩斯，久川綾瀬（编辑）
- 标签：贞操带（女），强制高潮（女），百合，不可脱下的穿戴物（女），强制刺激贞操带（女），阴道插入（女），按摩棒（女），含有女性，未来

# G 点刺激贞操带
“好了好了，这次的位置应该对了。”

她打开了制造器的柜门，从里面取出了刚刚制造完成的贞操带，并递给了我。和之前的失败品一样，这条贞操带的裆部向内延伸出了一只短短的牙刷形状的按摩器。

“我还是觉得力度不够。”

我接过了贞操带，将光着的腿迈入了贞操带的洞中。

“加大了力度会降低续航时间的嘛。毕竟按照现在这样的力度不加强的话大概可以持续 7 天。”

“可是，这个强度一点感觉都没有，我真的不觉得是位置没找对。而且，就算是真的没找对，不也应该是确保找对了以后再调节力度吗？”

说着，我将贞操带提到了大腿两侧，并将按摩器对准了我那充满了之前测试时留下的润滑液的阴道。

“啊呀... 都差不多啦...”

“什么差不多啊！而且，为什么还在测试的阶段就已经把锁做好了？上锁还那么费事，临时弄个搭扣不就好了？最后全部调试完了再装你那个贼复杂的锁岂不是可以省下很多时间？”

我将按摩棒缓缓推进了我的阴道。和之前一样，我感觉到按摩器的顶端顶在了我的 G 点上。

“确实呢...”

“和上次感觉一模一样。”

我用力将贞操带两侧的腰带向里一拉，扣在了小腹前的锁芯内。贞操带的腰带是根据我的体型量身定制的，因此贴合得非常紧实。

“嗯。好啦，上锁。”

她一边说着，一边从桌子上拿起了那张测试时一直在用的电子密钥卡。因为验证系统的私钥在制造完这张密钥卡后就删除了，这张卡目前存着能够打开或者上锁我身上的贞操带的私钥。

她将密钥卡移到了我的贞操带的锁芯边。随着“滴”的一声，我知道，上锁完成了。

“唉，我跟你说，你听我的，下次加、嗯！！！”

不出所料，上锁完成后，震动很快就会开始。只不过，不知道为什么，这一次震动带来的快感比之前的强十倍都不止。突然受到猛烈快感的我本能地用手捂住了自己的下体。

“嗯？你没事...等下，成功了？”

大量的快感从我的体内一阵阵地传来。我感觉就像是有人将输出快乐的电极直接接在了我的神经上一样。无法抵御快感的我很快跪倒了下来。

“你... 你调... 嗯、调强了吗？...哈...”

怎么可能... 怎么会这么强烈？

“没有啊... 这次位置找对了吗？如果找对了话就要...”

“别... 嗯、快、快停下... 太、哈... 太强、要、要去...”

我疯狂地用手拉扯着贞操带的底部，试图将按摩棒拉出来。然而，不出所料，贞操带牢牢地锁在我身上，纹丝不动。

“既然这样的话... 按照我们的约定...”

说着，她将电子密钥卡拿了起来。

“别、别管约定、啊...”

眼看灾难就要发生，我抵抗着猛烈的快感，努力地扑向了她，试图夺回密钥卡。

然而，她用左手一把抱住了我的身体，然后用右手将密钥卡塞入了一旁早已准备好的碎纸机中。

“不啊！哈、、嗯！啊、去、啊、、、”

我一边眼睁睁地看着释放自己的最后希望慢慢地被碎纸机粉碎，一边在她怀中颤抖着达到了高潮。

“抱歉哦，约定就是约定。”

爱液从贞操带上预留的孔中流出，淌到了地上。

不过即便是达到了高潮，阴道内的按摩器依然在以同样的频率工作着，准备将我送下一次高潮。

“啊...快拿下来...”

我在她的怀中挣扎着。然而，面对我难堪的样子，她只是轻轻地用她那只该死的右手抚摸着我的头。

“电用完了就能取下来了哦。”

path: chapters/1.3 - 微小说集/1 - G 点刺激贞操带.md