Author SHA1 Message Date
layue13 b51852c15b ci: ignore docs/reports-only pushes; remove reports/ (GTNHLib#67)
Build / build (push) Successful in 3m6s
Build / publish (push) Successful in 1m35s
2026-11-08 07:40:45 +00:00
layue13 9c7845580b Merge pull request 'perf: a tank looks each neighbour tile up once per tick' (#32) from perf/reuse-block-lookups into main
Build / build (push) Successful in 1m57s
Build / publish (push) Successful in 2m5s
2026-11-07 18:16:40 +00:00
layue13andClaude Sonnet 5.5 3b4bc76cc6 perf: a tank looks each neighbour tile up once per tick
Build / build (push) Skipped
Build / publish (push) Skipped
Build / build (pull_request) Successful in 5m55s
Build / publish (pull_request) Skipped
updateEntity asked the world for the tile above up to three times and the tile
below up to two times per tick; the blocks around cannot change between those
calls (a taint explosion earlier in the tick is handled before the first
lookup), so each is read once. Results identical, pinned by a test that runs
the old lookup sequence next to the new one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 01:59:24 +08:00
layue13 579c7064e2 Merge pull request 'perf: creative node verifies its registration instead of trusting it forever' (#31) from perf/creative-node-self-heal into main
Build / build (push) Successful in 3m31s
Build / publish (push) Successful in 1m59s
2026-11-07 16:02:53 +00:00
layue13andClaude Sonnet 5.5 0ae97629e3 perf: creative node verifies its registration instead of trusting it forever
Build / build (push) Skipped
Build / publish (push) Skipped
Build / build (pull_request) Successful in 2m11s
Build / publish (pull_request) Skipped
#29 registered the location once. The old per-tick rewrite also repaired the shared
TileNode.locations map when another mod cleared or altered it. One lookup and four
int compares per tick restore that: generateId runs again as soon as the entry is
missing or different.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 23:10:59 +08:00
layue13 206d7579ae Merge pull request 'perf: hyper node creator skips redundant aspect rewrites' (#30) from perf/hyper-node-creator into main
Build / build (push) Successful in 54s
Build / publish (push) Successful in 1m5s
2026-11-07 06:20:36 +00:00
layue13andClaude Sonnet 5.5 34f98ad930 perf: hyper node creator rewrites the node only when it no longer holds the wanted aspects
Build / build (push) Skipped
Build / publish (push) Skipped
Build / publish (pull_request) Skipped
Build / build (pull_request) Successful in 43s
Closes TDLM/thaumicadditions#16

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 14:19:39 +08:00
layue13 a0e3d1751f Merge pull request 'perf: creative node registers once, no server stack walk' (#29) from perf/creative-node-registry into main
Build / build (push) Successful in 51s
Build / publish (push) Successful in 45s
2026-11-07 06:18:49 +00:00
26 changed files with 319 additions and 472 deletions

No files matched your search

+5
View File
@@ -2,6 +2,11 @@ name: Build
on:
push:
paths-ignore:
- '**.md'
- 'reports/**'
- '.github/ISSUE_TEMPLATE/**'
- '.gitignore'
pull_request:
workflow_dispatch:
inputs:
-22
View File
@@ -1,22 +0,0 @@
# F-001
- **状态**:修·已修复(会话合入 TDLM/thaumicadditions#4,2026-10-26)
- **范式**:B1
- **位置**:`.github/workflows/build.yml:3-5`、`:55`
- **证据**:
```yaml
on:
push:
branches: [main]
```
(`.github/workflows/build.yml:3-5`;全文件无 `concurrency:` 键)
```yaml
if: ${{ github.event_name == 'push' || inputs.publish == 'true' }}
```
(`:55`;`:24`/`:57` `timeout-minutes` 在位)
- **机制**:workflow 无 concurrency 组而 push 即进入 publish job,连续 push 并行构建并争发构件。
- **频率**:每次 push(CI 期)。
- **修法**:补 `concurrency: { group: ${{ github.workflow }}-${{ github.ref }}, cancel-in-progress: false }`(含发布步骤,取 false 保逐 commit 构件)。
- **风险 · 置信度**:无(仅消并发发布竞争) · 高
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9
- **修复**:会话合入 TDLM/thaumicadditions#4,2026-10-26。
-19
View File
@@ -1,19 +0,0 @@
# F-002
- **状态**:修·已修复(会话合入 TDLM/thaumicadditions#4,2026-10-26)
- **范式**:B1
- **位置**:`build.gradle:19-28`
- **证据**:
```gradle
repositories {
maven {
name = 'TDLM Maven'
url = 'https://git.nest.potatolab.uk:8443/api/packages/TDLM/maven'
```
(`build.gradle:18-21`;块内无 `content { includeGroup … }`;对照 `settings.gradle:6-8` GTNH 块有 mavenContent 正例)
- **机制**:自建 TDLM maven 无 content 过滤,依赖解析对无关坐标反复探测 404。
- **频率**:每次依赖解析(构建期)。
- **修法**:maven 块补 `content { includeGroup("uk.potatolab") }`(或本仓实际 group 集合)。
- **风险 · 置信度**:无 · 高
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9
- **修复**:会话合入 TDLM/thaumicadditions#4,2026-10-26。
-16
View File
@@ -1,16 +0,0 @@
# F-003
- **状态**:修·已修复(会话合入 TDLM/thaumicadditions#4,2026-10-26)
- **范式**:T4
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/tileentity/TileVoidChest.java:55`
- **证据**:
```java
public void updateEntity() {}
```
(`:55`;全仓 `boolean canUpdate(` 覆写 0;1.7.10 `TileEntity.canUpdate()` 默认 true;注册见 `init/MTE.java:37`)
- **机制**:虚空箱有空体 updateEntity 且未关 tick 资格,世界内每箱每 tick 白付调度。
- **频率**:每 tick × 每虚空箱实例。
- **修法**:覆写 `canUpdate()` 返回 false(空体无副作用,零行为变更)。
- **风险 · 置信度**:无 · 高
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9;同族 F-004
- **修复**:会话合入 TDLM/thaumicadditions#4,2026-10-26。
-16
View File
@@ -1,16 +0,0 @@
# F-004
- **状态**:修·已修复(会话合入 TDLM/thaumicadditions#4,2026-10-26)
- **范式**:T4
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/tileentity/TileReturner.java:6`
- **证据**:
```java
public class TileReturner extends TileEntity {
```
(`:6`;类内无 `updateEntity`/`canUpdate`;`BlockReturner.java:22` `TileEntity.addMapping(TileReturner.class, …)`)
- **机制**:返回器仅存坐标 NBT,继承基类空 updateEntity,默认 canUpdate=true 仍进 tick 表。
- **频率**:每 tick × 每返回器。
- **修法**:覆写 `canUpdate()` 返回 false。
- **风险 · 置信度**:无 · 高
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9;同族 F-003
- **修复**:会话合入 TDLM/thaumicadditions#4,2026-10-26。
-19
View File
@@ -1,19 +0,0 @@
# F-005
- **状态**:观察·已修复(TDLM/thaumicadditions#11,2026-10-31:TESR 模型与贴图改为字段/静态常量,不再每帧新建模型(顺带消除每帧新编译显示列表))
- **范式**:F1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/renderer/RendererDarkGen.java:16`、`:22`;同型 `RendererHyperNodeCreator.java:17`、`:23`、`RendererInfuser.java:19`、`:24`、`RendererInfuserActivator.java:19`、`:37`、`RendererSymboller.java:17`、`:23`、`RendererThaumicGenerator.java:17`、`:23`、`RendererThaumicRestorer.java:16`、`:22`、`RendererSeal.java:41-48`、`RendererTankBasic.java:20`、`:32`、`TileRendererCreativeNode.java:22`、`:23`、`:34`(后者每帧 `new Random`/`Calendar.getInstance()`/RL 字符串拼接)
- **证据**:
```java
ResourceLocation textures = new ResourceLocation("thaumicadditions", "textures/models/DarkGen.png");
```
(RendererDarkGen.java:16)
```java
(new ModelDarkGen()).render((Entity)null, 0.0F, 0.0F, -0.1F, 0.0F, 0.0F, 0.0625F, false);
```
(`:22`;Seal 同帧多次 `new ModelSeal()` 见 RendererSeal.java:42/:48/:61/:72/:83)
- **机制**:多枚 TESR 每帧重建不可变 ResourceLocation 与 Model 实例;路径常量可 static final,Model 可字段复用。
- **频率**:每帧 × 可视 TESR 实例。
- **修法**:提 `private static final ResourceLocation`;Model 提实例字段复用(需确认无跨 TE 可变状态串扰,故本档观察)。
- **风险 · 置信度**:RL 提常量无;Model 复用需隔离论证 · 高(形态)
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9;P2 2026-10-29 c49b5f0 同型补记 `TileRendererCreativeNode.java:22`、`:23`、`:34`(计数不变);同族 F-006/F-016
-20
View File
@@ -1,20 +0,0 @@
# F-006
- **状态**:观察·已修复(TDLM/thaumicadditions#11,2026-10-31:Random 复用并 setSeed(序列不变),光环贴图预建,毫秒取 currentTimeMillis%1000)
- **范式**:F1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/tileentity/TileRendererCreativeNode.java:22-23`、`:34`
- **证据**:
```java
Random rnd = new Random((long)(tcn.getId() + "" + tcn.yCoord).hashCode() ^ 256L);
float rot = (float)(Calendar.getInstance().get(14) + rnd.nextInt(1000)) % 1000.0F;
```
(`:22-23`)
```java
Minecraft.getMinecraft().getTextureManager().bindTexture(new ResourceLocation("thaumicadditions", "textures/models/aura_" + (rnd.nextInt(2) + 1) + ".png"));
```
(`:34`)
- **机制**:创造节点 TESR 每帧 new Random、Calendar.getInstance、拼接 ResourceLocation;固定种子序列可预计算。
- **频率**:每帧 × 可视创造节点。
- **修法**:种子 Random/两张 aura RL 提字段;时间改 `System.currentTimeMillis()%1000` 或字段时钟。量级未证 ⇒ 观察。
- **风险 · 置信度**:低(动画相位需对齐) · 高(形态)
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9;同族 F-005
-18
View File
@@ -1,18 +0,0 @@
# F-007
- **状态**:观察·已驳回(TDLM/thaumicadditions#11,2026-10-31:每趟内爆炸会生成新奇点并改动实体,且每趟破坏随机方块,提出查询会改变吸取结果)
- **范式**:S1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/entity/EntitySingularity.java:70-73`
- **证据**:
```java
for(p = 0; p < 128; ++p) {
AxisAlignedBB ent = AxisAlignedBB.getBoundingBox(this.posX - (double)this.radius, this.posY - (double)this.radius, this.posZ - (double)this.radius, this.posX + (double)this.radius, this.posY + (double)this.radius, this.posZ + (double)this.radius);
l = this.worldObj.getEntitiesWithinAABB(Entity.class, ent);
l2 = this.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, ent);
```
(`:70-73`;`radius = 10` 见 `:22`;落地分支 `:56`)
- **机制**:奇点落地后每 tick 将同一 21³ 盒 AABB 查询重复 128 次×两类实体,盒有界但频率无 interval,乘子为设计放大。
- **频率**:每 tick × 每落地奇点 × 256 次查询(静态推断)。
- **修法**:单次双查询后在循环内处理;或降循环次数。行为/手感可能变 ⇒ 观察,量级需 profile。
- **风险 · 置信度**:中(牵引力/破坏节奏) · 高(形态)
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9
-16
View File
@@ -1,16 +0,0 @@
# F-008
- **状态**:观察·已驳回(TDLM/thaumicadditions#11,2026-10-31:加间隔会改变拾取时机)
- **范式**:S1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/event/sealing/SealEventCollectItems.java:25-26`
- **证据**:
```java
AxisAlignedBB aabb = AxisAlignedBB.getBoundingBox((double)(x - 16), (double)(y - 16), (double)(z - 16), (double)(x + 16), (double)(y + 16), (double)(z + 16));
List i = world.getEntitiesWithinAABB(EntityItem.class, aabb);
```
(`:25-26`;由 `TileSeal.updateEntity` `:62` 每 tick 调用)
- **机制**:收集印激活时每 tick 扫 33³ 盒掉落物,有半径无 interval/短路门。
- **频率**:每 tick × 每匹配收集印。
- **修法**:加 interval 或相位门;缩小盒需平衡裁决。量级未证 ⇒ 观察。
- **风险 · 置信度**:中(收集半径即玩法) · 高(形态)
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9
-19
View File
@@ -1,19 +0,0 @@
# F-009
- **状态**:观察·已驳回(TDLM/thaumicadditions#11,2026-10-31:配方缓存需槽位变更失效,反编译 TE 无干净钩子)
- **范式**:T1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/tileentity/TileInfuser.java:41-45`;`TileDarkInfuser.java:41-45`
- **证据**:
```java
List var5 = InfuserRecipeRegistry.complexOfRecipes.recipesOnTheRegularInfuser;
int foundRecipes = 0;
for(int i = 0; i < var5.size(); ++i) {
if(areItemStacksEqual(this.slots[1], ((InfuserRecipe)var5.get(i)).input1) && areItemStacksEqual(this.slots[2], ((InfuserRecipe)var5.get(i)).input2) && areItemStacksEqual(this.slots[3], ((InfuserRecipe)var5.get(i)).input3) && areItemStacksEqual(this.slots[4], ((InfuserRecipe)var5.get(i)).input4) && areItemStacksEqual(this.slots[5], ((InfuserRecipe)var5.get(i)).input5) && areItemStacksEqual(this.slots[6], ((InfuserRecipe)var5.get(i)).input6) && (this.slots[0] == null || areItemStacksEqual(this.slots[0], ((InfuserRecipe)var5.get(i)).output) && this.slots[0].stackSize + ((InfuserRecipe)var5.get(i)).output.stackSize < this.slots[0].getMaxStackSize())) {
```
(TileInfuser.java:41-45;Dark 表同型 `:41-45`;注册约 7+4 条见 `init/MIR.java`)
- **机制**:注能/暗注能每 tick 无缓存全表线性匹配六格输入;表近静态。
- **频率**:每 tick × 每台注能器。
- **修法**:槽位指纹缓存命中配方下标,槽变更失效。表小量级未证 ⇒ 观察。
- **风险 · 置信度**:低 · 高(形态)
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9
-15
View File
@@ -1,15 +0,0 @@
# F-010
- **状态**:观察·已修复(TDLM/thaumicadditions#11,2026-10-31:每向邻居 TE 只取一次)
- **范式**:T1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/tileentity/TileTileBooster.java:27`
- **证据**:
```java
TileEntity te = this.worldObj.getTileEntity(x, y, z) != null && !(this.worldObj.getTileEntity(x, y, z) instanceof TileTileBooster)?this.worldObj.getTileEntity(x, y, z):null;
```
(`:27`;六向循环 `:22-33`)
- **机制**:加速器每 tick 每向对同坐标最多三次 getTileEntity。
- **频率**:每 tick × 6 向 × 每加速器。
- **修法**:`TileEntity te = worldObj.getTileEntity(x,y,z)` 提局部一次。零行为;量级随实例未证 ⇒ 观察。
- **风险 · 置信度**:无 · 高
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9;同族 F-011/F-013
-15
View File
@@ -1,15 +0,0 @@
# F-011
- **状态**:观察·已修复(TDLM/thaumicadditions#11,2026-10-31:每向 TE 取一次;isModLoaded 静态常量;Botania 桥接单例;方向数组静态)
- **范式**:T1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/tileentity/TileThaumicGenerator.java:62-87`、`:71`
- **证据**:
```java
if(Loader.isModLoaded("Botania") && (new Botania()).canConnect(this.worldObj, this.xCoord, this.yCoord, this.zCoord, dirs[i])) {
```
(`:71`;同方法 `:62-82` 多处重复 `getTileEntity(...)`;`:85-86` 再次 `Loader.isModLoaded` + `new Botania()`)
- **机制**:发电机有能量时每 tick 六向重复 TE 查找,并反复 `Loader.isModLoaded` 与 `new Botania()`。
- **频率**:每 tick × 有能量的发电机 × 6 向。
- **修法**:`isModLoaded` 提 static final;Botania 桥接单例;getTileEntity 提局部。量级未证 ⇒ 观察。
- **风险 · 置信度**:低 · 高(形态)
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9;同族 F-010
-24
View File
@@ -1,24 +0,0 @@
# F-012
- **状态**:观察·已驳回(TDLM/thaumicadditions#11,2026-10-31:印面组合缓存需印面变更失效,语义难核)
- **范式**:T1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/tileentity/TileSeal.java:43`、`:57-62`
- **证据**:
```java
this.current = new SealCombo(new Seal[]{this.f});
```
(`:43`)
```java
List events = MSE.events.eventList;
for(int i = 0; i < events.size(); ++i) {
if(this.CompareSealCombos(this.f, ((SealEvent)events.get(i)).getSealCombination().getSeal1()) && this.CompareSealCombos(this.s, ((SealEvent)events.get(i)).getSealCombination().getSeal2()) && this.CompareSealCombos(this.t, ((SealEvent)events.get(i)).getSealCombination().getSeal3()) || this.current.length() == 3 && ((SealEvent)events.get(i)).getSealCombination().length() == 2 && ((SealEvent)events.get(i)).canWorkWith3rdSymbol(this.t)) {
SealEvent event = (SealEvent)events.get(i);
event.sealTick(this.worldObj, this.xCoord, this.yCoord, this.zCoord, this.f, this.s, this.t, this.extraNBT);
```
(`:57-62`)
- **机制**:封印每 tick 新建 SealCombo 并对近静态事件表线性匹配;印面未变时结果可记忆。
- **频率**:每 tick × 每封印方块。
- **修法**:印面指纹缓存 `currentWorkingComboID`,变更再全表扫。缓存语义需核对 ⇒ 观察。
- **风险 · 置信度**:低 · 中
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9
-16
View File
@@ -1,16 +0,0 @@
# F-013
- **状态**:观察·已修复(TDLM/thaumicadditions#11,2026-10-31:每向邻居 TE 只取一次)
- **范式**:T1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/piping/TileVisTube.java:61-75`、`:98`
- **证据**:
```java
if(this.worldObj.getTileEntity(x, y, z) instanceof ITubeConnectable) {
ITubeConnectable tube = (ITubeConnectable)this.worldObj.getTileEntity(x, y, z);
```
(`:61-62`;`:74-75` 同坐标再查 `ITubable`;`:98` 合并环再双查)
- **机制**:灵气管每 tick 六向对同坐标多次 getTileEntity 做连通与合并。
- **频率**:每 tick × 每管段 × 至多两轮六向。
- **修法**:每向 `TileEntity te = getTileEntity(...)` 一次后分支。零行为;管网密度未证 ⇒ 观察。
- **风险 · 置信度**:无 · 高
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9;同族 F-010
-19
View File
@@ -1,19 +0,0 @@
# F-014
- **状态**:边界·已修复(TDLM/thaumicadditions#11,2026-10-31:长度按包内剩余字节校验,超限解码失败)
- **范式**:N1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/playnet/packtetmng/PacketAuraVis.java:38`、`:45`
- **证据**:
```java
this.amts = new int[buf.readInt()];
```
(`:38`)
```java
this.aspects = new EnumAT[buf.readInt()];
```
(`:45`;分配后无 clamp)
- **机制**:两处数组长度直取网络 int,无上限;畸形包可驱动大分配。
- **频率**:每入站包。
- **修法**:记录不修(边界):协议本为短枚举列表;上限取值需安全侧/包维护者裁决。建议 clamp 至 `EnumAT` 基数后超限断连。
- **风险 · 置信度**:安全相邻 · 高(形态)
- **移交**:安全侧
- **来源**:P1 2026-10-26 2f7b9c9
-15
View File
@@ -1,15 +0,0 @@
# F-015
- **状态**:边界·已驳回(TDLM/thaumicadditions#11,2026-10-31:预约爆炸会改变时序分布)
- **范式**:T15
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/tileentity/TileTankBasic.java:45`
- **证据**:
```java
boolean ttb = rand.nextInt(1000) < 100 && ext > 0.5F;
```
(`:45`;`:43` `!isRemote` 门内每 tick)
- **机制**:污秽过半时每 tick 重掷约 10% 爆炸门,事件稀疏却抽样= tick×实例。
- **频率**:每 tick × 高污秽储罐。
- **修法**:记录不修:分布改倒计时会变爆炸时序;量级在污秽门后。若修则预约下次爆炸 tick。
- **风险 · 置信度**:中(时序) · 高(形态)
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9
-15
View File
@@ -1,15 +0,0 @@
# F-016
- **状态**:边界·已修复(TDLM/thaumicadditions#11,2026-10-31:十个 GUI 背景贴图 static final)
- **范式**:F1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/gui/GUIInfuser.java:24`;同型 `GUICondensator.java:26`、`GUIDarkGenerator.java:26`、`GUIDarkInfuser.java:26`、`GUIHyperNodeCreator.java:25`、`GUISealGlobe.java:38`、`GUIThaumicCrystallizer.java:24`、`GUIThaumicGenerator.java:24`、`GUIThaumicRestorer.java:25`、`GUIVoidChest.java:23`
- **证据**:
```java
Minecraft.getMinecraft().getTextureManager().bindTexture(new ResourceLocation("thaumicadditions", "textures/gui/guiinfuser.png"));
```
(GUIInfuser.java:24;其余 9 处同句式换路径)
- **机制**:十个容器 GUI 背景层每帧 new ResourceLocation;可提 static final。
- **频率**:每帧(GUI 打开期间)。
- **修法**:记录不修——量级算式:开窗期每帧 1 个 `ResourceLocation`(对象+两段字符串小写,约 0.2–0.5 µs)⇒ ≤0.003% 帧预算;若要收:提 `private static final ResourceLocation`,逐字节不变。
- **风险 · 置信度**:无 · 高
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9;同族 F-005
-15
View File
@@ -1,15 +0,0 @@
# F-017
- **状态**:观察·已修复(TDLM/thaumicadditions#11,2026-10-31:删除每包 INFO 日志)
- **范式**:D1
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/playnet/packtetmng/PacketAuraVis.java:25`
- **证据**:
```java
FMLLog.info("Received!", new Object[0]);
```
(`:25`;`onMessage` 内、try 块首行)
- **机制**:灵气同步包每包打 INFO,控制台同步 IO 随包频放大。
- **频率**:每包(客户端收包沿)。
- **修法**:删除该行或改 `FMLLog.fine`/debug 开关。若运维依赖该日志则裁决。
- **风险 · 置信度**:低(可观测性) · 高
- **移交**:无
- **来源**:P1 2026-10-26 2f7b9c9;同点位 F-014
-19
View File
@@ -1,19 +0,0 @@
# F-018
- **状态**:边界·已驳回(TDLM/thaumicadditions#11,2026-10-31:GUI 内测宽量级微,且脚本打不开这些 GUI 无法实测)
- **范式**:F13
- **位置**:`src/main/java/com/pengu/thaumcraft/additions/gui/GUIDarkGenerator.java:35`、`:36`;同型 `GUICondensator.java:39`、`GUIDarkInfuser.java:44`、`GUIHyperNodeCreator.java:31`、`:32`、`GUIInfuser.java:35`、`GUISealGlobe.java:76`、`GUIThaumicCrystallizer.java:34`、`GUIThaumicGenerator.java:32`、`GUIThaumicRestorer.java:33`;`src/main/java/com/pengu/thaumcraft/additions/utils/hud/TipMessage.java:35`(宽度环 `:69-81`,入口同包 `HUD.java:111`)
- **证据**:
```java
this.fontRendererObj.drawString(name, this.xSize / 2 - this.fontRendererObj.getStringWidth(name) / 2, 3, 4210752);
```
(GUIDarkGenerator.java:35;`:36` 对 `Lang.localiseRaw("container.inventory")` 取两次翻译再做同式居中)
```java
float decrease = this.getStringWidth(this.tipText) / 2.0F;
```
(TipMessage.java:35;`tipText` 为 final 字段 `:9`,宽度环 `:69-81` 逐字符 `loadGlyphTexture` → `renderEngine.bindTexture`)
- **机制**:9 个 `drawGuiContainerForegroundLayer` 与 HUD 提示每帧对不变串重测宽度、随即 `drawString` 内部再测一遍,偏移只用于居中。串与字体在开窗/提示存活期内不变(`GUISealGlobe.java:67`/`:70` 同帧重取 TE 与组合描述属另一面,本块不计)。
- **频率**:每帧(GUI 开启期)/每帧(提示存活期)。
- **修法**:记录不修——量级算式:GUI 侧 10–20 字符测宽约 100–300 ns,`GUIDarkGenerator:35-36` 另加两次 `localiseRaw`,`GUISealGlobe:76` 含每行翻译与测宽合计 ≤3 µs/帧(16.6 ms 的 ≤0.02%);HUD 侧 10–40 字符 × 每次 `bindTexture` 0.2–1 µs ⇒ 2–40 µs/帧(16.6 ms 的 ≤0.25%)且仅提示存活期间。若要收:开窗期把标题串与宽度缓存在 `initGui`/构造器,`TipMessage` 在构造器算好 `decrease`;宽度为定值,偏移逐像素不变。
- **风险 · 置信度**:无 · 高(形态)
- **移交**:无
- **来源**:P2 2026-10-29 c49b5f0
-135
View File
@@ -1,135 +0,0 @@
# thaumicadditions — reports
本目录为该模组 F-item 的**权威来源**(自 TDLMPack `reports/repos/` 迁入)。
## 索引
| ID | 状态 | 文件 |
|---|---|---|
| [F-001](F-001.md) | 修·已修复 | `F-001.md` |
| [F-002](F-002.md) | 修·已修复 | `F-002.md` |
| [F-003](F-003.md) | 修·已修复 | `F-003.md` |
| [F-004](F-004.md) | 修·已修复 | `F-004.md` |
| [F-005](F-005.md) | 观察·已修复 | `F-005.md` |
| [F-006](F-006.md) | 观察·已修复 | `F-006.md` |
| [F-007](F-007.md) | 观察·已驳回 | `F-007.md` |
| [F-008](F-008.md) | 观察·已驳回 | `F-008.md` |
| [F-009](F-009.md) | 观察·已驳回 | `F-009.md` |
| [F-010](F-010.md) | 观察·已修复 | `F-010.md` |
| [F-011](F-011.md) | 观察·已修复 | `F-011.md` |
| [F-012](F-012.md) | 观察·已驳回 | `F-012.md` |
| [F-013](F-013.md) | 观察·已修复 | `F-013.md` |
| [F-014](F-014.md) | 边界·已修复 | `F-014.md` |
| [F-015](F-015.md) | 边界·已驳回 | `F-015.md` |
| [F-016](F-016.md) | 边界·已修复 | `F-016.md` |
| [F-017](F-017.md) | 观察·已修复 | `F-017.md` |
| [F-018](F-018.md) | 边界·已驳回 | `F-018.md` |
未关闭项(不含已修复/已延后):**6**(F-007, F-008, F-009, F-012, F-015, F-018)
## 迁移备注
自 TDLMPack 报告拆分迁入;状态字段保持 Pack 原文。未来扫描不再把 scan.sh exit 99、Semgrep、PMD 作为必跑流水线工具(历史正文中的提及可保留,本 README 不要求再跑它们)。
## 一、概况
| 项 | 值 |
|---|---|
| 仓库 | TDLM/thaumicadditions(git 仓,`src/` 直源) |
| 分支 · HEAD | `main` · `c49b5f03fb07907632830659ff83da18ab824c8c`(P2 扫描时 HEAD) |
| 最近更新 | 2026-10-29(P2) |
| 形态 | Forge 1.7.10 TC4 附属内容 mod(`Thaumic Additions - 0.41`,P2 起按实仓):java 307 / 32,017 行(src/main,无 kt/scala/Mixin);updateEntity 20、TESR 16(`ClientProxy.java:63-78`);`@SubscribeEvent` 含 PlayerTick/ClientTick/HUD;SimpleNetworkWrapper 通道 `thaumicadditions-playnet`;实体 4;构建走 Stratum 组织插件(`.github/workflows/build.yml`) |
| 工具 | 人工形态画像+候选式 grep+命中点全文回读;P2 覆 F8–F15 与 J5/J6 |
**计数矩阵**(处置 × 状态;合计 18)
| 处置\状态 | 待处理 | 待裁决 | 已延后 | 已驳回 | 已修复 |
|---|---|---|---|---|---|
| 修 | 0 | 0 | 0 | 0 | 4 |
| 观察 | 0 | 0 | 0 | 4 | 6 |
| 边界 | 0 | 0 | 0 | 2 | 2 |
| **合计** | **14** | 0 | 0 | 0 | **4** |
**扫描轮次**
| 轮次 | 日期 | SHA | 范围 | 净增(修/观/边) |
|---|---|---|---|---|
| P1 | 2026-10-26 | 2f7b9c9 | 建档:B1+D1 必扫+TE/TESR/包/封印/奇点热入口 | 4/10/3 |
| P2 | 2026-10-29 | c49b5f0 | F8–F15+J5/J6 过堂;F-005 同型补记(计数不变) | 0/0/1 |
## 四、命中(已记)
| 范式 | 定位 | 备注 |
|---|---|---|
| B1 | F-001、F-002 | concurrency;maven content |
| T4 | F-003、F-004 | 空转 TE |
| F1 | F-005、F-006、F-016 | TESR/创造节点/GUI(P2 同型补记 `TileRendererCreativeNode`) |
| S1 | F-007、F-008 | 奇点;收集印 |
| T1 | F-009–F-013 | 配方/重复 getTE/封印/管 |
| D1 | F-017 | 包路径 INFO |
| N1 | F-014 | 安全相邻 |
| T15 | F-015 | 储罐抽样 |
| F13 | F-018 | GUI 前景与 HUD 提示每帧重测文本宽度(边界) |
## 五、排除面
| 范式 | 排除码 | 依据 |
|---|---|---|
| T2 | E-守卫在位 | 未见无变化守卫即恒 `markDirty`/`markBlockForUpdate` 的周期路径;创造节点脏写在 `mfu` 门内(TileCreativeNode.java:145-147) |
| T3 | E-形态全零 | 无全服实例对齐同一 `worldTime%N` 周期谓词的 tick 群 |
| T5 | E-玩家回执 | `TileTaintedCrops` 反射仅坏土掉落分支(`:24-29`);`Class.forName` 在 TA/API 启动或交互沿 |
| T6 | E-守卫在位 | 主要 TE 工作含 `isRemote` 门(如 TileDarkGen/Infuser/Crystallizer);BlockVisTube.playerTick 为方块碰撞箱语义非无门世界扫描 |
| T7 | E-设计本体 | `Util.setBlockMetadata` 用 flag 2(Util.java:31)为元数据同步本体;未见无变化双通道失效广播热路径 |
| T8 | E-形态全零 | 配方表为 ArrayList 线性匹配已记 T1(F-009);无拼接键/契约破损 Map 热查询 |
| T9 | E-形态全零 | 自研实体为奇点/焦点弹/粒子,无「在地静止仍全价移动」物品实体形态 |
| T10 | E-形态全零 | 无红石粉式逐级衰减重通知链 |
| T11 | E-形态全零 | 无锁+装箱/弱键双查型热查询间接层 |
| T12 | E-形态全零 | 无长期混入未就绪项的工作队列全表 drain |
| T13 | E-设计本体 | SealCombo 每 tick new 已并入 F-012(T1);不另立临时对象条 |
| T14 | E-形态全零 | 全仓 `.stream()` 热路径 0 |
| F2 | E-形态全零 | 未见每帧 NBT 反序列化/深拷贝/全树文本重编译链 |
| F3 | E-形态全零 | 无超集过滤缺候选索引的热查询 |
| F4 | E-形态全零 | 同帧同参重复昂贵求值未独立确认(重复 getTE 归 T1) |
| F5 | E-形态全零 | TESR 无每帧射线/无节流世界反查主形态 |
| F6 | E-产品形态 | 无自研网格面剔除;渲染走 Model/原版通道 |
| F8 | E-守卫在位 | 16 个 TESR 于 `ClientProxy.java:63-78` 绑定,全仓 0 处覆写 `getMaxRenderDistanceSquared` ⇒ 走原版默认 4096(64 格)封顶;模型为 1 格级小几何,64 格处 1 格≈12 px(1080p、垂直 FOV 70°:1080/(2·tan35°·64)),再裁即可辨 ⇒ 无可裁点;每帧 `new Model`/RL 归 F-005 |
| F9 | E-形态全零 | `glFinish`/`glReadPixels`/`glGetInteger`/`glGetFloat`/`glGetBoolean`/`glGetError`/`glGetUniformLocation*`/`glGetAttribLocation` 全仓 0 命中;无同步点与回读面 |
| F10 | E-形态全零 | `glNewList`/`glCallList`/`compileDisplayList`/`glGenLists` 0;23 处 `setRotationAngles` 全在 `ModelBase` 子类 `render()` 内按原版约定调用或仅 `super` 转发(`ModelSeal.java:60`、`:94`),非输入未变仍全价重建 |
| F11 | E-设计本体 | 11 处发射全为玩法事件/封印工作态上的固定小数目:封印 reddust 每次 1 枚(`SealEventDestroySoil.java:37`、`SealEventTillSoil.java:37`)、熔炼结果 20 枚一次性(`SealEventItemSmelt.java:68`,栈耗尽即 `setDead`)、修复 1 枚(`SealEventItemRepairer.java:36`)、`randomDisplayTick` ≤10 枚(`BlockReturner.java:54`)、API 单枚(`APIProvide.java:40`、`AbstractParticle.java:34`);无每实例每帧无上限倾倒与拖尾无界追加 |
| F12 | E-形态全零 | `glBufferData`/`glBufferSubData`/`glGenBuffers`/`BufferManagement` 0;几何只经 Tessellator 立即模式提交 |
| F14 | E-形态全零 | `glUseProgram`/`glUniform*`/`ARBShaderObjects` 0,无自建着色器程序与逐元素 uniform 设定 |
| F15 | E-启动期 | 图集申请全在注册沿:`registerBlockIcons`(`BlockDarkGen.java:59` 等)、`registerIcons`(`FocusProjectile.java:34`)、`setTextureName`(`init/MF.java:34` 等);运行期无重复申请 |
| S2 | E-设计本体 | 周期 `getBlock`/`getTileEntity` 均为已加载邻域作业读取,非「结果可弃的试读加载」 |
| N2 | E-产品形态 | SimpleNetworkWrapper 1.7.10 主线程投递标准形态;无裸 Netty 业务线程改世界 |
| N3 | E-形态全零 | 未见无 shutdown 线程池或静态滞留 World/TE 注册表只增 |
| N4 | E-启动期 | `OpenLibDownloadEngine`/`TACore` 静态下载为类加载/coremod 沿;本提取无 META-INF 清单,运行可达性未证 |
| N5 | E-形态全零 | 出站包直接写 ByteBuf,无每包整份中间拷贝 |
| L1 | E-启动期 | 无 transformer 每类建全树/脚本 O(k·n) 注册表重扫;OpenLib 见 N4 |
| H1 | E-设计本体 | Infuser 事件 post 有配方完成语义;非「无订阅仍每实体每 tick 构造」 |
| M1 | E-形态全零 | 无长生命周期回调捕获仅创建时需要的宿主 |
| M2 | E-形态全零 | 未见本可 interning 的大量等价常驻谓词/形状重复创建热面 |
| J1 | E-形态全零 | 无已确认热路径上可 final 却阻碍内联的短链形态立案 |
| J2 | E-形态全零 | 热路径无定幂 `Math.pow`/仅比较用开方等未廉价化算式立案 |
| J3 | E-形态全零 | `parkNanos`/`yield`/`onSpinWait` 0 |
| J4 | E-形态全零 | 无基本类型数组同构手写循环可换批量原语的热面 |
| J5 | E-形态全零 | 粒子/TESR 非「同纹理逐元素 startDrawing…draw 成对未合并」主形态;P2 复核全仓 3 处 `startDrawingQuads` 各为单 quad 批(`RendererInfuser.java:69`、`TileRendererCreativeNode.java:48`、`HUD.java:191`) |
| J6 | E-形态全零 | 未见同一拍同源多谓词可融合的独立遍历立案;P2 复核封印路径为单趟扫事件表(`TileSeal.java:57-62`,频次已记 F-012) |
## 六、规则反馈
| 范式 | 盲区/建议 | 本仓锚点 |
|---|---|---|
| F1 | 反编译 TESR 常「每帧 new Model + new ResourceLocation」成对出现,规则文案可明示 Model 与 RL 同判 | F-005 RendererDarkGen.java:16/:22 |
| T1 | `Loader.isModLoaded` + `new` 桥接对象出现在 updateEntity 六向环,易被只扫配方表的规则漏掉 | F-011 TileThaumicGenerator.java:71 |
| S1 | 「盒有界但循环内重复查询同一盒」应明示计入频率乘子,勿因 radius 字面量有界直接排除 | F-007 EntitySingularity.java:70 |
| T4 | 1.7.10 默认 `canUpdate()==true`;无覆写 + 空/继承空 updateEntity 即命中,勿写「默认 false」 | F-003/F-004 |
| F8 | 1.7.10 `TileEntity.getMaxRenderDistanceSquared()` 默认 4096(64 格)即原版门;未覆写应记为门在位正例,勿把「没写 if(distance)」当缺门 | `ClientProxy.java:63-78` |
| F11 | 拟补判定款:固定小数目(1/≤10/一次性 20)且挂在玩法事件或工作态上的点光源发射,不算「无预算倾倒」;不认这条会把封印类与 `randomDisplayTick` 形态全拖进复核面 | `SealEventDestroySoil.java:37`、`BlockReturner.java:54` |
| F13 | 证据强度仍标「来源数 0」;本仓 9 个 GUI 前景+HUD 逐帧 `getStringWidth`(含 HUD 逐字符 `bindTexture`)可回填来源数,并明示「测宽=排版面、`drawString` 内部重测仍算一次」 | `GUIThaumicGenerator.java:32`、`TipMessage.java:35` |
## 七、修订史
| 日期 | 轮次 | 变更 |
|---|---|---|
| 2026-10-26 | P1 | 建档:修 4 / 观 10 / 边 3;源树 HEAD `2f7b9c9696ee6cceb11d93c54592fed30ca218de` |
| 2026-10-29 | P2 | F8–F15+J5/J6 过堂(SHA `c49b5f0`):F13 立 F-018(边界,GUI 前景 9 处+`TipMessage` 逐帧测宽);F8/F9/F10/F11/F12/F14/F15 入排除面;F-005 同型补记 `TileRendererCreativeNode`(计数不变);F-016 修法补量级算式(处置/状态不变);概况改按 git 实仓(HEAD `c49b5f0`@`main`),矩阵补合计行 → 边 3→4、合计 17→18、未关闭 13→14 |
@@ -45,12 +45,33 @@ public class TileCreativeNode extends TileVisNode implements INode, IWandable {
if(!this.hasUpdatedOnce) {
this.hasUpdatedOnce = !this.hasUpdatedOnce;
} else if(!this.locationRegistered) {
} else if(!this.locationRegistered || !this.isRegistrationIntact()) {
this.generateId();
}
}
/**
* True while the shared location map still holds this node's entry unchanged. The old code rewrote the entry every
* tick, which also repaired it after another mod cleared or altered the map; checking it is one lookup and four
* int compares, and generateId runs again as soon as the entry is missing or different.
*/
private boolean isRegistrationIntact() {
if(this.id == null || TileNode.locations == null || this.worldObj == null) {
return false;
}
java.util.List entry = (java.util.List)TileNode.locations.get(this.id);
if(entry == null || entry.size() != 4) {
return false;
}
return Integer.valueOf(this.worldObj.provider.dimensionId).equals(entry.get(0))
&& Integer.valueOf(this.xCoord).equals(entry.get(1))
&& Integer.valueOf(this.yCoord).equals(entry.get(2))
&& Integer.valueOf(this.zCoord).equals(entry.get(3));
}
public void invalidate() {
this.unregisterLocation();
super.invalidate();
@@ -35,25 +35,17 @@ public class TileHyperNodeCreator extends TileThaumcraft implements IInventory,
}
}
TileNode var5 = this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord) != null && this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord) instanceof TileNode?(TileNode)this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord):null;
net.minecraft.tileentity.TileEntity above = this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord);
TileNode var5 = above instanceof TileNode?(TileNode)above:null;
this.node = var5 != null;
if(this.node) {
AspectList list = new AspectList();
int corner;
for(corner = 0; corner < Aspect.getPrimalAspects().size(); ++corner) {
list.add((Aspect)Aspect.getPrimalAspects().get(corner), totalItems);
// the node is only rewritten when it no longer holds the wanted amounts (drained, or the stock changed)
if(!holdsExactly(var5.getAspects(), totalItems, this.primalsOnly)) {
var5.setAspects(wantedAspects(totalItems, this.primalsOnly));
}
if(!this.primalsOnly) {
for(corner = 0; corner < Aspect.getCompoundAspects().size(); ++corner) {
list.add((Aspect)Aspect.getCompoundAspects().get(corner), totalItems);
}
}
var5.setAspects(list);
if(this.worldObj.rand.nextBoolean()) {
corner = this.worldObj.rand.nextInt(8);
int corner = this.worldObj.rand.nextInt(8);
if(corner == 0) {
Thaumcraft.proxy.nodeBolt(this.worldObj, (float)this.xCoord, (float)this.yCoord + 2.0F, (float)this.zCoord, (float)this.xCoord + 0.5F, (float)this.yCoord + 1.5F, (float)this.zCoord + 0.5F);
}
@@ -90,6 +82,52 @@ public class TileHyperNodeCreator extends TileThaumcraft implements IInventory,
}
/** The aspect list a node above gets: every primal (and unless primalsOnly every compound) at {@code total}. */
public static AspectList wantedAspects(int total, boolean primalsOnly) {
AspectList list = new AspectList();
int i;
for(i = 0; i < Aspect.getPrimalAspects().size(); ++i) {
list.add((Aspect)Aspect.getPrimalAspects().get(i), total);
}
if(!primalsOnly) {
for(i = 0; i < Aspect.getCompoundAspects().size(); ++i) {
list.add((Aspect)Aspect.getCompoundAspects().get(i), total);
}
}
return list;
}
/** Whether {@code current} already equals {@link #wantedAspects(int, boolean)} without building it. */
public static boolean holdsExactly(AspectList current, int total, boolean primalsOnly) {
if(current == null) {
return false;
}
int expected = Aspect.getPrimalAspects().size() + (primalsOnly?0:Aspect.getCompoundAspects().size());
return current.size() == expected && allAmounts(current, total, primalsOnly);
}
private static boolean allAmounts(AspectList current, int total, boolean primalsOnly) {
for(int i = 0; i < Aspect.getPrimalAspects().size(); ++i) {
if(current.getAmount((Aspect)Aspect.getPrimalAspects().get(i)) != total) {
return false;
}
}
if(!primalsOnly) {
for(int i = 0; i < Aspect.getCompoundAspects().size(); ++i) {
if(current.getAmount((Aspect)Aspect.getCompoundAspects().get(i)) != total) {
return false;
}
}
}
return true;
}
public static boolean areItemStacksEqual(ItemStack s1, ItemStack s2) {
return s1 == null && s2 == null?true:s1 != null && s2 != null && s1.getItem() == s2.getItem() && s1.getItemDamage() == s2.getItemDamage();
}
@@ -46,14 +46,17 @@ public class TileTankBasic extends TileThaumcraft implements ITubable {
}
}
boolean var4 = this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord) instanceof TileTankBasic && this.vis + this.taint < this.capacity;
// each neighbour tile is looked up once; nothing between here and the uses changes the blocks around
net.minecraft.tileentity.TileEntity aboveTile = this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord);
boolean var4 = aboveTile instanceof TileTankBasic && this.vis + this.taint < this.capacity;
if(var4 && this.taint + this.vis > this.capacity - 5) {
MagicType var5 = Math.max(this.taint, this.vis) == this.vis?MagicType.VIS:MagicType.TAINT;
this.ejectVis(var5, ((ITubable)this.worldObj.getTileEntity(this.xCoord, this.yCoord + 1, this.zCoord)).acceptVis(var5, var5 == MagicType.VIS?this.vis:this.taint, ForgeDirection.DOWN), ForgeDirection.UP);
this.ejectVis(var5, ((ITubable)aboveTile).acceptVis(var5, var5 == MagicType.VIS?this.vis:this.taint, ForgeDirection.DOWN), ForgeDirection.UP);
}
if(this.worldObj.getTileEntity(this.xCoord, this.yCoord - 1, this.zCoord) instanceof TileTankBasic) {
TileTankBasic var6 = (TileTankBasic)this.worldObj.getTileEntity(this.xCoord, this.yCoord - 1, this.zCoord);
net.minecraft.tileentity.TileEntity belowTile = this.worldObj.getTileEntity(this.xCoord, this.yCoord - 1, this.zCoord);
if(belowTile instanceof TileTankBasic) {
TileTankBasic var6 = (TileTankBasic)belowTile;
if(var6.vis + var6.taint < var6.capacity - 5) {
for(int i = 5; i > 0; --i) {
if(this.vis >= i && var6.vis + var6.taint + i <= this.capacity - 5) {
@@ -71,6 +71,39 @@ public class CreativeNodeRegistryTest {
assertNotNull(TileNode.locations.get("0:3:70:5"));
}
@Test
public void aClearedMapIsRepairedOnTheNextTick() {
this.node.updateEntity();
this.node.updateEntity();
assertNotNull(TileNode.locations.get("0:3:70:5"));
TileNode.locations.clear(); // another mod or TC4 wipes the shared map
this.node.updateEntity();
assertNotNull(TileNode.locations.get("0:3:70:5"));
}
@Test
public void anAlteredEntryIsRestoredOnTheNextTick() {
this.node.updateEntity();
this.node.updateEntity();
ArrayList wrong = new ArrayList();
wrong.add(Integer.valueOf(9));
wrong.add(Integer.valueOf(9));
wrong.add(Integer.valueOf(9));
wrong.add(Integer.valueOf(9));
TileNode.locations.put("0:3:70:5", wrong);
this.node.updateEntity();
assertEquals(Integer.valueOf(3), TileNode.locations.get("0:3:70:5").get(1));
}
@Test
public void anIntactEntryIsNotRewritten() {
this.node.updateEntity();
this.node.updateEntity();
ArrayList first = TileNode.locations.get("0:3:70:5");
this.node.updateEntity();
assertSame(first, TileNode.locations.get("0:3:70:5"));
}
public static class FixtureWorld extends WorldServer {
FixtureWorld() {
@@ -0,0 +1,47 @@
package com.pengu.thaumcraft.additions.tileentity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import thaumcraft.api.aspects.Aspect;
import thaumcraft.api.aspects.AspectList;
/** TDLM/thaumicadditions#16: the node is only rewritten when it no longer holds what the creator wants. */
public class HyperNodeCreatorAspectsTest {
@Test
public void aFreshlyBuiltListIsRecognisedAsCurrent() {
for (boolean primalsOnly : new boolean[] { false, true }) {
for (int total : new int[] { 0, 1, 64, 5000 }) {
AspectList list = TileHyperNodeCreator.wantedAspects(total, primalsOnly);
assertTrue("total " + total + " primalsOnly " + primalsOnly,
TileHyperNodeCreator.holdsExactly(list, total, primalsOnly));
}
}
}
@Test
public void wantedListHasTheSameShapeAsBefore() {
AspectList all = TileHyperNodeCreator.wantedAspects(7, false);
assertEquals(Aspect.getPrimalAspects().size() + Aspect.getCompoundAspects().size(), all.size());
assertEquals(7, all.getAmount(Aspect.getPrimalAspects().get(0)));
assertEquals(Aspect.getPrimalAspects().size(), TileHyperNodeCreator.wantedAspects(7, true).size());
}
@Test
public void aDrainedOrChangedNodeIsRewritten() {
AspectList list = TileHyperNodeCreator.wantedAspects(10, false);
list.reduce(Aspect.getPrimalAspects().get(0), 3);
assertFalse(TileHyperNodeCreator.holdsExactly(list, 10, false));
assertFalse(TileHyperNodeCreator.holdsExactly(TileHyperNodeCreator.wantedAspects(10, false), 11, false));
assertFalse(TileHyperNodeCreator.holdsExactly(TileHyperNodeCreator.wantedAspects(10, true), 10, false));
assertFalse(TileHyperNodeCreator.holdsExactly(null, 10, false));
}
@Test
public void primalsOnlyDoesNotMatchAnUnrelatedFullList() {
assertFalse(TileHyperNodeCreator.holdsExactly(TileHyperNodeCreator.wantedAspects(10, false), 10, true));
}
}
@@ -0,0 +1,153 @@
package com.pengu.thaumcraft.additions.tileentity;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import net.minecraft.block.Block;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.minecraft.world.WorldProviderSurface;
import net.minecraft.world.WorldServer;
import org.junit.Test;
/** TDLM/thaumicadditions#20: a tank looks each neighbour tile up once per tick and moves vis/taint exactly as before. */
public class TileTankBasicLookupTest {
@SuppressWarnings("unchecked")
private static <T> T allocate(Class<T> type) throws Exception {
Constructor<?> ctor = sun.reflect.ReflectionFactory.getReflectionFactory()
.newConstructorForSerialization(type, Object.class.getDeclaredConstructor());
return (T) ctor.newInstance();
}
public static class CountingWorld extends WorldServer {
Map<Long, TileEntity> tiles;
int tileLookups;
int blockLookups;
CountingWorld() {
super(null, null, "tanks", 0, null, null);
}
static long key(int x, int y, int z) {
return ((long) x << 40) ^ ((long) y << 20) ^ z;
}
@Override
public TileEntity getTileEntity(int x, int y, int z) {
this.tileLookups++;
return this.tiles.get(key(x, y, z));
}
@Override
public Block getBlock(int x, int y, int z) {
this.blockLookups++;
return null; // equals Blocks.air, which is unset without a Minecraft bootstrap
}
@Override
public int getBlockMetadata(int x, int y, int z) {
this.blockLookups++;
return 0;
}
}
private static CountingWorld world() throws Exception {
CountingWorld world = allocate(CountingWorld.class);
world.rand = new Random(1);
Field provider = World.class.getDeclaredField("provider");
provider.setAccessible(true);
provider.set(world, new WorldProviderSurface());
world.isRemote = true; // no taint explosion branch
world.tiles = new HashMap<Long, TileEntity>();
return world;
}
private static TileTankBasic tank(CountingWorld world, int y, int vis, int taint) throws Exception {
TileTankBasic tank = allocate(TileTankBasic.class);
tank.setWorldObj(world);
tank.xCoord = 3;
tank.yCoord = y;
tank.zCoord = 5;
tank.capacity = 1005;
tank.vis = vis;
tank.taint = taint;
world.tiles.put(CountingWorld.key(3, y, 5), tank);
return tank;
}
/** The lookups of the old method, in order, on the same fields. */
private static void referenceUpdate(TileTankBasic t) {
World w = t.getWorldObj();
w.getBlock(t.xCoord, t.yCoord + 1, t.zCoord);
w.getBlock(t.xCoord, t.yCoord - 1, t.zCoord);
w.getBlockMetadata(t.xCoord, t.yCoord, t.zCoord);
boolean var4 = w.getTileEntity(t.xCoord, t.yCoord + 1, t.zCoord) instanceof TileTankBasic && t.vis + t.taint < t.capacity;
if (var4 && t.taint + t.vis > t.capacity - 5) {
TileTankBasic above = (TileTankBasic) w.getTileEntity(t.xCoord, t.yCoord + 1, t.zCoord);
int type = Math.max(t.taint, t.vis) == t.vis ? 0 : 1;
int q = above.acceptVis(type == 0 ? com.pengu.api.thaumicadditions.tubesyst.ITubable.MagicType.VIS : com.pengu.api.thaumicadditions.tubesyst.ITubable.MagicType.TAINT,
type == 0 ? t.vis : t.taint, net.minecraftforge.common.util.ForgeDirection.DOWN);
t.ejectVis(type == 0 ? com.pengu.api.thaumicadditions.tubesyst.ITubable.MagicType.VIS : com.pengu.api.thaumicadditions.tubesyst.ITubable.MagicType.TAINT, q,
net.minecraftforge.common.util.ForgeDirection.UP);
}
if (w.getTileEntity(t.xCoord, t.yCoord - 1, t.zCoord) instanceof TileTankBasic) {
TileTankBasic var6 = (TileTankBasic) w.getTileEntity(t.xCoord, t.yCoord - 1, t.zCoord);
if (var6.vis + var6.taint < var6.capacity - 5) {
for (int i = 5; i > 0; --i) {
if (t.vis >= i && var6.vis + var6.taint + i <= t.capacity - 5) {
var6.vis += i;
t.vis -= i;
}
if (t.taint >= i && var6.vis + var6.taint + i <= t.capacity - 5) {
var6.taint += i;
t.taint -= i;
}
}
}
}
}
private static int[] state(TileTankBasic... tanks) {
int[] out = new int[tanks.length * 2];
for (int i = 0; i < tanks.length; i++) {
out[2 * i] = tanks[i].vis;
out[2 * i + 1] = tanks[i].taint;
}
return out;
}
@Test
public void aStackOfTanksEndsInTheSameStateWithFewerLookups() throws Exception {
Random rnd = new Random(9);
for (int round = 0; round < 40; round++) {
int[] seedValues = new int[6];
for (int i = 0; i < 6; i++) {
seedValues[i] = rnd.nextInt(1000);
}
CountingWorld oldWorld = world();
CountingWorld newWorld = world();
TileTankBasic[] oldTanks = new TileTankBasic[3];
TileTankBasic[] newTanks = new TileTankBasic[3];
for (int i = 0; i < 3; i++) {
oldTanks[i] = tank(oldWorld, 64 + i, seedValues[2 * i], seedValues[2 * i + 1] % 500);
newTanks[i] = tank(newWorld, 64 + i, seedValues[2 * i], seedValues[2 * i + 1] % 500);
}
for (int tick = 0; tick < 30; tick++) {
for (int i = 0; i < 3; i++) {
referenceUpdate(oldTanks[i]);
newTanks[i].updateEntity();
}
org.junit.Assert.assertArrayEquals("round " + round + " tick " + tick, state(oldTanks), state(newTanks));
}
org.junit.Assert.assertTrue("the old method looks up at least as often", oldWorld.tileLookups >= newWorld.tileLookups);
assertEquals("new: two tile lookups per tank per tick", 2 * 3 * 30, newWorld.tileLookups);
assertEquals(oldWorld.blockLookups, newWorld.blockLookups);
}
}
}