Designing 3D Volleyball Training Tools on the Fly | January 01 2026, 21:21

What I did on the plane to/from vacation and sometimes in between: 3D visualization and editing volleyball schemes for Nadya (she’s a coach). This court in the attached image freely rotates, players can be placed on it, and the ball and player paths are shown – all in 3D.

The ball’s trajectory is calculated so that it does not cross the net when moving from A to B (Bezier formula). Players can take several poses – right now there are hastily made poses for serve, attack, block, pass/receive. Interestingly, in the code: I had to write a bit of “volleyball brains”. The system itself calculates the ball’s trajectory through Bezier curves so that it always passes over the net. Moreover, the height of the launch depends on the type of action: for an attack, the ball “launches” from a higher point than for a pass. I also added auto-rotation: the 3D model itself turns its face to where, according to the scheme, it needs to pass or run.

The longest and most difficult task was creating the 3D model of a female volleyball player. To generate a realistic volleyball player, I used the tripo3D service. It gave me a model in a neutral pose (for free). Theoretically, you can then use Blender and the Rigify plugin to attach an armature to it and move its arms and legs, which would recalculate the model.

However, in reality, this approach does not work well: the AI-generated model contains a large number of geometric errors, which the renderer forgives but Rigify does not. They can be roughly divided into two types — incorrect polygon normals and issues with non-manifold geometry, which are significantly more challenging to fix. Inside the body, there may be “floating” clusters of polygons or intersecting surfaces. When Rigify tries to calculate weights (which bone affects which part of the skin), this internal noise confuses the algorithm, and as a result, the weights are distributed chaotically (for example, moving the arm might start pulling the mesh on the stomach). Plus, the model is slightly asymmetrical.

Non-manifold is a geometry error where the topology of an object ceases to be correct in terms of a three-dimensional body: edges may belong to more than two polygons, polygons may only touch at vertices or edges without a common volume, and “hanging” surfaces or zero thickness may appear inside the model. Such geometry formally does not describe a closed volume, causing problems with rigging and deformations. Moreover, the model needs to be simplified because millions of polygons are not needed for rendering in real-time in a browser.

I fixed these using MashLab, additionally refining by hand (“with a file”). In the end, the model turns out slightly different from the original almost everywhere. The original model had “skin” in the form of textures – the face, shirt, and shorts had to be colored. How to transfer all this to a simplified model? For that, there’s a special operation in Blender called Baking. This also involves some tricks. In the end, it didn’t transfer perfectly, but perfection isn’t necessary yet.

Next, we attach the armature to the “joints”, and after about three hours of figuring out why everything does not work as it should, it finally worked. I made four poses, and now each circle (player) can be told which pose it is in.

I’ll also need to make dynamic changes to the uniform colors – that shouldn’t be difficult. There’s also an idea to transfer poses from photographs – this is more complicated, but generally feasible. Using MediaPipe/AlphaPose, you can detect key points in 2D, then some models like HMR/HybrIK can “lift” flat coordinates into 3D space, outputting relative joint rotation angles. The resulting data can be attempted to be projected onto a Rigify skeleton. Since the proportions of the generated volleyball player and the person in the photo may not match, that’s exactly why Inverse Kinematics (IK) is used. This part is quite complex, but overall it’s not strictly necessary – just interesting to figure out and make something functional.

Video in the comments

Crafting a Custom Volleyball Play Editor | December 23 2025, 21:39

Tomorrow is the flight to Costa Rica, and here I am creating (or created) a volleyball playbook editor for Nadya. As a coach, she prepares for her sessions and leaves behind hundreds of pages of text with diagrams on each page. The text is handwritten, and theoretically, it’s simple to convert to a digital format, but converting the diagrams into high-quality vector format is exhaustive—there are so many. So, I decided to make the software yesterday. And today, the first version is ready to use. This is a diagram editor, somewhat remotely similar to a diagram editor. Also got to dig into the fabric framework.

