rimbisect 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rimbisect-0.1.0/.gitignore +17 -0
- rimbisect-0.1.0/CHANGELOG.md +16 -0
- rimbisect-0.1.0/LICENSE +21 -0
- rimbisect-0.1.0/PKG-INFO +317 -0
- rimbisect-0.1.0/README.md +289 -0
- rimbisect-0.1.0/probe-src/Probe.cs +342 -0
- rimbisect-0.1.0/probe-src/RimbisectProbe.csproj +33 -0
- rimbisect-0.1.0/pyproject.toml +49 -0
- rimbisect-0.1.0/src/rimbisect/__init__.py +3 -0
- rimbisect-0.1.0/src/rimbisect/__main__.py +5 -0
- rimbisect-0.1.0/src/rimbisect/about.py +109 -0
- rimbisect-0.1.0/src/rimbisect/cli.py +449 -0
- rimbisect-0.1.0/src/rimbisect/errors.py +2 -0
- rimbisect-0.1.0/src/rimbisect/install.py +147 -0
- rimbisect-0.1.0/src/rimbisect/modlist.py +244 -0
- rimbisect-0.1.0/src/rimbisect/probe/About/About.xml +11 -0
- rimbisect-0.1.0/src/rimbisect/probe/Assemblies/RimbisectProbe.dll +0 -0
- rimbisect-0.1.0/src/rimbisect/report.py +236 -0
- rimbisect-0.1.0/src/rimbisect/search.py +223 -0
- rimbisect-0.1.0/src/rimbisect/signature.py +108 -0
- rimbisect-0.1.0/src/rimbisect/trial.py +424 -0
- rimbisect-0.1.0/tests/fake_rimworld.py +79 -0
- rimbisect-0.1.0/tests/fakegame.py +74 -0
- rimbisect-0.1.0/tests/test_about.py +73 -0
- rimbisect-0.1.0/tests/test_cli.py +363 -0
- rimbisect-0.1.0/tests/test_modlist.py +144 -0
- rimbisect-0.1.0/tests/test_report.py +75 -0
- rimbisect-0.1.0/tests/test_search.py +212 -0
- rimbisect-0.1.0/tests/test_signature.py +64 -0
- rimbisect-0.1.0/tests/test_trial.py +203 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.pytest_cache/
|
|
4
|
+
.venv/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
runs/
|
|
9
|
+
probe-src/bin/
|
|
10
|
+
probe-src/obj/
|
|
11
|
+
.tools/
|
|
12
|
+
.phase0/
|
|
13
|
+
acceptance/work/
|
|
14
|
+
acceptance/mapfail-src/bin/
|
|
15
|
+
acceptance/mapfail-src/obj/
|
|
16
|
+
acceptance/flood-src/bin/
|
|
17
|
+
acceptance/flood-src/obj/
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-09-30
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
- `rimbisect bisect` finds the mod, or the set of mods that only fail together, behind
|
|
8
|
+
an error you pick from the log, a piece of text, a regex, a slow start or a crash.
|
|
9
|
+
- Trials keep dependencies loaded and your load order intact, and run on a throwaway
|
|
10
|
+
copy of your Config folder, so your mod list, settings and saves are never written.
|
|
11
|
+
- A confirm run without the culprit finds separate causes of the same error.
|
|
12
|
+
- `rimbisect check` saves a known good list; the next bisect tries recently changed
|
|
13
|
+
mods first.
|
|
14
|
+
- `rimbisect mods` shows the mod list with missing dependencies and incompatibilities.
|
|
15
|
+
|
|
16
|
+
See the README for how it works and what it can't do.
|
rimbisect-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christiaan Bosch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rimbisect-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: rimbisect
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Find the RimWorld mod, or combination of mods, behind an error by relaunching the game on halves of your load order.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Booyaka101/rimbisect
|
|
6
|
+
Project-URL: Repository, https://github.com/Booyaka101/rimbisect
|
|
7
|
+
Project-URL: Issues, https://github.com/Booyaka101/rimbisect/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Booyaka101/rimbisect/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Christiaan Bosch <cbosch101@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: bisect,debugging,load-order,modding,mods,rimworld,troubleshooting
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Games/Entertainment
|
|
22
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: lxml>=5.2.2
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# rimbisect
|
|
30
|
+
|
|
31
|
+
Find the RimWorld mod, or combination of mods, behind an error, unattended.
|
|
32
|
+
|
|
33
|
+
You have 300 mods and a red error at startup, or a map that never finishes loading. The
|
|
34
|
+
usual fix is to disable half your list, start the game, see if the error is still there,
|
|
35
|
+
and repeat until you are down to one mod. That takes an evening and it is easy to get
|
|
36
|
+
wrong once dependencies are involved. rimbisect does the same halving for you. It
|
|
37
|
+
launches the game on dependency-safe parts of your mod list, reads each run's log,
|
|
38
|
+
closes the game and narrows down, until it can name the mod.
|
|
39
|
+
|
|
40
|
+
It never writes to your real mod list, settings or saves, and never reorders your mods.
|
|
41
|
+
The only thing it puts in the game folder is a small helper mod, `Mods\rimbisect-probe`,
|
|
42
|
+
which it deletes when the run ends.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
You need Windows, RimWorld 1.6 from Steam, and Python 3.11 or newer. If you don't have
|
|
47
|
+
Python, get it from [python.org](https://www.python.org/downloads/) and tick "Add
|
|
48
|
+
python.exe to PATH" in the installer. Then, in PowerShell:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
py -m pip install rimbisect
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If `rimbisect` is then "not recognized", Python's Scripts folder isn't on your PATH;
|
|
55
|
+
`py -m rimbisect` works the same everywhere. With pipx, `pipx install rimbisect` does it
|
|
56
|
+
in one go.
|
|
57
|
+
|
|
58
|
+
## Use it
|
|
59
|
+
|
|
60
|
+
Close RimWorld, make sure Steam is running (the game only loads Workshop mods through
|
|
61
|
+
it), and start a bisect:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
rimbisect bisect
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
rimbisect finds your install through Steam and takes the mod list you have active in the
|
|
68
|
+
game. It first runs your full list once and shows the errors it logged, grouped and
|
|
69
|
+
numbered. Type the number of the one you want gone, then leave it. (`--pick N` answers
|
|
70
|
+
ahead of time, but the numbers can shift between runs when errors come and go.) The game
|
|
71
|
+
opens and closes on its own for every trial; don't click in it, and don't use the PC for
|
|
72
|
+
games meanwhile. Expect 10 to 20 trials, each one a game start. A 200 mod list took 12
|
|
73
|
+
minutes on a fast PC; count on two to three times as long as your full list takes to load
|
|
74
|
+
into a map. Ctrl+C stops the run at any point and closes the game.
|
|
75
|
+
|
|
76
|
+
If you already know what the error says, skip the question. `--match-text` takes a piece
|
|
77
|
+
of the error as you see it in the log (`Player.log`, next to the `Config` folder
|
|
78
|
+
mentioned below); `--match` takes a regular expression:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
rimbisect bisect --match-text "doesn't correspond to any field"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This is a real run on a 200 mod list with a test mod that has a broken def at position
|
|
85
|
+
150. It was made for testing with `--config`, which is why the paths are in a repo; yours
|
|
86
|
+
go to `%LOCALAPPDATA%\rimbisect\runs`.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
rimbisect bisect --config acceptance\work\ModsConfig-200.xml --workdir acceptance\work\rb --match rimbisectNoSuchField
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Progress goes to the terminal as it happens:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
rimbisect 0.1.0: 200 active mods, 194 to search
|
|
96
|
+
run folder D:\Repos\ideas\rimbisect\acceptance\work\rb\runs\2026-09-30_12-34-49
|
|
97
|
+
trial 1 baseline 200 mods FAIL 78.0s
|
|
98
|
+
the full list took 78s; expect about 11 more trials for a single culprit, 20 for two mods that only fail together
|
|
99
|
+
trial 2 bisect 103 mods PASS 114.4s
|
|
100
|
+
trial 3 bisect 57 mods FAIL 8.3s
|
|
101
|
+
trial 4 bisect 31 mods PASS 41.5s
|
|
102
|
+
trial 5 bisect 23 mods PASS 54.8s
|
|
103
|
+
trial 6 bisect 20 mods FAIL 5.1s
|
|
104
|
+
trial 7 bisect 13 mods PASS 34.7s
|
|
105
|
+
trial 8 bisect 11 mods PASS 35.7s
|
|
106
|
+
trial 9 bisect 11 mods FAIL 4.6s
|
|
107
|
+
trial 10 bisect 9 mods PASS 39.5s
|
|
108
|
+
trial 11 bisect 7 mods FAIL 2.8s
|
|
109
|
+
trial 12 without the culprits 199 mods PASS 320.1s
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
and the report at the end:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
rimbisect 0.1.0 found the mods that cause the error
|
|
116
|
+
|
|
117
|
+
game D:\SteamLibrary\steamapps\common\RimWorld (1.6.4871 rev590)
|
|
118
|
+
mod list D:\Repos\ideas\rimbisect\acceptance\work\ModsConfig-200.xml (200 active, 194 searched)
|
|
119
|
+
error log matches /rimbisectNoSuchField/
|
|
120
|
+
run folder D:\Repos\ideas\rimbisect\acceptance\work\rb\runs\2026-09-30_12-34-49
|
|
121
|
+
|
|
122
|
+
CULPRIT rimbisect acceptance: broken def (rimbisect.acceptance.brokendef)
|
|
123
|
+
workshop -
|
|
124
|
+
folder D:\SteamLibrary\steamapps\common\RimWorld\Mods\rimbisect-acceptance-brokendef
|
|
125
|
+
modified 2026-09-30T09:23:26
|
|
126
|
+
|
|
127
|
+
Your mod list without 1 mod:
|
|
128
|
+
D:\Repos\ideas\rimbisect\acceptance\work\rb\runs\2026-09-30_12-34-49\ModsConfig.fixed.xml
|
|
129
|
+
note: the probe closed windows that paused the game in trials 2, 12: Dialog_ModFeatures, Dialog_UpdateFeatures
|
|
130
|
+
|
|
131
|
+
TRIALS 12 runs, 12m20s total
|
|
132
|
+
|
|
133
|
+
# label mods outcome time
|
|
134
|
+
1 baseline 200 FAIL 78.0s
|
|
135
|
+
| XML error: <rimbisectNoSuchField>1</rimbisectNoSuchField> doesn't correspond to any field in type ThingCategoryDef. Context: <ThingCategoryDef><def...
|
|
136
|
+
2 bisect 103 PASS 114.4s
|
|
137
|
+
...
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Trial 12 is the check at the end: the full list without the culprit, which no longer
|
|
141
|
+
shows the error. The two windows in the note are HugsLib's update news and the feature
|
|
142
|
+
popup of Achtung and Camera+, which would otherwise have kept the game paused.
|
|
143
|
+
|
|
144
|
+
Six of the 200 mods (Core, the five DLCs) are always loaded, so 194 were searched. The run
|
|
145
|
+
folder keeps `report.txt`, `report.json`, the fixed mod list and every trial's log. Old
|
|
146
|
+
run folders are safe to delete.
|
|
147
|
+
|
|
148
|
+
The simplest fix is to disable the culprit in the game's mod manager. The fixed list is
|
|
149
|
+
your list without the culprits and without the mods that need them; to use it, close
|
|
150
|
+
RimWorld, back up `ModsConfig.xml` in
|
|
151
|
+
`%USERPROFILE%\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios\Config`, and copy
|
|
152
|
+
`ModsConfig.fixed.xml` over it. To undo, put the backup back.
|
|
153
|
+
|
|
154
|
+
## What counts as the error
|
|
155
|
+
|
|
156
|
+
A trial fails when:
|
|
157
|
+
|
|
158
|
+
- the error you picked from the full list's log shows up again (the default). Numbers,
|
|
159
|
+
hex ids and quoted names are masked before comparing, and for an exception only its
|
|
160
|
+
type and the method it came from count, so the same error from a different pawn still
|
|
161
|
+
counts.
|
|
162
|
+
- a log line or error contains `--match-text TEXT`, or matches `--match REGEX`.
|
|
163
|
+
- the map takes longer than `--slower-than SECONDS` to be ready, for slow starts.
|
|
164
|
+
- the game crashes, exits early, gives up on the map or drops back to Core alone after a
|
|
165
|
+
load error, with `--crash-is-fail`. Without
|
|
166
|
+
it those trials are reported as CRASH, run again once (big lists do crash now and then
|
|
167
|
+
for no repeatable reason), and treated as passing if the game crashes again. With it
|
|
168
|
+
and without `--match`, `--match-text`, `--slower-than` or `--pick`, the crash itself is
|
|
169
|
+
what rimbisect hunts.
|
|
170
|
+
|
|
171
|
+
Only one of `--match`, `--match-text` and `--slower-than` can be given.
|
|
172
|
+
|
|
173
|
+
A trial passes when the map has run for `--settle` seconds of game time (20 by default)
|
|
174
|
+
without the error. Raise it if your error only shows up a while into the game. A PC that
|
|
175
|
+
runs the game too slowly to get there still stops after three times as long in real time
|
|
176
|
+
(at least a minute), and the report points out trials that got through less than half of
|
|
177
|
+
it. If the full list does not reproduce the error, rimbisect says so and stops.
|
|
178
|
+
|
|
179
|
+
A trial that gives no answer, because it hit `--timeout` or the game did not load all the
|
|
180
|
+
mods it was given, is run again once. If the same list gives no answer twice, rimbisect
|
|
181
|
+
stops with the status "inconclusive" and says which list it was. It also stops that way
|
|
182
|
+
when the mods it narrowed down to don't show the error when run alone, which is what an
|
|
183
|
+
error that only shows up some of the time looks like; `--repeats 3` helps there.
|
|
184
|
+
|
|
185
|
+
## How long it takes
|
|
186
|
+
|
|
187
|
+
Every trial is a full game start, so time depends on your PC and the size of the list.
|
|
188
|
+
On the run above (an i9-14900K, game on an NVMe SSD, RimWorld 1.6.4871) the full 199 mod
|
|
189
|
+
list took 5 minutes to reach a map and run it for 20 game seconds, a list of about 100
|
|
190
|
+
mods took under two minutes, and trials under 30 mods took 35 to 55 seconds. A failing
|
|
191
|
+
trial is often much quicker: the run stops the moment the error appears, and def errors
|
|
192
|
+
appear before the map is generated.
|
|
193
|
+
|
|
194
|
+
Trials needed after the first full run, measured with
|
|
195
|
+
[`acceptance/simulate.py`](https://github.com/Booyaka101/rimbisect/blob/main/acceptance/simulate.py) over every
|
|
196
|
+
single culprit and 300 random pairs:
|
|
197
|
+
|
|
198
|
+
| mods | one culprit | two mods that only fail together |
|
|
199
|
+
| ---: | --- | --- |
|
|
200
|
+
| 64 | 8.6 on average, at most 11 | 16.2 on average, at most 21 |
|
|
201
|
+
| 200 | 10.6 on average, at most 13 | 19.9 on average, at most 26 |
|
|
202
|
+
| 400 | 11.7 on average, at most 15 | 22.4 on average, at most 28 |
|
|
203
|
+
|
|
204
|
+
The real 200 mod run above needed 11, as the estimate after the full run said. For 400
|
|
205
|
+
mods, count on about 12 trials plus the full run. The first trials load half the list and
|
|
206
|
+
the last one nearly all of it, so they take most of the time; a single culprit in 400
|
|
207
|
+
mods should take somewhere around 25 to 40 minutes on a PC like this one. That is an
|
|
208
|
+
estimate from the 200 mod timings, not a measurement.
|
|
209
|
+
|
|
210
|
+
## How it works
|
|
211
|
+
|
|
212
|
+
Each trial runs the game with `-savedatafolder` pointing at the run folder, which holds a
|
|
213
|
+
copy of your `Config` folder (mod settings included), HugsLib's settings and the trial's
|
|
214
|
+
mod list. The copy is made again before every trial, so a setting one trial changes
|
|
215
|
+
doesn't leak into the next, and it is deleted when the run ends. The copied `Prefs.xml`
|
|
216
|
+
runs the game in a 1280x720 window with the sound off and the UI scale at 1. Your own
|
|
217
|
+
`ModsConfig.xml` and `Prefs.xml` are only read, and your saves are never opened.
|
|
218
|
+
|
|
219
|
+
For the run, rimbisect copies a small mod, `rimbisect-probe`, into the game's `Mods`
|
|
220
|
+
folder and deletes it afterwards, also on Ctrl+C. It loads last, writes errors and
|
|
221
|
+
progress to a file rimbisect watches, and closes the game once the map has settled. It
|
|
222
|
+
has no Harmony patches and does nothing when rimbisect did not start the game. If
|
|
223
|
+
rimbisect is killed hard, delete `Mods\rimbisect-probe` yourself or let the next run
|
|
224
|
+
replace it.
|
|
225
|
+
|
|
226
|
+
RimWorld stops logging for good after 10,000 messages, which a big list can reach while
|
|
227
|
+
loading. The probe keeps the game's count from getting there, and switches logging back
|
|
228
|
+
on if it was reached before the probe loaded, so a later error is still seen.
|
|
229
|
+
|
|
230
|
+
The probe also reports which mods the game actually loaded. If a trial is missing some
|
|
231
|
+
(usually because Steam was closed mid-run), it counts as no answer rather than a pass.
|
|
232
|
+
Windows that pause a new game, like HugsLib's update news or the feature popups of
|
|
233
|
+
Achtung and Camera+, are closed so the map actually runs; the report says which ones.
|
|
234
|
+
|
|
235
|
+
Every trial includes the mods that the mods under test need, so there are no "missing
|
|
236
|
+
dependency" errors that the full list doesn't have. Missing dependencies are never
|
|
237
|
+
invented; rimbisect warns about them instead, and about pairs of active mods that say
|
|
238
|
+
they are incompatible. A trial is your list with some mods left out, never reordered.
|
|
239
|
+
Mods given with `--keep` are loaded in every trial, together with what they need.
|
|
240
|
+
|
|
241
|
+
When neither half fails on its own, rimbisect narrows each half with the other one
|
|
242
|
+
loaded, so two or three mods that only break together come out as a set.
|
|
243
|
+
|
|
244
|
+
Once it has a culprit, rimbisect runs the list once more without it and the mods that
|
|
245
|
+
need it. If the error is still there, something else causes it too, and the search goes
|
|
246
|
+
on through the rest. The report lists each cause on its own.
|
|
247
|
+
|
|
248
|
+
`rimbisect check` runs your full list once and, if it gets through, remembers it as the
|
|
249
|
+
last good state. The next bisect first tries the list without the mods whose folders
|
|
250
|
+
changed since then (updates, new mods). `--since DATE` does the same without a check.
|
|
251
|
+
|
|
252
|
+
For errors that come and go, `--repeats N` runs a passing list again, up to N times,
|
|
253
|
+
before trusting it. Lists that gave different results are listed in the report.
|
|
254
|
+
|
|
255
|
+
rimbisect only kills the game process it started, by process id. The game runs in a
|
|
256
|
+
Windows job object, so it also closes if rimbisect is killed or its window is closed.
|
|
257
|
+
|
|
258
|
+
## Commands
|
|
259
|
+
|
|
260
|
+
| | |
|
|
261
|
+
| --- | --- |
|
|
262
|
+
| `rimbisect bisect` | Find the mods behind an error |
|
|
263
|
+
| `rimbisect check` | Run the full list once; if it gets through, save it as the last good state |
|
|
264
|
+
| `rimbisect mods` | Show the mod list the way rimbisect reads it, with warnings |
|
|
265
|
+
|
|
266
|
+
Every command takes these:
|
|
267
|
+
|
|
268
|
+
| Option | |
|
|
269
|
+
| --- | --- |
|
|
270
|
+
| `--game PATH` | RimWorld install folder. Found through Steam if omitted |
|
|
271
|
+
| `--config FILE` | `ModsConfig.xml` to test instead of the game's own. Only read |
|
|
272
|
+
| `--json` | Print JSON instead of text |
|
|
273
|
+
|
|
274
|
+
`bisect` and `check` also take these:
|
|
275
|
+
|
|
276
|
+
| Option | |
|
|
277
|
+
| --- | --- |
|
|
278
|
+
| `--workdir PATH` | Where runs and `last-good.json` go. Default `%LOCALAPPDATA%\rimbisect` |
|
|
279
|
+
| `--match REGEX` | Fail when an error or log line matches |
|
|
280
|
+
| `--match-text TEXT` | Fail when an error or log line contains TEXT, taken literally |
|
|
281
|
+
| `--slower-than SECONDS` | Fail when the map is not ready after this long |
|
|
282
|
+
| `--settle SECONDS` | Game seconds a map has to run without the error to pass. Default 20 |
|
|
283
|
+
| `--timeout MINUTES` | Give up on a single trial after this long. Default 20 |
|
|
284
|
+
|
|
285
|
+
and `bisect` these:
|
|
286
|
+
|
|
287
|
+
| Option | |
|
|
288
|
+
| --- | --- |
|
|
289
|
+
| `--pick N` | Hunt error N from the full list's errors without asking |
|
|
290
|
+
| `--keep ID[,ID]` | Load these packageIds in every trial. Core and DLCs always are |
|
|
291
|
+
| `--since DATE` | First try the list without mods changed after DATE, like `2026-09-01` |
|
|
292
|
+
| `--ignore-last-good` | Don't try the mods changed since the last good run first |
|
|
293
|
+
| `--repeats N` | Run a passing list up to N times, for errors that come and go |
|
|
294
|
+
| `--crash-is-fail` | Count a crash, early exit or failed map as the problem |
|
|
295
|
+
|
|
296
|
+
`rimbisect --version` prints the version.
|
|
297
|
+
|
|
298
|
+
Exit codes: 0 when culprits were found (or `check` passed), 1 when the full list did not
|
|
299
|
+
reproduce the error, the base game alone fails, the search was inconclusive, or `check`
|
|
300
|
+
failed, 2 for usage errors such as a missing install or RimWorld already running, 130
|
|
301
|
+
when interrupted.
|
|
302
|
+
|
|
303
|
+
## Limitations
|
|
304
|
+
|
|
305
|
+
- Windows only, and only the Steam version has been tried. The probe is built for 1.6.
|
|
306
|
+
- It finds errors that happen while the game starts and during the first `--settle`
|
|
307
|
+
seconds on a fresh map. Problems that need your save, a raid or a year of play are out
|
|
308
|
+
of reach.
|
|
309
|
+
- Settings of mods that keep them outside `Config` (other than HugsLib) are not copied,
|
|
310
|
+
so those mods run with their defaults.
|
|
311
|
+
- A problem that depends on load order, rather than on which mods are present, is only
|
|
312
|
+
found as the set of mods involved. rimbisect does not try other orders.
|
|
313
|
+
- RimWorld has to be closed while it runs, and the machine is busy the whole time.
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
MIT
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# rimbisect
|
|
2
|
+
|
|
3
|
+
Find the RimWorld mod, or combination of mods, behind an error, unattended.
|
|
4
|
+
|
|
5
|
+
You have 300 mods and a red error at startup, or a map that never finishes loading. The
|
|
6
|
+
usual fix is to disable half your list, start the game, see if the error is still there,
|
|
7
|
+
and repeat until you are down to one mod. That takes an evening and it is easy to get
|
|
8
|
+
wrong once dependencies are involved. rimbisect does the same halving for you. It
|
|
9
|
+
launches the game on dependency-safe parts of your mod list, reads each run's log,
|
|
10
|
+
closes the game and narrows down, until it can name the mod.
|
|
11
|
+
|
|
12
|
+
It never writes to your real mod list, settings or saves, and never reorders your mods.
|
|
13
|
+
The only thing it puts in the game folder is a small helper mod, `Mods\rimbisect-probe`,
|
|
14
|
+
which it deletes when the run ends.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
You need Windows, RimWorld 1.6 from Steam, and Python 3.11 or newer. If you don't have
|
|
19
|
+
Python, get it from [python.org](https://www.python.org/downloads/) and tick "Add
|
|
20
|
+
python.exe to PATH" in the installer. Then, in PowerShell:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
py -m pip install rimbisect
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If `rimbisect` is then "not recognized", Python's Scripts folder isn't on your PATH;
|
|
27
|
+
`py -m rimbisect` works the same everywhere. With pipx, `pipx install rimbisect` does it
|
|
28
|
+
in one go.
|
|
29
|
+
|
|
30
|
+
## Use it
|
|
31
|
+
|
|
32
|
+
Close RimWorld, make sure Steam is running (the game only loads Workshop mods through
|
|
33
|
+
it), and start a bisect:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
rimbisect bisect
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
rimbisect finds your install through Steam and takes the mod list you have active in the
|
|
40
|
+
game. It first runs your full list once and shows the errors it logged, grouped and
|
|
41
|
+
numbered. Type the number of the one you want gone, then leave it. (`--pick N` answers
|
|
42
|
+
ahead of time, but the numbers can shift between runs when errors come and go.) The game
|
|
43
|
+
opens and closes on its own for every trial; don't click in it, and don't use the PC for
|
|
44
|
+
games meanwhile. Expect 10 to 20 trials, each one a game start. A 200 mod list took 12
|
|
45
|
+
minutes on a fast PC; count on two to three times as long as your full list takes to load
|
|
46
|
+
into a map. Ctrl+C stops the run at any point and closes the game.
|
|
47
|
+
|
|
48
|
+
If you already know what the error says, skip the question. `--match-text` takes a piece
|
|
49
|
+
of the error as you see it in the log (`Player.log`, next to the `Config` folder
|
|
50
|
+
mentioned below); `--match` takes a regular expression:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
rimbisect bisect --match-text "doesn't correspond to any field"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
This is a real run on a 200 mod list with a test mod that has a broken def at position
|
|
57
|
+
150. It was made for testing with `--config`, which is why the paths are in a repo; yours
|
|
58
|
+
go to `%LOCALAPPDATA%\rimbisect\runs`.
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
rimbisect bisect --config acceptance\work\ModsConfig-200.xml --workdir acceptance\work\rb --match rimbisectNoSuchField
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Progress goes to the terminal as it happens:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
rimbisect 0.1.0: 200 active mods, 194 to search
|
|
68
|
+
run folder D:\Repos\ideas\rimbisect\acceptance\work\rb\runs\2026-09-30_12-34-49
|
|
69
|
+
trial 1 baseline 200 mods FAIL 78.0s
|
|
70
|
+
the full list took 78s; expect about 11 more trials for a single culprit, 20 for two mods that only fail together
|
|
71
|
+
trial 2 bisect 103 mods PASS 114.4s
|
|
72
|
+
trial 3 bisect 57 mods FAIL 8.3s
|
|
73
|
+
trial 4 bisect 31 mods PASS 41.5s
|
|
74
|
+
trial 5 bisect 23 mods PASS 54.8s
|
|
75
|
+
trial 6 bisect 20 mods FAIL 5.1s
|
|
76
|
+
trial 7 bisect 13 mods PASS 34.7s
|
|
77
|
+
trial 8 bisect 11 mods PASS 35.7s
|
|
78
|
+
trial 9 bisect 11 mods FAIL 4.6s
|
|
79
|
+
trial 10 bisect 9 mods PASS 39.5s
|
|
80
|
+
trial 11 bisect 7 mods FAIL 2.8s
|
|
81
|
+
trial 12 without the culprits 199 mods PASS 320.1s
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
and the report at the end:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
rimbisect 0.1.0 found the mods that cause the error
|
|
88
|
+
|
|
89
|
+
game D:\SteamLibrary\steamapps\common\RimWorld (1.6.4871 rev590)
|
|
90
|
+
mod list D:\Repos\ideas\rimbisect\acceptance\work\ModsConfig-200.xml (200 active, 194 searched)
|
|
91
|
+
error log matches /rimbisectNoSuchField/
|
|
92
|
+
run folder D:\Repos\ideas\rimbisect\acceptance\work\rb\runs\2026-09-30_12-34-49
|
|
93
|
+
|
|
94
|
+
CULPRIT rimbisect acceptance: broken def (rimbisect.acceptance.brokendef)
|
|
95
|
+
workshop -
|
|
96
|
+
folder D:\SteamLibrary\steamapps\common\RimWorld\Mods\rimbisect-acceptance-brokendef
|
|
97
|
+
modified 2026-09-30T09:23:26
|
|
98
|
+
|
|
99
|
+
Your mod list without 1 mod:
|
|
100
|
+
D:\Repos\ideas\rimbisect\acceptance\work\rb\runs\2026-09-30_12-34-49\ModsConfig.fixed.xml
|
|
101
|
+
note: the probe closed windows that paused the game in trials 2, 12: Dialog_ModFeatures, Dialog_UpdateFeatures
|
|
102
|
+
|
|
103
|
+
TRIALS 12 runs, 12m20s total
|
|
104
|
+
|
|
105
|
+
# label mods outcome time
|
|
106
|
+
1 baseline 200 FAIL 78.0s
|
|
107
|
+
| XML error: <rimbisectNoSuchField>1</rimbisectNoSuchField> doesn't correspond to any field in type ThingCategoryDef. Context: <ThingCategoryDef><def...
|
|
108
|
+
2 bisect 103 PASS 114.4s
|
|
109
|
+
...
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Trial 12 is the check at the end: the full list without the culprit, which no longer
|
|
113
|
+
shows the error. The two windows in the note are HugsLib's update news and the feature
|
|
114
|
+
popup of Achtung and Camera+, which would otherwise have kept the game paused.
|
|
115
|
+
|
|
116
|
+
Six of the 200 mods (Core, the five DLCs) are always loaded, so 194 were searched. The run
|
|
117
|
+
folder keeps `report.txt`, `report.json`, the fixed mod list and every trial's log. Old
|
|
118
|
+
run folders are safe to delete.
|
|
119
|
+
|
|
120
|
+
The simplest fix is to disable the culprit in the game's mod manager. The fixed list is
|
|
121
|
+
your list without the culprits and without the mods that need them; to use it, close
|
|
122
|
+
RimWorld, back up `ModsConfig.xml` in
|
|
123
|
+
`%USERPROFILE%\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios\Config`, and copy
|
|
124
|
+
`ModsConfig.fixed.xml` over it. To undo, put the backup back.
|
|
125
|
+
|
|
126
|
+
## What counts as the error
|
|
127
|
+
|
|
128
|
+
A trial fails when:
|
|
129
|
+
|
|
130
|
+
- the error you picked from the full list's log shows up again (the default). Numbers,
|
|
131
|
+
hex ids and quoted names are masked before comparing, and for an exception only its
|
|
132
|
+
type and the method it came from count, so the same error from a different pawn still
|
|
133
|
+
counts.
|
|
134
|
+
- a log line or error contains `--match-text TEXT`, or matches `--match REGEX`.
|
|
135
|
+
- the map takes longer than `--slower-than SECONDS` to be ready, for slow starts.
|
|
136
|
+
- the game crashes, exits early, gives up on the map or drops back to Core alone after a
|
|
137
|
+
load error, with `--crash-is-fail`. Without
|
|
138
|
+
it those trials are reported as CRASH, run again once (big lists do crash now and then
|
|
139
|
+
for no repeatable reason), and treated as passing if the game crashes again. With it
|
|
140
|
+
and without `--match`, `--match-text`, `--slower-than` or `--pick`, the crash itself is
|
|
141
|
+
what rimbisect hunts.
|
|
142
|
+
|
|
143
|
+
Only one of `--match`, `--match-text` and `--slower-than` can be given.
|
|
144
|
+
|
|
145
|
+
A trial passes when the map has run for `--settle` seconds of game time (20 by default)
|
|
146
|
+
without the error. Raise it if your error only shows up a while into the game. A PC that
|
|
147
|
+
runs the game too slowly to get there still stops after three times as long in real time
|
|
148
|
+
(at least a minute), and the report points out trials that got through less than half of
|
|
149
|
+
it. If the full list does not reproduce the error, rimbisect says so and stops.
|
|
150
|
+
|
|
151
|
+
A trial that gives no answer, because it hit `--timeout` or the game did not load all the
|
|
152
|
+
mods it was given, is run again once. If the same list gives no answer twice, rimbisect
|
|
153
|
+
stops with the status "inconclusive" and says which list it was. It also stops that way
|
|
154
|
+
when the mods it narrowed down to don't show the error when run alone, which is what an
|
|
155
|
+
error that only shows up some of the time looks like; `--repeats 3` helps there.
|
|
156
|
+
|
|
157
|
+
## How long it takes
|
|
158
|
+
|
|
159
|
+
Every trial is a full game start, so time depends on your PC and the size of the list.
|
|
160
|
+
On the run above (an i9-14900K, game on an NVMe SSD, RimWorld 1.6.4871) the full 199 mod
|
|
161
|
+
list took 5 minutes to reach a map and run it for 20 game seconds, a list of about 100
|
|
162
|
+
mods took under two minutes, and trials under 30 mods took 35 to 55 seconds. A failing
|
|
163
|
+
trial is often much quicker: the run stops the moment the error appears, and def errors
|
|
164
|
+
appear before the map is generated.
|
|
165
|
+
|
|
166
|
+
Trials needed after the first full run, measured with
|
|
167
|
+
[`acceptance/simulate.py`](https://github.com/Booyaka101/rimbisect/blob/main/acceptance/simulate.py) over every
|
|
168
|
+
single culprit and 300 random pairs:
|
|
169
|
+
|
|
170
|
+
| mods | one culprit | two mods that only fail together |
|
|
171
|
+
| ---: | --- | --- |
|
|
172
|
+
| 64 | 8.6 on average, at most 11 | 16.2 on average, at most 21 |
|
|
173
|
+
| 200 | 10.6 on average, at most 13 | 19.9 on average, at most 26 |
|
|
174
|
+
| 400 | 11.7 on average, at most 15 | 22.4 on average, at most 28 |
|
|
175
|
+
|
|
176
|
+
The real 200 mod run above needed 11, as the estimate after the full run said. For 400
|
|
177
|
+
mods, count on about 12 trials plus the full run. The first trials load half the list and
|
|
178
|
+
the last one nearly all of it, so they take most of the time; a single culprit in 400
|
|
179
|
+
mods should take somewhere around 25 to 40 minutes on a PC like this one. That is an
|
|
180
|
+
estimate from the 200 mod timings, not a measurement.
|
|
181
|
+
|
|
182
|
+
## How it works
|
|
183
|
+
|
|
184
|
+
Each trial runs the game with `-savedatafolder` pointing at the run folder, which holds a
|
|
185
|
+
copy of your `Config` folder (mod settings included), HugsLib's settings and the trial's
|
|
186
|
+
mod list. The copy is made again before every trial, so a setting one trial changes
|
|
187
|
+
doesn't leak into the next, and it is deleted when the run ends. The copied `Prefs.xml`
|
|
188
|
+
runs the game in a 1280x720 window with the sound off and the UI scale at 1. Your own
|
|
189
|
+
`ModsConfig.xml` and `Prefs.xml` are only read, and your saves are never opened.
|
|
190
|
+
|
|
191
|
+
For the run, rimbisect copies a small mod, `rimbisect-probe`, into the game's `Mods`
|
|
192
|
+
folder and deletes it afterwards, also on Ctrl+C. It loads last, writes errors and
|
|
193
|
+
progress to a file rimbisect watches, and closes the game once the map has settled. It
|
|
194
|
+
has no Harmony patches and does nothing when rimbisect did not start the game. If
|
|
195
|
+
rimbisect is killed hard, delete `Mods\rimbisect-probe` yourself or let the next run
|
|
196
|
+
replace it.
|
|
197
|
+
|
|
198
|
+
RimWorld stops logging for good after 10,000 messages, which a big list can reach while
|
|
199
|
+
loading. The probe keeps the game's count from getting there, and switches logging back
|
|
200
|
+
on if it was reached before the probe loaded, so a later error is still seen.
|
|
201
|
+
|
|
202
|
+
The probe also reports which mods the game actually loaded. If a trial is missing some
|
|
203
|
+
(usually because Steam was closed mid-run), it counts as no answer rather than a pass.
|
|
204
|
+
Windows that pause a new game, like HugsLib's update news or the feature popups of
|
|
205
|
+
Achtung and Camera+, are closed so the map actually runs; the report says which ones.
|
|
206
|
+
|
|
207
|
+
Every trial includes the mods that the mods under test need, so there are no "missing
|
|
208
|
+
dependency" errors that the full list doesn't have. Missing dependencies are never
|
|
209
|
+
invented; rimbisect warns about them instead, and about pairs of active mods that say
|
|
210
|
+
they are incompatible. A trial is your list with some mods left out, never reordered.
|
|
211
|
+
Mods given with `--keep` are loaded in every trial, together with what they need.
|
|
212
|
+
|
|
213
|
+
When neither half fails on its own, rimbisect narrows each half with the other one
|
|
214
|
+
loaded, so two or three mods that only break together come out as a set.
|
|
215
|
+
|
|
216
|
+
Once it has a culprit, rimbisect runs the list once more without it and the mods that
|
|
217
|
+
need it. If the error is still there, something else causes it too, and the search goes
|
|
218
|
+
on through the rest. The report lists each cause on its own.
|
|
219
|
+
|
|
220
|
+
`rimbisect check` runs your full list once and, if it gets through, remembers it as the
|
|
221
|
+
last good state. The next bisect first tries the list without the mods whose folders
|
|
222
|
+
changed since then (updates, new mods). `--since DATE` does the same without a check.
|
|
223
|
+
|
|
224
|
+
For errors that come and go, `--repeats N` runs a passing list again, up to N times,
|
|
225
|
+
before trusting it. Lists that gave different results are listed in the report.
|
|
226
|
+
|
|
227
|
+
rimbisect only kills the game process it started, by process id. The game runs in a
|
|
228
|
+
Windows job object, so it also closes if rimbisect is killed or its window is closed.
|
|
229
|
+
|
|
230
|
+
## Commands
|
|
231
|
+
|
|
232
|
+
| | |
|
|
233
|
+
| --- | --- |
|
|
234
|
+
| `rimbisect bisect` | Find the mods behind an error |
|
|
235
|
+
| `rimbisect check` | Run the full list once; if it gets through, save it as the last good state |
|
|
236
|
+
| `rimbisect mods` | Show the mod list the way rimbisect reads it, with warnings |
|
|
237
|
+
|
|
238
|
+
Every command takes these:
|
|
239
|
+
|
|
240
|
+
| Option | |
|
|
241
|
+
| --- | --- |
|
|
242
|
+
| `--game PATH` | RimWorld install folder. Found through Steam if omitted |
|
|
243
|
+
| `--config FILE` | `ModsConfig.xml` to test instead of the game's own. Only read |
|
|
244
|
+
| `--json` | Print JSON instead of text |
|
|
245
|
+
|
|
246
|
+
`bisect` and `check` also take these:
|
|
247
|
+
|
|
248
|
+
| Option | |
|
|
249
|
+
| --- | --- |
|
|
250
|
+
| `--workdir PATH` | Where runs and `last-good.json` go. Default `%LOCALAPPDATA%\rimbisect` |
|
|
251
|
+
| `--match REGEX` | Fail when an error or log line matches |
|
|
252
|
+
| `--match-text TEXT` | Fail when an error or log line contains TEXT, taken literally |
|
|
253
|
+
| `--slower-than SECONDS` | Fail when the map is not ready after this long |
|
|
254
|
+
| `--settle SECONDS` | Game seconds a map has to run without the error to pass. Default 20 |
|
|
255
|
+
| `--timeout MINUTES` | Give up on a single trial after this long. Default 20 |
|
|
256
|
+
|
|
257
|
+
and `bisect` these:
|
|
258
|
+
|
|
259
|
+
| Option | |
|
|
260
|
+
| --- | --- |
|
|
261
|
+
| `--pick N` | Hunt error N from the full list's errors without asking |
|
|
262
|
+
| `--keep ID[,ID]` | Load these packageIds in every trial. Core and DLCs always are |
|
|
263
|
+
| `--since DATE` | First try the list without mods changed after DATE, like `2026-09-01` |
|
|
264
|
+
| `--ignore-last-good` | Don't try the mods changed since the last good run first |
|
|
265
|
+
| `--repeats N` | Run a passing list up to N times, for errors that come and go |
|
|
266
|
+
| `--crash-is-fail` | Count a crash, early exit or failed map as the problem |
|
|
267
|
+
|
|
268
|
+
`rimbisect --version` prints the version.
|
|
269
|
+
|
|
270
|
+
Exit codes: 0 when culprits were found (or `check` passed), 1 when the full list did not
|
|
271
|
+
reproduce the error, the base game alone fails, the search was inconclusive, or `check`
|
|
272
|
+
failed, 2 for usage errors such as a missing install or RimWorld already running, 130
|
|
273
|
+
when interrupted.
|
|
274
|
+
|
|
275
|
+
## Limitations
|
|
276
|
+
|
|
277
|
+
- Windows only, and only the Steam version has been tried. The probe is built for 1.6.
|
|
278
|
+
- It finds errors that happen while the game starts and during the first `--settle`
|
|
279
|
+
seconds on a fresh map. Problems that need your save, a raid or a year of play are out
|
|
280
|
+
of reach.
|
|
281
|
+
- Settings of mods that keep them outside `Config` (other than HugsLib) are not copied,
|
|
282
|
+
so those mods run with their defaults.
|
|
283
|
+
- A problem that depends on load order, rather than on which mods are present, is only
|
|
284
|
+
found as the set of mods involved. rimbisect does not try other orders.
|
|
285
|
+
- RimWorld has to be closed while it runs, and the machine is busy the whole time.
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
MIT
|