But before we dive into the first steps I took, maybe a few introductory words and maybe some references in case you want to go a similar route.
Having learned one or another programming language in my life time, I thought just getting started with Rust would be easy. All you need to learn is some syntax and the standard library. After falling into that trap for some days, unable to produce anything working at all, I took by heart the following references:
And, in case you don’t want to go through all this, yet still want to follow me along, here are some basic introductory words from someone that did most stuff in Java back in the days.
Rust main building block are crates. You can think of those being libraries, and there is a great pool of crates from others that you can use out of the box most of the time. Confusingly crate may also stand for an application crate. More or less, a crate is a bundled set of at least one compiled source file, you can have more than one.
Traits in the Rust world can be thought of interfaces in other languages. There is a large basic set of traits the language ships with. If you use external crates in your programs, there may be more traits to come.
Rust builds on explicit borrowing of values. Once you borrowed a variable to another function, you can no longer call certain functions with it, unless that function returns the borrowing before. This leads to fewer problems with concucrrency and leaked memory.
Rust also can interoperate with other languages. For the sake of our project here, we can annotate functions to be extern “C”, so that the compiler offers the option for C-programs to call your Rust function. Usually the process macro #[no_mangle] helps here to tell the compiler not to mangle your extern “C” functions, in other words, they will be known to the C-world by the same name you use in the Rust world.
You can also call C functions with the right set-up. Rust considers extern “C” functions as “unsafe”, and the compiler will prompt you to put such calls into unsafe{} blocks in your code. That does not lead to down-graded performance or anything like a try..catch block in other languages. It just means that you tell the compiler that you put the right thoughts into place to make sure it’s safe to call that unsafe function at this point in the program.
Let’s get started.
In order to comply with the goals I stated in my previous blog entry, I decided to go for the replacement of the C-hooks in Rust. The idea would be to have the C code call our Rust code, that will then delegate some things back to the original minqlx C-code – up until we know how we want to replace that. Let’s work through one of such replacements.
The simplest thing is probably the ClientSpawn hook. As a reminder: minqlx pre-loads itself in Linux before the Quake Live dedicated server is loaded and started. It searches for interesting functions and hooks up its own replacement functions that call the original Quake Live function, while forwarding the particular game event towards the python world, where server plugins then can customize the play experience for the players on that server. ClientSpawn usually gets called after a player connected successfully, and entered a match, and his player gets spawned into the server. Here is the original C source that we want to transfer to Rust:
void __cdecl My_ClientSpawn(gentity_t* ent) {
ClientSpawn(ent);
ClientSpawnDispatcher(ent - g_entities);
}
ClientSpawn is the original Quake Live function that gets called first. ClientSpawnDispatcher is the forwarding dispatcher to the python world. gentity_t is a Quake Live native game entity, which could be a player, a rocket, or other map entities that players can interact with. ent – g_entities calculates the player’s client id. g_entities is a long list of all currently available game entities. This struct holds the players on the server always in its first 64 entries.
Ideally, we could write a ShiNQlx_ClientSpawn function, let the C-world know about that function, replace the whole hooking mechanism there going from My_ClientSpawn towards ShiNQlx_ClientSpawn, and we are done.
Here is the corresponding Rust function:
#[no_mangle]
pub extern "C" fn ShiNQlx_ClientSpawn(ent: *mut gentity_t) {
extern "C" {
static g_entities: *mut gentity_t;
static ClientSpawn: extern "C" fn(*const gentity_t);
fn ClientSpawnDispatcher(ent: c_int);
}
unsafe { ClientSpawn(ent) };
unsafe { ClientSpawnDispatcher(ent.offset_from(g_entities) };
}
Let me explain. The first extern “C” block declares various external C functions and structs. We will probably need the original g_entities from the Quake Live engine. We certainly still have to call the game’s ClientSpawn function. For the time being, we will leave the ClientSpawnDispatcher with the original minqlx source, and call it directly here, until we made up our mind on how to forward directly from Rust to Python.
When we put this function into the quake_common.h header file, replace the hook from My_ClientSpawn with ShiNQlx_ClientSpawn, we notice, it works. But is it really holding up to the safety claims of Rust?
The various Rust sources always call for “creating a safe interface” for the Rust world whenever you want to interoperate with the outside C-world. Our function still gets a pointer to a gentity_t, which I also explicitly declared as mutable pointer here. Thanks to Adrian Heine who recommended the safe interface in this way. Here is a version of the above function after added some more Rust structs, that is somewhat more “safe” and Rust-like:
#[no_mangle]
pub extern "C" fn ShiNQlx_ClientSpawn(ent: *mut gentity_t) {
let Some(game_entity): Option<GameEntity> = ent.try_into().ok() else {
return;
};
QuakeLiveEngine::default().client_spawn(&mut game_entity);
extern "C" {
fn ClientSpawnDispatcher(ent: c_int);
}
unsafe { ClientSpawnDispatcher(game_entity.get_client_id() };
}
pub(crate) trait ClientSpawn {
fn client_spawn(&self, ent: &mut GameEntity);
}
impl ClientSpawn for QuakeLiveEngine {
fn client_spawn(&self, ent: &mut GameEntity) {
extern "C" {
static ClientSpawn: extern "C" fn(*const gentity_t);
}
unsafe { ClientSpawn(ent.gentity_t) };
}
}
pub(crate) struct GameEntity {
gentity_t: &'static mut gentity_t,
}
impl TryFrom<*mut gentity_t> for GameEntity {
type Error = &'static str;
fn try_from(game_entity: *mut gentity_t) -> Result<Self, Self::Error> {
unsafe {
game_entity
.as_mut()
.map(|gentity| Self { gentity_t: gentity })
.ok_or("null pointer passed")
}
}
}
impl GameEntity {
pub(crate) fn get_client_id(&self) -> i32 {
extern "C" {
static g_entities: *mut gentity_t;
}
unsafe { (self.gentity_t as *const gentity_t).offset_from(g_entities) as i32 }
}
}
The Rust standard trait TryFrom creates a safe Rust struct out of our raw C pointer, or delivers an Error. We encapsulate the raw gentity_t with a GameClient struct in Rust. The Result of the TryFrom trait in Rust here has the type Result<GameEntity, Error>. You can convert Results into Options by calling the .ok() function on it. Then you will either have Some(game_entity) or None. The declaration let Some(game_entity)… extract the GameEntity from the Option<GameEntity> here to further work with. The else block behind that simply returns if the conversion failed.
Since we are identifying the client_id from the GameEntity, this seems like behavior that really wants to be on the GameEntity struct, so I moved all the calculation there, declaring the g_entities there as well. Then we can call the ClientSpawnDispatcher of minqlx directly with the calculated client_id.
Phew, quite a bit of boiler-plate code, but it seems to work. So, I went ahead and transferred in a similar manner (with more logic) the various hooking function as best as I could to Rust. Once everything ran, I was able to start-up the server, and see where this went. After a few more round of fiddling, I managed to get a running server and a sort of Hello World for our ShiNQlx project. How did I do that? Let me go into the cargo build system and build steps to have C code compiled in there in the next blog entry.
Last year, Craig Larman recommended learning the programming language Rust to help, especially with embedded programming. Linus Torvalds also allowed Rust as a third language in the Linux kernel development around the same time. It took me a while to actually try it when I found the perfect for me pet project to dive into.
Over the next couple of blog entries, I want to dive into some of my learnings and approaches. Today, let me give you an introduction to the project I picked for my personal Rust learning curve, where I am, and maybe give you an update now and then about the things I still have open to learn.
For a while now, I have been playing a bit of Quake Live, a first-person shooter. I was amazed when I found out that there is a server extension that allows you to customize the server experience by writing plugins in Python. minqlx which stands for Mino’s Quake Live eXtension (I think, and found out way too late) is a C-to-Python bridge that preloads (with some Linux LD_ magic) on the server side, and offers such extension possibilities. Since the server behavior has at times been a bit buggy, and I heard lots of things about Rust’s speed and stability, that project came into consideration for me. Maybe I can manage to port all this C stuff over to Rust, and maybe I can extend what’s already in there with some additional functionality. Yeah, I oftentimes consider myself better at taking another one’s initial idea and thinking it further.
Let’s take a look into the different parts and tricks used in minqlx before trying to port some or maybe all of it over to Rust.
minqlx runs as a library pre-loaded before the actual game server. So, the library gets constructed before the game server loads, then finds interesting game functions to hook into and replace the default behavior with additional functionality, like forwarding stuff to the Python world.
In dllmain.c right at the bottom of the file, there is the main entry point to the minqlx library:
__attribute__((constructor))
void EntryPoint(void) {
if (strcmp(__progname, qzeroded))
return;
SearchFunctions();
// Initialize some key structure pointers before hooking, since we
// might use some of the functions that could be hooked later to
// get the pointer, such as SV_SetConfigstring.
#if defined(__x86_64__) || defined(_M_X64)
// 32-bit pointer. intptr_t added to suppress warning about the casting.
svs = (serverStatic_t*)(intptr_t)(*(uint32_t*)OFFSET_PP_SVS);
#elif defined(__i386) || defined(_M_IX86)
svs = *(serverStatic_t**)OFFSET_PP_SVS;
#endif
// TODO: Write script to automatically set version based on git output
// when building and then make it print it here.
DebugPrint("Shared library loaded!\n");
HookStatic();
}
There is not much in here. serverStatic_t is a Quake struct where the static contents of the server will be placed in. HookStatic() is the first entry point for the real magic later on. I will spare you the majority of code snippets from here on, as the blog entry would become too long.
Quake Live has some static portions, but the majority of the server runs on a virtual machine in memory. minqlx first of all hooks into some static functions, in order to be able to hook into the VM functions later on. While trying to understand the logic in the C-world in order to replicate it in the Rust-world, I learned that you can replace function calls in libraries with your own functions by just replacing the pointer to the function called in the lookup table. (Yeah, it’s more complicated than that, but for brevity, we’ll leave it at this explanation.)
SearchFunctions in the main constructor looks up the addresses of the functions we want to hook into, while HookStatic hooks into the static functions of the server library. Later on, there is a similar approach taken for finding the various VM functions we would be interested in, and hooking into these as well, replacing the original call with our own delegate, i.e. ideally calling the original function alongside with triggering some logic on the python side to have plugins notice things, or react to game events. Let’s see how this is done.
python_embed.c and python_dispatchers.c are responsible for the majority of the work here. The different replacement functions will translate the C types into suitable Python types and then make them available to customize game logic in the Python plugins.
The various dispatchers consist of a bridge between our hooking functions that we intersected in the game server, and the Python world, while the embed functions provide additional functionality on the Python side via direct Python functions that can be used in your plugins. For example, in python minqlx.player_state(player_id) will return you a Python object reflecting the current state of the player with that id that is derived from the various Quake Live structures and translated in the according Python embed function.
In the source files, one header file holds type definitions of all the different Quake types and enums. That is quake_common.h. The majority of the types in there have probably been reverse-engineered from open source counter-part games, and probably lots of hours of tinkering.
There are more source files in there, but these will be the main focus for my efforts on transforming the C-world of minqlx towards Rust. But hold on, there are a few different goals a Rust implementation needs to serve while I am underway.
There are many useful plugins for minqlx out there. I don’t want to have to port all of them over. So, a backward compatibility to minqlx would be ideal, so that plugin authors don’t need to re-write all their useful plugins.
The resulting library should also be comparable in size to the C one. As it stands, the 64-bit release version of minqlx has roughly 120kB in size. We certainly should not waste several megabytes with our version.
Speaking of 64-bits: minqlx also provides a 32-bit product in the compilation process. Yeah, this is an old game. We will try to focus on 64-bits, the standard nowadays, but if we can also provide the ability to run this on a 32-bit machine, that would be ideal.
Oh, and finally, the result should have at least the same stability as the C version, or players and server admins will not want to use it.
Let’s see how far we can take this.
Experiential Learning is at the heart of the Problem-solving Leadership course as well as the Amplify your Effectiveness conference. Having attended PSL in 2011, and being a trainer myself, I was eager to get to know some of the training design thoughts that went into all the different formats. Since these three books don’t have a pendant in physical form, this was sort of the only way to dive into the topic for me.
Basically, I liked the whole series due to the fact that I could re-live some of my Problem-solving leadership exercises, now explained by the designer himself. In the first of the three volumes, Jerry lays some of the groundwork for designing experiential learning exercises while explaining many exercises that he used over the course of his career.
Beginning takes the reader from starting points in experiential learning exercises over to over increasingly improving the exercise. You will follow along with many of the examples, and combine the design of the exercise with ever more of Jerry’s other concepts.
For example, the very first exercise describes splitting the group into two and asking each sub-group to perform a simple process. One of the groups gets to choose an observer, while the other just works on their own following their own process. In later chapters you will learn about applying metaphor’s to your exercises, all the while reminding the reader of keeping the exercise simple and transparent.
A lesson I rather had to learn the hard way deals with laying out traps. Sure, you can introduce traps into your learning exercises, but in an experiential learning set-up, it’s hard, maybe even impossible to tell up-front what people will learn. So, participants will always bring their own traps with them and might learn something totally new for them that they did not intend to be in there. And that is perfectly fine. As an experiential trainer, you need to create the ground for the learning to flourish without knowing which kinds of seeds will prosper into flowers of vegetables.
Overall, Beginning lays the soil for the two other volumes in this series, and you will leave with a basic understanding of experiential learning from this volume, ready for the next things to come.
Inventing deals with all the things we humans take pride in: tinkering, drawing, painting, and the like. If you can combine your learning content with people inventing things, they will not only enjoy themselves but also appreciate the lessons they take from it.
Similar to the other books in this series, you will dive into a variety of lessons, from the house of card exercise and how to improve it, over tinkering with toys, and what you, as an experiential learning designer, might want to watch out for. Since learning from your experiences is so important for experiential learning to happen, this book covers all the practical ways that invention can become part of your learner’s journey.
The final book in the series took on the probably longest exercise in the whole problem-solving leadership class: Verseworks. Anybody who has dealt with the mines, solving jigsaw puzzles in a dark room without a chair, too small to stand up straight if you’re my size, over to income and expense calculation for your startup and the like basically puts together all the elements from the two previous books into a single large-scale experiential learning exercise.
Simulations are an important way to learn something about your daily job by doing something totally different, yet, seeing the pattern between the two different environments that emerge. The unnecessary traps find their way back in here, as you should not design your simulations with too many intended traps. Learners always bring their own traps and might get confused over too many confusions if there are so many misleading things in the way.
I really enjoyed Simulation, and tried to incorporate a larger simulation into one of my training classes to some large extent. Other colleagues tried to do the same simulation, too, though I think Jerry’s guide from the third volume here helped me to be mindful of all the things I should be aware of when designing and facilitating a simulation in my own class – even though I really can’t name them.
Overall, all these years after reading the three books, I wonder why these books only published on LeanPub did not meet a higher level of interest, yet, rarely talked about. I think Jerry laid a good cause for better pieces of training in these three books, and it’s really easy for the reader to follow along. But maybe, not all of us are in the position to design experiential learning exercises, and that’s why I see so little talk about this series.
I recall that I read an early of this book when Jerry was writing it – providing him with feedback on aspects. I just realized I never came back to revisit the final version that he published.
That said, from his mere 60 years of experience in the technical industry, Jerry offers his perspective on the many things he finds in the Agile cosmos. Of course, he picks up on the Agile Manifesto, not only the values but also the principles. Beyond that, I recall a very critical view from Jerry on the Agile space.
But beyond that, Jerry offers what has served him well in his experience. Of course, there are those misunderstood concepts in the Agile sphere as well. You may call them Scrum-but, Water-Scrum-Fall, or Dark Agile, yet, they do exist in the field. Pretty much the same fallacies happened to structured programming, software engineering, yada, yada, yada. Jerry has been around to see most of the current fallacies happening before.
From this perspective, I started to think more critically about things happening in the Agile world. Some worries of mine turned out to become true – others not so much. In general, I think we would be wise not to disregard such warnings, yet, try to overcome them and incorporate those lessons stemming from 60 years of experience into our work.
I’m probably illusional, so I will stop my ill-fated illusions and dreaming ups of a better future for the time being.
In 2015 I interviewed Jerry on a couple of questions for our agile review magazine. I asked him whether I could publish the whole English version on my blog as well. In the first question, I had to ask him about this book. Here’s the relevant excerpt from that interview:
Markus:
Jerry, you have been around in software development for roughly the past 60 years. That’s a long time, and you certainly have seen one or another trend passing by in all these years. Recently you reflected on your personal impressions on Agile in a book that you called Agile Impressions. What are your thoughts about the recent up-rising of so called Agile methodologies?
Jerry:
My gut reaction is ” Another software development fad.” Then, after about ten seconds, my brain gets in gear, and I think, “Well, these periodic fads seem to be the way we advance the practice of software development, so let’s see what Agile has to offer.” Then I study the contents of the Agile approach and realize that most of it is good stuff I’ve been preaching about for those 60 years. I should pitch in an help spread the word.
As I observe teams that call themselves “Agile,” I see the same problems that other fads have experienced: people miss the point that Agile is a system. They adopt the practices selectively, omitting the ones that aren’t obvious to them. For instance, the team has a bit of trouble keeping in contact with their customer surrogate, so they slip back to the practice of guessing what the customers want. Or, they “save time” by not reviewing all parts of the product they’re building. Little by little, they slip into what they probably call “Agile-like” or “modified-Agile.” Then they report that “Agile doesn’t make all that much difference.”
“Life is long, some lives are not long enough,” I recall a tweet from Michael Bolton when Jerry was first diagnosed with cancer. The title of this book fits here as well. The chapters in this book were brought together for Jerry’s 75th birthday and mostly deal with lessons from attendees of his Problem-solving Leadership (PSL) class.
Having attended PSL myself, it’s hard to share what you will learn when attending a PSL since every learning is personal for that person. Your experiences and what you bring to your attendance are what will shape your lessons from PSL. I can share mine, yet, yours will be totally different.
The stories in this book keep true to the essence of PSL. While you might come across elements of the 5,5 days course, they are not revealing anything about PSL, yet, bring you into the minds of the people attending while they are having their PSL lesson.
You can read about the PSL that took place in early September 2001, and how the trainers made room for calls to relatives in New York City. What I grasped from this experience report is that they shifted the whole course to incorporate the circumstances happening in the world.
Reading through these stories made some lessons forgotten to me alive again. Even if you have not (yet?) attended PSL, you will learn from the shared learnings of prior attendees. I personally think it’s the next best thing besides attending PSL in person.
My copy of the book was signed, I think by Michael Bolton on one of the occasions I met him in person.
I recall that I mentioned to Michael once that I would be humbled to meet Jerry in person, and he recommended attending PSL to make that happen. That’s probably why I asked Michael to sign my copy of these PSL stories.
I never made it to one of the AYE conferences. So this book was a great first insight into the conference. That said, I really enjoyed the look into the conference format.
The book contains a collection of various topics around software development, just like Jerry’s books overall. It’s structured into four parts:
Part One: Empowering the Individual
Part Two: Improving Interpersonal Interaction
Part Three: Mastering Projects
Part Four: Changing the Organization
Among the contributors are people like Jerry himself, Johanna Rothman, Naomi Karten, Esther Derby, James Bach, Don Gray, and Steven M. Smith.
I recall that the essays were really insightful in various fields around software development, and interpersonal relationships. Especially with the contributions from such tenured authors and practitioners. For me, it was a first glimpse of the repertoire of Jerry’s other books. If any of my reviews here did not convince you about a particular book of Jerry’s thus far, this one might be a good starting point, to really make up your mind.
As the title notes, Jerry wrote over 40 books in his career, spanning at least from 1958, up until his death in 2018. So, when such an accomplished author writes a book about writing, it’s a must-read in the case are considering writing on your own.
I recall how Jerry made early on a distinguishment between an author and a writer, but I can’t find that reference in the book’s index. So it might as well have been a blog entry he published on one of his blogs at the time. Basically, an author could be a one-time contributor to a book or an article, while a writer makes a living from his writing work.
The subtitle of his book can be explained rather simply, and Jerry offers his explanation from the get-go. A fieldstone wall is built with stones lying around in the wild. These are not perfectly shaped. So, if you want to build something like a wall out of fieldstones, you need to craft them into your wall by seeking opportunities where they fit in.
Of course, this is an analogy. Over the years, I found myself re-using some of my earlier writings. Mostly, it’s not the whole previous piece that I reuse, but just portions of it. For example, the Satir Change model could be one, the Lean Change cycle, another one, and so on. For the piece at hand, I need to make the different fieldstones fall together in their places to form a thing of beauty for the reader to enjoy.
Jerry has lots of tips on how to organize your writing, overcome writer’s block, and how to strengthen your writing muscle by exercising it through daily writing – something I picked up on again in late August here on my blog as well.
Yet, the most important lesson on writing is offered right in the first chapter of this book:
Never attempt to write something you don’t care about.
Writing Lesson Number One, page 6, Weinberg on Writing
Over the years, I have violated this number one lesson in my writing and paid the price for it. Don’t do it. Jerry is right. Read the book if you strive to pursue becoming better at writing.
During my attendance at the Problem-solving leadership course back in 2011, I asked Jerry to sign my copy of the book.
When I co-facilitated the black-box software testing courses from Cem Kaner through the Association for Software Testing, the instructors had a wiki with different templates that were completed and sent out to the participants. These templates were highly motivated by this book, thus called fieldstones.
Personally, I use fieldstones in a different manner. Rather than using templates for my articles, blog posts, and the like, I organized my articles so that I can easily find past content, and maybe re-write the portions around a particular concept to fit into the fieldstone wall that is the current article that I am working on. Thus far, that works well for me. I fear there might be a point when my memory will stop working like this.
That might change in 2023 – or I might be back on a regular hiatus. As with all my writings, I try to keep my writing as an exercise for me, to get my thoughts straight, train the muscle to take something away from a day of work that I want to write about every day at work, and reflect on the things I perceive during the work day. I just took you along for the ride for the past few months.
I hope you will occasionally check back for new content, and maybe during brighter days, there will be an opportunity to meet in person in the future. Thanks for reading along all my thought-sorting here. I hope I can keep my writing going, but doubt I want to continue this particular kind of pace from the past months.
Merry Christmas, happy holidays, and a Happy New Year.
There I was yak-shaving, during code and merge
All inside it’s so frustrating as I drift from run to purge
Feel as though nobody cares if the build works or fails
So I might as well begin to put some action in my trails
Breaking the build, breaking the build
Breaking the build, breaking the build
Breaking the build, breaking the build
Breaking the build, breaking the build
So much for the golden future, we can’t even start
We’ve had every promise broken, there’s anger in our hearts
You don’t know what it’s like, you don’t have a clue
If you did you’d find yourselves never ever branching too
Breaking the build, breaking the build
Breaking the build, breaking the build
Breaking the build, breaking the build
Breaking the build, breaking the build
You don’t know what it’s like
Breaking the build, breaking the build
Breaking the build, breaking the build
Breaking the build, breaking the build
Breaking the build, breaking the build
Breaking the build, breaking the build
Breaking the build, breaking the build
Breaking the build, breaking the build
Breaking the build, breaking the build
Jerry has been around for a long time – even up to the early days of computing, as he introduces the reader to his Perfect Software book. With the topic of cognitive dissonance regarding testing your own programs, he also tackles the topic of testing in very early books like the Computer Programming Fundamentals. Yet, Jerry wrote just this one book totally dedicated to the field of software testing in his career. If you stick around long enough, though, you will hear professional testers claim that all of Jerry’s books are about testing.
So, what could a dedicated book on software testing add to these already existing books? Having written Perfect Software rather late into his career in the software industry, Jerry is able to combine together the most relevant topics on testing. If you read his Weinberg on Writing book, – a book I still have on my list to cover here – you will certainly see how the fieldstones are put together here – especially if you read previous work from him.
There are several chapters on the Satir communication model, Intake, Meaning, Significance, and Response though I need to admit that his take on it in the Quality Software Management series was more in-depth and meaningful to me.
The book covers also scams in testing in the latter part, including scams we are not aware we are creating for ourselves. The introductory part has a vivid motivational character to me: Why do we need to test at all? What kinds of traps lie in front of us if we organize software testing in certain or other ways?
All of that said, I think Jerry compiled together a good set of fieldstones for anyone working in the software industry that may not have crossed his previous writings. Sure, there are some additions to his previous writings with the dedicated viewport of a software tester, yet, by and large, there will not be too much new for you if you read his other works before.
For someone new to all these topics, it has great value. That probably motivates one of the appraisals, that I came across over the years (if I recall correctly, it came from Michael Bolton): Give this book to your manager, if you want him to understand what you are dealing with as a software tester. (my re-phrasing)
During my attendance at the Problem-solving leadership course back in 2011, I asked Jerry to sign my copy of the book.
The different chapters have some key points, that I had to write down for my own memories. I ended up with this pdf for my reminders.
Finally, I know by personal communication that Jerry started this book together with Elisabeth Hendrickson. At some point, Elisabeth had to drop out of the writing, and Jerry eventually finished the final version together with James Bach. I think few people know that, and from the cover, it does not look like it. I don’t know the story behind that. In hindsight, I guess, the Satir Communication model looks like Jerry’s writings mostly to me, but I’m not sure about the other chapters.