The process looks like this. Gemini/ChatGPT through an API can convert hand-drawn diagrams into a structure that my program understands. Then we open this file in the program, and tweak a bit if necessary. Or maybe even redraw from scratch – for simple diagrams, it’s even easier. There are four types of objects – player, cone, target, text. Any can be connected with arrows, solid or dashed, labeled with text or numbers or not, in any chosen color, straight or curved. If you touch an object with the mouse, all connected arrows will follow.

The result can be saved in a file. You can open a template and based on it create something new. You can generate a Python script – yesterday it was still relevant, today generally not needed anymore – high-resolution SVG/PNGs are made directly from this app (yesterday they were made separately in Python).

It’s clear why you wouldn’t just ask Gemini/ChatGPT to do something for ready-made vector editors: firstly, they are too flexible and limiting LLM’s imagination is quite difficult. As a result, you get stylized, unusable images. Here, instead, there is a framework consisting of four objects and that’s all, LLM knows about it and only generates what can be represented with them. Secondly, this framework operates with objects, not elementary vector primitives.

Overall, this is the first step towards my idea of an automatic diagramming system based on descriptions. Where you give an LLM a diagram description, and it consistently generates what is written in the description, and if you make any corrections, they will be taken into account during regeneration.

Decoding the Beast: Migrating from Excel to Code | December 17 2025, 18:56

We’ve all encountered it — the “Main Excel Spreadsheet Managing the Business.” The very one B2B companies use to calculate million dollar quotes. It has 12 tabs, 1000+ nested formulas, and zero documentation. For ten years, it had “quick fixes” slapped on and constants hidden away. It’s no longer just a file, but a living organism that no one fully understands except for the guy who quit years ago. That’s how puzzled I was. Moreover, there was uncertainty whether even half of the formulas were needed, or if they were vestiges of the past.

Typical cell:

=IF($D11=$D10,””, IF(ISNUMBER( INDEX(Data!$T$10:$U$17,

MATCH(TabCalc!$F11,Data!$T$10:$T$17,0),2)),

INDEX(Data!$T$10:$U$17, MATCH(TabCalc!$F11,Data!$T$10:$T$17,0),2),

INDEX(TabProd!$C$8:$U$112,TabCalc!$D11,I$1)))

I was tasked with transferring this logic into code so that it was all computed by software. The Excel file seemed to have everything it needed, but in reality — it was a complicated black box. 1069 formulas.

The challenge was in how to translate a thousand interdependent formulas into clean code without losing any edge cases.

Here’s what I ended up doing.

Instead of rewriting everything from scratch at once with uncertain prospects of bug proliferation, I used a strategy of lazy computations and mocks.

I built a structure on Groovy that mimicked Excel’s behavior. Each computation (from a cell) I defined as a function that executed only when it was called. And the functions were a multidimensional dictionary.

I started from the end of the computation graph: from results to inputs. If a formula depended on something I hadn’t yet written, I “mocked” it in the code, simply substituting the value from the Excel sheet.

Bit by bit I replaced these mocks with real logic. Comparing the output of my code to the Excel at each step, I could clearly see where my logic diverged.

In other words, I moved from the result to the input data. At each step, it was clear which mocks needed to be turned into code, and I could compare version +1 with version -1 — the result had to match. As soon as all mocks were replaced with calls — the task was done.

The real “secret ingredient” was the dynamic nature of Groovy for creating a multidimensional map of functions. Instead of static variables, I used a deeply nested structure, where each “leaf” was a closure. This allowed access to any part of the table — be it an input parameter, a config constant, or a complex intermediate result — through a simple, unified syntax, and some components were dynamic.

Here’s an example:

conf[“group”] = { x -> [“a”, “b”, “c”] }

conf[“group”]().each {

calculate[“Group”][“Subgroup”][it][“TotalQuantity”] =

{

x -> calculate[“Group”][“Subgroup”][it][“Someparameter”]() * conf[“someConstant”]()

}

}

Using dynamic keys and closures, I could iterate through product groups or data sets. Since these were dynamic functions, not stored values, the entire system worked like a living graph of dependencies.

Testing was possible right from the start of transferring the formulas. The charm was that you were kind of addressing a cell through syntax like calculate[“Totals”][“A”](), but in reality, you were launching an entire tree of calculations at that moment. And this was incredibly convenient for debugging.

In two weeks, the “Black Box” was transformed into a transparent, modular library with clear logic, which produced exactly the same result as the original table.

P.S. Of course, all the data in all the screenshots are thoroughly obfuscated, or rather, written from scratch for this text.

Decoding Complex Queries: A Transformative Approach to Search Functionality | December 17 2025, 03:25

Oh, I just solved a really cool problem. It’s tricky to explain though. But I’ll try.

So, the client has 10 search websites. They all use one index but throw different queries at it. To what the user enters, a very long and complex query is added, generated by a module on Sitecore. It includes template and page IDs that need to be included or excluded. Ultimately, it’s impossible to understand what’s going on there. There could be ten opening brackets and some randomly closing ones, but it worked with Coveo. Reformatting helped, but not much.

And each site has its own version of this. Meanwhile, the same IDs appear periodically. I first tried to manually figure this out, but it was a nightmare. Nothing helped. There are also nested conditions. For example, “exclude this template” not globally, but only if that field equals one.

Here’s what I did:

I wrote a script that parses this textual “mess” into an abstract syntax tree (AST). This allowed to turn an unreadable string into a structured JSON object, where it’s clear: here’s AND, there’s OR, and here — a specific condition.

Then I turned these conditions into Boolean algebra formulas. Using the SymPy library, I “fed” these formulas to simplification algorithms. Mathematics itself eliminated duplicates, collapsed excessive nesting, and removed conditions that are logically absorbed by others. As a result, the “trees” became flat and understandable.

In the attachment — the original tree and the simplified one.

To be sure that I didn’t break anything during simplification, I wrote a test generator. It takes the simplified logic, puts it back into a working curl, and checks whether the number of found documents (totalCount) matches the original request. The numbers matched — meaning, the logic is preserved 100%.

Having simplified and standardized structures for each site in hand, I built a comparison matrix. The script analyzed them and highlighted Common Core — conditions that are guaranteed to be required (or prohibited) on all sites without exception, and Specifics — unique “tails” that distinguish one site from another.

In the attached screenshot: REQ means that the condition is guaranteed to be met for any document that goes through this request. NOT — definitely not met. OPT — the condition is present in the request, but it’s not strict by itself. It only works in conjunction with something else. “.” — the condition is not mentioned in the request at all.

For 3 sites it responds instantly, for 10 it takes about 30 minutes.

And of course, all data in all screenshots are thoroughly obfuscated.

From Idea to Chess AI: Building a Neural Network to Predict Moves | December 15 2025, 04:33

While figuring out neural networks, I decided to come up with a game-related task for myself. What if I find some ready-made games, and train a neural net to predict moves based on the board situation. Said and done. Of course, generating code is faster with LLM, but I wrote the detailed assignment myself and designed the architecture on my own. In 40 minutes (!) from the idea to the result, I already had a working solution that, at least in the first half of the game, does not mess up too much.

In the screenshot is CuteChess – it works with any chess engine, and in my case, it’s a simple Python script. The script takes the board situation and feeds it to the model. It selects the top 5 moves, and only these top 5 are analyzed deeply for several moves ahead and assesses the position. That is, the neural network suggests possible moves based on the analysis of 20,000 games (534,453 positions). From the results, the best is chosen. It uses the minimax algorithm for this, if that means anything to anyone (it didn’t to me, so Gemini here helped me)

How the model is trained. On the lichess website, you can download games, there are hundreds of gigabytes. I took a file with 800,000 played games from the year 2014. From these 800,000, I select 20,000, specifically looking with a script for games where the result is not a draw (1-0 or 0-1). Next, I calculate the difference (Winner_Rating minus Loser_Rating). It’s not the best metric, but it’s better than nothing. The bigger this difference, the more “confident” the win should be (the strong punish the weak). Thus, I get 20,000 such games.

“Ignoring the moves of the weak” (to avoid teaching the model bad play) is implemented during the training stage of the model. Essentially, the logic is: “If it’s White’s turn now, and White won this game — we learn. If it’s Black’s turn now, and Black lost — we skip and don’t teach the net this move.”.

The neural network is trained in batches of 128 positions at a time. The network receives a board position as input and outputs 4096 — the probability assessment for each possible move.

Selecting games takes about 5 minutes. Training the model on my computer takes about 10 minutes for 20,000 games. You could leave it to train on 100K or a million, and it would definitely be better. No need anymore – I figured it out 🙂

You can view the game here:

https://lichess.org/JWeaIrVW

The Evolution of Personalized Video Advertising | December 14 2025, 17:08

I kept seeing ads for an AI language tutor that I ignored, and the system forgot about me for a while before coming back with a noticeably older tutor.

But really, how soon will video advertising become personalized for us? Where in the same ad, New Yorkers will see their city, black people will see black people, in the morning the main character will be drinking coffee, and a car with the logo of their alma mater will flicker in the background?

Harnessing GPU Power Beyond Machine Learning: A Data Processing Experiment | December 13 2025, 01:16

Torturing my supercomputer. Illustration that the GPU is not just for machine learning and some complex math.

My script takes a thick English dictionary (Webster) and multiplies it by 30, creating a list of 12 million words. Then, the algorithm looks through all 12 million words and replaces all the vowels with asterisks using regex. To add more load, a “word length” column is added, and then we take words longer than 10 letters and find the most frequent (top 5).

So, in Python this is

df[‘masked’] = df[‘text’].str.replace(r'[aeiou]’, ‘*’, regex=True)

df[‘len’] = df[‘masked’].str.len()

res = df[df[‘len’] > 10][‘masked’].value_counts().head(5)

and this code is executed first through the main processor, then through a GPU.

The main processor (I have the top-tier Intel i9 285k) completes this task in 24 seconds, while the Nvidia RTX 5090 does it in 0.51 seconds. That’s a 46 times difference!

[Pandas CPU] Top Patterns:

masked

s*r w. sc*tt. 23280

s*r t. br*wn*. 23220

j*r. t*yl*r. 16140

bl*ckst*n*. 10860

b***. & fl. 10830

Name: count, dtype: int64

[Pandas CPU] Computation Time: 23.5596 sec.

Transferring data to GPU…

Transfer complete in 1.16s

— Running Benchmark: cuDF GPU —

[cuDF GPU] Top Patterns:

masked

s*r w. sc*tt. 23280

s*r t. br*wn*. 23220

j*r. t*yl*r. 16140

bl*ckst*n*. 10860

b***. & fl. 10830

Name: count, dtype: int64

[cuDF GPU] Computation Time: 0.5108 sec.

TOTAL SPEEDUP: 46.12x

Misadventures in AWS: Misusing aws-nuke for Configuration Exports | December 12 2025, 16:29

Just for laughs. I asked Gemini how to export the entire AWS configuration for local analysis, and they recommended using the aws-nuke command for permanently deleting everything, but if you add a dry-run flag, you’ll get the configuration… and someone actually follows such advice 🙂 and then we wonder

Unleashing the Power: RTX 5090 for Advanced AI and Digital Art Creations | December 01 2025, 01:39

Nvidia RTX 5090 32Gb! Happy as an elephant. Installed ArchLinux and CUDA. Planning to soon get smart about boosting transformer deep neural networks and have a bunch of ideas for digital art based on concepts other than diffusion models.

Performance: Just ran a test, model GPT_OSS_20b_UD_Q4_K_XL generates 350 tokens per second with a context of 131072 tokens. That’s roughly an A4 page in a few seconds. Gemma3 27B – 55 tokens per second. Qwen3_30B_A3B_Q6_K – 259 tokens per second.